跳到主要内容

Modbus

Modbus RTU 协议​

Modbus RTU 是 Modbus 协议在串行通信(RS-232 / RS-485)上的实现,采用紧凑的二进制编码传输数据,每帧末尾附带 CRC 校验,无需额外帧头。

串口通信参数​

串口通信通过以下参数定义帧格式,收发两端的所有参数必须完全一致,否则无法正常通信:

参数常见值说明
波特率9600 / 19200 / 38400 / 115200每秒传输的比特数
数据位8每字符有效数据比特数,Modbus RTU 固定为 8
校验位NONE / EVEN / ODD奇偶校验,NONE 时无校验位
停止位1 / 2帧结束标志;有校验时通常为 1,无校验时建议为 2

Modbus RTU 数据帧​

Modbus RTU 帧无 MBAP 头部,结构更紧凑,以 CRC 校验取代 TCP 的校验机制:

Modbus RTU Frame = Device Address + Function Code + Data + CRC
字段长度说明
Device Address1 字节从站地址,范围 1–247;0 为广播地址
Function Code1 字节与 TCP 相同,指定操作类型
Data不定长起始地址、数量、寄存器值等
CRC2 字节循环冗余校验(多项式 0xA001),低字节在前

帧间静默间隔​

RTU 用静默时间区分帧边界:帧与帧之间须保持至少 3.5 个字符时间的静默。字符时间 = (起始位 + 数据位 + 校验位 + 停止位) / 波特率。超出 3.5 个字符时间无数据,则认为当前帧结束。


Modbus RTU 转 Modbus TCP​

在工业现场,许多设备仅支持 RS-485 + Modbus RTU 通信,而上位机(WCS、SCADA 等)通常通过以太网接入。串口服务器可实现 Modbus RTU → Modbus TCP 的协议透明转换,无需修改从站设备。

转换原理:串口服务器将以太网侧收到的 Modbus TCP 报文去掉 MBAP 头、加上 CRC,转为 RTU 帧发往 RS-485 总线;从站返回的 RTU 帧则反向去掉 CRC、加上 MBAP 头,封装为 TCP 帧返回客户端。

典型设备:USR-W610s​

USR-W610s 是一款工业级双串口服务器,支持将 RS-485 总线上的 Modbus RTU 从站透明转换为 Modbus TCP 服务端,供以太网侧客户端直接读写。接线方式、串口参数与通信配置详见 USR 模块文档。


Modbus TCP 协议​

1996 年,施耐德公司推出了基于以太网 TCP/IP 的 Modbus TCP 协议。

Modbus TCP 数据帧​

Modbus 协议定义了独立于传输层的 PDU(Protocol Data Unit,协议数据单元)。当 Modbus 通过 TCP/IP 传输时,会在 PDU 前增加一个 MBAP(Modbus Application Protocol Header)头部,从而形成完整的 Modbus TCP 数据帧。

Modbus TCP Frame = MBAP Header + PDU
PDU = Function Code + Data

Modbus TCP 帧结构​

一帧完整的 Modbus TCP 报文由 7 字节的 MBAP 头部和 PDU 两部分组成,按字节排列如下(下图按比特位标注,每 8 比特为 1 字节):

字段长度说明
MBAP Header7 字节事务标识、协议标识、长度、单元标识
PDUN 字节功能码 + 数据

MBAP 头部​

MBAP 头部固定为 7 字节,结构如下:

字段长度说明
Transaction Identifier2 字节请求/响应配对标识
Protocol Identifier2 字节Modbus TCP 固定为 0x0000
Length2 字节后续字段长度,通常为 Unit Identifier + PDU 的总长度
Unit Identifier1 字节从站地址或单元标识

PDU 结构​

PDU 由“功能码 + 数据”组成,结构如下:

字段长度说明
Function Code1 字节指定本次操作类型
Data不定长起始地址、数量、寄存器值、线圈值等

Modbus 的操作对象​

Modbus 通过访问逻辑地址空间来监控和控制设备。设备厂商通常会提供寄存器映射表,用于说明每个地址的实际含义。在与具体设备通信前,应先确认其寄存器定义。

