Modbus 协议介绍

Modbus 协议介绍

Modbus RTU 是一种基于串口通信(RS-232/RS-485)的主从式通信协议。主站(Master)发起请求报文,从站(Slave)响应报文。通信采用二进制帧结构,带 CRC16 校验,传输效率高,广泛应用于工业自动化领域。

Modbus协议采用主从(以太网的客户端-服务器)架构实现的请求-响应协议。其中有1个中央的主设备(询问器或主机Master)和理论最多 247 个从设备(响应器或从机Slave)连接到同一个网络。

  • Modbus阐明了协议类型(协议层),而RS485定义了协议的信号电平(物理层)。
  • 注意:以下示例中,CRC16校验码 是乱写的。如需手动校验 CRC16,可以参考这里 CRC在线计算 ,算法选择 CRC-16-MODBUS

功能码

常见功能码示例:

  • 0x01: 读线圈状态 (Read Coils)
  • 0x02: 读离散输入状态 (Read Discrete Inputs)
  • 0x03: 读保持寄存器 (Read Holding Registers)
  • 0x04: 读输入寄存器 (Read Input Registers)
  • 0x05: 写单个线圈 (Write Single Coil)
  • 0x06: 写单个寄存器 (Write Single Register)
  • 0x0F: 写多个线圈 (Write Multiple Coils)
  • 0x10: 写多个寄存器 (Write Multiple Registers)

异常码

当原始请求功能码 + 0x80(即最高位置 1),即为异常功能码。

异常码是单字节值(范围 0x010x0B),定义了具体的错误类型。以下是标准异常码及含义:

异常码 名称 原因说明 常见场景
0x01 非法功能码(Illegal Function) 从站不支持请求的功能码。 主站请求了设备未实现的功能(如旧设备不支持 0x18)。
0x02 非法数据地址(Illegal Data Address) 请求的地址超出设备支持范围。 读取地址 0x0500,但设备仅支持 0x00000x04FF
0x03 非法数据值(Illegal Data Value) 请求中的数据值无效(如写入值超出范围)。 写入线圈时发送 0x02(非 0x00/0xFF),或写入寄存器值超出设备量程。
0x04 服务器设备故障(Server Device Failure) 从站内部错误(如硬件故障、内存损坏)。 设备过热、固件崩溃或硬件异常。
0x05 确认(Acknowledge) 从站已接收请求,但需较长时间处理(建议主站稍后重试)。 设备正在执行耗时操作(如电机启动)。
0x06 服务器设备忙(Server Device Busy) 从站正处理其他请求,无法立即响应。 高并发请求时设备资源不足。
0x07 负数确认(Negative Acknowledge) 从站无法执行请求(如未配置网关路由)。 网关设备无法转发请求到目标子设备。
0x08 内存奇偶校验错误(Memory Parity Error) 从机检测到内存错误(较少见)。 设备内存硬件故障。
0x0A 网关路径不可用(Gateway Path Unavailable) 网关无法建立到目标设备的连接(TCP/RTU 转换失败)。 网关配置错误或目标设备离线。
0x0B 网关目标设备无响应(Gateway Target Device Failed to Respond) 网关已发送请求,但目标设备未响应。 目标设备故障或通信中断。

:异常码 0x090x0C0xFF 为保留码,标准未定义。

从机地址

从机地址占一个字节,同一个 RS485 总线上的设备从机地址必须唯一。

  • 当从机地址为 0 的时候,视为广播消息。
  • 可用地址 1 ~ 247
  • 保留地址 248 ~ 255

单播和广播

支持单播和广播两种模式。

单播消息

  • 主节点向从节点发送明确的命令并处理响应。
  • 主节点同一时间只能启动一个Modbus事务。一个MODBUS事务包括2条消息:主站的请求和从站的回复。
  • 从节点不会主动发起通讯。
  • 从节点之间永远不会相互通信。

广播消息

  • 从机地址为 0。
  • 无响应(No Response)广播消息不要求任何从站返回响应。主站发送后立即结束本次通信,不会等待从站应答(即使从站成功执行指令)。
  • 广播仅支持写入类指令,禁止使用读取类功能码(否则会导致网络冲突或无意义行为)。支持的功能码包括:0x05、0x06、0x0F、0x10.
  • 常用语:同步控制、系统初始化、时间同步等。

寄存器地址

