• linux 下 C++ 与三菱PLC 通过MC Qna3E 二进制 协议进行交互


    西门子plc 有snap7库 进行交互,并且支持c++ 而且跨平台。但是三菱系列PLC并没有现成的开源项目,没办法只能自己拼接,我这里实现了MC 协议 Qna3E 帧,并使用二进制进行交互。

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. using namespace std;
    14. namespace MelsecMC
    15. {
    16. class PlcSocket
    17. {
    18. private:
    19. bool is_open;
    20. int global_socket_fd; // 用于发送/接受数据
    21. mutex m;
    22. public:
    23. PlcSocket();
    24. ~PlcSocket();
    25. // 初始化socket
    26. bool initSocket(string ip, int port, int milSecond);
    27. // 关闭socket
    28. bool closeSocket();
    29. // 发送数据
    30. bool write(unsigned char *buffer, int len);
    31. // 接收数据
    32. bool read(unsigned char *buffer, int len);
    33. };
    34. }
    1. #include "socket.h"
    2. #include <chrono>
    3. #include <thread>
    4. namespace MelsecMC
    5. {
    6. PlcSocket::PlcSocket()
    7. {
    8. global_socket_fd = -1;
    9. }
    10. PlcSocket::~PlcSocket()
    11. {
    12. }
    13. bool PlcSocket::initSocket(string ip, int port, int milSecond)
    14. {
    15. // create
    16. int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    17. if (socket_fd == -1)
    18. {
    19. cout << "socket 创建失败:" << endl;
    20. return false;
    21. }
    22. struct sockaddr_in addr;
    23. addr.sin_family = PF_INET;
    24. addr.sin_port = htons(port);
    25. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    26. // connect
    27. int res = connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr));
    28. if (res == -1)
    29. {
    30. cout << "connect 链接失败:" << endl;
    31. return false;
    32. }
    33. cout << "connect 链接成功:" << endl;
    34. // 设置timeout
    35. setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, (char *)20, sizeof(int));
    36. setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char *)20, sizeof(int));
    37. cout << "setsockopt 成功:" << endl;
    38. global_socket_fd = socket_fd;
    39. return true;
    40. }
    41. bool PlcSocket::write(unsigned char *buffer, int len)
    42. {
    43. m.lock();
    44. long result = send(global_socket_fd, buffer, len, 0);
    45. m.unlock();
    46. if (result < 0)
    47. {
    48. return false;
    49. }
    50. return true;
    51. }
    52. bool PlcSocket::read(unsigned char *buffer, int len)
    53. {
    54. m.lock();
    55. long result = recv(global_socket_fd, buffer, len, 0);
    56. m.unlock();
    57. if (result < 0)
    58. {
    59. cout << "recv失败:" << endl;
    60. return false;
    61. }
    62. return true;
    63. }
    64. bool PlcSocket::closeSocket()
    65. {
    66. close(global_socket_fd);
    67. return true;
    68. }
    69. }
    1. /***
    2. MC协议的通讯方式有很多种:4C、3C、2C、1C、4E、3E、1E帧格式 数据格式分为二进制格式和ASCII码格式
    3. 本代码采用 3E + 二进制格式
    4. https://www.jianshu.com/p/ca7f1609c8c1
    5. ***/
    6. #pragma once
    7. #include <sys/sem.h>
    8. #include <sys/shm.h>
    9. #include <iostream>
    10. #include <memory>
    11. #include "socket.h"
    12. namespace MelsecMC
    13. {
    14. class MelsecMcClient : public std::enable_shared_from_this<MelsecMcClient>
    15. {
    16. public:
    17. using Ptr = std::shared_ptr<MelsecMcClient>;
    18. explicit MelsecMcClient();
    19. ~MelsecMcClient();
    20. bool connectTo(const string & ip, int port);
    21. bool disconnect();
    22. bool readInt32(std::string area,int start,int &value);
    23. bool writeInt32(std::string area,int start,int value);
    24. bool readShort(std::string area,int start,short &value);
    25. bool writeShort(std::string area,int start,short value);
    26. private:
    27. PlcSocket socket;
    28. unsigned char head[7] = {0x50,0x00,0x00,0xFF,0xFF,0x03,0x00};
    29. private:
    30. unsigned char decodeArea(std::string area);
    31. };
    32. }
    1. #include "client.h"
    2. #include <sstream>
    3. #include <iomanip>
    4. namespace MelsecMC
    5. {
    6. MelsecMcClient::MelsecMcClient()
    7. {
    8. }
    9. MelsecMcClient::~MelsecMcClient()
    10. {
    11. disconnect();
    12. }
    13. bool MelsecMcClient::connectTo(const string &ip, int port)
    14. {
    15. return socket.initSocket(ip, port, 2000);
    16. }
    17. bool MelsecMcClient::disconnect()
    18. {
    19. return socket.closeSocket();
    20. }
    21. bool MelsecMcClient::writeInt32(std::string area,int start,int value)
    22. {
    23. unsigned char cmd[25] = {0};
    24. //
    25. memcpy(cmd, head, 7);
    26. //50 00 00 FF FF 03 00 10 00 0A 00 01 14 00 00 64 00 00 A8 02 00 01 00 00 001
    27. //请求数据物理长度
    28. cmd[7] = 0x10;
    29. cmd[8] = 0x00;
    30. // CPU监视定时器 表示等待PLC响应的timeout时间
    31. cmd[9] = 0x0A;
    32. cmd[10] = 0x00;
    33. //写命令 跟读的差别是:读是0104,写是0114 ;就是0414的差别
    34. cmd[11] = 0x01;
    35. cmd[12] = 0x14;
    36. //(子命令) : 值是0表示按字读写入1个字=16位),如果值是1就按位写入
    37. cmd[13] = 0x00;
    38. cmd[14] = 0x00;
    39. //(首地址):地址因为跨度比较大,所以用了3个字节;值640000 返过来是000064,十进制就是100
    40. cmd[17] = start / 255 / 255 % 255;
    41. cmd[16] = start / 255 % 255;
    42. cmd[15] = start % 255;
    43. //(软元件 读取的区域) : 表示读取PLC寄存器的类型: 这里的A8表示D点;其他常见的有: 90-M点;9C-X点;9D-Y点;B0-ZR外部存储卡
    44. unsigned char areaHex = decodeArea(area);
    45. if (areaHex == 0x00)
    46. {
    47. std::cout << "不存在的地址 " << area << std::endl;
    48. return false;
    49. }
    50. cmd[18] = areaHex;
    51. //写入长度 00 02 =10进制2个字 =32= 4个字节 =1个int
    52. cmd[19] = 0x02;
    53. cmd[20] = 0x00;
    54. //写入int值
    55. cmd[24] = (value >> 24) & 0xFF;
    56. cmd[23] = (value >> 16) & 0xFF;
    57. cmd[22] = (value >> 8) & 0xFF;
    58. cmd[21] = value & 0xFF;
    59. if (!socket.write(cmd, sizeof(cmd)))
    60. {
    61. return false;
    62. }
    63. // 读取数据
    64. unsigned char recv[512] = {0};
    65. if (!socket.read(recv, sizeof(recv)))
    66. {
    67. return false;
    68. }
    69. if (recv[0] != 0xD0 && recv[1] != 0x00)
    70. {
    71. std::cout << "数据格式不正确" << std::endl;
    72. return false;
    73. }
    74. return true;
    75. }
    76. bool MelsecMcClient::readInt32(std::string area, int start, int &value)
    77. {
    78. unsigned char cmd[21] = {0};
    79. //
    80. memcpy(cmd, head, 7);
    81. //请求数据长度 也要反过来,值是000C,也就是12;表示后面的报文内容的长度是12
    82. cmd[7] = 0x0C;
    83. cmd[8] = 0x00;
    84. // CPU监视定时器 表示等待PLC响应的timeout时间
    85. cmd[9] = 0x0A;
    86. cmd[10] = 0x00;
    87. // 批量读命令 值是0401(所有值都要反过来看);表示批量读取;如果是1401就是随机写取;
    88. cmd[11] = 0x01;
    89. cmd[12] = 0x04;
    90. //(子命令) : 值是0表示按字读取(1个字=16位),如果值是1就按位读取
    91. cmd[13] = 0x00;
    92. cmd[14] = 0x00;
    93. //(首地址):地址因为跨度比较大,所以用了3个字节;值640000 返过来是000064,十进制就是100
    94. cmd[17] = start / 255 / 255 % 255;
    95. cmd[16] = start / 255 % 255;
    96. cmd[15] = start % 255;
    97. //(软元件 读取的区域) : 表示读取PLC寄存器的类型: 这里的A8表示D点;其他常见的有: 90-M点;9C-X点;9D-Y点;B0-ZR外部存储卡
    98. unsigned char areaHex = decodeArea(area);
    99. if (areaHex == 0x00)
    100. {
    101. std::cout << "不存在的地址 " << area << std::endl;
    102. return false;
    103. }
    104. cmd[18] = areaHex;
    105. // 读取长度 00 02 =10进制2个字 =32= 4个字节 =1个int
    106. cmd[19] = 0x02;
    107. cmd[20] = 0x00;
    108. // 发送数据
    109. if (!socket.write(cmd, sizeof(cmd)))
    110. {
    111. return false;
    112. }
    113. // 读取数据
    114. unsigned char recv[512] = {0};
    115. if (!socket.read(recv, sizeof(recv)))
    116. {
    117. return false;
    118. }
    119. // 解析数据
    120. // D0 00 00 FF FF 03 00 06 00 00 00 BB 02 96 49
    121. // D0 00 (响应) :表示反馈信息,固定D0 00
    122. // 00 (网络编号 ): 与上同
    123. // FF (PLC编号) : 与上同
    124. // FF 03 (请求目标模块IO编号) : 与上同
    125. // 00 (请求目标模块站编号): 与上同
    126. // 06 00 应答数据物理长度
    127. if (recv[0] != 0xD0 && recv[7] != 0x06)
    128. {
    129. std::cout << "数据格式不正确" << std::endl;
    130. return false;
    131. }
    132. value = recv[14] << 24 | recv[13] << 16 | recv[12] << 8 | recv[11];
    133. std::cout << "value " << value << std::endl;
    134. return true;
    135. }
    136. bool MelsecMcClient::readShort(std::string area,int start,short &value){
    137. unsigned char cmd[21] = {0};
    138. memcpy(cmd, head, 7);
    139. cmd[7] = 0x0C;
    140. cmd[8] = 0x00;
    141. cmd[9] = 0x0A;
    142. cmd[10] = 0x00;
    143. cmd[11] = 0x01;
    144. cmd[12] = 0x04;
    145. cmd[13] = 0x00;
    146. cmd[14] = 0x00;
    147. cmd[17] = start / 255 / 255 % 255;
    148. cmd[16] = start / 255 % 255;
    149. cmd[15] = start % 255;
    150. unsigned char areaHex = decodeArea(area);
    151. if (areaHex == 0x00)
    152. {
    153. std::cout << "不存在的地址 " << area << std::endl;
    154. return false;
    155. }
    156. cmd[18] = areaHex;
    157. cmd[19] = 0x01;
    158. cmd[20] = 0x00;
    159. if (!socket.write(cmd, sizeof(cmd)))
    160. {
    161. return false;
    162. }
    163. unsigned char recv[512] = {0};
    164. if (!socket.read(recv, sizeof(recv)))
    165. {
    166. return false;
    167. }
    168. if (recv[0] != 0xD0 && recv[7] != 0x04)
    169. {
    170. std::cout << "数据格式不正确" << std::endl;
    171. return false;
    172. }
    173. value = recv[12] << 8 | recv[11];
    174. std::cout << "value " << value << std::endl;
    175. return true;
    176. }
    177. bool MelsecMcClient::writeShort(std::string area,int start,short value){
    178. unsigned char cmd[23] = {0};
    179. memcpy(cmd, head, 7);
    180. cmd[7] = 0x0E;
    181. cmd[8] = 0x00;
    182. cmd[9] = 0x0A;
    183. cmd[10] = 0x00;
    184. cmd[11] = 0x01;
    185. cmd[12] = 0x14;
    186. cmd[13] = 0x00;
    187. cmd[14] = 0x00;
    188. cmd[17] = start / 255 / 255 % 255;
    189. cmd[16] = start / 255 % 255;
    190. cmd[15] = start % 255;
    191. unsigned char areaHex = decodeArea(area);
    192. if (areaHex == 0x00)
    193. {
    194. std::cout << "不存在的地址 " << area << std::endl;
    195. return false;
    196. }
    197. cmd[18] = areaHex;
    198. cmd[19] = 0x01;
    199. cmd[20] = 0x00;
    200. //写入short值
    201. cmd[22] = (value >> 8) & 0xFF;
    202. cmd[21] = value & 0xFF;
    203. if (!socket.write(cmd, sizeof(cmd)))
    204. {
    205. return false;
    206. }
    207. unsigned char recv[512] = {0};
    208. if (!socket.read(recv, sizeof(recv)))
    209. {
    210. return false;
    211. }
    212. if (recv[0] != 0xD0 && recv[1] != 0x00)
    213. {
    214. std::cout << "数据格式不正确" << std::endl;
    215. return false;
    216. }
    217. return true;
    218. }
    219. unsigned char MelsecMcClient::decodeArea(std::string area)
    220. {
    221. if (area == "D")
    222. {
    223. return 0xA8;
    224. }
    225. else if (area == "M")
    226. {
    227. return 0x90;
    228. }
    229. else if (area == "X")
    230. {
    231. return 0x9C;
    232. }
    233. else if (area == "Y")
    234. {
    235. return 0x9D;
    236. }
    237. else if (area == "ZR")
    238. {
    239. return 0xB0;
    240. }
    241. else
    242. {
    243. return 0x00;
    244. }
    245. }
    246. }
    1. #include "client.h"
    2. using namespace MelsecMC;
    3. int main(int argc, char **argv)
    4. {
    5. MelsecMcClient::Ptr client = std::make_shared<MelsecMcClient>();
    6. client->connectTo("10.10.14.60",6000);
    7. //int value;
    8. //melsecMcNet->readInt32("D",100,value);
    9. //client->writeInt32("D",101,122234);
    10. //client->writeShort("D",102,223);
    11. short value;
    12. client->readShort("D",102,value);
    13. return 0;
    14. }

    可利用 这个工具进行测试:

     协议参考:

    https://www.jianshu.com/p/ca7f1609c8c1

  • 相关阅读:
    【unocss】apply聚合语法,unocss配置
    Python 实践
    如何实现条件组合组件
    OC-块对象
    【Hive】快速入门~
    数据库及ADO.NET学习(七)
    1-Docker安装MySQL8.0
    网络篇 DHCP静态绑定地址-17.1
    $attrs,$listeners
    003_IO 扩展(串转并)-74HC595
  • 原文地址:https://blog.csdn.net/weixin_38416696/article/details/132709315