地址分区与对象类型​

Modbus 数据模型包含 4 类基本对象。原先文档中的“离散输入”和“线圈”地址分区写反了,正确对应关系如下:

对象类型常见地址前缀访问属性典型功能码
线圈(Coils)0xxxx读/写,1 bit0x01、0x05、0x0F
离散输入(Discrete Inputs)1xxxx只读,1 bit0x02
输入寄存器(Input Registers)3xxxx只读,16 bit0x04
保持寄存器(Holding Registers)4xxxx读/写,16 bit0x03、0x06、0x10

说明:

  • 0xxxx 和 1xxxx 操作的是位(bit)数据。
  • 3xxxx 和 4xxxx 操作的是 16 位寄存器数据。
  • 文档、设备手册、驱动程序中有时会使用“去掉前缀后的偏移地址”,例如把 40001 写成地址 0。实际使用时应以设备寄存器表定义为准。

地址类型与功能码适配关系​

功能码名称适用对象是否可写
0x01读线圈线圈(0xxxx)否
0x02读离散输入离散输入(1xxxx)否
0x03读保持寄存器保持寄存器(4xxxx)否
0x04读输入寄存器输入寄存器(3xxxx)否
0x05写单个线圈线圈(0xxxx)是
0x06写单个保持寄存器保持寄存器(4xxxx)是
0x0F写多个线圈线圈(0xxxx)是
0x10写多个保持寄存器保持寄存器(4xxxx)是

四类对象说明​

线圈:表示从站中的开关量输出,每个线圈只占 1 位,可读可写。常用功能码为 0x01、0x05、0x0F。

离散输入:表示从站中的开关量输入,例如传感器状态,每个输入只占 1 位,只能读取,常用功能码为 0x02。

输入寄存器:表示只读的 16 位输入数据,例如模拟量采集值,常用功能码为 0x04。

保持寄存器:表示可读可写的 16 位寄存器,常用于参数配置、状态控制和数值下发,常用功能码为 0x03、0x06、0x10。

常用功能码说明​

下面示例中的“起始地址”均表示报文中的地址偏移量,不是 00001、10001、30001、40001 这类逻辑地址全称。

每个功能码均以“报文结构 + 报文示例(按字节拆解)”的方式说明:

  • 报文结构:列出请求 / 响应 PDU 由哪些字段组成。
  • 报文示例:先给出完整十六进制报文,再用表格把每一段字节对应到具体含义。所有报文都不含 CRC(CRC 是 Modbus RTU 才有的,TCP 由 MBAP 头替代校验)。

0x01 读线圈​

用于读取线圈(0xxxx)的状态信息。请求中需要指定线圈的起始地址和数量。

报文结构:

报文字段构成
请求MBAP 头 + 功能码 + 起始地址 + 线圈数量
响应MBAP 头 + 功能码 + 字节数 + 线圈状态数据(1 个线圈占 1 位)

示例:读取从站 ID 为 1 的设备中,地址 1000(0x03E8)开始的 10 个线圈状态。

请求报文:00 01 00 00 00 06 01 01 03 E8 00 0A

响应报文:00 01 00 00 00 05 01 01 02 55 AA

0x02 读离散量输入​

用于读取离散输入(1xxxx)的状态信息,例如传感器开关量。请求中需要指定离散输入的起始地址和数量。

报文结构:

报文字段构成
请求MBAP 头 + 功能码 + 起始地址 + 输入数量
响应MBAP 头 + 功能码 + 字节数 + 输入状态数据(字节数 = ⌈数量 / 8⌉)

示例:读取从站 ID 为 1 的设备中,地址 5000(0x1388)开始的 8 个离散输入状态。

请求报文:00 01 00 00 00 06 01 02 13 88 00 08

响应报文:00 01 00 00 00 04 01 02 01 3F

0x05 写单个线圈​

向线圈(0xxxx)写入单个位状态。请求中需要指定线圈地址和写入值,0xFF00 表示 ON,0x0000 表示 OFF。响应与请求内容一致。

