作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处
前言
之前博主写过一篇通过 MX 插件与三菱PLC通信的文章,具体请参考:Qt使用 MX Component 插件与三菱PLC通信
使用 MX 插件有一个弊端,那就是 MX 插件提供的 COM 组件是 32 位的,如果是 64 位的编译器则无法使用。因为一些特殊因素的影响,我们的项目需要从 32 位转到 64 位,所以 PLC 通信这一块就不能再使用 MX 插件了。考虑到我们的 PLC 是三菱Q系列,所以博主选择了 MC 协议。
从三菱官网下载了协议文档,也从网上查找了一些资料,发现都是对 MC 协议的解析,也有一些已经实现了的第三方库,但是没有实际的代码可以参考,而且第三方库都是 VB、C# 的。于是,博主自己做了一个例子,在这里分享一下,希望对大家有所帮助。
三菱 MC 协议介绍
- 下载官方文档
下载地址:网盘下载
提取码:rx8g
为了方便初学者快速上手,博主标记了一些重要的页数,方便快速切换查看:
- 目录 6
- 报文格式 40
- 指令及子指令 59
- 软元件 66
- 通信示例 84
-
MC 协议的功能
外部设备(个人计算机、显示器等)可以通过 MC 协议来管理可编程控制器设备的数据通信,简单来说就是允许外部设备通过 MC 协议来读写 PLC 里的寄存器。 -
通信方式
MC 协议的通讯方式有很多种:4C、3C、2C、1C、4E、3E、1E帧格式
数据格式分为二进制格式和ASCII码格式:
以二进制代码进行数据通信时,将2字节的数值从低位字节(L:位0~7)进行发送
以ASCII代码进行数据通信时,将数值转换为ASCII代码4位(16进制数)后从高位进行发送
博主选择的是最大众的:3E + 二进制格式 -
报文格式
- 头部 - 是以太网的帧头,通常自动被添加
- 副头部 - 以3E帧为例,固定请求报文 50 00
- Q头部 - 指定访问路径,以3E帧为例,解析如下:
网络编号 - 上位访问下位,固定 00
可编程控制器编号 - 上位访问下位,固定 FF
请求目标模块I/O编号 - 固定 03 FF,十进制是 1023(二进制格式从低位开始,所以为 FF 03)
请求目标模块站号 - 上位访问下位,固定 00 - 请求数据长 - 二进制格式两个数字为一个字,ASCII格式一个数字一个字(本例中14个字,为 0x000E,所以是 0E 00)
- 监视定时器 - 等待时间(0000H(0): 无限等待;0001H~FFFFH(1~65535)(单位: 250ms))10 00 即 0x0010,十进制为 16,16 * 250ms = 4 秒
- 请求数据 - 请求数据设置表示请求内容的指令等,解析如下:
指令 - 批量读取 04 01,批量写入 14 01
子指令 - 字单位(1个字=16位) 00 00,位单位 00 01
起始软元件编号 - 58 1B 00 即 001B58,十进制为 7000
软元件代码 - A8 代表 D点,90 代表 M点
软元件点数 - 01 00 即 0x0001,十进制为 1
写入数据 - 0C 00 即 0x00C,十进制为 12
- 头部 - 是以太网的帧头,通常自动被添加
- 副头部 - 以3E帧为例,固定响应报文 D0 00
- Q头部 - 和请求中的Q头部完全相同
- 响应数据长 - 02 00 即 0x0012,十进制为 2
- 结束代码 - 00 00,0 代表正确,写入成功
-
指令及子指令
最常用的指令:批量读取 04 01,批量写入 14 01
-
软元件代码
最常用的软元件代码:D点 A8,M点 90
-
错误代码
-
通信示例
-
从地址 D100 开始依次写入 3 个软元件的值:
50 00 00 FF FF 03 00 12 00 10 00 01 14 00 00 64 00 00 A8 03 00 95 19 02 12 30 11
D0 00 00 FF FF 03 00 02 00 00 00 -
读取地址 D100 开始连续 5 个软元件的值:
50 00 00 FF FF 03 00 0C 00 10 00 01 04 00 00 64 00 00 A8 05 00
D0 00 00 FF FF 03 00 0C 00 00 00 95 19 02 12 30 11 00 00 00 00
设置 PLC
-
下载 GX Works2
下载地址:网盘下载
提取码:2q5d -
新建工程,选择自己的 PLC 型号,博主这里用的是Q系列的 Q03UDV
-
设置 PLC 内置以太网参数
选择 TCP + MC 协议,如果需要和多个上位机通信,可以提供多个端口号
-
测试 PLC 是否连接成功
-
把配置写入 PLC
跳出弹窗,全部选择 “是”,写入完成之后需要给 PLC 断电重启 -
我的工程分享
下载地址:网盘下载
提取码:zxi9
在 Qt 中的应用
-
在 pro 中添加网络模块
使用 TCP 需要在 pro 文件中添加 network 模块:
QT += network
-
核心代码
初始化,绑定信号槽
// init
network = new QTcpSocket(this);
timeout = 1000;
connect(network, &QTcpSocket::connected, [](){ qDebug() << "Connected to PLC successfully !" << endl; });
connect(network, &QTcpSocket::disconnected, [](){ qDebug() << "Disconnected from plc !" << endl; });
connect(network, &QTcpSocket::stateChanged, [](QAbstractSocket::SocketState socketState){
qDebug() << "SocketState changed: " << socketState;
});
connect(network, &QTcpSocket::readyRead, [&](){ readData(); });
通过 ip 地址和端口号连接 PLC
network->connectToHost(ui->ip->text(), ui->port->text().toInt());
network->waitForConnected(timeout)
按照 MC 协议发送数据给 PLC
QString head = "50 00 00 FF FF 03 00";
QString length;
QString timeout = "10 00";
QString command = "01 14";
QString subCommand = "00 00";
QString address = convert10216(ui->address->text().toInt(), 6);
QString soft = ui->comboBox->currentIndex() == 0 ? "A8" : "90";
QString count = convert10216(ui->length->text().toInt(), 4);
QString data = timeout + " " + command + " " + subCommand + " " + address + " " + soft + " " + count + " " + str;
QString temp = data;
QRegExp regExp("[^a-fA-F0-9]");
temp.replace(regExp, "");
int len = temp.length()/2;
length = convert10216(len, 4);
QString ret = head + " " + length + " " + data;
QByteArray array = QByteArray::fromHex(ret.toLatin1()); // 使用 16 进制格式
network->write(array);
network->waitForBytesWritten();
network->flush();
接收 PLC 的响应
QByteArray array = network->readAll();
QString ret = array.toHex().toUpper();
- 效果展示