Modbus 是一种工业通信协议,主要用于控制器、触摸屏、仪表、传感器和驱动器之间的数据交换。
Modbus 规定了通信双方如何组织地址、功能码、数据和校验信息,但不规定设备内部必须采用什么硬件或如何存储数据。某个寄存器代表温度、速度还是报警状态,需要由设备的通信说明书确定。
Modbus 具有以下特点:
在 Modbus 串口通信中,设备分为主站和从站:
Modbus 常见于以下工业控制场景:
Modbus 适合周期性读取和写入少量工业数据。它没有统一规定变量名称,因此通信前通常需要准备地址表,明确每个线圈和寄存器的用途、数据类型、读写权限及单位。
Modbus RTU 使用二进制方式传输数据,通常运行在 RS-485 或 RS-232 接口上。
主要特点:
Modbus ASCII 把每个数据字节转换为两个 ASCII 字符发送。
例如:
数据字节 0x3A -> 字符 '3' 和 'A'
主要特点:
: 开始。Modbus TCP 运行在 TCP/IP 网络上,默认使用 TCP 端口 502。
主要特点:
| 对比项 | Modbus RTU | Modbus ASCII | Modbus TCP |
|---|---|---|---|
| 常用接口 | RS-485、RS-232 | RS-485、RS-232 | 以太网 |
| 数据形式 | 二进制 | ASCII 字符 | 二进制 |
| 校验方式 | CRC16 | LRC | TCP/IP 校验 |
| 帧边界 | 静默时间 | 起止字符 | MBAP 长度字段 |
| 传输效率 | 高 | 较低 | 高 |
三种模式的外部封装不同,但功能码和主要数据含义基本一致。
主站使用从站地址 1~247访问指定从站。从站处理请求并返回应答。
主站发送请求 -> 指定从站处理请求 -> 从站返回应答
主站可以根据应答判断本次事务是否成功。如果规定时间内没有收到应答,则可以进行超时或重试处理。
地址 0 是 Modbus 串口通信中的广播地址。主站向地址 0 发送请求时,所有从站都可以接收并执行。
广播模式具有以下特点:
广播请求不能产生应答,是因为多个从站同时发送数据会造成总线冲突。
一次 Modbus 请求和应答过程称为一个事务。
确定从站地址和操作内容
|
生成请求数据
|
添加功能码和校验信息
|
发送请求帧
|
启动应答超时计时
|
接收从站应答
|
检查地址、功能码、长度和校验
|
处理正常应答或异常应答
主站需要处理以下情况:
接收请求帧
|
检查帧长度和校验
|
检查从站地址
|
检查功能码
|
检查数据地址、数量和字节数
|
执行读取或写入
|
生成正常应答或异常应答
|
添加校验并发送
从站应先完成报文校验和参数检查,再修改内部数据。CRC 错误、帧不完整或单播地址不属于本机的请求通常直接丢弃。
主站发送请求后,如果在规定时间内没有收到完整且正确的应答,则本次事务超时。
主站可以进行有限次数重试:
发送请求
-> 超时
-> 第一次重试
-> 仍然超时
-> 第二次重试
-> 仍然超时
-> 判断从站离线
重试次数不能无限增加,否则离线设备会长期占用通信总线。
Modbus 定义了四个相互独立的数据区。
| 数据区 | 数据宽度 | 访问权限 | 读取功能码 | 常用写入功能码 |
|---|---|---|---|---|
| 线圈 Coils | 1 bit | 读/写 | 0x01 | 0x05、0x0F |
| 离散输入 Discrete Inputs | 1 bit | 只读 | 0x02 | 无 |
| 保持寄存器 Holding Registers | 16 bit | 读/写 | 0x03 | 0x06、0x10 |
| 输入寄存器 Input Registers | 16 bit | 只读 | 0x04 | 无 |
不同数据区相互独立,可以拥有相同的地址。例如,线圈地址 0 和保持寄存器地址 0 不冲突,因为访问它们所使用的功能码不同。
一个线圈只保存一个二进制状态:
0 = OFF
1 = ON
多个线圈在报文中按位打包。请求中的第一个线圈放在第一个数据字节的最低位 bit0。
一个 Modbus 寄存器为 16 位,占 2 字节。
Modbus 只规定寄存器宽度,不规定寄存器中的数据类型。一个实际变量可以使用一个或多个寄存器表示:
使用多个寄存器表示一个数据时,通信双方必须约定高低字顺序。
部分资料使用 00001 表示第一个线圈,使用 40001 表示第一个保持寄存器。这些是便于阅读的逻辑编号,不一定是报文中的实际地址。
例如:
逻辑编号 40001 -> 报文地址通常为 0x0000
逻辑编号 40002 -> 报文地址通常为 0x0001
不同设备软件可能采用从 0 开始或从 1 开始的显示方式。通信时应以设备手册和实际发送的报文地址为准。
Modbus RTU 报文由以下字段组成:
| 字段 | 长度 | 说明 |
|---|---|---|
| 从站地址 | 1 字节 | 单播地址 1~247,广播地址 0 |
| 功能码 | 1 字节 | 表示读取或写入操作 |
| 数据 | 可变 | 起始地址、数量、字节数和数据值 |
| CRC16 | 2 字节 | 低字节先发送,高字节后发送 |
Modbus RTU ADU 的最大长度为 256 字节。
地址、数量和寄存器值等 16 位字段采用高字节在前:
数值 0x1234 -> 发送 12 34
CRC 在线路上采用低字节在前:
CRC 计算结果 0x0A84 -> 发送 84 0A
Modbus RTU 的 CRC16 参数如下:
0xFFFF。0xA001。CRC 不正确的报文应直接丢弃,不能返回异常应答。
Modbus RTU 通过静默时间区分报文:
一帧内部的字节应连续发送。如果相邻字节间隔超过 T1.5,当前报文可能被判定为不完整。两帧之间至少保持 T3.5 的静默时间。
波特率高于 19200 bit/s 时,通常建议采用:
功能码 0x01 用于读取线圈。
| 字段 | 长度 |
|---|---|
| 从站地址 | 1 字节 |
| 功能码 0x01 | 1 字节 |
| 起始地址 | 2 字节 |
| 线圈数量 | 2 字节 |
| CRC | 2 字节 |
请求读取从站 1 的线圈地址 0,共读取 1 个线圈:
01 01 00 00 00 01 FD CA
字段解释:
01 从站地址
01 功能码
00 00 起始地址
00 01 读取数量
FD CA CRC
线圈读取数量的标准范围为 1~2000。
| 字段 | 长度 |
|---|---|
| 从站地址 | 1 字节 |
| 功能码 0x01 | 1 字节 |
| 数据字节数 | 1 字节 |
| 线圈数据 | N 字节 |
| CRC | 2 字节 |
如果线圈地址 0 为 ON:
01 01 01 01 90 48
线圈按最低位优先排列:
数据字节 bit0 -> 请求中的第 1 个线圈
数据字节 bit1 -> 请求中的第 2 个线圈
...
数据字节 bit7 -> 请求中的第 8 个线圈
最后一个数据字节中没有使用的高位应置 0。
功能码 0x03 用于读取保持寄存器。
| 字段 | 长度 |
|---|---|
| 从站地址 | 1 字节 |
| 功能码 0x03 | 1 字节 |
| 起始地址 | 2 字节 |
| 寄存器数量 | 2 字节 |
| CRC | 2 字节 |
请求读取从站 1 的保持寄存器地址 0,共读取 1 个寄存器:
01 03 00 00 00 01 84 0A
保持寄存器读取数量的标准范围为 1~125。
| 字段 | 长度 |
|---|---|
| 从站地址 | 1 字节 |
| 功能码 0x03 | 1 字节 |
| 数据字节数 | 1 字节 |
| 寄存器数据 | N 字节 |
| CRC | 2 字节 |
假设保持寄存器地址 0 的值为 0x1234:
01 03 02 12 34 B5 33
其中:
02 后续数据占 2 字节
12 34 寄存器值
读取多个寄存器时,数据按照寄存器地址递增顺序排列。
功能码 0x0F 用于写多个线圈。
| 字段 | 长度 |
|---|---|
| 从站地址 | 1 字节 |
| 功能码 0x0F | 1 字节 |
| 起始地址 | 2 字节 |
| 线圈数量 | 2 字节 |
| 数据字节数 | 1 字节 |
| 线圈数据 | N 字节 |
| CRC | 2 字节 |
把地址 0~7 的 8 个线圈写成 0x55:
01 0F 00 00 00 08 01 55 3E AA
字段解释:
01 从站地址
0F 功能码
00 00 起始地址
00 08 写入 8 个线圈
01 数据字节数
55 线圈数据
3E AA CRC
线圈写入数量的标准范围为 1~1968。
数据字节数的计算方法:
数据字节数 = (线圈数量 + 7) / 8,采用整数除法
正常应答回显起始地址和实际写入数量:
| 字段 | 长度 |
|---|---|
| 从站地址 | 1 字节 |
| 功能码 0x0F | 1 字节 |
| 起始地址 | 2 字节 |
| 写入数量 | 2 字节 |
| CRC | 2 字节 |
应答示例:
01 0F 00 00 00 08 54 0D
功能码 0x10 用于写多个保持寄存器。
| 字段 | 长度 |
|---|---|
| 从站地址 | 1 字节 |
| 功能码 0x10 | 1 字节 |
| 起始地址 | 2 字节 |
| 寄存器数量 | 2 字节 |
| 数据字节数 | 1 字节 |
| 寄存器数据 | N 字节 |
| CRC | 2 字节 |
从地址 10 开始写入两个寄存器,数据分别为 0x1234 和 0x5678:
01 10 00 0A 00 02 04 12 34 56 78 08 E4
字段解释:
01 从站地址
10 功能码
00 0A 起始地址
00 02 写入 2 个寄存器
04 数据字节数
12 34 第一个寄存器的数据
56 78 第二个寄存器的数据
08 E4 CRC
寄存器写入数量的标准范围为 1~123,数据字节数必须等于寄存器数量乘以 2。
正常应答回显起始地址和实际写入数量:
| 字段 | 长度 |
|---|---|
| 从站地址 | 1 字节 |
| 功能码 0x10 | 1 字节 |
| 起始地址 | 2 字节 |
| 写入数量 | 2 字节 |
| CRC | 2 字节 |
应答示例:
01 10 00 0A 00 02 61 CA
当请求报文校验正确,但从站无法执行请求时,从站可以返回异常应答。
异常功能码等于原功能码加 0x80:
0x01 -> 0x81
0x03 -> 0x83
0x0F -> 0x8F
0x10 -> 0x90
异常应答格式:
| 字段 | 长度 |
|---|---|
| 从站地址 | 1 字节 |
| 异常功能码 | 1 字节 |
| 异常码 | 1 字节 |
| CRC | 2 字节 |
常见异常码:
| 异常码 | 名称 | 含义 |
|---|---|---|
| 0x01 | 非法功能 | 从站不支持该功能码 |
| 0x02 | 非法数据地址 | 请求地址不存在或访问范围越界 |
| 0x03 | 非法数据值 | 数量、字节数或数据值不合法 |
| 0x04 | 从站设备故障 | 从站执行请求时发生内部错误 |
| 0x06 | 从站设备忙 | 从站暂时不能处理请求 |
例如,从站 1 对 0x03 请求返回非法数据地址:
01 83 02 C0 F1