Modbus 协议完全入门:主从通信、四大寄存器、8 个功能码与报文逐字节拆解

Modbus 是工业自动化、物联网网关里出现频率极高的一种通信协议,PLC、变送器、传感器、电表、温控仪表几乎都支持它。本文从"主站/从站"讲起,系统梳理 Modbus 的三种传输模式、四大数据区、8 个常用功能码,并结合真实报文逐字节拆解读线圈(01H)和读保持寄存器(03H)的完整问答过程,最后讲清初学者最容易懵的"寄存器地址与偏移量"以及 CRC 校验。零基础可读,也适合作为工作中的查询手册。


一、Modbus 是什么

Modbus 是一种用于自动化设备之间数据传输的通信协议,它由 Modicon(现施耐德)于 1979 年提出,因为协议简单、公开、免费,成了工业领域事实上的通用语言。

它支持多种物理介质/传输方式,最常见的三种:

  • Modbus-RTU:跑在串口(RS-232 / RS-485)上,二进制紧凑编码,使用 CRC 校验,工业现场最常用;
  • Modbus-ASCII:同样可跑在串口上,但把数据编码成可见的 ASCII 字符(十六进制字符),使用 LRC 校验,可读性好、效率较低;
  • Modbus-TCP:跑在以太网(TCP/IP)上,端口默认 502,在 RTU 数据基础上加了一个 MBAP 报文头,不再需要 CRC(TCP 本身已保证可靠性)。

主站与从站:一主多从、有问才有答

Modbus 是典型的主从(Master/Slave,现也称 Client/Server)协议,核心是"一主多从、主站问询、从站应答":

  • 主站(Master / Modbus Poll):发起通信的一方,通常是上位机软件或控制器(可以用软件模拟);
  • 从站(Slave / Modbus Slave):响应请求的一方,是各种硬件设备,如变送器、检测器、PLC、电表等。

通信永远由主站发起。例如主站想知道当前温度,就在软件上点击发送一条"问询报文",对应地址的从站收到后,再把温度数据放在"应答报文"里返回。从站不会主动说话。

在 Modbus Poll / Slave 这类调试软件中,常见缩写含义如下:

缩写含义
Tx发送出去的数据帧(Transmit)
Rx接收到的数据帧(Receive)
Err错误的数据帧(校验失败/异常应答)
ID从站编号(设备地址)
F功能码(Function)
SR帧间隔时间(请求间隔)

在这里插入图片描述

二、报文长什么样:RTU 与 ASCII 的帧结构

Modbus 报文(协议数据)整体可以概括为:地址 + 功能码 + 数据 + 校验。RTU 模式和 ASCII 模式的封装略有不同。

ASCII 模式(以冒号 : 开头,以回车换行 CRLF 结尾,中间都是可见字符):

起始地址功能代码数据数量数据 1 … 数据 nLRC 高LRC 低回车换行
:2 字符2 字符……2 字符2 字符CRLF

RTU 模式(纯二进制,靠帧间至少 3.5 个字符时间的空闲来分帧):

地址功能代码数据数量数据 1 … 数据 nCRC 低字节CRC 高字节

注意两个细节:

  1. RTU 的 CRC 是低字节在前、高字节在后(小端顺序),这和寄存器数据"高字节在前"正好相反,初学者很容易看反;
  2. ASCII 的 LRC 占一个字节,但在报文里用两个十六进制 ASCII 字符表示(高、低各一个字符)。

Modbus-TCP 则在功能码和数据前面加了一个 7 字节的 MBAP 头:

事务标识(2)协议标识(2,固定 0)长度(2)单元标识(1,即从站地址)功能码 + 数据

TCP 模式没有 CRC,因为 TCP 协议本身已经保证了数据的可靠与有序。


在这里插入图片描述

三、核心中的核心:四大数据区(寄存器模型)

Modbus 把设备里的数据抽象成四类,每一类都有自己的编号区间和读写属性。搞懂这张表,Modbus 就懂了一半。

数据区俗称PLC(逻辑)地址协议地址位/字读/写典型用途
线圈 Coil0 区(0xxxx)00001–099990000H–FFFFH位(bit)可读可写控制设备的开/关,0=OFF,1=ON
离散输入 Discrete Input1 区(1xxxx)10001–199990000H–FFFFH位(bit)只读设备状态检测:急停按钮、行程开关位置
输入寄存器 Input Register3 区(3xxxx)30001–399990000H–FFFFH字(16bit)只读数据采集:温度、电压、电流、转速
保持寄存器 Holding Register4 区(4xxxx)40001–499990000H–FFFFH字(16bit)可读可写参数设置:运行速度、调频、定时、阈值、通信地址

一个非常实用的记忆口诀:

  • 开关量(0/1):要"控制"就找线圈(可写);只能"看"状态就找离散输入(只读);
  • 数值量(16 位字):设备采集上来、只读的是输入寄存器(温度/电压等);需要设置、可读可写的是保持寄存器(如机器转速、阈值)。

名词说明:Holding Register 标准译名为"保持寄存器"(部分资料/口语也写作"保存寄存器",指同一个东西)。"寄存器"就是从站设备中存放数据的一块区域,例如地址 40001、30002。