modbus实际上并非使用PLC地址作为读写地址,而是使用modbus地址。由于modbus地址是从0开始的,其换算规则如下:

地址类型 PLC地址范围 modbus地址换算
线圈地址范围 1 ~ 10000 = PLC地址-1
离散量地址范围 10001 ~ 20000 = PLC地址-10001
输入寄存器地址范围 30001 ~ 40000 = PLC地址-30001
保持寄存器地址范围 40001 ~ 50000 = PLC地址-40001

比如某个属性的地址为411234,则实际上modbus读写的地址为411234-410001=1233

CRC校验

  • 计算 modbusRTU 的 CRC16 校验码的代码
function modbusCRC16(buffer) {
    let crc = 0xFFFF;
    for (let b of buffer) {
        crc ^= b;
        for (let i = 0; i < 8; i++) {
            if (crc & 0x0001) {
                crc >>= 1;
                crc ^= 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc & 0xFFFF;
}

// 示例
const message = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A];
const crc = modbusCRC16(message);
console.log("CRC16:", crc.toString(16).toUpperCase());

线圈

  • 一个线圈对应一个继电器(因为继电器里面有电磁线圈)
  • 每一个线圈有独立的 2 字节地址
  • 默认读线圈是读取多个
  • 支持单个线圈写入和多个线圈写入
特性 单个线圈写入 (0x05) 多个线圈写入 (0x0F)
操作粒度 1 个线圈 1~1968 个连续线圈
数据格式 直接值(0xFF00/0x0000) 位打包(每字节 8 个线圈)
请求长度 固定 6 字节 可变(最小 8 字节)
效率 低(逐个操作) 高(批量操作)
适用场景 单点控制(如启动/停止电机) 批量配置(如初始化设备状态)

数据类型

参考 Modbus Poll,常见数据格式:

类型 长度 说明
Coils 1 bit 开关量输出
Discrete Inputs 1 bit 开关量输入
Short 16-bit 有符号整数
Unsigned Short 16-bit 无符号整数
Long 32-bit 有符号整数(2 寄存器)
Unsigned Long 32-bit 无符号整数(2 寄存器)
Float 32-bit IEEE754 浮点数(2 寄存器)
Short BCD 16-bit BCD 编码
Long BCD 32-bit BCD 编码(2 寄存器)

Modbus 字节序

MODBUS 使用“大端”表示法来处理地址和数据项。这意味着当传输大于单字节的数值时,最重要的字节首先发送。例如:

例如,一个 16 位( (2 Byte ,也叫做 1 个 Word))的寄存器的值 = 0x1234,首先发送的字节是0x12,然后是0x34。

Big-endian(大端) 是计算机科学中用于描述多字节数据存储方式的一个术语。在计算机存储数据时,数据可以以不同的字节顺序存储。

Big-endian 指的是在多字节数据中,最"重要"的字节(通常是最高位字节)存储在最低的内存地址处(地址总是从左到右递增),而剩余的字节按照重要性递减的顺序存储在更高的内存地址处。这种存储方式类似于书写和阅读时的从左到右顺序。

与之相反的是 "Little-endian"(小端),它将最低位字节存储在最低的内存地址处(左侧),而最高位字节存储在最高的内存地址处。

例如:寄存器值 0x1234 在报文中表示为:12 34

虽然标准规定寄存器是 高字节在前 (AB),但是 部分设备/厂家可能会用 Little-Endian (BA) 来存储或解析 16bit 数据。

由于 32-bit 数据需要两个寄存器表示,不同设备采用不同的字节序(ByteOrder) 和 WordOrder。常见的字节序组合如下:

数据类型 寄存器个数 字节数 位数 支持字节序
16 位整数 1 2 16 AB/BA
16 位无符号整数 1 2 16 AB/BA
32 位整数 2 4 32 ABCD/BADC/CDAB/DCBA
32 位无符号整数 2 4 32 ABCD/BADC/CDAB/DCBA
32 位浮点数 2 4 32 ABCD/BADC/CDAB/DCBA

以 16-bit 数据 0x1234 , 32-bit 数据 0x12345678 为例,

模式 存储顺序 (寄存器1 寄存器2)
AB 12 34
BA 34 12
ABCD 12 34 56 78
CDAB 56 78 12 34
BADC 34 12 78 56
DCBA 78 56 34 12

四种数据表

四种基本的数据表,分别是离散量(Discrete Inputs)、线圈(Coils)、输入寄存器(Input Registers)和保持寄存器(Holding Registers)。

数据表类型 值类型 访问类型 备注
离散量(Discrete Inputs) 位(bit) 只读 道常由输入/输出(I/O)系统提供,表示开关状态的输入信号。
线圈(Coils) 位(bit) 读写 可以被应用程序修改,表示开关状态的输出信号。
输入寄存器(Input Registers) 16位字 只读 通道常由I/O系统提供,表示模拟量输入的数据或其他只读信息。
保持寄存器(Holding Registers) 16位字 读写 可以被应用程序修改,用于存储数据或配置参数。

功能码 0x01 — 读线圈状态

示例:

  • 请求:01 01 [ 00 13 ] [ 00 0A ] 0E 84

    (读从站 0x01,起始地址 0x0013,读 0x000A=10 个线圈)

  • 响应:01 01 05 [ 4D 01 ] 45 E6

    (返回 10 个线圈状态,所以返回数据需要 2个字节)

关于地址格式:
Modbus 线圈地址从 0 开始(如 0 表示第一个线圈)。
注意:部分设备文档使用 1 起始地址(即地址 1 对应协议中的 0),需根据设备手册转换。


关于位序(Bit Order):

协议规定:Modbus 采用 小端序(Little-Endian) 的位排列方式。
字节内位序:在响应数据中,一个字节包含 8 个线圈状态,最低位(LSB, Bit 0)对应起始地址最小的线圈。

示例:读取地址 0 到 7 的线圈,返回字节 0xCD(二进制 11001101):

- `Bit 0`(LSB)→ 线圈 `0`  
- `Bit 1` → 线圈 `1`  
- ...  
- `Bit 7`(MSB)→ 线圈 `7`

所以,对于此次读取的返回值:

- 4D(二进制 01001101):线圈 0-7 状态。线圈 0 是 on
- 01(二进制 00000001):线圈 8-15 状态(实际只用到 8-9)。

后面的离线输入的位序和线圈是一样的。

请求报文

从机地址 功能码 起始地址高 起始地址低 线圈数量高 线圈数量低 CRC低 CRC高
01 01 00 13 00 25 0E 84

响应报文

从机地址 功能码 字节数 5字节数据 CRC低 CRC高
01 01 05 CD 6B B2 0E 1B 45 E6

功能码 0x02 — 读离散输入

此功能码是读取离散输入状态(即只读的开关量信号,如传感器状态、限位开关等)。每个位(bit)表示一个输入点的状态(1=ON,0=OFF)

地址与线圈的区别:

  • 读离散输入(0x02):只读信号(如传感器输入)。
  • 读线圈(0x01):可读写信号(如继电器输出)。

报文结构如下:

  • 地址范围 1-65536
  • 起始地址 2字节 要读取的第一个离散输入地址(多数设备从 地址 0 开始计数,但部分设备手册可能使用 地址 1)。
  • 读取数量 2字节 要读取的离散输入数量(1~2000)

请求报文

从机地址 功能码 起始地址高 起始地址低 读取数量高 读取数量低 CRC低 CRC高
01 02 00 C4 00 16 B9 B8

响应报文

  • 字节数:返回数据的字节数,每个字节包含 8 个输入状态(高位在前)(N = ceil(读取数量/8))
从机地址 功能码 字节数 数据字节1 数据字节2 CRC低 CRC高
01 02 02 AC DB 45 6E

功能码 0x03 — 读保持寄存器

请求示例

  • 请求:01 03 00 6B 00 03 76 87

    (从站 0x01,起始地址 0x006B,读 3 个寄存器)

响应示例

  • 响应:01 03 06 02 2B 00 00 00 64 85 DB

    (返回 3 个寄存器:0x022B, 0x0000, 0x0064)

请求报文

从机地址 功能码 起始地址高 起始地址低 寄存器数量高 寄存器数量低 CRC低 CRC高
01 03 00 6B 00 03 76 87

响应报文

从机地址 功能码 字节数 数据高1 数据低1 数据高2 数据低2 数据高3 数据低3 CRC低 CRC高
01 03 06 02 2B 00 00 00 64 85 DB

功能码 0x04 — 读输入寄存器

请求报文

从机地址 功能码 起始地址高 起始地址低 寄存器数量高 寄存器数量低 CRC低 CRC高
01 04 00 08 00 01 B2 0B

响应报文

从机地址 功能码 字节数 数据高 数据低 CRC低 CRC高
01 04 02 00 0A F8 F4

功能码 0x05 — 写单个线圈

请求示例

  • 请求:01 05 00 AC FF 00 4E 8B

    (写线圈 0x00AC 为 ON)

响应示例

  • 响应:01 05 00 AC FF 00 4E 8B

    (回显请求报文)

请求报文

从机地址 功能码 线圈地址高 线圈地址低 数据高 数据低 CRC低 CRC高
01 05 00 AC FF 00 4E 8B

响应报文(回显请求)

从机地址 功能码 线圈地址高 线圈地址低 数据高 数据低 CRC低 CRC高
01 05 00 AC FF 00 4E 8B

功能码 0x06 — 写单个寄存器

请求报文

从机地址 功能码 寄存器地址高 寄存器地址低 数据高 数据低 CRC低 CRC高
01 06 00 01 00 03 9A 9B

响应报文(回显请求)

从机地址 功能码 寄存器地址高 寄存器地址低 数据高 数据低 CRC低 CRC高
01 06 00 01 00 03 9A 9B

功能码 0x0F — 写多个线圈

请求:01 0F 00 13 00 0A 02 CD 01 [CRC]
解析:起始地址=0x0013,数量=10,字节数=2,状态字节=CD 01 = 0b1100 1101 0000 0001(低位在前)

请求报文

从机地址 功能码 起始地址高 起始地址低 线圈数量高 线圈数量低 字节数 数据字节1 CRC低 CRC高
01 0F 00 13 00 0A 02 CD 01 4F 36

响应报文

从机地址 功能码 起始地址高 起始地址低 线圈数量高 线圈数量低 CRC低 CRC高
01 0F 00 13 00 0A B0 45

功能码 0x10 — 写多个寄存器

请求示例

  • 请求:01 10 00 01 00 02 04 00 0A 01 02 C6 F0

    (写寄存器 0x0001 和 0x0002,值分别为 0x000A 和 0x0102)

响应示例

  • 响应:01 10 00 01 00 02 71 CB

    (确认写入 2 个寄存器)

请求报文

从机地址 功能码 起始地址高 起始地址低 寄存器数量高 寄存器数量低 字节数 数据1高 数据1低 数据2高 数据2低 CRC低 CRC高
01 10 00 01 00 02 04 00 0A 01 02 C6 F0

响应报文

从机地址 功能码 起始地址高 起始地址低 寄存器数量高 寄存器数量低 CRC低 CRC高
01 10 00 01 00 02 71 CB

Modbus RTU 与 Modbus TCP 对比

项目 Modbus RTU Modbus TCP
传输介质 串口 (RS-232/485) TCP/IP 网络
报文格式 二进制帧,带 CRC 无 CRC,增加 MBAP 头
地址 从站地址 1 字节 单独 TCP 连接区分
应用场景 工业现场设备 工业以太网、远程监控
帧边界 靠定时间隔(3.5 字符间隔) TCP 数据包边界
速度 较低 (9600/115200bps) 较高 (100Mbps+)

两者在 功能码与数据模型 上完全一致,区别主要在 传输层与报文封装

读保持寄存器请求为例对比介绍

modbusTCP消息结构如下:

事务ID 协议ID 长度 单元ID PDU
Transaction ID 2 字节 Protocol ID 2 字节 Length 2 字节 Unit ID 1 字节 协议数据
用于请求/响应匹配 固定为 0x00 0x00 单元标识符 + PDU 从机地址,网关中用于路由 变长
事务ID 协议ID 长度 单元ID 功能码 起始地址 寄存器数量
00 01 00 00 00 06 01 03 00 6B 00 03

转换成 modbusRTU 的消息:

从站地址 PDU CRC 校验
1 字节 变长数据, 功能码 + 数据(与 TCP 的 PDU 相同) CRC16 校验
01 03 00 6B 00 03 XX XX

modbusTCP 的消息结构中:

  • 事务ID:用于请求/响应匹配(类似 TCP 的序列号),服务器响应时需原样返回。一般是设备自动生成。
  • PDU:和 modbusRTU 的功能码和数据部分一样。示例 PDU 是 功能码 0x03(读保持寄存器),起始地址 0x006B,寄存器数量 0x0003
Last updated on September 11, 2025 by 北盛信息