Qt ModbusTCP通讯
前言
Modbus在工业控制中的应用非常多,由于其免费使用加上一定的历史环境,Modbus在PLC上的通讯应用非常多,本文主要介绍Mosbus TCP master(主站)的实现。
一、关于Modbus
Modbus是由MODICON公司开发的一种工业现场总线协议标准,随后施耐德推出了基于TCP/IP的MOdbus协议:Modbus tcp;
Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。
标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。
Modbus有4种操作对象:线圈、离散输入、输入寄存器、保持寄存器
Coils、DiscreteInputs、InputRegisters、HoldingRegisters
- 线圈:PLC的输出位,开关量,在MOdbus中可读可写;
- 离散输入:PLC的输入位,开关量,在Modbus中只读;
- 输入寄存器:PLC中只能从模拟量输入端改变的寄存器,在MODBUS中只读
- 保持寄存器:PLC中用于输出模拟量信号的寄存器,在MODBUS中可读可写
由于是基于QT去实现的ModbusTCP通讯,所以对Modbus的功能码不需要做过多的掌握,了解即可。
二、Modbus TCP Master的实现
//主站的实现,一般都是上位机做主站,PLC做从站1.封装自己的Modbus类
QT.pro文件中添加 serialbus 模块:
让自定义类继承QObject,在头文件中添加相应的头文件
QModbusTcpClient 类 和 QModbusDataUnit 类
在自定义类中创建modbus TCP client 对象指针。
QModbusTcpClient *My_client;2.Modbus 通过TCP/IP进行连接
自定义类的构造函数中实例化Modbus tcp对象:
My_client = new QModbusTcpClient();Modbus TCP/IP协议进行连接的时候需要通过IP + Port ;
//端口号一般用502
1 /******************************************** 2 * 函数名称:Connect_to_modbus(QString IP_address,int Port) 3 * 功能:连接到modbus设备 4 * 工作方式: 5 * 参数: 6 参数1:modbus设备的IP地址 QString 类型 7 参数2:modbus设备的端口号(一般用502) int 类型 8 * 返回值:成功返回true,失败返回fasle。 9 * 备注: 10 * 修改记录 11 *********************************************/ 12 bool My_modbus_tcp::Connect_to_modbus(QString IP_address,int Port) 13 17 18 if (My_client>state() != QModbusDevice::ConnectedState) 28 else 32 } 33 34 else 38 39 }上述代码qDebug()<< "成功连接到modbs设备"不太合理,判断连接成功其实应该以Modbus的连接状态来进行判断的。
连接状态参考帮助手册 enum QModbusDevice::State:
有UnconnectedState、ConnectingState、ConnectedState、ClosingState4种
QModbusClient 类自带了状态改变的信号:QModbusClient::stateChanged
//我在自定义类中添加了两个信号,statechange_on()和statechange_off();用于判断当前是否连接
连接状态相关的代码如下:
先在自定义类的构造函数中绑定信号(QModbusClient::stateChanged)和槽(自定义槽函数)
1 connect(My_client, &QModbusClient::stateChanged, 2 this, &My_modbus_tcp::onStateChanged);//连接状态发生改变时处理函数(connect or discennect)实现状态改变的槽函数,状态进行变化时发出相应的信号,可以使用该信号进行信号与槽的绑定实现状态改变时的功能。
1 /******************************************** 2 * 函数名称:onStateChanged() 3 * 功能:监听TCP连接的状态,若状态发生改变,发出对应的信号 4 * 工作方式: 5 * 参数:无参数 6 * 返回值:无返回值 7 * 备注: 8 * 修改记录: 9 *********************************************/ 10 void My_modbus_tcp::onStateChanged() //连接状态改变时的槽函数 11 16 17 else 20 }3.Modbus 通过TCP/IP读取数据
Modbus对象的4种数据都可以进行读取,线圈和离散输入都是位数据,结果只能是0/1;输入寄存器和保持寄存器可以实现0x00~0xFF;
(1)读取线圈数据
1 /******************************************** 2 * 函数名称:read_modbus_tcp_Coils(int start_add,quint16 numbers ,int Server_ID) 3 * 功能:发送读取modbus设备线圈数据请求 4 * 工作方式: 5 * 参数: 6 * 参数1:int start_add 读取的起始地址 7 * 参数2:quint16 numbers 读取的个数 8 * 参数3:int Server_ID Modbus的设备ID 9 * 返回值:成功返回true,失败返回fasle。 10 * 备注: 11 * 修改记录: 12 *********************************************/ 13 bool My_modbus_tcp::read_modbus_tcp_Coils(int start_add,quint16 numbers,int Server_ID) 14 18 19 QModbusDataUnit ReadUnit(QModbusDataUnit::Coils,start_add,numbers); 20 qDebug()<< "配置ReadUnit完成"; 21 if (auto *reply = My_client>sendReadRequest(ReadUnit, Server_ID)) //1是Server_ID 22 29 else 30 35 36 } 37 38 else 42 }1 /******************************************** 2 * 函数名称:ReadReady_Coils() 3 * 功能:接收到读取请求后执行的槽函数 4 * 工作方式: 5 * 参数:无参数 6 * 返回值:没有返回值 7 * 备注: 8 * 修改记录 9 *********************************************/ 10 void My_modbus_tcp::ReadReady_Coils() 11 18 if (reply>error() == QModbusDevice::NoError) 19 32 33 } 34 else 35 37 38 reply>deleteLater(); // delete the reply 39 emit my_readC_finished(); //coils读取完成后emit 读取完成的信号; 40 41 }
读取离散变量时和读取线圈数据一样,唯一区别就是配置读取数据单元时换成
QModbusDataUnit ReadUnit(QModbusDataUnit::DiscreteInputs,start_add,numbers);
离散变量读取完成时发出自己的信号(自定义信号)。
(2)读取保持寄存器数据
1 /******************************************** 2 * 函数名称:read_modbus_tcp_HoldingRegisters(int start_add,quint16 numbers ,int Server_ID) 3 * 功能:发送读取modbus设备HoldingRegisters数据请求 4 * 工作方式: 5 * 参数 6 * 参数1:读取数据的起始地址 7 * 参数2:读取多少个数据 8 * 参数3:SerVer ID号 9 * 返回值:成功返回true,失败返回fasle。 10 * 备注: 11 * QModbusDataUnit ReadUnit(QModbusDataUnit::HoldingRegisters,参数1,参数2); 12 * 参数1:读取modbus设备的起始地址 int 类型 13 参数2:读取几个modbus数据 quint16 类型 14 * 修改记录: 15 *********************************************/ 16 bool My_modbus_tcp::read_modbus_tcp_HoldingRegisters(int start_add,quint16 numbers ,int Server_ID) 17 27 else 28 31 32 } 33 34 }1 /******************************************** 2 * 函数名称:ReadReady_HoldingRegisters() 3 * 功能:槽函数,发送请求成功后,接收数据将其存储在Hold_Bufer[]数组中 4 * 工作方式: 5 * 参数:无参数 6 * 返回值:没有返回值 7 * 备注: 8 * 修改记录 9 *********************************************/ 10 void My_modbus_tcp::ReadReady_HoldingRegisters() 11 16 if (reply>error() == QModbusDevice::NoError) 17 26 27 } 28 else 29 31 32 reply>deleteLater(); // delete the reply 33 emit my_readH_finished(); //自定义的信号 34 }
输入寄存器类似,换掉配置单元的数据即可。
(3)给线圈写入数据
1 /******************************************** 2 * 函数名称: Write_modbus_tcp_Coils(QString str1,int star_add,int number) 3 * 功 能: 将想要修改的数据写入到modbus设备某个(某些)地址的Coils中。 4 * 工作方式: 5 * 参 数: 6 * 参数1:要写入的数据(例:1 0 1 0 1 0) QString 类型 7 * 参数2:写入数据的起始地址 int 类型 8 * 参数3:写入数据的个数 quint16 9 * 返回值:没有返回值 10 * 备注:一次性可以写入单个或者多个数据,取决于该函数执行时参数。 11 * 修改记录 12 *********************************************/ 13 bool My_modbus_tcp::Write_modbus_tcp_Coils(QString str1,int star_add,int number) 14 25 26 if (auto *reply = My_client>sendWriteRequest(writeUnit, 1)) else if (reply>error() != QModbusDevice::NoError) 37 reply>deleteLater(); 38 }); 39 } else 43 } else 46 47 }!!!写数据时必须转化位16进制写入
(4)给保持寄存器写数据
1 /******************************************** 2 * 函数名称: Write_modbus_tcp_HoldingRegisters(QString str1,int star_add,int number) 3 * 功 能: 将想要修改的数据写入到modbus设备某个(某些)地址的HoldingRegisters中。 4 * 工作方式: 5 * 参 数: 6 * 参数1:要写入的数据(例:FF A0 00等) QString 类型 7 * 参数2:写入数据的起始地址 int 类型 8 * 参数3:写入数据的个数 quint16 9 * 返回值:没有返回值 10 * 备注:一次性可以写入单个或者多个数据,取决于该函数执行时参数。 11 * 修改记录 12 *********************************************/ 13 bool My_modbus_tcp::Write_modbus_tcp_HoldingRegisters(QString str1,int star_add,int number) 14 27 else 30 QString stt = str1.mid (j1,2); 31 bool ok; 32 quint16 hex1 =static_cast<quint16>(stt.toInt(&ok,16));//将textedit中读取到的数据转换为16进制发送 33 writeUnit.setValue(static_cast<int>(i1),hex1);//设置发送数据 34 } 35 36 if (auto *reply = My_client>sendWriteRequest(writeUnit, 1)) else if (reply>error() != QModbusDevice::NoError) 47 reply>deleteLater(); 48 }); 49 } else 53 } else 56 }Qt Modbus