四、8 个常用功能码

对四大数据区的"读/写、单个/多个"操作,组合起来就是 Modbus 最常用的 8 个功能码。

功能码描述PLC 地址位/字操作操作数量
01H读线圈寄存器00001–09999位单个或多个
02H读离散输入寄存器10001–19999位单个或多个
03H读保持寄存器40001–49999字单个或多个
04H读输入寄存器30001–39999字单个或多个
05H写单个线圈寄存器00001–09999位单个
06H写单个保持寄存器40001–49999字单个
0FH(15)写多个线圈寄存器00001–09999位多个
10H(16)写多个保持寄存器40001–49999字多个

规律非常好记:

  • 01/02/03/04 都是"读",依次对应线圈、离散输入、保持寄存器、输入寄存器;
  • 05/06 是"写单个",分别写单个线圈、单个保持寄存器;
  • 15(0FH)/16(10H) 是"写多个",分别写多个线圈、多个保持寄存器;
  • 位操作(线圈/离散输入)的最小单位是 bit;字操作(寄存器)的最小单位是 16 bit(2 个字节)。

五、报文实例精讲(逐字节拆解)

理论看完,下面用两个真实例子把"问询—应答"完整走一遍,这是理解 Modbus 最快的方式。

5.1 实例一:03H 读保持寄存器(最常用)

需求:读取 11H(17)号从站的保持寄存器,起始协议地址为 006BH(=107),读取 2 个寄存器。

主站询问报文:

从站地址功能码起始寄存器(高位)起始寄存器(低位)寄存器数量(高位)寄存器数量(低位)CRC
0x110x030x000x6B0x000x02XXXX

从站应答报文:

从站地址功能码字节计数40108(高位)40108(低位)40109(高位)40109(低位)CRC
0x110x030x040x020x2B0x010x06XXXX

逐字节解读:

  • 请求里 00 6B = 107,是协议地址(从 0 开始的偏移);00 02 表示读 2 个保持寄存器;
  • 应答里 04 是字节计数:2 个寄存器 × 每个 2 字节 = 4 字节;
  • 数据按"高字节在前"排列为 02 2B 01 06,所以:
    • 40108 寄存器的值 = 0x022B
    • 40109 寄存器的值 = 0x0106

5.2 重点:寄存器地址与偏移量(Offset)的换算

这是初学者最容易懵的地方,Modbus Poll 软件里显示的 00000、00107 并不是物理地址,而是从 0 开始的协议地址(偏移量),必须结合功能码换算成大家熟悉的 5 位 PLC 地址:

  • 功能码 03/06/16 操作保持寄存器(4 区),软件里偏移 00000 对应 40001;
  • 偏移 00107(=107)对应 40001 + 107 = 40108;
  • 读 2 个,就是 40108 和 40109(40108 + 2 − 1)。

通用换算关系:

PLC(逻辑)地址 = 区起始地址 + 协议偏移量
例:保持寄存器 40001 + 107 = 40108
协议偏移量     = PLC 地址 − 区起始地址
例:40108 − 40001 = 107 = 0x006B

各区起始地址:线圈 00001、离散输入 10001、输入寄存器 30001、保持寄存器 40001。

5.3 再看一条真实问询帧

笔记中抓到的一条 RTU 帧:

Tx:064 - 01 03 00 00 00 0A C5 CD
字段值含义
064—这是第 64 次问询(软件计数,不是报文内容)
01从站地址问询 1 号从站
03功能码读保持寄存器
00 00起始地址从偏移 0(即 40001)开始
00 0A寄存器数量读 10 个(0x0A = 10)
C5 CDCRC冗余校验,检测报文在传输中是否出错

5.4 实例二:01H 读线圈(按位返回)

需求:从站地址为 01H,读取线圈的起始地址为 0017H,读取 38(十进制,即 26H) 个线圈。

【插图 4:功能码 01H 读线圈的发送与响应帧】(对应图片 消息格式1.png,发布时在此上传)

主站询问报文:

从站地址功能码起始地址高位起始地址低位数量高位数量低位CRC 高位CRC 低位
0101001700260DD4

从站应答报文:

从站地址功能码返回字节数数据 1数据 2数据 3数据 4数据 5CRC 高位CRC 低位
010105CD6BB20E1B44EA

解读要点:

  • 线圈是**按位(bit)**返回的:每个 bit 对应一个线圈的状态,1 代表 ON,0 代表 OFF;
  • 一个字节有 8 个 bit,所以 38 个线圈需要 ⌈38 / 8⌉ = 5 个字节,这就是返回字节数 05 的由来;
  • 当线圈数量不是 8 的倍数时,最后一个字节多余的高位补 0;
  • 字节内线圈的排列从**最低位(LSB)**开始对应第一个线圈。例如数据第一字节 CDH = 1100 1101,就依次表示了前 8 个线圈的通断状态。

六、CRC / LRC 校验是干什么的

