文章目录
- 一、MODBUS规约
- 1.MODBUS-RTU规约
- 2.MODBUS-TCP规约
- 二、报文解析
- 1.MODBUS-RTU报文帧解析
- 2.MODBUS-TCP报文帧解析
- 三、C++代码实现组织报文与解析报文
一、MODBUS规约
Modbus规约
是一种广泛使用的串行通信协议(应用层报文传输协议),用于工业环境中控制器和电子设备之间的数据交换。
Modbus协议的特点:
-
Modbus
是一种应用广泛的问答式通信规约信息交换是以主站采取主动实现的,即由主站启动交换。所有的一个完整交换由下行和上行两个报文组成:
- 下行报文:主站发出的一个请求到从站
- 上行报文:从站发回的一个回答到主站
-
MODBUS
是一种主/从架构Modbus
协议中,主设备发起通信请求,而从设备响应这些请求。 -
Modbus
设备中的数据存储在寄存器中,包括位寄存器和16位寄存器,每个寄存器都有一个唯一的地址 -
Modbus
支持多种通信方式,包括Modbus RTU
和Modbus TCP
。Modbus RTU
主要用于串口通信,而Modbus TCP
适用于以太网通信 -
Modbus
协议的帧格式简单紧凑,易于理解和实现。Modbus RTU
帧以CRC
校验(错误检测机制)结束,确保数据的完整性。而Modbus TCP
帧则不包含CRC
校验,因为TCP/IP
协议本身提供了错误检测和重传机制。 -
设备地址唯一性:在
Modbus
网络中,每个从设备都有一个唯一的地址,确保了通信的准确性 -
支持多种电气接口:
Modbus
可以支持多种电气接口,如RS-232、RS-485等,并且可以在多种介质上传输,包括双绞线、光纤、无线等
1.MODBUS-RTU规约
报文格式:
所有交换的 RTU 类型报文(帧),无论上/下行,具有相同的结构:
从站号 | 功能码 | 数据区 | CRC16校验和 |
---|---|---|---|
1字节 | 1字节 | n字节 | 2字节 |
每帧报文包含 4 种类型的信息:
-
从站号
从站号为 1 字节,取值范围为 0~FFH.例外的,如果此值为 0,则作为主站的广播信文标识.因此,物理上使用的从站号只能在 01H~FFH 之间(即1~255 之间)
-
功能码
功能码为 1 字节,它被用来选择一个命令(读、写或回答校验是否正确等),有效功能码范围为 1~255 之间。Modbus标准协议中可使用如下功能码:
功能码(十进制) 含义 01 读线圈状态 02 读离散输入量寄存器 03 读多个保持寄存器 04 读输入型寄存器 05 强制单个线圈 06 写单个寄存器 15 强制多个线圈 16 写多个保持寄存器 20 读变量 21 写变量 上述是Modbus协议支持的所有功能码(标准),并不代表所有支持Modbus-RTU通信协议的设备支持所有功能码。比如:
- 安科瑞
ADL400
导轨式多功能电能表的RS485
通信接口支持MODBUS-RTU
通信协议,但只支持MODBUS-RTU
协议中的03H
命令与10H
命令,03H
为读多个寄存器,10H
为写多个寄存器 - 高特
ESBCM
的物理接口同时支持485接口与TCP接口,支持的功能码有02、03、04、06、41 - 汇川PCS支持采用
MODBUS RTU
或MODBUS TCP/IP
通讯规约实现PCS与EMS之间的通信。支持的功能码有03、06 TX-CSL60-230
除湿机具有RS-485通信口,支持功能码03、06
功能码解读:
读线圈状态–功能码
01
:- 线圈通常指的是设备的输出线圈,如继电器或接触器的线圈
- 这个功能用于读取这些输出线圈的状态,即它们是被激活(闭合)还是未激活(断开)
读离散输入量寄存器–功能码
02
:-
这些寄存器一般是只读位寻址寄存器,一般可用于存储设备的告警信息,如:高特ESBCM
读多个保持寄存器–功能码
03
:-
这些寄存器是读写寻址寄存器,可接受多种数据类型,只要每一帧报文要采集的点位对应的数据类型相同即可,如安科瑞
ADL400
电能表
读输入型寄存器–功能码
04
:-
这些寄存器为只读字寻址寄存器,输入型寄存器通常包含模拟输入信号的数字化值,如从传感器或变位器获取的信息,如高特ESBCM
强制单个线圈–功能码
05
字面意思感觉是遥控的功能码,不确定
Modbus的数据传输被定义为对以下4个存储块的读写:(方便理解功能码的具体含义)
-
线圈–操作单位为1位字的开关量,PLC的输出位,在Modbus中可读可写
一般指的是触点的“开”与“关”的状态
-
离散量–操作单位为1位字的开关量,PLC的输入位,在Modbus中只读
-
输入寄存器-- 操作单位为16位字(两个字节)数据,PLC中只能从模拟量输入端改变的寄存器,在Modbus中只读
-
保持寄存器-- 操作单位为16位字(两个字节)数据,PLC中用于输出模拟量信号的寄存器,在Modbus中可读可写
- 安科瑞
-
数据区
数据区为 n 字节,它包含与功能码相关的一串十六进制数据,
如果是读,则包含寄存器起始地址与寄存器数量,数据格式基本一致。
- 寄存器起始地址指的是要通讯设备的寄存器地址,查设备的使用说明书就可以得到,如:
- 寄存器数指的是一帧报文连续读多少个寄存器
如果是写,则需要分写单个与写多个
- 示例
-
CRC16
校验码和CRC16
校验码通常被添加到数据帧的末尾,用于接收端校验数据的完整性。CRC
在线计算网址为http://www.ip33.com/crc.html,该报文帧为01 03 00 01 00 01 D5 CA
。
2.MODBUS-TCP规约
Modbus
由MODICON
公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IP
的Modbus
协议:Modbus-TCP
。与传统的串口方式,MODBUS TCP插入一个标准的MODBUS报文到TCP报文中,不再带有数据校验和地址。
Modbus-TCP
结合了Modbus
应用协议和TCP/IP
网络协议,主要用于工业自动化领域,允许设备通过以太网进行数据交换。
以下是Modbus-TCP
通信规约的关键点:
-
基于
TCP/IP
:Modbus-TCP
运行在TCP/IP
网络上,使用TCP作为传输层协议,确保数据的可靠传输 -
MBAP报文头:
Modbus-TCP
的数据帧包含一个Modbus
应用协议(MBAP
)报文头,长度为7字节,包含了事务处理标识、协议标识、长度和单元标识符等信息 -
端口号:
Modbus-TCP
通信通常使用TCP端口号502,这是由互联网编号分配管理机构(IANA
)为Modbus
协议指定的端口号 -
主从架构:
Modbus-TCP
遵循主从架构,有一个主站(客户端)和多个从站(服务器)。主站发起请求,从站响应请求 -
数据表示:
Modbus-TCP
在以太网TCP/IP
数据包中传输Modbus RTU
或ASCII
格式的数据帧,但不包含数据校验和地址 -
功能码:
Modbus-TCP
保留了Modbus RTU
和ASCII
的功能码,用于执行不同的操作,如读取和写入寄存器 -
异常响应:当从站设备无法正确处理主站的请求时,会返回异常功能码和错误代码,以指示错误类型
-
通信过程:
Modbus-TCP
的通信过程包括建立TCP
连接、发送Modbus
报文、接收响应报文和关闭TCP连接 -
数据帧格式:
Modbus-TCP
的数据帧格式包括MBAP
报文头和PDU
(协议数据单元),其中PDU
包含Modbus
功能码和数据
Modbus-TCP报文帧格式:
Modbus-TCP
的数据帧可分为两部分:MBAP+PDU
。
-
MBAP
报文头,长度为7个字节,组成如下:序号 名称 字节长度 说明 1 事务处理标识 2 可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文 2 协议标识 2 协议标识符, 00 00
表示ModbusTCP
协议3 长度 2 表示后续字节数,单位为字节 4 单元标识符 1 设备标识符,即从站地址 -
帧结构
PDU
,由功能码与数据区组成,-
功能码为1个字节,同上
-
数据区长度不定,同上
-
二、报文解析
1.MODBUS-RTU报文帧解析
以ADL400
导轨式多功能电能表为例,解析报文的请求(查询报文帧)与响应(返回数据帧)。
读A相电流数据:
请求/返回 | 报文数据 |
---|---|
RTU报文_请求 | 01 03 00 64 00 01 C5 D5 |
01
:从机地址03
:读功能码00 64
:寄存器起始地址00 01
:读取寄存器个数C5 D5
:CRC16校验码和
请求/返回 | 报文数据 |
---|---|
RTU报文_回复 | 01 03 02 03 B2 38 C1 |
-
01
:从机地址 -
03
:读功能码 -
02
:表示后面有两个字节长度的数据,如果一个寄存器占两个字节,则数据长度等于寄存器量*2 -
03 B2
:对应点位的数据,以十六进制表示,这里是A相电流数据处理如下:
03 B2
(十六进制) = 946(十进制)计算:946 * 0.01 = 9.46,单位:A
-
38 C1
:循环冗余校验码
读总有功电能数据:
请求/返回 | 报文数据 |
---|---|
RTU报文_请求 | 01 03 00 00 00 02 C4 0B |
01
:从机地址03
:读功能码00 00
:寄存器起始地址00 02
:读取寄存器个数C4 0B
:CRC16校验码和
请求/返回 | 报文数据 |
---|---|
RTU报文_返回 | 01 03 04 00 00 30 26 6F 9E |
-
01
:从机地址 -
03
:读功能码 -
04
:表示后面有4个字节长度的数据,如果一个寄存器占两个字节,则数据长度等于寄存器量*2 -
00 00 30 26
:对应点位的数据,需要用4个字节,以十六进制表示高位:00 00(16 进制) = 0 (10 进制)
低位:30 26(16 进制) = 12326 (10 进制)
因此该仪表二次测有功电能为:(0×65536 + 12326)*0.01 = 123.26;单位:kWh
2.MODBUS-TCP报文帧解析
以汇川IES1000-M-04-100
系列储能变流器PCS
为例,解析报文的请求(查询报文帧)与响应(返回数据帧)。
读取电网相电压与输出相电压:
请求/返回 | 报文数据 |
---|---|
TCP报文_请求 | 00 00 00 00 00 06 01 03 ED 64 00 06 |
00 00
:事务处理标识00 00
:表示ModbusTCP
协议00 06
:表示后续字节数为6个字节01
:从机地址03
:读功能码ED 64
:寄存器起始地址00 06
:寄存器的数量为6个
请求/返回 | 报文数据 |
---|---|
TCP报文_返回 | 00 00 00 00 00 0F 01 03 0C 09 7C 09 7D 09 8A 00 06 00 0A 00 0C |
-
00 00
:事务处理标识 -
00 00
:表示ModbusTCP
协议 -
00 0F
:表示后续字节数为15个 -
01
:从机地址 -
03
:读功能码 -
0C
:表示后面有12个字节长度的数据,如果一个寄存器占两个字节,则数据长度等于寄存器量*2 (6*2=12=0CH
) -
09 7C 09 7D 09 8A 00 06 00 0A 00 0C
:-
A相电网相电压
处理如下:
09 7c
(十六进制) = 2428(十进制)计算:2428 * 0.1 = 242.8,单位:V
-
B相电网相电压
处理如下:
09 7D
(十六进制) = 2429(十进制)计算:2429 * 0.1 = 242.9,单位:V
-
C相电网相电压
处理如下:
09 8A
(十六进制) = 2442(十进制)计算:2442 * 0.1 = 244.2,单位:V
-
A相输出相电压
处理如下:
00 06
(十六进制) = 6(十进制)计算:6 * 0.1 = 0.6,单位:V
-
B相输出相电压
处理如下:
00 0A
六进制) = 10(十进制)计算:10 * 0.1 = 1,单位:V
-
C相输出相电压
处理如下:
00 0C
(十六进制) = 12(十进制)计算:12 * 0.1 = 1.2,单位:V
-
写寄存器报文帧解析:
-
写单寄存器
-
写多寄存器
三、C++代码实现组织报文与解析报文
执行逻辑
-
首先,从数据库表中获取规约的编号,如:
ptl_modbus_cj
-
然后,加载规约动态库,得到该规约对应的组织数据与解析数据的函数
QLibrary m_LibPtl; //加载规约的libchar chParseFuncName[SIZE_SN] = "parseData"; // 解析报文函数 char chBuildFuncName[SIZE_SN] = "buildData"; //组织报文函数m_LibPtl.setFileName(m_qsPtlLibName); //设置外部动态链接库文件名 //调用QLibrary对象的load()方法来加载指定的库。如果库文件存在并且可以成功加载,此方法返回true;否则返回false。 if(m_LibPtl.load()) //加载动态链接库 {//resolve()方法被用来查找库中指定名称的函数,返回一个指向该函数的指针m_pFuncParseData = (PFunc_ParseData)(m_LibPtl.resolve(chParseFuncName)); //得到规约对应的解析数据与组织数据的函数m_pFuncbuildData = (PFunc_BuildData)(m_LibPtl.resolve(chBuildFuncName));}
-
通道通信发送数据处理
-
调用规约报文组织函数组织报文
m_pFuncbuildData(m_SPTLRecvSenddata, dynamic_cast<CUnitBase*>(m_pCurrentUnit)); //调用规约报文组织函数组织报文
PTL_API void buildData(S_RecvSendData &sRecvSendData, CUnitBase* pUnit) //组织报文 {S_PtlVariant sVariant;memset(&sVariant, 0, sizeof(S_PtlVariant));if(pUnit != NULL){sVariant.pUnit = pUnit;sVariant.pBySendBuffer = sRecvSendData.pSendBuf;sVariant.uwSendDataLen = sRecvSendData.uiSendLen;sVariant.bMainChannel = sRecvSendData.bMainChannel;sVariant.byChannelMode = sRecvSendData.byChannelMode;sVariant.byChannelIndex = sRecvSendData.byChannelIndex;sVariant.pPtlFlag = (S_PtlFlag*)(pUnit->getTUFlagBuffer()); // 获取规约标志位使用的缓冲区sendFrame(sVariant); //组织数据sRecvSendData.uiSendLen = sVariant.uwSendDataLen;} }
根据组织发送什么类型的报文,调用不同的组织函数
- 遥测查询帧
- 遥信查询帧
- 遥脉查询帧
- 遥调帧
- 遥控帧
- 校时命令
以组织发送遥测查询帧为例,
void buildCmdQueryYC(S_PtlVariant &sVariant) {if(sVariant.uwSendDataLen + 14 > sVariant.uwMaxSendLen){return;}quint16 uwTUAddr = sVariant.pUnit->getTUAddress();if(uwTUAddr > 255){return;}quint8 byDataLen = 0;quint8* pBySendBuffer = sVariant.pBySendBuffer + sVariant.uwSendDataLen;//校验和开始地址quint8* pSumPos = pBySendBuffer;pBySendBuffer[0] = uwTUAddr; //从站地址//功能码if(sVariant.pPtlFlag->byQueryYCNum >= sVariant.pPtlFlag->pPtlModbus->pModbusQueryYCVec.size()){sVariant.pPtlFlag->byQueryYCNum = 0;}const S_ModbusQuery* pModbusQuery = sVariant.pPtlFlag->pPtlModbus->pModbusQueryYCVec[sVariant.pPtlFlag->byQueryYCNum];pBySendBuffer[1] = pModbusQuery->byFUNCCODE;//寄存器起始地址HLquint16 uwRegStartAddr = pModbusQuery->uwStartAddr;if(MODBUS_BYTEORDER_TYPE_H_L == sVariant.pPtlFlag->pPtlModbus->pModbus->byRegisterByteOrder){uwRegStartAddr = qByteOrderConvert(uwRegStartAddr);}memcpy(pBySendBuffer + 2, &uwRegStartAddr, 2);//寄存器数量HLquint16 uwRegisterNum = pModbusQuery->byRegisterNum;uwRegisterNum = qByteOrderConvert(uwRegisterNum);memcpy(pBySendBuffer + 4, &uwRegisterNum, 2);byDataLen = 6;quint16 uwCheckSum = 0;if(buildCheckSum(sVariant, pSumPos, byDataLen, uwCheckSum)) //有校验和{memcpy(pBySendBuffer + byDataLen, &uwCheckSum, 2);byDataLen += 2;}buildTCPHead(sVariant, byDataLen); //TCP头sVariant.uwSendDataLen += byDataLen;sVariant.pPtlFlag->tLastSendYCTime = qGetCurrentTime();sVariant.pPtlFlag->byQueryYCNum++ ;sVariant.pPtlFlag->byQueryType = QUERYDATA_TYPE_YCDATA;sVariant.pPtlFlag->sendCmdQueryYC = 0;sVariant.pPtlFlag->bWaitDataReturn = 1;sVariant.pPtlFlag->byResentCount = sVariant.uwSendDataLen; }
其中,组织校验和与组织MBAP头的函数分别为
void buildTCPHead(S_PtlVariant &sVariant, quint8& byDataLen) //组织TCP头 调用该函数之前 其余报文已经组织完毕 {if(MODBUS_TYPE_TCP == sVariant.pPtlFlag->pPtlModbus->pModbus->byType){memmove(sVariant.pBySendBuffer + 6, sVariant.pBySendBuffer, byDataLen);sVariant.pBySendBuffer[0] = sVariant.pPtlFlag->pPtlModbus->pModbus->uwTCPHeadHigh;sVariant.pBySendBuffer[1] = sVariant.pPtlFlag->pPtlModbus->pModbus->uwTCPHeadLow;sVariant.pBySendBuffer[2] = 0;sVariant.pBySendBuffer[3] = 0;quint16 uwDataLen = byDataLen;uwDataLen = qByteOrderConvert(uwDataLen);memcpy(sVariant.pBySendBuffer + 4, &uwDataLen, sizeof(quint16));byDataLen += 6;} }bool buildCheckSum(S_PtlVariant &sVariant, quint8 *pStartCheckPost, quint8 byDataLen, quint16 &uwCheckSum) //组织校验和报文 返回字节转换后的校验后 {//校验if(MODBUS_TYPE_RTU == sVariant.pPtlFlag->pPtlModbus->pModbus->byType){quint8 byCheckSumType=sVariant.pPtlFlag->pPtlModbus->pModbus->byCheckCodeMode;if(CHECK_CODE_CRC == byCheckSumType) //CRC校验{uwCheckSum = getCheckCRC16SUM(pStartCheckPost, byDataLen);}else //累加和校验{uwCheckSum = getCheckCumlationSum(pStartCheckPost, byDataLen);}if(MODBUS_BYTEORDER_TYPE_H_L == sVariant.pPtlFlag->pPtlModbus->pModbus->byCCBYTEORDER){uwCheckSum = qByteOrderConvert(uwCheckSum);}return true;}return false; }
-
组织好报文后,基于通道通讯类型,分别调用串口或网络的发送数据接口
switch(m_pChannel->byChannelCOMType) {case CHANNEL_TYPE_TCP_CLIENT:case CHANNEL_TYPE_TCP_SERVER:case CHANNEL_TYPE_UDP_CLIENT:case CHANNEL_TYPE_UDP_SERVER:m_SocketMutex.lock();m_SPTLRecvSenddata.iSendedLen = m_pNetComm->sendData(m_pSocketHandle, m_SPTLRecvSenddata.pSendBuf, m_SPTLRecvSenddata.uiSendLen);m_SocketMutex.unlock();if(m_SPTLRecvSenddata.iSendedLen > 0){if(m_qsPtlLibName.contains("_zf")){//ljx debugqMSleep(50);}else{// qMSleep(30);}}break;case CHANNEL_TYPE_COM_485:case CHANNEL_TYPE_COM_232:{m_SPTLRecvSenddata.iSendedLen = m_pSerial->sendData(m_SPTLRecvSenddata.pSendBuf,static_cast<int>(m_SPTLRecvSenddata.uiSendLen));}break;case CHANNEL_TYPE_CAN:break;default:break; }
-
-
通道通信接收数据处理
-
基于通道类型,采用不同的处理方法将接收到的数据存放到接收缓存区中
switch(m_pChannel->byChannelCOMType) //通道类型 {case CHANNEL_TYPE_TCP_CLIENT:case CHANNEL_TYPE_TCP_SERVER:case CHANNEL_TYPE_UDP_CLIENT:case CHANNEL_TYPE_UDP_SERVER:m_NewRecvedDataMutex.lock();while(m_sNewRecvedDataList.size() > 0){S_NewRecvedData& sNewRecvedData = m_sNewRecvedDataList.first();quint32 uiFreeRecvBufLen = MAX_BUF_SIZE - m_SPTLRecvSenddata.uiRecvedLen; //剩余的接收缓冲区长度if(uiFreeRecvBufLen >= sNewRecvedData.uiRecvedLen) //若剩余的缓冲区长度大于新接收的缓冲区长度,则新接收到的数据全部复制 {memcpy(m_SPTLRecvSenddata.pRecvBuf + m_SPTLRecvSenddata.uiRecvedLen, sNewRecvedData.pRecvBuf, sNewRecvedData.uiRecvedLen);m_SPTLRecvSenddata.uiRecvedLen += sNewRecvedData.uiRecvedLen;m_sNewRecvedDataList.removeFirst();}else if(uiFreeRecvBufLen > 0) //若剩余的接收缓冲区长度大于0,则只复制剩余的接收缓冲区长度{memcpy(m_SPTLRecvSenddata.pRecvBuf + m_SPTLRecvSenddata.uiRecvedLen, sNewRecvedData.pRecvBuf, uiFreeRecvBufLen);m_SPTLRecvSenddata.uiRecvedLen += uiFreeRecvBufLen;sNewRecvedData.uiRecvedLen -= uiFreeRecvBufLen;memmove(sNewRecvedData.pRecvBuf, sNewRecvedData.pRecvBuf + uiFreeRecvBufLen, sNewRecvedData.uiRecvedLen);break;}else{break;}}m_NewRecvedDataMutex.unlock();break;case CHANNEL_TYPE_COM_485:case CHANNEL_TYPE_COM_232:m_uiMaxRecvLen = MAX_BUF_SIZE - m_SPTLRecvSenddata.uiRecvedLen;if(m_uiMaxRecvLen > 0){m_SPTLRecvSenddata.iRecvedLen = m_pSerial->recvData(m_SPTLRecvSenddata.pRecvBuf + m_SPTLRecvSenddata.uiRecvedLen,static_cast<int>(m_uiMaxRecvLen)); //从串口中接收数据if(m_SPTLRecvSenddata.iRecvedLen > 0){m_tRecvedTime = time(NULL);addCommMsg(true, static_cast<quint16>(m_SPTLRecvSenddata.iRecvedLen), m_SPTLRecvSenddata.pRecvBuf + m_SPTLRecvSenddata.uiRecvedLen); //添加通信报文setUpCommFault(false);setDownCommFault(false);m_SPTLRecvSenddata.uiRecvedLen += static_cast<quint32>(m_SPTLRecvSenddata.iRecvedLen);}else if(m_SPTLRecvSenddata.iRecvedLen < 0){setCommState(COMM_ERROR);}}break;case CHANNEL_TYPE_CAN:break;default:break; }
-
调用规约报文解析函数来解析报文
m_pFuncParseData(m_SPTLRecvSenddata, dynamic_cast<CUnitBase*>(m_pCurrentUnit)
PTL_API bool parseData(S_RecvSendData &sRecvSendData, CUnitBase* pUnit) //解析报文 {S_PtlVariant sVariant;memset(&sVariant, 0, sizeof(S_PtlVariant));if(pUnit != NULL){sVariant.pUnit = pUnit;sVariant.pByReadBuffer = sRecvSendData.pRecvBuf;sVariant.uwRecvDataLen = sRecvSendData.uiRecvedLen;sVariant.bMainChannel = sRecvSendData.bMainChannel;sVariant.byChannelMode = sRecvSendData.byChannelMode;sVariant.byChannelIndex = sRecvSendData.byChannelIndex;memset(sVariant.pParsedCommMsg, 0, MAX_BUF_SIZE);sVariant.uwParsedCommMsgLen = 0;sVariant.pPtlFlag = (S_PtlFlag*)(pUnit->getTUFlagBuffer());if(readFrame(sVariant)) //解析数据{sRecvSendData.uiRecvedLen = sVariant.uwRecvDataLen;return true;}}sRecvSendData.uiRecvedLen = sVariant.uwRecvDataLen;return false; }
根据要解析什么类型的报文,调用不同的解析函数
- 解析查询到的数据,可细分为遥测、遥信、遥脉
- 解析遥控命令的响应
- 解析遥调命令的响应
以解析查询到的数据为例,
bool readFrame(S_PtlVariant &sVariant) //解析数据 {if(!sVariant.pPtlFlag->bPtlInitOK){if(initPtl(sVariant)){sVariant.pPtlFlag->bPtlInitOK = true;}else{return false;}}//判断接收长度 数据包长度if(sVariant.uwRecvDataLen < 6){return false;}if(FRAME_STATE_NO == sVariant.byCurrentRecvedFrameState){ //移除到接受数据中的MBAP头parseFrameStateNoToSyncing(sVariant);if(sVariant.byCurrentRecvedFrameState != FRAME_STATE_SYNING){return false;}}if(FRAME_STATE_SYNING == sVariant.byCurrentRecvedFrameState){parseFrameStateSyncingToSync(sVariant);if(sVariant.byCurrentRecvedFrameState != FRAME_STATE_SYN){return false;}}memcpy(sVariant.pParsedCommMsg + sVariant.uwParsedCommMsgLen, sVariant.pByReadBuffer, sVariant.uwCurrentRecvedFrameLen);sVariant.uwParsedCommMsgLen += sVariant.uwCurrentRecvedFrameLen;sVariant.pUnit->addCommMsg(true, sVariant.uwParsedCommMsgLen, sVariant.pParsedCommMsg, sVariant.byChannelMode, sVariant.byChannelIndex, sVariant.bMainChannel);//数据接收完毕,开始解析帧switch(sVariant.byCurrentRecvedFrameType){case FRAME_TYPE_DATA:praseQueryedData(sVariant);break;case FRAME_TYPE_CMD:if (sVariant.pUnit->getHaveCmd() && sVariant.pUnit->isCmdProcing()){switch(sVariant.pUnit->getCmdType()){case CMD_TYPE_YK:praseAllYK(sVariant);break;case CMD_TYPE_YT:parseAllYT(sVariant);break;default:break;}}break;default:break;}sVariant.byCurrentRecvedFrameState = FRAME_STATE_NO;sVariant.byCurrentRecvedFrameType = FRAME_TYPE_MAX;offsetRecvBuffer(sVariant, sVariant.uwCurrentRecvedFrameLen);sVariant.uwCurrentRecvedFrameLen = 0;sVariant.pPtlFlag->bWaitDataReturn = 0;return true;}void praseQueryedData(S_PtlVariant &sVariant) {//目前没考虑校验和switch(sVariant.pPtlFlag->byQueryType){case QUERYDATA_TYPE_YCDATA:if(0 == sVariant.pPtlFlag->byQueryYCNum ||sVariant.pPtlFlag->byQueryYCNum > static_cast<quint8>(sVariant.pPtlFlag->pPtlModbus->pModbusQueryYCVec.size())){return;}if(sVariant.pByReadBuffer[1] != sVariant.pPtlFlag->pPtlModbus->pModbusQueryYCVec[sVariant.pPtlFlag->byQueryYCNum - 1]->byFUNCCODE){return;}praseYC(sVariant);break;case QUERYDATA_TYPE_YXDATA:if(0 == sVariant.pPtlFlag->byQueryYXNum ||sVariant.pPtlFlag->byQueryYXNum > static_cast<quint8>(sVariant.pPtlFlag->pPtlModbus->pModbusQueryYXVec.size())){return;}if (sVariant.pByReadBuffer[1] != sVariant.pPtlFlag->pPtlModbus->pModbusQueryYXVec[sVariant.pPtlFlag->byQueryYXNum - 1]->byFUNCCODE){return;}praseYX(sVariant);break;case QUERYDATA_TYPE_YMDATA:if(0 == sVariant.pPtlFlag->byQueryYMNum ||sVariant.pPtlFlag->byQueryYMNum > static_cast<quint8>(sVariant.pPtlFlag->pPtlModbus->pModbusQueryYMVec.size())){return;}if(sVariant.pByReadBuffer[1] != sVariant.pPtlFlag->pPtlModbus->pModbusQueryYMVec[sVariant.pPtlFlag->byQueryYMNum - 1]->byFUNCCODE){return;}praseYM(sVariant);break;default:break;} }
解析查询帧响应时,需要对于不同的数据类型进行不同的处理,并在解析完成后设置数据库表中对应点位的值
void praseYC(S_PtlVariant &sVariant) {const S_ModbusQuery* pModbusQuery = sVariant.pPtlFlag->pPtlModbus->pModbusQueryYCVec[sVariant.pPtlFlag->byQueryYCNum -1];switch(sVariant.pPtlFlag->pPtlModbus->pModbusQueryYCVec[sVariant.pPtlFlag->byQueryYCNum - 1]->byDataType){case DATA_TYPE_UINT8:{double dYCValue = 0;quint8 bytempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint8);for(quint32 i = 0; i < uiCount; ++i){bytempYCValue = sVariant.pByReadBuffer[3 + i];dYCValue = double(bytempYCValue);sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_INT8:{double dYCValue = 0;qint8 bytempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(qint8);for(quint32 i = 0; i < uiCount; ++i){bytempYCValue = sVariant.pByReadBuffer[3 + i];dYCValue = double(bytempYCValue);sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_UINT16:{quint32 uiTUModelID = 0;char chTUModelName[SIZE_NAME] = "";sVariant.pUnit->getValue(S_DataID(TABLE_TU, sVariant.pUnit->getTUID(), COL_TU_TUMODELID), &uiTUModelID);sVariant.pUnit->getValue(S_DataID(TABLE_TUMODEL, uiTUModelID, COL_TUMODEL_TUMODELNAME), chTUModelName);QString qsTUModelName = QString(chTUModelName);double dYCValue = 0;quint16 uwTempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint16);for(quint32 i = 0; i < uiCount; ++i){memcpy(&uwTempYCValue, sVariant.pByReadBuffer + 3 + i * sizeof(quint16), sizeof(quint16));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){uwTempYCValue = qByteOrderConvert(uwTempYCValue);}uwTempYCValue = qBigLittleEndianConvert(uwTempYCValue);dYCValue = uwTempYCValue;if(qsTUModelName.contains("DLJTCW") && uwTempYCValue == 0xffff){qDebug()<<"值为:0XFFFF不上送";continue;}sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_INT16:{double dYCValue = 0;qint16 wTempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(qint16);for(quint32 i = 0; i < uiCount; ++i){memcpy(&wTempYCValue, sVariant.pByReadBuffer + 3 + i * sizeof(qint16), sizeof(qint16));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){wTempYCValue = qByteOrderConvert(wTempYCValue);}wTempYCValue = qBigLittleEndianConvert(wTempYCValue);dYCValue = wTempYCValue;sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_UINT32:{double dYCValue = 0;quint32 uiTempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint32);for(quint32 i = 0; i < uiCount; ++i){memcpy(&uiTempYCValue, sVariant.pByReadBuffer + 3 + i * sizeof(quint32), sizeof(quint32));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){uiTempYCValue = qByteOrderConvert(uiTempYCValue);}uiTempYCValue = qBigLittleEndianConvert(uiTempYCValue);dYCValue = uiTempYCValue;sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_INT32:{double dYCValue = 0;qint32 iTempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(qint32);for(quint32 i = 0; i < uiCount; ++i){memcpy(&iTempYCValue, sVariant.pByReadBuffer + 3 + i * sizeof(qint32), sizeof(qint32));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){iTempYCValue = qByteOrderConvert(iTempYCValue);}iTempYCValue = qBigLittleEndianConvert(iTempYCValue);dYCValue = iTempYCValue;sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_FLOAT:{double dYCValue = 0;float fTempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(float);for(quint32 i = 0; i < uiCount; ++i){memcpy(&fTempYCValue, sVariant.pByReadBuffer + 3 + i * sizeof(float), sizeof(float));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){fTempYCValue = qByteOrderConvert(fTempYCValue);}fTempYCValue = qBigLittleEndianConvert(fTempYCValue);dYCValue = fTempYCValue;sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_DOUBLE:{double dYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(double);for(quint32 i = 0; i < uiCount; ++i){memcpy(&dYCValue, sVariant.pByReadBuffer + 3 + i * sizeof(double), sizeof(double));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){dYCValue = qByteOrderConvert(dYCValue);}dYCValue = qBigLittleEndianConvert(dYCValue);sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;default:break;} }
-
补充:
- 数据类型如何影响报文帧
在解析数据时,对不同的数据类型进行不同的解析处理- 字节序如何影响报文
大端字节序通常被认为是“网络字节序”,若系统的硬件架构是按照小端存储的,则网络字节序需先转换成小端字节序再直接解析- 设备的点位由字节的一个位来决定(遥信点位)
此时,Modbus查询帧表中数据类型为单字节(也可以是其他,由设备说明书决定),是否位偏移设置为是,
void praseYX(S_PtlVariant &sVariant)
{const S_ModbusQuery* pModbusQuery = sVariant.pPtlFlag->pPtlModbus->pModbusQueryYXVec[sVariant.pPtlFlag->byQueryYXNum -1];bool bIfOffset = sVariant.pPtlFlag->pPtlModbus->pModbusQueryYXVec[sVariant.pPtlFlag->byQueryYXNum -1]->bISOffset;quint32 uiIndex = 0;if(bIfOffset){switch(sVariant.pPtlFlag->pPtlModbus->pModbusQueryYXVec[sVariant.pPtlFlag->byQueryYXNum -1]->byDataType){case DATA_TYPE_UINT8:case DATA_TYPE_INT8:{quint8 bytempYXValue = 0;quint16 uwYXValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount;for(quint32 i = 0; i < uiCount; ++i){bytempYXValue = sVariant.pByReadBuffer[3 + i];for(quint32 j = 0; j < 8; ++j){uwYXValue = (bytempYXValue >> j) & 0x01;uiIndex = i * 8 + pModbusQuery->uwStartINFO + j;if(!sVariant.pUnit->isCommStateYX(uiIndex)) //不设置通信状态相关的遥信{sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYX, uiIndex, COL_PREYX_YXVALUE), &uwYXValue);}}}}break;case DATA_TYPE_UINT16:case DATA_TYPE_INT16:{quint16 uwTempYXValue = 0;quint16 uwYXValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint16);for(quint32 i = 0; i < uiCount; ++i){memcpy(&uwTempYXValue, sVariant.pByReadBuffer + 3 + i * sizeof(quint16), sizeof(quint16));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){uwTempYXValue = qByteOrderConvert(uwTempYXValue);}uwTempYXValue = qBigLittleEndianConvert(uwTempYXValue);for(quint32 j = 0; j < 16; ++j){uwYXValue = (uwTempYXValue >> j) & 0x01;uiIndex = i * 16 + pModbusQuery->uwStartINFO + j;if(!sVariant.pUnit->isCommStateYX(uiIndex)) //不设置通信状态相关的遥信{sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYX, uiIndex, COL_PREYX_YXVALUE), &uwYXValue);}}}}break;case DATA_TYPE_UINT32:case DATA_TYPE_INT32:{quint32 uiTempYXValue = 0;quint16 uwYXValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint32);for(quint32 i = 0; i < uiCount; ++i){memcpy(&uiTempYXValue, sVariant.pByReadBuffer + 3 + i * sizeof(quint32), sizeof(quint32));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){uiTempYXValue = qByteOrderConvert(uiTempYXValue);}uiTempYXValue = qBigLittleEndianConvert(uiTempYXValue);for(quint32 j = 0; j < 32; ++j){uwYXValue = (uiTempYXValue >> j) & 0x01;uiIndex = i * 32 + pModbusQuery->uwStartINFO + j;if(!sVariant.pUnit->isCommStateYX(uiIndex)) //不设置通信状态相关的遥信{sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYX, uiIndex, COL_PREYX_YXVALUE), &uwYXValue);}}}}break;default:break;}}else{switch(sVariant.pPtlFlag->pPtlModbus->pModbusQueryYXVec[sVariant.pPtlFlag->byQueryYXNum -1]->byDataType){case DATA_TYPE_UINT8:case DATA_TYPE_INT8:{quint16 uwYXValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount;for(quint32 i = 0; i < uiCount; ++i){uwYXValue = sVariant.pByReadBuffer[3 + i];uiIndex = pModbusQuery->uwStartINFO + i;if(!sVariant.pUnit->isCommStateYX(uiIndex)) //不设置通信状态相关的遥信{sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYX, uiIndex, COL_PREYX_YXVALUE), &uwYXValue);}}}break;case DATA_TYPE_UINT16:case DATA_TYPE_INT16:{quint16 uwYXValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint16);for(quint32 i = 0; i < uiCount; ++i){memcpy(&uwYXValue, sVariant.pByReadBuffer + 3 + i * sizeof(quint16), sizeof(quint16));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){uwYXValue = qByteOrderConvert(uwYXValue);}sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYX, pModbusQuery->uwStartINFO + i, COL_PREYX_YXVALUE), &uwYXValue);}}break;case DATA_TYPE_UINT32:case DATA_TYPE_INT32:{quint32 uiYXvalue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint32);for(quint32 i = 0; i < uiCount; ++i){memcpy(&uiYXvalue, sVariant.pByReadBuffer + 3 + i * sizeof(quint32), sizeof(quint32));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){uiYXvalue = qByteOrderConvert(uiYXvalue);}sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYX, pModbusQuery->uwStartINFO + i, COL_PREYX_YXVALUE), &uiYXvalue);}}break;default:break;}}
}
参考文献:
-
Modbus-3: Modbus TCP通信协议解析
-
modbus tcp 协议详解