一、編寫緣由
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 }