西门子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

#include

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

#include

#include

#include

#include "socket.h"

namespace MelsecMC

{

class MelsecMcClient : public std::enable_shared_from_this

{

public:

using Ptr = std::shared_ptr;

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

#include

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();

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

精彩内容

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: