Modbus协议——ModbusRTU、ModbusASCII

Modbus 是一项应用层报文传输协议,用于在通过不同类型的总线或网络连接的设备之间的客户机/服务器通信。常用于串口、以太网上的数据传输。

Modbus数据帧格式

  • ADU:应用数据单元。最大不超过256个字节。一次Modbus通讯报文,可以被成为1个ADU。
  • PDU:协议数据单元。指代Modbus通讯报文中的 功能码 + 数据

串行通讯上的实现

Modbus 串行链路协议是一个主/从协议

常用物理接口为RS485、RS232、RS422

Modbus串行链路上所有设备的 串口参数传输模式 必须相同。

串口参数

参数名称 常见选项
波特率 9600、19200、38400、115200 (单位:bps)
数据长度 8 、7 (单位:bit)
校验方式 偶校验(Even)、奇校验(Odd)、无校验(None)
停止位 1、2 (单位:bit)

传输模式

该协议在串口上2种传输模式:Modbus-RTUModbus-ASCII

两种传输模式的不同点

Modbus-RTU Modbus-ASCII
编码格式 二进制码 ASCII码
起始符 “冒号”(:)(ASCII码:0x3A)
结束符 “回车 + 换行”(CR LF)(ASCII码:0x0D、0x0A)
报文起停判断 时间间隔 起始符+结束符
校验方式 CRC LRC

Modbus-RTU

数据帧格式

从站地址(站号) 功能码 数据 CRC校验
1 byte 1 byte 0~252 byte 2 byte

从站地址(站号)

广播地址 从站地址 保留
0 1~247 248~255
  • 地址0 为广播地址。所有的子节点必须识别广播地址。(标准而已,并不是所有从站都完全遵照标准设计。在实际应用中较为少见)
  • Modbus主站没有节点地址,Modbus从站则必须拥有一个在串行链路上唯一的节点地址

功能码

功能描述 功能码(十进制) 功能码(十六进制) 备注
读线圈 01 0x01 常用
写单个线圈 05 0x05
写多个线圈 15 0x0F
读输入寄存器 04 0x04
读保持寄存器 03 0x03 常用
写单个保持寄存器 06 0x06
写多个保持寄存器 16 0x10 常用
读/写多个保持寄存器 23 0x17

功能码 0x03 命令格式解析

请求命令格式

大小 取值范围 备注
从站地址 1 byte 0x01 至 0xFF
功能码 1 byte 0x03
起始地址 2 byte 0x0000 至 0xFFFF
寄存器数量(N) 2 byte 0x0000 至 0x007D
CRC校验 2 byte 0x0000 至 0xFFFF 所有数据的CRC校验

响应命令格式

大小 取值范围 备注
从站地址 1 byte 0x01 至 0xFF
功能码 1 byte 0x03
字节数 1 byte 2 * N 寄存器值所包括的字节数
寄存器值 2 * N byte 数据值 1个寄存器占用2个字节
CRC校验 2 byte 0x0000 至 0xFFFF 所有数据的CRC校验

注:N = 寄存器数量

错误响应格式

大小 取值范围
从站地址 1 byte 0x01 至 0xFF
错误码 1 byte 0x83 (0x80 + 0x03)
异常码 1 byte 0x01:该功能码不支持
0x02:起始地址不支持 或者 起始地址+寄存器数量不支持
0x03:读取寄存器数量不支持
0x04:读取寄存器失败
CRC校验 2 byte 0x0000 至 0xFFFF

eg.使用 功能码0x03 读取 站号01 地址0x0000~0x0009 的数据

请求命令:SEND HEX>

(站号)01 (功能码)03 (起始地址)00 00 (读取寄存器数量)00 0A (CRC校验)C5 CD

响应命令:RECV HEX>

(站号)01 (功能码)03 (字节数)14 (读取的各个寄存器值)00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09 00 0A (CRC校验)8F 16

ModbusSlave03

功能码 0x10 命令格式解析

请求命令格式

大小 取值范围 备注
从站地址 1 byte 0x01 至 0xFF
功能码 1 byte 0x10
起始地址 2 byte 0x0000 至 0xFFFF
寄存器数量(N) 2 byte 0x0000 至 0x007D
字节数 1 byte 2 * N 寄存器值所包括的字节数
寄存器值 2 * N byte 数据值 1个寄存器占用2个字节
CRC校验 2 byte 0x0000 至 0xFFFF 所有数据的CRC校验

注:N = 寄存器数量

响应命令格式

大小 取值范围 备注
从站地址 1 byte 0x01 至 0xFF
功能码 1 byte 0x03
起始地址 2 byte 0x0000 至 0xFFFF
寄存器数量(N) 2 byte 0x0000 至 0x007D
CRC校验 2 byte 0x0000 至 0xFFFF 所有数据的CRC校验

错误响应格式

大小 取值范围
从站地址 1 byte 0x01 至 0xFF
错误码 1 byte 0x90 (0x80 + 0x10)
异常码 1 byte 0x01:该功能码不支持
0x02:起始地址不支持 或者 起始地址+寄存器数量不支持
0x03:读取寄存器数量不支持
0x04:读取寄存器失败
CRC校验 2 byte 0x0000 至 0xFFFF

eg.使用 功能码0x10 写入 站号01 地址0x0000~0x0009 的数据

请求命令:SEND HEX >

(站号)01 (功能码)10 (起始地址)00 00 (写入寄存器数量)00 0A (字节数)14 (各个寄存器的写入值)00 0A 00 14 00 1E 00 28 00 32 00 3C 00 46 00 50 00 5A 00 64 (CRC校验)69 8A

响应命令:RECV HEX >

(站号)01 (功能码)10 (起始地址)00 00 (寄存器数量)00 0A (CRC校验)40 0E

ModbusSlave10

CRC校验

除了CRC校验码外的所有Modbus-RTU帧的数据,均需要进行CRC校验计算。

移位法计算步骤:

步骤一:加载一个内容为 0xFFFF 的 16位缓存器,称之为『CRC』缓存器。

步骤二:将需要计算的数据的第一个字节与 CRC 缓存器的低字节进行 异或运算(XOR),并将结果存回 CRC 缓存器。

步骤三:检查 CRC 缓存器的最低位(LSB),若此位为 0,则右移一位;若此位为 1,则 CRC 缓存器值右移一位后,再与 0xA001 进行 异或运算(XOR)。

步骤四:回到步骤三,直到步骤三已被执行过 8 次(即当前字节已经处理完),才进到步骤五。

步骤五:对数据的下一个字节重复步骤二到步骤四,直到所有字节皆完全处理过,此时 CRC 缓存器的内容即是 CRC校验码。

半查表法

鉴于移位法、全查表法网上的资料已经太多了,这里介绍另外一种计算CRC校验码的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//codesys3.5 Structured Text

//{attribute 'hide_all_locals'}
FUNCTION F_CRC16 : WORD
VAR_INPUT
pData : POINTER TO BYTE ; //计算数据首字节的指针 adr(data)
usiLen : USINT ; //计算数据的长度
END_VAR
VAR
i : USINT ;
temp : BYTE ;
result : WORD ;
END_VAR
VAR CONSTANT
aTable : ARRAY[0..15] OF WORD :=[16#0, 16#CC01, 16#D801, 16#1400, 16#F001, 16#3C00, 16#2800, 16#E401, 16#A001, 16#6C00, 16#7800, 16#B401, 16#5000, 16#9C01, 16#8801, 16#4400];
END_VAR

//半字节查表法计算CRC16
result := 16#FFFF ;
FOR i:=0 TO usiLen-1 BY 1 DO
temp := TO_BYTE((result AND 16#000F)) XOR (pData^ AND 16#0F) ;
result := SHR(result, 4) ;
result := result XOR aTable[temp] ;
temp := TO_BYTE((result AND 16#000F)) XOR SHR(pData^, 4) ;
result := SHR(result, 4) ;
result := result XOR aTable[temp] ;
pData := pData + 1 ;
END_FOR
F_Crc16 := result ;

Modbus-ASCII

数据帧格式

起始字符 从站地址(站号) 功能码 数据 LRC校验 结束字符
1 byte
冒号(:)
2 byte 2 byte 0~504(252*2) byte 2 byte 2 byte
回车换行(CR LF)

** 注:**

  • ASCII数据项最大长度为252 * 2 个字节。是RTU的两倍
  • 除了起始字符、结束字符,以及校验方式。其他项的数据是将RTU的报文直接翻译为十六进制数字对应的ASCII码,即0x01 翻译为 ‘01’(ASCII码:0x30、0x31)

eg.使用 功能码0x03 读取 站号01 地址0x0000~0x0009 的数据

请求命令:SEND ASCII >

(冒号):(站号)01(功能码)03(起始地址)0000(读取的寄存器数量)000A(LRC校验)F2(回车换行)CRLF

(对比)请求命令:SEND Hex >

(冒号)0x3A(站号)0x30 0x31(功能码)0x30 0x33(起始地址)0x30 0x30 0x30 0x30(读取的寄存器数量)0x30 0x30 0x30 0x3A(LRC校验)0x41 0x32(回车换行)0x0D 0x0A

LRC校验

除了起始字符、结束字符,以及LRC校验外的所有数据,均需要进行LRC校验计算。

LRC计算步骤:

步骤一:对需要校验的数据(2n个字符)两两组成一个16进制的数值求和。

步骤二:将求和结果与256求模。

步骤三:用256减去所得模值得到校验结果。(另一种方法:将模值按位取反然后加1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//codesys3.5 Structured Text

//{attribute 'hide_all_locals'}
FUNCTION F_LRC8 : BYTE
VAR_INPUT
pData : POINTER TO BYTE ; //计算数据首字节的指针 adr(data)
usiLen : USINT ; //计算数据的长度
END_VAR
VAR
i : USINT ;
temp : BYTE ;
result : BYTE ;
END_VAR

FOR i:=0 TO usiLen-1 BY 1 DO
result := result + pData^ ;
pData := pData + 1 ;
END_FOR
result := TO_BYTE(result MOD 16#100) ;
F_LRC8 := TO_BYTE(16#100 - result) ;

使用命令字符对应的数字进行LRC计算。

以请求命令(:01030000000AF2 CRLF)为例子说明:

需要进行LRC校验计算的数据为 01030000000A

另外一种LRC计算步骤如下:

步骤一:(累加)0+1+0+3+0+0+0+0+0+0+0+10 = 0xE (不是 30 + 31 … + 40 = 0x176)

步骤二:(FF减去步骤一累加值)0xFF - 0xE = 0xF1

步骤三:(步骤二值加1)0xF1 + 1 = 0xF2


Modbus协议——ModbusRTU、ModbusASCII
https://jacobblog.pages.dev/2026/03/08/Modbus-01Rtu-Ascii/
作者
Jacob Chen
发布于
2026年3月8日
许可协议