4、功能码详解(一):01读线圈、02读离散输入、03读保持寄存器、04读输入寄存器

各位同学,咱们今天正式进入Modbus协议的核心地带——功能码。

说实话,功能码就是Modbus的“指令集”。你想想看,主站和从站之间怎么沟通?全靠这些数字代码。我刚开始接触Modbus时,总觉得功能码就是一堆枯燥的数字,直到在项目里被坑过一次,才真正重视起来。

今天我们先讲四个最常用的读操作功能码:01、02、03、04。它们负责从从站设备里“读”数据。至于写操作,咱们后面再聊。

4.1 功能码概览:读操作四兄弟

这四个功能码,说白了就是四种不同的“读”法。它们分别对应Modbus协议中定义的四种数据对象:

功能码 名称 操作对象 单位 典型用途
0x01 读线圈 线圈(Coil) 位(Bit) 读取开关量输出状态
0x02 读离散输入 离散输入(Discrete Input) 位(Bit) 读取开关量输入状态
0x03 读保持寄存器 保持寄存器(Holding Register) 字(Word,16位) 读取可读可写的参数/数据
0x04 读输入寄存器 输入寄存器(Input Register) 字(Word,16位) 读取只读的测量值/状态

核心区别一句话:

  • 01和02:读的是“位”,一个bit代表一个开关量(通/断、0/1)。
  • 03和04:读的是“字”,一个寄存器16位,可以表示数值、状态字等。
  • 01和03:读的对象是“可读可写”的(线圈和保持寄存器)。
  • 02和04:读的对象是“只读”的(离散输入和输入寄存器)。

嗯,这里要注意:很多初学者容易把03和04搞混。我个人习惯是这么记的——“保持”意味着可以保持住你写的值,所以可读可写;“输入”意味着外部信号输入,你只能看,不能改。

4.2 功能码0x01:读线圈

这个功能码用来读取从站设备里线圈的当前状态。线圈是什么?就是PLC或者远程IO模块里的数字量输出点。比如控制一个继电器的通断、一个指示灯的亮灭。

4.2.1 报文格式

咱们直接看报文结构,这样最直观。

请求报文(主站→从站):

从站地址 | 功能码(0x01) | 起始地址(高字节) | 起始地址(低字节) | 线圈数量(高字节) | 线圈数量(低字节) | CRC校验(低字节) | CRC校验(高字节)
  1字节   |    1字节     |      1字节       |      1字节       |      1字节       |      1字节       |      1字节       |      1字节

响应报文(从站→主站):

从站地址 | 功能码(0x01) | 字节数 | 数据字节1 | 数据字节2 | ... | CRC校验(低字节) | CRC校验(高字节)
  1字节   |    1字节     | 1字节  |   n字节   |   n字节   |     |      1字节       |      1字节

个人经验:线圈数量是按“位”算的,但响应报文里是按“字节”打包的。比如你要读10个线圈,响应里会用2个字节来装(第一个字节装8个,第二个字节装2个)。我曾经在项目里犯过傻,以为读10个线圈就返回10个字节,结果解析数据全乱了。

4.2.2 实战示例

假设我们要读取从站地址为0x01的设备,从线圈地址0x0000开始,连续读10个线圈。

请求报文:

01 01 00 00 00 0A BC 0D
  • 01:从站地址
  • 01:功能码
  • 00 00:起始线圈地址(从0开始)
  • 00 0A:读取10个线圈(10 = 0x0A)
  • BC 0D:CRC校验

响应报文(假设线圈0~7全通,线圈8和9断开):

01 01 02 FF 00 39 84
  • 01:从站地址
  • 01:功能码
  • 02:数据字节数(2个字节)
  • FF:第一个字节,二进制11111111,表示线圈0~7全为1(通)
  • 00:第二个字节,二进制00000000,表示线圈8和9为0(断),其余6位填充0
  • 39 84:CRC校验

避坑指南:我曾经调试一个设备,读线圈总是返回错误。查了半天发现,有些从站设备对“起始地址”的理解不一样。有的从站地址从0开始,有的从1开始。一定要看设备手册确认地址映射!

4.3 功能码0x02:读离散输入

离散输入和线圈很像,也是读一个bit的状态。区别在于:离散输入是只读的,通常对应物理按钮、限位开关、传感器信号等输入信号。

4.3.1 报文格式

报文结构和01功能码几乎一模一样,只是功能码换成了0x02。

请求报文:

从站地址 | 功能码(0x02) | 起始地址(高字节) | 起始地址(低字节) | 输入数量(高字节) | 输入数量(低字节) | CRC校验

响应报文:

从站地址 | 功能码(0x02) | 字节数 | 数据字节1 | 数据字节2 | ... | CRC校验

4.3.2 实战示例

读取从站地址0x01的设备,从离散输入地址0x0000开始,连续读8个输入点。

请求报文:

01 02 00 00 00 08 79 CC

响应报文(假设输入0~3为1,输入4~7为0):

01 02 01 0F 90 48
  • 01:从站地址
  • 02:功能码
  • 01:数据字节数(1个字节)
  • 0F:二进制00001111,表示输入0~3为1,输入4~7为0
  • 90 48:CRC校验

小技巧:如果你只需要读一个离散输入点,也可以用这个功能码。把数量设为1就行。但要注意,响应报文里依然会返回一个完整的字节,只是其他位填充0。

