一、编写缘由
1.发现问题
最近项目上要把之前的modbus RTU改为TCP形式,因此之前的modbus通讯线程得重构,一开始当然是使用Qt自带的QModbusTcpClient类,很快就重构好线程,读取数据没有问题,但是只要一发送写数据请求,整个tcp连接就会断开,做了很多尝试,排除了从站的问题,即使直接连modbusslave也是出现这种问题。
2.查找问题
于是自己写了一个tcp server,抓取QModbusTcpClient写数据的报文,和modbuspoll上的对比,果然对不上,qt中的报文比modbuspoll上的多出来一截,想必是协议错误了。
3.解决策略
QModbusTcpClient不就是在tcp通讯上添加了modbus协议嘛,既然它的协议都错了,那就没有使用的必要了,我们直接用QTcpSocket手搓一个ModbusTcp类就好了。
二、代码编写
1.协议解析
通过modbuspoll上的通信日志和网络上的modbustcp协议分析文章对比,研究出协议的标准格式。ModbusTCP协议报文分析
2.封装函数
1 void writeCoil(quint16 address,bool value);
2 void writeCoils(quint16 address,QVector<bool> values);
3 void writeRegist(quint16 address,quint16 value);
4 void writeRegists(quint16 address,QVector<quint16> values);
我共封装了以上4个函数,分别是写单个线圈、写多个线圈、写单个保持寄存器、写多个保持寄存器。
具体实现如下:
1 void ModbusTcp::writeRegist(quint16 address,quint16 value)
2 {
3 QByteArray request;
4 request.resize(12);
5 request[0]=0x0;
6 request[1]=0x0;
7 request[2]=0x0;
8 request[3]=0x0;
9 request[4]=0x0;
10 request[5]=0x06;
11 request[6]=0x01;
12 request[7]=0x06;
13 uchar addh = address/256;
14 request[8]=addh;//起始地址h
15 uchar addl = address%256;
16 request[9]=addl;//起始地址l
17 uchar valueh = value/256;
18 uchar valuel = value%256;
19 request[10]=valueh;//值1h
20 request[11]=valuel;//值1l
21 client->write(request);
22 }
23
24 void ModbusTcp::writeRegists(quint16 address, QVector<quint16> values)
25 {
26 QByteArray request;
27 request.resize(13+values.count()*2);
28 request[0]=0x0;
29 request[1]=0x0;
30 request[2]=0x0;
31 request[3]=0x0;
32 uchar lenh = (7+values.count()*2)/256;
33 uchar lenl = (7+values.count()*2)%256;
34 request[4]=lenh;//往后包长度h
35 request[5]=lenl;//往后包长度l
36 request[6]=0x01;
37 request[7]=0x10;
38 uchar addh = address/256;
39 request[8]=addh;//起始地址h
40 uchar addl = address%256;
41 request[9]=addl;//起始地址l
42 uchar numh = values.count()/256;
43 uchar numl = values.count()%256;
44 request[10]=numh;//数量h
45 request[11]=numl;//数量l
46 request[12]=values.count()*2;//字节数
47 for(int i=0;i<values.count();i++){
48 uchar valueh = values.at(i)/256;
49 uchar valuel = values.at(i)%256;
50 request[13+i*2]=valueh;
51 request[14+i*2]=valuel;
52 }
53 client->write(request);
54 }
55
56 void ModbusTcp::writeCoil(quint16 address, bool value)
57 {
58 QByteArray request;
59 request.resize(12);
60 request[0]=0x0;
61 request[1]=0x0;
62 request[2]=0x0;
63 request[3]=0x0;
64 request[4]=0x0;
65 request[5]=0x06;
66 request[6]=0x01;
67 request[7]=0x05;
68 uchar addh = address/256;
69 uchar addl = address%256;
70 request[8]=addh;//起始地址h
71 request[9]=addl;//起始地址l
72 if(value)
73 request[10]=0xFF;
74 else
75 request[10]=0x0;
76 request[11]=0x0;
77 client->write(request);
78 }
79
80 void ModbusTcp::writeCoils(quint16 address, QVector<bool> values)
81 {
82 QByteArray request;
83 request.resize(13+ceil(values.count()/8));
84 request[0]=0x0;
85 request[1]=0x0;
86 request[2]=0x0;
87 request[3]=0x0;
88 uchar lenh = (7+ceil(values.count()/8))/256;
89 uchar lenl = int(7+ceil(values.count()/8))%256;
90 request[4]=lenh;//往后包长度h
91 request[5]=lenl;//往后包长度l
92 request[6]=0x01;
93 request[7]=0xF;
94 uchar addh = address/256;
95 request[8]=addh;//起始地址h
96 uchar addl = address%256;
97 request[9]=addl;//起始地址l
98 uchar numh = values.count()/256;
99 uchar numl = values.count()%256;
100 request[10]=numh;//数量h
101 request[11]=numl;//数量l
102 request[12]=ceil(values.count()/8);//字节数
103 QVector<uchar> bs;
104 uchar a=0;
105 uchar dy=values.count()%8;
106 for(uchar i=0;i<dy;i++){
107 if(values.at(values.count()-1-i))
108 a+=pow(2,i);
109 }
110 bs.append(a);
111 for(uchar i=0;i<values.count()/8;i++){
112 a = 0;
113 for(uchar j=dy+i*8;j<dy+i*8+8;j++){
114 if(values.at(values.count()-1-j))
115 a+=pow(2,(j-dy)%8);
116 }
117 bs.append(a);
118 }
119 for(uchar k=0;k<bs.count();k++){
120 request[13+k]=bs.at(bs.count()-1-k);
121 }
122 client->write(request);
123 }
四个函数中除了写多个线圈还有问题外,其他都已验证,可以正确写入。
最后,我的tcp是作为一个子线程的,线程初始化函数如下:
1 void ModbusTcp::initModbus()
2 {
3 client = new QTcpSocket(this);
4 connect(client,&QTcpSocket::readyRead,this,&ModbusTcp::parseData);
5 client->connectToHost("192.168.1.100",502);
6 if(client->waitForConnected(3000)){
7 qDebug()<<"trans connect success";
8 timer = new QTimer(this);
9 connect(timer,&QTimer::timeout,this,&ModbusTcp::startThread);
10 timer->setSingleShot(false);
11 timer->setInterval(1000);
12 timer->start();
13 }
14 else{
15 qDebug()<<"trans connect faild";
16 }
17 }