西门子plc 有snap7库 进行交互,并且支持c++ 而且跨平台。但是三菱系列PLC并没有现成的开源项目,没办法只能自己拼接,我这里实现了MC 协议 Qna3E 帧,并使用二进制进行交互。
- #pragma once
-
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #include
- #include
- using namespace std;
-
- namespace MelsecMC
- {
- class PlcSocket
- {
- private:
- bool is_open;
- int global_socket_fd; // 用于发送/接受数据
- mutex m;
-
- public:
- PlcSocket();
- ~PlcSocket();
-
- // 初始化socket
- bool initSocket(string ip, int port, int milSecond);
- // 关闭socket
- bool closeSocket();
- // 发送数据
- bool write(unsigned char *buffer, int len);
- // 接收数据
- bool read(unsigned char *buffer, int len);
- };
- }
- #include "socket.h"
- #include <chrono>
- #include <thread>
-
- namespace MelsecMC
- {
- PlcSocket::PlcSocket()
- {
- global_socket_fd = -1;
- }
-
- PlcSocket::~PlcSocket()
- {
- }
-
- bool PlcSocket::initSocket(string ip, int port, int milSecond)
- {
- // create
- int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
- if (socket_fd == -1)
- {
- cout << "socket 创建失败:" << endl;
- return false;
- }
-
- struct sockaddr_in addr;
- addr.sin_family = PF_INET;
- addr.sin_port = htons(port);
- addr.sin_addr.s_addr = inet_addr(ip.c_str());
-
- // connect
- int res = connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr));
- if (res == -1)
- {
- cout << "connect 链接失败:" << endl;
- return false;
- }
- cout << "connect 链接成功:" << endl;
-
- // 设置timeout
- setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, (char *)20, sizeof(int));
- setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char *)20, sizeof(int));
-
- cout << "setsockopt 成功:" << endl;
- global_socket_fd = socket_fd;
-
- return true;
- }
- bool PlcSocket::write(unsigned char *buffer, int len)
- {
- m.lock();
- long result = send(global_socket_fd, buffer, len, 0);
- m.unlock();
- if (result < 0)
- {
- return false;
- }
- return true;
- }
- bool PlcSocket::read(unsigned char *buffer, int len)
- {
- m.lock();
- long result = recv(global_socket_fd, buffer, len, 0);
- m.unlock();
- if (result < 0)
- {
- cout << "recv失败:" << endl;
- return false;
- }
- return true;
- }
- bool PlcSocket::closeSocket()
- {
- close(global_socket_fd);
- return true;
- }
- }
- /***
- MC协议的通讯方式有很多种:4C、3C、2C、1C、4E、3E、1E帧格式 数据格式分为二进制格式和ASCII码格式
- 本代码采用 3E + 二进制格式
- https://www.jianshu.com/p/ca7f1609c8c1
- ***/
- #pragma once
- #include <sys/sem.h>
- #include <sys/shm.h>
- #include <iostream>
- #include <memory>
- #include "socket.h"
-
- namespace MelsecMC
- {
- class MelsecMcClient : public std::enable_shared_from_this<MelsecMcClient>
- {
- public:
- using Ptr = std::shared_ptr<MelsecMcClient>;
-
- explicit MelsecMcClient();
- ~MelsecMcClient();
-
- bool connectTo(const string & ip, int port);
- bool disconnect();
-
- bool readInt32(std::string area,int start,int &value);
- bool writeInt32(std::string area,int start,int value);
-
- bool readShort(std::string area,int start,short &value);
- bool writeShort(std::string area,int start,short value);
-
- private:
- PlcSocket socket;
- unsigned char head[7] = {0x50,0x00,0x00,0xFF,0xFF,0x03,0x00};
-
- private:
- unsigned char decodeArea(std::string area);
- };
- }
- #include "client.h"
- #include <sstream>
- #include <iomanip>
- namespace MelsecMC
- {
- MelsecMcClient::MelsecMcClient()
- {
- }
-
- MelsecMcClient::~MelsecMcClient()
- {
- disconnect();
- }
-
- bool MelsecMcClient::connectTo(const string &ip, int port)
- {
- return socket.initSocket(ip, port, 2000);
- }
-
- bool MelsecMcClient::disconnect()
- {
- return socket.closeSocket();
- }
-
- bool MelsecMcClient::writeInt32(std::string area,int start,int value)
- {
- unsigned char cmd[25] = {0};
- // 头
- memcpy(cmd, head, 7);
-
- //50 00 00 FF FF 03 00 10 00 0A 00 01 14 00 00 64 00 00 A8 02 00 01 00 00 00 写1
- //请求数据物理长度
- cmd[7] = 0x10;
- cmd[8] = 0x00;
-
- // CPU监视定时器 表示等待PLC响应的timeout时间
- cmd[9] = 0x0A;
- cmd[10] = 0x00;
-
- //写命令 跟读的差别是:读是0104,写是0114 ;就是04和14的差别
- cmd[11] = 0x01;
- cmd[12] = 0x14;
-
- //(子命令) : 值是0表示按字读写入1个字=16位),如果值是1就按位写入
- cmd[13] = 0x00;
- cmd[14] = 0x00;
-
- //(首地址):地址因为跨度比较大,所以用了3个字节;值640000 返过来是000064,十进制就是100
- cmd[17] = start / 255 / 255 % 255;
- cmd[16] = start / 255 % 255;
- cmd[15] = start % 255;
-
- //(软元件 读取的区域) : 表示读取PLC寄存器的类型: 这里的A8表示D点;其他常见的有: 90-M点;9C-X点;9D-Y点;B0-ZR外部存储卡
- unsigned char areaHex = decodeArea(area);
- if (areaHex == 0x00)
- {
- std::cout << "不存在的地址 " << area << std::endl;
- return false;
- }
- cmd[18] = areaHex;
-
- //写入长度 00 02 =10进制2个字 =32位 = 4个字节 =1个int
- cmd[19] = 0x02;
- cmd[20] = 0x00;
-
- //写入int值
- cmd[24] = (value >> 24) & 0xFF;
- cmd[23] = (value >> 16) & 0xFF;
- cmd[22] = (value >> 8) & 0xFF;
- cmd[21] = value & 0xFF;
-
- if (!socket.write(cmd, sizeof(cmd)))
- {
- return false;
- }
-
- // 读取数据
- unsigned char recv[512] = {0};
- if (!socket.read(recv, sizeof(recv)))
- {
- return false;
- }
- if (recv[0] != 0xD0 && recv[1] != 0x00)
- {
- std::cout << "数据格式不正确" << std::endl;
- return false;
- }
-
- return true;
- }
-
- bool MelsecMcClient::readInt32(std::string area, int start, int &value)
- {
- unsigned char cmd[21] = {0};
- // 头
- memcpy(cmd, head, 7);
-
- //请求数据长度 也要反过来,值是000C,也就是12;表示后面的报文内容的长度是12
- cmd[7] = 0x0C;
- cmd[8] = 0x00;
-
- // CPU监视定时器 表示等待PLC响应的timeout时间
- cmd[9] = 0x0A;
- cmd[10] = 0x00;
-
- // 批量读命令 值是0401(所有值都要反过来看);表示批量读取;如果是1401就是随机写取;
- cmd[11] = 0x01;
- cmd[12] = 0x04;
-
- //(子命令) : 值是0表示按字读取(1个字=16位),如果值是1就按位读取
- cmd[13] = 0x00;
- cmd[14] = 0x00;
-
- //(首地址):地址因为跨度比较大,所以用了3个字节;值640000 返过来是000064,十进制就是100
- cmd[17] = start / 255 / 255 % 255;
- cmd[16] = start / 255 % 255;
- cmd[15] = start % 255;
-
- //(软元件 读取的区域) : 表示读取PLC寄存器的类型: 这里的A8表示D点;其他常见的有: 90-M点;9C-X点;9D-Y点;B0-ZR外部存储卡
- unsigned char areaHex = decodeArea(area);
- if (areaHex == 0x00)
- {
- std::cout << "不存在的地址 " << area << std::endl;
- return false;
- }
- cmd[18] = areaHex;
-
- // 读取长度 00 02 =10进制2个字 =32位 = 4个字节 =1个int
- cmd[19] = 0x02;
- cmd[20] = 0x00;
-
- // 发送数据
- if (!socket.write(cmd, sizeof(cmd)))
- {
- return false;
- }
-
- // 读取数据
- unsigned char recv[512] = {0};
- if (!socket.read(recv, sizeof(recv)))
- {
- return false;
- }
-
- // 解析数据
- // D0 00 00 FF FF 03 00 06 00 00 00 BB 02 96 49
- // D0 00 (响应) :表示反馈信息,固定D0 00
- // 00 (网络编号 ): 与上同
- // FF (PLC编号) : 与上同
- // FF 03 (请求目标模块IO编号) : 与上同
- // 00 (请求目标模块站编号): 与上同
- // 06 00 应答数据物理长度
- if (recv[0] != 0xD0 && recv[7] != 0x06)
- {
- std::cout << "数据格式不正确" << std::endl;
- return false;
- }
- value = recv[14] << 24 | recv[13] << 16 | recv[12] << 8 | recv[11];
- std::cout << "value " << value << std::endl;
- return true;
- }
-
- bool MelsecMcClient::readShort(std::string area,int start,short &value){
- unsigned char cmd[21] = {0};
- memcpy(cmd, head, 7);
-
- cmd[7] = 0x0C;
- cmd[8] = 0x00;
-
- cmd[9] = 0x0A;
- cmd[10] = 0x00;
-
- cmd[11] = 0x01;
- cmd[12] = 0x04;
-
- cmd[13] = 0x00;
- cmd[14] = 0x00;
-
- cmd[17] = start / 255 / 255 % 255;
- cmd[16] = start / 255 % 255;
- cmd[15] = start % 255;
-
- unsigned char areaHex = decodeArea(area);
- if (areaHex == 0x00)
- {
- std::cout << "不存在的地址 " << area << std::endl;
- return false;
- }
- cmd[18] = areaHex;
-
- cmd[19] = 0x01;
- cmd[20] = 0x00;
-
- if (!socket.write(cmd, sizeof(cmd)))
- {
- return false;
- }
-
- unsigned char recv[512] = {0};
- if (!socket.read(recv, sizeof(recv)))
- {
- return false;
- }
-
- if (recv[0] != 0xD0 && recv[7] != 0x04)
- {
- std::cout << "数据格式不正确" << std::endl;
- return false;
- }
- value = recv[12] << 8 | recv[11];
- std::cout << "value " << value << std::endl;
- return true;
- }
-
- bool MelsecMcClient::writeShort(std::string area,int start,short value){
- unsigned char cmd[23] = {0};
- memcpy(cmd, head, 7);
-
- cmd[7] = 0x0E;
- cmd[8] = 0x00;
-
- cmd[9] = 0x0A;
- cmd[10] = 0x00;
-
- cmd[11] = 0x01;
- cmd[12] = 0x14;
-
- cmd[13] = 0x00;
- cmd[14] = 0x00;
-
- cmd[17] = start / 255 / 255 % 255;
- cmd[16] = start / 255 % 255;
- cmd[15] = start % 255;
-
- unsigned char areaHex = decodeArea(area);
- if (areaHex == 0x00)
- {
- std::cout << "不存在的地址 " << area << std::endl;
- return false;
- }
- cmd[18] = areaHex;
-
- cmd[19] = 0x01;
- cmd[20] = 0x00;
-
- //写入short值
- cmd[22] = (value >> 8) & 0xFF;
- cmd[21] = value & 0xFF;
-
- if (!socket.write(cmd, sizeof(cmd)))
- {
- return false;
- }
-
- unsigned char recv[512] = {0};
- if (!socket.read(recv, sizeof(recv)))
- {
- return false;
- }
- if (recv[0] != 0xD0 && recv[1] != 0x00)
- {
- std::cout << "数据格式不正确" << std::endl;
- return false;
- }
-
- return true;
- }
-
- unsigned char MelsecMcClient::decodeArea(std::string area)
- {
- if (area == "D")
- {
- return 0xA8;
- }
- else if (area == "M")
- {
- return 0x90;
- }
- else if (area == "X")
- {
- return 0x9C;
- }
- else if (area == "Y")
- {
- return 0x9D;
- }
- else if (area == "ZR")
- {
- return 0xB0;
- }
- else
- {
- return 0x00;
- }
- }
- }
- #include "client.h"
-
- using namespace MelsecMC;
-
- int main(int argc, char **argv)
- {
- MelsecMcClient::Ptr client = std::make_shared<MelsecMcClient>();
- client->connectTo("10.10.14.60",6000);
-
- //int value;
- //melsecMcNet->readInt32("D",100,value);
-
- //client->writeInt32("D",101,122234);
- //client->writeShort("D",102,223);
- short value;
- client->readShort("D",102,value);
- return 0;
- }
-
可利用 这个工具进行测试:

协议参考:
https://www.jianshu.com/p/ca7f1609c8c1