4.4 功能码0x03:读保持寄存器

这是Modbus里最常用的功能码,没有之一。保持寄存器是16位的,可以存放数值、参数、配置等。而且它是可读可写的,你不仅能读,后面还能用功能码0x06或0x10去写。

4.4.1 报文格式

请求报文:

从站地址 | 功能码(0x03) | 起始地址(高字节) | 起始地址(低字节) | 寄存器数量(高字节) | 寄存器数量(低字节) | CRC校验

响应报文:

从站地址 | 功能码(0x03) | 字节数 | 数据高字节1 | 数据低字节1 | 数据高字节2 | 数据低字节2 | ... | CRC校验

注意:响应报文里的“字节数” = 寄存器数量 × 2。因为每个寄存器占2个字节。而且数据是大端模式(高字节在前,低字节在后)。

4.4.2 实战示例

读取从站地址0x01的设备,从保持寄存器地址0x0000开始,连续读2个寄存器。

请求报文:

01 03 00 00 00 02 C4 0B

响应报文(假设寄存器0的值是0x1234,寄存器1的值是0x5678):

01 03 04 12 34 56 78 9A BC
  • 01:从站地址
  • 03:功能码
  • 04:数据字节数(2个寄存器 × 2字节 = 4字节)
  • 12 34:第一个寄存器的值(高字节0x12,低字节0x34)
  • 56 78:第二个寄存器的值(高字节0x56,低字节0x78)
  • 9A BC:CRC校验

避坑指南:我曾经在项目里遇到过一个问题——读回来的寄存器数值总是对不上。后来发现是字节序搞错了。有些设备用大端,有些用小端。虽然Modbus标准规定是大端,但总有些厂商不按套路出牌。所以,拿到设备后第一件事:用调试工具读一个已知值,验证字节序!

4.5 功能码0x04:读输入寄存器

输入寄存器和保持寄存器一样,也是16位的。唯一的区别:它是只读的。通常用来读取传感器的测量值,比如温度、压力、流量等。

4.5.1 报文格式

报文结构和03功能码完全一样,只是功能码换成了0x04。

请求报文:

从站地址 | 功能码(0x04) | 起始地址(高字节) | 起始地址(低字节) | 寄存器数量(高字节) | 寄存器数量(低字节) | CRC校验

响应报文:

从站地址 | 功能码(0x04) | 字节数 | 数据高字节1 | 数据低字节1 | ... | CRC校验

4.5.2 实战示例

读取从站地址0x01的设备,从输入寄存器地址0x0000开始,读1个寄存器(比如当前温度值)。

请求报文:

01 04 00 00 00 01 31 CA

响应报文(假设温度值是25.6℃,设备用整数256表示):

01 04 02 01 00 39 84
  • 01:从站地址
  • 04:功能码
  • 02:数据字节数(1个寄存器 = 2字节)
  • 01 00:寄存器值 = 0x0100 = 256,对应25.6℃
  • 39 84:CRC校验

个人经验:很多传感器会把浮点数拆成两个寄存器来传输。比如一个32位的浮点数,占用两个连续的输入寄存器。这时候你需要自己拼装。我习惯用Python的struct模块来处理,非常方便。

4.6 知识体系图:读操作功能码核心逻辑

下面这张图帮你理清这四个功能码的关系和选择逻辑。

Modbus读操作功能码选择逻辑 要读什么数据? 读“位”数据(开关量) 读“字”数据(16位数值) 可读可写? → 用功能码 0x01 只读? → 用功能码 0x02 可读可写? → 用功能码 0x03 只读? → 用功能码 0x04 总结: 01和02读“位”,03和04读“字” 01和03可读可写,02和04只读

4.7 常见错误与避坑指南

做Modbus开发,有些坑是绕不开的。我把自己踩过的坑分享给你,希望能帮你省点时间。

  1. 地址偏移问题

    很多PLC的地址是从1开始的,但Modbus协议里地址是从0开始的。比如PLC里标着“线圈1”,在Modbus报文里地址是0x0000。我曾经因为这个搞混过,读回来的数据全是错的。

  2. 一次读取数量限制

    Modbus协议规定,一次最多读取2000个线圈(01/02)或125个寄存器(03/04)。但实际设备往往有更严格的限制。我建议你保守一点,一次读100个寄存器以内比较安全。

  3. 响应超时处理

    有些从站设备处理速度慢,尤其是老旧的PLC。我习惯把超时时间设长一点,比如1000ms。如果连续超时3次,就认为设备离线了。

  4. CRC校验不能省

    别偷懒!我见过有人为了省事,直接跳过CRC校验。结果在工业现场,电磁干扰导致数据错了一个字节,整个系统都乱了。CRC是Modbus的保命符,一定要算。

核心要点回顾:

  • 01读线圈:读开关量输出,可读可写
  • 02读离散输入:读开关量输入,只读
  • 03读保持寄存器:读数值/参数,可读可写,最常用
  • 04读输入寄存器:读测量值,只读
  • 报文结构:请求带起始地址和数量,响应带字节数和数据
  • 数据格式:位按bit打包,字按大端字节序

好了,这四个功能码就讲到这里。你想想看,掌握了它们,你已经能读取Modbus设备里绝大部分数据了。下一章咱们接着聊写操作的功能码——05、06、15、16。到时候你会发现,写数据比读数据要小心得多。


公众号:蓝海资料掘金营,微信deep3321