https://blog.csdn.net/qq153471503/article/details/124317894
目录
Modbus是什么?
Modbus分类
Modbus-RTU协议数据帧结构
功能码01:读线圈状态
功能码02:读离散量输入
功能码03:读保持寄存器
功能码04:读输入寄存器
功能码05:写单个线圈
功能码06:写单个寄存器
功能码15:写多个线圈
功能码16:写多个寄存器
附录:Modbus CRC校验函数C语言实现
~~~~~~~~ Modbus是一个总线协议,属于应用层的一层协议。应用层面的协议还有TCP、UDP。因modbus其协议流程简单明了,易于组网被广泛使用,目前应该是在工业上使用的最多的,像是与PLC通信。
~~~~~~~~ 嵌入式领域最常见的用法就是硬件电路采用RS485,在此硬件基础上使用modbus。
Modbus协议分为三种,包括:
最常见使用的就是RTU了,所以本篇的重点放在讲解RTU上。
Modbus通讯过程
~~~~~~~~ Modbus是主从方式通信,通信由主机发起,一问一答式,从机无法主动向主机发送数据。通信方式类似于IIC、SPI协议。
~~~~~~~~ modbus数据帧在传输过程中,两个字节之间的相邻时间不得大于3.5个字符的时间,否则视为一帧数据传输结束。
~~~~~~~~ 以:波特率9600、1bit起始位、8bit数据位、1bit停止位,1bit校验位、无流控为例,那么1s内就可以传输(1+8+1+1)/9600*3.5*1000≈4ms,所以,如果从机在接收过程中,超过了4ms没有收到数据,则认为本帧数据接收结束;同样的,在发送完数据后也要延时等待4ms的延时时间。
《GB/T 19582.2》中规定:
~~~~~~~~
~~~~~~~~ RTU模式中每个字节为11位,格式为:8bit数据位(先发低位)、1bit起始位、1bit奇偶校验、1bit停止位
~~~~~~~~
~~~~~~~~ 要求使用偶校验。也可以使用其他模式(奇校验、无校验)。为了保证与其他产品的最大兼容性,建议还支持无校验模式。默认校验模式必须是偶校验。
~~~~~~~~
~~~~~~~~ 注:使用无校验时要求2个停止位,以此来满足11bit的数据。
~~~~~~~~
串行的传输字符的方法为:
发送每个字符或字节的顺序是从左到右,如下图:
地址码 | 功能码 | 数据区 | CRC校验 |
---|---|---|---|
1 Bytes | 1Byte | N Bytes | 2Byte |
地址码:1个字节的从机地址码,=0:广播地址,=1-247:从机地址,=248-255:保留
功能码:常用的就是01、02、03、04、05、06、15、16,具体描述见下图
数据区:数据区包含这么几部分:起始地址、数量、数据,这三项是大端模式
CRC校验:两个字节,小端模式,校验的数据范围为:地址码+功能码+数据区
下面将实际将常用的6个功能码进行实际的演示示例。
示例1:读1个线圈状态,线圈地址为0:
主机发送:01 01 00 00 00 01 FD CA
从机返回:01 01 01 00 51 88
解析主机发送的数据:
01 | 01 | 00 00 | 00 01 | FD CA |
---|---|---|---|---|
从机地址 | 功能码 | 要读的线圈起始地址(大端模式) | 要读取的线圈数量(大端模式) | CRC校验码(小端模式) |
解析从机返回的数据,只说数据区:
示例2:读从线圈0开始的10个线圈状态:
主机发送:01 01 00 00 00 0A BC 0D
从机返回:01 01 02 00 00 b9 fc
解析从机返回的数据,只说数据区:
协议格式同功能码01。
示例1:读1个保持寄存器,保持寄存器地址为0:
主机发送:01 03 00 00 00 01 84 0A
从机接收:01 03 02 00 00 b8 44
解析主机发送的数据:
01 | 03 | 00 00 | 00 01 | 84 0A |
---|---|---|---|---|
从机地址 | 功能码 | 要读取的保持寄存器起始地址(大端模式) | 要读取的保持寄存器数量(大端模式) | CRC校验码(小端模式) |
解析主机返回的数据,只说数据区:
示例2:读10个保持寄存器,保持寄存器起始地址为0:
主机发送:01 03 00 00 00 0A C5 CD
从机返回:01 03 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a3 67
解析主机返回的数据(只说数据区):
协议格式同功能码03。
示例1:写线圈0为0:
主机发送:01 05 00 00 00 00 CD CA
从机返回:01 05 00 00 00 00 cd ca
解析主机发送的数据:
01 | 05 | 00 00 | 00 00 | CD CA |
---|---|---|---|---|
从机地址 | 功能码 | 要写的线圈地址(大端模式) | 要写的线圈状态(大端模式) | CRC校验码(小端模式) |
主机发送什么,从机原样返回。
示例2:写线圈0为1:
主机发送:01 05 00 00 FF 00 8C 3A
从机返回:01 05 00 00 ff 00 8c 3a
解析主机发送的数据,只说数据区:
示例1:写寄存器0为0:
主机发送:01 06 00 00 00 00 89 CA
从机返回;01 06 00 00 00 00 89 CA
解析主机发送的数据:
01 | 06 | 00 00 | 00 00 | 89 CA |
---|---|---|---|---|
从机地址 | 功能码 | 要写的寄存器地址(大端模式) | 要写的寄存器(大端模式) | CRC校验码(小端模式) |
主机发送什么,从机原样返回。
示例2:写寄存器0为1:
主机发送:01 06 00 00 00 01 48 0A
从机返回:01 06 00 00 00 01 48 0a
写从线圈编号0开始的10个线圈:0-3线圈写1,4-7线圈写0,8-9线圈写1(0F 03):
主机发送:01 0F 00 00 00 0A 02 0F 03 A0 C9
从机返回:01 0f 00 00 00 0a d5 cc
解析主机发送的数据:
解析从机返回的数据,只说数据区:
写寄存器编号0开始的10个寄存器:0-3寄存器写1,4-7寄存器写0,8-9寄存器写1:
主机发送:01 10 00 00 00 0A 14 00 01 00 01 00 01 00 01 00 00 00 00 00 00 00 00 00 01 00 01 4F 13从机返回:01 10 00 00 00 0a 40 0e
解析主机发送的数据:
解析从机返回的数据,只说数据区:
USHORT usMBCRC16( UCHAR * pucFrame, USHORT usLen )
{
static const UCHAR aucCRCHi[] =
{
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40
};
static const UCHAR aucCRCLo[] =
{
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
0x41, 0x81, 0x80, 0x40
};
UCHAR ucCRCHi = 0xFF;
UCHAR ucCRCLo = 0xFF;
int iIndex;
while( usLen-- )
{
iIndex = ucCRCLo ^ *( pucFrame++ );
ucCRCLo = ( UCHAR )( ucCRCHi ^ aucCRCHi[iIndex] );
ucCRCHi = aucCRCLo[iIndex];
}
return ( USHORT )( ucCRCHi << 8 | ucCRCLo );
}