一、简介
1.modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。
2.modbus是工业领域通信协议的业界标准,并且现在是工业电子设备之间常用的连接方式。
3.包括RTU、ASCII、TCP三种报文类型,采用poll/slave(主/从)方式通信。
二、主机/从机
主机:处理业务逻辑,发出指令,接收从机反馈,一般只有一个
从机:接收并执行指令,对主机反馈执行结果,可以有多个,但存在数量限制(两个字节表示数量有限)
过程:主机处理业务逻辑后,发出当前所需要执行的指令,对应的从机接收到指令后开始执行,并将执行结果反馈回主机,一次完整的modbus通讯结束
三、Modbus-RTU
1.rtu通讯协议使用二进制数据,优势是信息密度高
2.数据帧
地址码 | 功能码 | 数据 | 校验码 |
1字节 | 1字节 | n字节 | 2字节(CRC校验) |
地址码-1字节 表示对应的从机
功能码-1字节 表示需执行的功能
数据块-n字节 表示此指令参数
校验码-2字节 一般使用crc循环校验算法计算
3.RTU没有帧头和帧尾,所以协议里明确两帧之间要大于3.5个字节时间间隔,作为一帧结束的判断依据
4.crc-modbus循环校验算法
void XCRC::InvertUint8(unsigned char* DesBuf, unsigned char* SrcBuf)
{
{
int i;
unsigned char temp = 0;
for (i = 0; i < 8; i++)
{
if (SrcBuf[0] & (1 << i))
{
temp |= 1 << (7 - i);
}
}
DesBuf[0] = temp;
}
}
void XCRC::InvertUint16(unsigned short* DesBuf, unsigned short* SrcBuf)
{
int i;
unsigned short temp = 0;
for (i = 0; i < 16; i++)
{
if (SrcBuf[0] & (1 << i))
{
temp |= 1 << (15 - i);
}
}
DesBuf[0] = temp;
}
unsigned short XCRC::CRC16_MODBUS(unsigned char* puchMsg, unsigned int usDataLen)
{
unsigned short wCRCin = 0xFFFF;
unsigned short wCPoly = 0x8005;
unsigned char wChar = 0;
while (usDataLen--)
{
wChar = *(puchMsg++);
InvertUint8(&wChar, &wChar);
wCRCin ^= (wChar << 8);
for (int i = 0; i < 8; i++)
{
if (wCRCin & 0x8000)
{
wCRCin = (wCRCin << 1) ^ wCPoly;
}
else
{
wCRCin = wCRCin << 1;
}
}
}
InvertUint16(&wCRCin, &wCRCin);
return (wCRCin);
}
5.示例
主机发送: 01 03 00 00 00 01 XX XX (X需要CRC计算)
地址码:01 //01从机执行
功能码:03 //查询指令
数据块:00 00 //起始寄存器地址 00 01 //向后查询1个寄存器
校验码:XX XX //前6字节通过crc算法计算出的值
从机回复: 01 03 02 12 34 XX XX (X需要CRC计算)
地址码:01 //01从机的反馈
功能码:03 //查询指令的反馈
数据块:02 //数据长度2字节(1个寄存器长度就是2字节) 12 34 //查询的寄存器储存的数据
校验码:XX XX //前5字节通过crc算法计算出的值
四、Modbus-ASCII
1.ascii通讯协议使用ascii码传输数据,优势是可读性高,人可以看懂
2.数据帧
起始 | 地址码 | 功能码 | 数据 | 校验 | 终止 |
字符 ':'(3A) | 2字节 | 2字节 | 0到2 * 252字节 | 2字节(LRC校验) | 2字节(CR,LF) |
起始码-1字节:固定字符 ':' ,十六进制为 3A
地址码、功能码和数据块:计算和rtu一致,但在最后二进制需转为ASCII码,此时字节长度会变为两倍
校验码-2字节:一般使用lrc校验算法计算
终止码-2字节:回车(CR) 十六进制为 0D 换行(LF) 十六进制为 0A
3.ASCII多了帧头和帧尾,也就是说可以有用这个头尾判断一帧字节来判断是否结束
4.lrc-modbus循环校验算法
unsigned char calculateLRC(unsigned char * auchMsg, unsigned short usDataLen)
{
unsigned char uchLRC = 0 ; /* LRC 初始化*/
while (usDataLen--) /* 完成整个报文缓冲区*/
uchLRC += *auchMsg++ ; /* 缓冲区字节相加,无进位*/
return ((unsigned char)(-((char)uchLRC))) ; /* 返回二进制补码*/
}
5.ascii码与十六进制的互相转换
void xcode::tosl(char data, char* txtdata) {
txtdata[0] = (data & 0xf0) >> 4;
txtdata[1] = (data & 0x0f) >> 0;
txtdata[0] = (txtdata[0] < 10 ? txtdata[0] + '0' : txtdata[0] + 'A' - 10);
txtdata[1] = (txtdata[1] < 10 ? txtdata[1] + '0' : txtdata[1] + 'A' - 10);
txtdata[2] = '\0';
}
void xcode::tonu(char* txtdata, char& data) {
data = 0x00;
data |= (txtdata[1] < 'A' ? (txtdata[1] - '0') << 0 : (txtdata[1] - 'A' + 10) << 0);
data |= (txtdata[0] < 'A' ? (txtdata[0] - '0') << 4 : (txtdata[0] - 'A' + 10) << 4);
}
6.示例
rtu: 01 03 00 00 00 01 XX XX (X需要LRC计算)
转ascii并添加起始码和终止码:“:3031303330303030303030313X3X3X3X\r\n”
所以
主机发送的ASCII数据为:“:3031303330303030303030313X3X3X3X\r\n”
主机发送的十六进制数据为:3A 01 03 00 00 00 01 XX XX 0D 0A
rtu: 01 03 02 12 34 XX XX (X需要LRC计算)
转ascii并添加起始码和终止码:“:303130333032313233343X3X3X3X\r\n”
所以
从机反馈的ASCII数据为:“:303130333032313233343X3X3X3X\r\n”
从机反馈的十六进制数据为:3A 01 03 02 12 34 XX XX 0D 0A
五、Modbus-TCP
1.主站为client端,主动建立连接;从站为server端,等待连接。
2.ModbusTCP的数据帧可分为两部分:MBAP+PDU
MBAP部分
事务处理标识 | 协议标识 | 长度 | 单元标识(地址码) |
2字节 | 2字节 | 2字节 | 1字节 |
事务处理标识 :可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文。
协议标识符 :00 00表示ModbusTCP协议。
长度 :表示接下来的数据长度,单位为字节。
单元标识符 :可以理解为设备地址,和rtu中地址码一样。
PDU部分
功能码 | 数据 |
1字节 | n字节 |
功能码和数据:rtu中含义一致。
3.ModbusTCP不需要crc或lrc验证
4.示例
主机发送: 01 C8 00 00 00 06 01 03 00 00 00 01
事务处理标识:01 C8 //每次通信之后就要加1以区别不同的通信数据报文
协议标识:00 00 //ModbusTCP协议
长度:00 06 //指令长度6个字节
地址码:01 //01从机执行
功能码:03 //查询指令
数据块:00 00 //起始寄存器地址 00 01 //向后查询1个寄存器
从机回复: 01 C8 00 00 00 05 01 03 02 12 34
事务处理标识:01 C8 //01 C8的通信数据报文的反馈
协议标识:00 00 //ModbusTCP协议
长度:00 05 //反馈长度5个字节
地址码:01 //01从机的反馈
功能码:03 //查询指令的反馈
数据块:02 //数据长度2字节(1个寄存器长度就是2字节) 12 34 //查询的寄存器储存的数据
六、常见指令
查询
主机发送: 01 03 00 00 00 01
从机回复: 01 03 02 12 34
/*发送解析*/
01-地址
03-功能码,代表查询功能,其他功能后面再说
00 00-代表查询的起始寄存器地址.说明从0x0000开始查询.
(这里需要说明以下,Modbus把数据存放在寄存器中,通过查询寄存器来得到不同变量的值,一个寄存器地址对应2字节数据;)
00 01-代表查询了一个寄存器.结合前面的00 00,意思就是查询从0开始的1个寄存器值;
/*回复解析*/
01-地址
03-功能码
02-代表后面数据的字节数,因为上面说到,一个寄存器有2个字节,所以后面的字节数肯定是2*查询的寄存器个数;
12 34-寄存器的值是12 34,结合发送的数据看出,01这个寄存器的值为12 34
修改
主机发送: 01 06 00 00 00 01
从机回复: 01 06 00 00 00 01
*发送解析*/
01-主机要查的地址
06-功能码,代表修改单个寄存器功能,修改有些不同,有修改一个寄存器和修改多个寄存器;
00 00-代表修改的起始寄存器地址.说明从0x0000开始.
00 01-代表修改的值为00 01.结合前面的00 00,意思就是修改0号寄存器值为00 01;
/*回复解析*/
01-从机返回的地址,说明这就是主机查的从机
06-功能码,代表修改单个寄存器功能;
00 00-代表修改的起始寄存器地址.说明是0x0000.
00 01-代表修改的值为00 01.结合前面的00 00,意思就是修改0号寄存器值为00 01;
主机发送: 01 10 00 00 00 01 02 11 22
从机回复: 01 10 00 00 00 01
/*发送解析*/
01-主机要查的地址
10-功能码,代表修改多个寄存器功能;
00 00-代表修改的起始寄存器地址.说明从0x0000开始.
00 01-代表修改的寄存器数量,这里开始于0x06的修改不同;
02 -表示修改的总字节数,由于只修改了1个寄存器,所以数据要有两个字节;
11 22-表示修改的值,结合上面,就是从第0000寄存器开始修改一个寄存器值为11 22,就是把0000寄存器改为11 22;
/*回复解析*/
01-从机返回的地址,说明这就是主机查的从机
10-功能码
00 00-代表修改的起始寄存器地址.说明是0x0000.
00 01-代表修改的寄存器数量,只需要回复这么多久足够了,从机告诉主机,你修改了哪几个寄存器就足够了;
七、参考
1.Modbus协议解析--小白一看就懂的协议 http://t.csdn.cn/dIRtk
2.MODBUS三种通讯模式RTU,ASCII,TCP,功能码,RCR校验 http://t.csdn.cn/F4Qz1
3.Modbus的CRC16和LRC计算方式 http://t.csdn.cn/j2bWY
————星辉20221018
Comments NOTHING