报文最后的 CRC / LRC 是校验码,用来检测数据在串口传输过程中是否因为干扰而出错:

  • RTU 用 CRC-16(循环冗余校验):发送方对"地址 + 功能码 + 数据"按固定多项式(Modbus 用 0xA001)算出 2 字节 CRC,附在帧尾(低字节在前);接收方用同样算法重算并比对,不一致就丢弃该帧(软件里表现为 Err / 超时)。
  • ASCII 用 LRC(纵向冗余校验):对前面各字节求和取反加一,结果 1 字节,用两个 ASCII 字符表示。

实际开发中一般不需要手写 CRC 算法——串口调试助手会自动算,NModbus4 等库也会自动处理。理解"它用来保证帧没传错、且 RTU 低字节在前"即可,需要手写时再查标准算法表。


七、动手实践:模拟软件与 C# 代码

7.1 没有硬件也能学:Modbus Poll + Modbus Slave

学习和调试阶段不需要真实设备:

  • Modbus Slave:模拟从站,配置好从站 ID、寄存器数据,相当于一台虚拟仪表;
  • Modbus Poll:模拟主站,选择功能码、起始地址、数量后发送请求并显示应答;
  • 串口模式下可用虚拟串口软件(如 VSPD)创建一对互联的 COM 口,分别给 Poll 和 Slave 使用;TCP 模式下直接连 127.0.0.1:502。

在 Modbus Poll 中:

  • Alias(列名):给某个寄存器地址自定义一个名字(如"温度设定"),方便辨认;
  • 地址栏的 00000 是偏移量,配合功能码决定从哪个寄存器开始(见 5.2)。

7.2 C# 中使用 NModbus4

在 C# / WinForms 项目里操作 Modbus,常用开源库 NModbus4(NuGet 直接搜索安装),它把组帧、CRC、解析都封装好了。读保持寄存器的核心代码大致如下(具体 API 随库版本略有差异):

串口 RTU 模式:

using (SerialPort port = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One))
{
    port.Open();
    var adapter = new SerialPortAdapter(port);

    // 创建 RTU 主站
    IModbusSerialMaster master = ModbusSerialMaster.CreateRtu(adapter);

    // 读 1 号从站、从地址 0 开始的 10 个保持寄存器(对应 40001~40010)
    ushort[] values = master.ReadHoldingRegisters(
        slaveAddress: 1, startAddress: 0, numberOfInputs: 10);

    Console.WriteLine(string.Join(", ", values));
}

TCP 模式:

using (TcpClient client = new TcpClient("127.0.0.1", 502))
{
    // 创建 TCP 主站
    var master = ModbusIpMaster.CreateIp(client);

    // 读 1 号从站、地址 107 开始的 2 个保持寄存器(对应 40108、40109)
    ushort[] values = master.ReadHoldingRegisters(1, 107, 2);
}

写操作对应的方法是 WriteSingleRegister / WriteMultipleRegisters / WriteSingleCoil / WriteMultipleCoils,命名与功能码一一对应。


八、常见疑问与易错点

  1. 软件里的地址 00000 和手册里的 40001 对不上?
    一个是从 0 开始的协议偏移,一个是从 1 开始的 PLC 逻辑地址,相差一个区起始值(保持寄存器相差 40001)。换算见 5.2。
  2. 读出来是乱码 / 一直超时?
    先查串口四参数(波特率、数据位、校验位、停止位)是否一致、从站 ID 是否正确、RS-485 的 A/B 是否接反;TCP 则查 IP 和端口 502。
  3. CRC 到底谁在前?
    RTU 帧尾 CRC 低字节在前、高字节在后;寄存器数值则是高字节在前,方向相反。
  4. 线圈为什么几个状态被打包成一个字节?
    线圈是位操作,8 个线圈塞在 1 个字节里返回,从最低位开始对应,不足 8 个时高位补 0。
  5. 保持寄存器和输入寄存器怎么选?
    要下发/修改参数用保持寄存器(4 区,可读写);设备采集上来、只读的测量值用输入寄存器(3 区,只读)。
  6. 一个寄存器能表示多大的数?
    标准寄存器是 16 位无符号数(0~65535);表示负数、32 位整数或浮点数时,需要设备手册约定字节序并把相邻 2 个寄存器拼起来解析。

九、小结

  • Modbus 是主从式、问询—应答协议,主站发起、从站回答,支持 RTU / ASCII / TCP 三种传输;
  • 数据分四类:线圈(0 区,位,读写)、离散输入(1 区,位,只读)、输入寄存器(3 区,字,只读)、保持寄存器(4 区,字,读写);
  • 8 个功能码覆盖"读/写 × 单个/多个":01–04 读、05/06 写单个、15/16 写多个;
  • 报文 = 地址 + 功能码 + 数据 + 校验,RTU 用 CRC(低字节在前),ASCII 用 LRC,TCP 用 MBAP 头、无校验;
  • 软件显示的是从 0 开始的偏移地址,要加上区起始值才是 40001 这类 PLC 地址;
  • C# 开发直接用 NModbus4,调试用 Modbus Poll / Slave 即可在单机完成全流程验证。

理解了"四大数据区 + 8 个功能码 + 一条报文的逐字节含义",再去对接任何 Modbus 设备,基本都能照着手册快速调通。

更多推荐