Qt 基於QTcpSocket的ModbusTCP協議

一杯清酒邀明月發表於2024-03-09

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

相關文章