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位填充039 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为090 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 知识体系图:读操作功能码核心逻辑
下面这张图帮你理清这四个功能码的关系和选择逻辑。
4.7 常见错误与避坑指南
做Modbus开发,有些坑是绕不开的。我把自己踩过的坑分享给你,希望能帮你省点时间。
-
地址偏移问题
很多PLC的地址是从1开始的,但Modbus协议里地址是从0开始的。比如PLC里标着“线圈1”,在Modbus报文里地址是0x0000。我曾经因为这个搞混过,读回来的数据全是错的。
-
一次读取数量限制
Modbus协议规定,一次最多读取2000个线圈(01/02)或125个寄存器(03/04)。但实际设备往往有更严格的限制。我建议你保守一点,一次读100个寄存器以内比较安全。
-
响应超时处理
有些从站设备处理速度慢,尤其是老旧的PLC。我习惯把超时时间设长一点,比如1000ms。如果连续超时3次,就认为设备离线了。
-
CRC校验不能省
别偷懒!我见过有人为了省事,直接跳过CRC校验。结果在工业现场,电磁干扰导致数据错了一个字节,整个系统都乱了。CRC是Modbus的保命符,一定要算。
核心要点回顾:
- 01读线圈:读开关量输出,可读可写
- 02读离散输入:读开关量输入,只读
- 03读保持寄存器:读数值/参数,可读可写,最常用
- 04读输入寄存器:读测量值,只读
- 报文结构:请求带起始地址和数量,响应带字节数和数据
- 数据格式:位按bit打包,字按大端字节序
好了,这四个功能码就讲到这里。你想想看,掌握了它们,你已经能读取Modbus设备里绝大部分数据了。下一章咱们接着聊写操作的功能码——05、06、15、16。到时候你会发现,写数据比读数据要小心得多。
公众号:蓝海资料掘金营,微信deep3321