3、Modbus协议基础(下):功能码详解(01-06, 15, 16)、数据模型与报文结构解析

好,咱们接着聊。上一节我们把Modbus协议的“骨架”搭起来了,这一节咱们往里面填“血肉”。说白了,就是搞清楚那些数字到底怎么读、怎么写。

我个人习惯把Modbus协议比作一个“仓库”。仓库里有四种货架:线圈、离散输入、输入寄存器、保持寄存器。而功能码,就是你去仓库取货或放货的“指令单”。你拿着不同的单子,操作不同的货架,流程自然不一样。

3.1 四种数据模型:仓库里的四种货架

先把这个“仓库”的结构搞清楚。你想想看,如果连货架都分不清,那指令单肯定用错。

数据模型 对象类型 访问权限 数据大小 我常用来干啥
线圈 (Coil) 离散量 读写 1 bit (0或1) 控制风机启停、阀门开关
离散输入 (Discrete Input) 离散量 只读 1 bit (0或1) 读取水流开关、压差开关状态
输入寄存器 (Input Register) 模拟量 只读 16 bits (0-65535) 读取温度传感器、压力变送器数值
保持寄存器 (Holding Register) 模拟量 读写 16 bits (0-65535) 设定温度目标值、PID参数、变频器频率

核心区别一句话:线圈和离散输入是“开关”,只有通断;输入寄存器和保持寄存器是“刻度盘”,能读出具体数值。而且,线圈和保持寄存器是“双向”的,你能改;离散输入和输入寄存器是“单向”的,你只能看。

嗯,这里要注意。很多新手会把“线圈”和“离散输入”搞混。我在项目中遇到过,有人用读线圈的功能码去读一个水流开关,结果死活读不到数据。为什么?因为水流开关是干接点,属于“离散输入”,只能读,不能写。你用错了指令单,仓库管理员当然不理你。

3.2 功能码详解:你的六张核心指令单

Modbus功能码很多,但暖通空调里常用的,说白了就这六个:01, 02, 03, 04, 05, 06, 15, 16。咱们一个一个过。

3.2.1 01 (0x01):读线圈

这张单子用来读取“线圈”货架上的数据。一次可以读1个,也可以读连续的一批(最多2000个)。

报文结构(请求):

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

报文结构(响应):

从站地址 | 功能码(0x01) | 数据字节数 | 数据(线圈状态打包) | CRC校验

举个例子,我想读1号从站,从地址0开始的10个线圈状态。请求报文就是:01 01 00 00 00 0A ? ?(CRC自己算)。响应里,数据字节数会是2(因为10个bit需要2个字节来装),然后按位排列。

3.2.2 02 (0x02):读离散输入

和01功能码几乎一样,只是操作对象变成了“离散输入”货架。报文结构完全一致,就是把功能码换成0x02。

避坑指南:我曾经见过有人用02功能码去读一个变频器的运行状态,但变频器的运行状态通常映射在“线圈”区域。你拿错了单子,读回来的永远是0。一定要先看设备手册,搞清楚数据到底放在哪个货架上。

3.2.3 03 (0x03):读保持寄存器

这是暖通空调集成里最常用的功能码,没有之一。因为几乎所有设定值、参数都在保持寄存器里。

报文结构(请求):

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

报文结构(响应):

从站地址 | 功能码(0x03) | 数据字节数 | 数据(寄存器值,每个2字节) | CRC校验

比如,我想读1号从站,地址0的1个保持寄存器(假设是温度设定值)。请求:01 03 00 00 00 01 ? ?。响应可能是:01 03 02 01 F4 ? ?,其中01 F4就是16进制的500,代表50.0℃(如果精度是0.1℃的话)。

3.2.4 04 (0x04):读输入寄存器

和03功能码结构一样,只是操作对象是“输入寄存器”货架。用来读传感器数值。

3.2.5 05 (0x05):写单个线圈

这张单子用来控制单个开关量。比如,远程启动一台风机。

报文结构(请求):

从站地址 | 功能码(0x05) | 线圈地址高字节 | 线圈地址低字节 | 数据(0xFF00=ON, 0x0000=OFF) | CRC校验

注意!数据不是简单的0或1。ON必须是0xFF00,OFF必须是0x0000。其他值都是非法的。这是Modbus协议的规定,别问为什么,记住就行。

3.2.6 06 (0x06):写单个保持寄存器

用来修改单个参数。比如,把温度设定值从50.0℃改成45.0℃。

报文结构(请求):

从站地址 | 功能码(0x06) | 寄存器地址高字节 | 寄存器地址低字节 | 数据高字节 | 数据低字节 | CRC校验

请求:01 06 00 00 01 C2 ? ?,其中01 C2是16进制的450,代表45.0℃。

3.2.7 15 (0x0F):写多个线圈

一次控制多个开关量。比如,同时启动一组阀门。

报文结构(请求):

从站地址 | 功能码(0x0F) | 起始地址 | 线圈数量 | 字节数 | 数据(线圈状态打包) | CRC校验

3.2.8 16 (0x10):写多个保持寄存器

一次修改多个参数。比如,同时修改PID的三个参数。

报文结构(请求):

从站地址 | 功能码(0x10) | 起始地址 | 寄存器数量 | 字节数 | 数据(寄存器值,每个2字节) | CRC校验

我的个人经验:在写多个寄存器时,一定要确保所有数据一次性发送成功。如果网络不稳定,部分寄存器写成功了,部分没成功,那设备状态就乱了。我建议在写多个寄存器前,先读一遍当前值,确认无误后再写。写完后,再读回来校验一遍。这叫“写后读校验”,能避免很多坑。

3.3 报文结构解析:拆开信封看内容

好了,现在指令单和货架都清楚了。咱们来拆一个完整的报文看看。以“读保持寄存器”为例。

请求报文: 01 03 00 00 00 02 C4 0B

字节位置 值 (Hex) 含义
1 01 从站地址:1号设备
2 03 功能码:读保持寄存器
3-4 00 00 起始地址:从0开始
5-6 00 02 寄存器数量:读2个
7-8 C4 0B CRC校验:确保报文没被干扰

响应报文: 01 03 04 01 F4 00 64 ? ?

字节位置 值 (Hex) 含义
1 01 从站地址:1号设备回应
2 03 功能码:确认是读保持寄存器
3 04 数据字节数:后面有4个字节的数据
4-5 01 F4 寄存器0的值:500 (0x01F4)
6-7 00 64 寄存器1的值:100 (0x0064)
8-9 ? ? CRC校验

重要提醒:CRC校验是Modbus报文的“防伪标签”。如果CRC算错了,从站会直接丢弃报文,不会响应。很多初学者在调试时,报文发出去石沉大海,90%的原因是CRC算错了。我建议你写代码时,直接用现成的CRC计算库,别自己手算,容易出错。

好了,这一节的内容就这些。说白了,Modbus协议没那么神秘。你只要记住:先看数据模型(货架),再选功能码(指令单),最后拼报文(填单子)。下一节,咱们聊聊Modbus的“坑”——异常码和常见故障排查。到时候我会分享几个我当年被坑得最惨的案例,保证让你印象深刻。