报文结构:

报文字段构成
请求MBAP 头 + 功能码 + 线圈地址 + 输出值
响应MBAP 头 + 功能码 + 线圈地址 + 输出值(原样回显)

示例:向从站 ID 为 1 的设备中,地址 100(0x0064)的线圈写入 ON。

请求报文:00 01 00 00 00 06 01 05 00 64 FF 00

响应报文:00 01 00 00 00 06 01 05 00 64 FF 00(字段含义与请求相同,为原样回显)

0x0F 写多个线圈​

向线圈(0xxxx)批量写入状态。请求中需要指定起始地址、数量及写入值,数据域中为 1 的位表示 ON,为 0 的位表示 OFF。

报文结构:

报文字段构成
请求MBAP 头 + 功能码 + 起始地址 + 线圈数量 + 字节数 + 输出值
响应MBAP 头 + 功能码 + 起始地址 + 线圈数量

示例:向从站 ID 为 1 的设备中,地址 500(0x01F4)开始的 4 个线圈写入状态 0110。

请求报文:00 01 00 00 00 09 01 0F 01 F4 00 04 01 06

响应报文:00 01 00 00 00 06 01 0F 01 F4 00 04

0x03 读保持寄存器​

用于读取保持寄存器(4xxxx)的数据,例如参数值、控制字、状态字等。请求中需要指定起始地址和寄存器数量。

报文结构:

报文字段构成
请求MBAP 头 + 功能码 + 起始地址 + 寄存器数量
响应MBAP 头 + 功能码 + 字节数 + 寄存器数据(字节数 = 寄存器数量 × 2)

示例:读取从站 ID 为 1 的设备中,地址 1000(0x03E8)开始的 5 个保持寄存器,值依次为 1、2、3、4、5。

请求报文:00 01 00 00 00 06 01 03 03 E8 00 05

响应报文:00 01 00 00 00 0D 01 03 0A 00 01 00 02 00 03 00 04 00 05

0x04 读输入寄存器​

用于读取输入寄存器(3xxxx)的数据,例如模拟量采样值。请求中需要指定起始地址和寄存器数量。

报文结构:

报文字段构成
请求MBAP 头 + 功能码 + 起始地址 + 寄存器数量
响应MBAP 头 + 功能码 + 字节数 + 寄存器数据(字节数 = 寄存器数量 × 2)

示例:读取从站 ID 为 1 的设备中,地址 2000(0x07D0)开始的 3 个输入寄存器,值依次为 1、2、3。

请求报文:00 01 00 00 00 06 01 04 07 D0 00 03

响应报文:00 01 00 00 00 09 01 04 06 00 01 00 02 00 03

0x06 写单个保持寄存器​

用于向保持寄存器(4xxxx)写入单个 16 位数值。请求中需要指定寄存器地址和写入值,响应与请求内容一致。

报文结构:

报文字段构成
请求MBAP 头 + 功能码 + 寄存器地址 + 寄存器值
响应MBAP 头 + 功能码 + 寄存器地址 + 寄存器值(原样回显)

示例:向从站 ID 为 1 的设备中,地址 300(0x012C)的寄存器写入值 0x000A。

请求报文:00 01 00 00 00 06 01 06 01 2C 00 0A

响应报文:00 01 00 00 00 06 01 06 01 2C 00 0A(字段含义与请求相同,为原样回显)

0x10 写多个保持寄存器​

用于向保持寄存器(4xxxx)批量写入多个 16 位数值。请求中需要指定起始地址、数量和写入值。

报文结构:

报文字段构成
请求MBAP 头 + 功能码 + 起始地址 + 寄存器数量 + 字节数 + 寄存器值
响应MBAP 头 + 功能码 + 起始地址 + 寄存器数量

示例:向从站 ID 为 1 的设备中,地址 400(0x0190)开始的 3 个寄存器写入值 0x0101、0x0202、0x0303。

请求报文:00 01 00 00 00 0D 01 10 01 90 00 03 06 01 01 02 02 03 03

响应报文:00 01 00 00 00 06 01 10 01 90 00 03