modbus协议之-02-数据寻址与功能码解析 📅 2026/7/15 2:19:06 1. Modbus数据寻址的两种方式第一次接触Modbus协议时最让人困惑的就是设备手册上写的40001这类地址到底代表什么。这就像你拿到一个新小区的门牌号但快递员却要求你提供经纬度坐标。Modbus协议中存在着两种地址表示方法绝对地址和相对地址。绝对地址就是我们常看到的5位或6位数字比如40001、300002。这种地址的特点是第一位数字代表存储区类型0-输出线圈1-输入线圈3-输入寄存器4-输出寄存器后面几位表示该存储区中的具体位置这种表示方法直观易懂设备手册和配置软件通常都采用这种格式相对地址则是协议实际传输时使用的地址特点包括从0开始计数不包含存储区类型信息需要结合功能码才能确定操作的是哪个存储区举个例子假设要读取输出寄存器40001的值绝对地址40001转换为相对地址0因为40001是输出寄存器的第一个地址实际发送的报文中的地址字段就是0x000016进制我在实际项目中遇到过这样一个坑某设备的温度传感器值存放在输入寄存器30001-30010但在协议报文中需要使用的地址是0x0000-0x0009。如果直接发送30001设备会返回错误码02非法数据地址。2. 核心功能码详解Modbus的功能码就像是给设备下命令的动词告诉设备要执行什么操作。最常用的功能码有01、02、03、04、05、06、15、16这几种。让我们通过具体案例来理解它们的使用方法。2.1 读取类功能码功能码01读线圈 这个功能码用于读取输出线圈的状态ON/OFF。比如要读取设备1的线圈00001-00016的状态报文格式如下请求报文01 01 00 00 00 10 3D C601设备地址01功能码00 00起始地址00001对应0x000000 10读取数量16个线圈3D C6CRC校验响应报文01 01 02 CD 6B 9001设备地址01功能码02返回字节数CD线圈状态11001101表示线圈8-15的状态6B线圈状态01101011表示线圈1-7的状态90CRC校验功能码03读保持寄存器 这是最常用的功能码之一用于读取输出寄存器的值。假设要读取设备1的寄存器40001-40002请求报文01 03 00 00 00 02 C4 0B01设备地址03功能码00 00起始地址00 02读取数量C4 0BCRC校验响应报文01 03 04 00 0A 00 64 1A 3501设备地址03功能码04返回字节数4字节因为每个寄存器2字节00 0A第一个寄存器的值1000 64第二个寄存器的值1001A 35CRC校验2.2 写入类功能码功能码05写单个线圈 用于设置单个线圈的状态。比如要将设备1的线圈00005设为ON请求报文01 05 00 04 FF 00 8C 3A01设备地址05功能码00 04线圈地址00005对应0x0004FF 00设置ON的值FF00表示ON0000表示OFF8C 3ACRC校验响应报文与请求相同01 05 00 04 FF 00 8C 3A功能码06写单个寄存器 用于写入单个寄存器的值。比如要将设备1的寄存器40001写入值1234请求报文01 06 00 00 04 D2 8A 5B01设备地址06功能码00 00寄存器地址04 D2要写入的值1234的16进制8A 5BCRC校验响应报文与请求相同01 06 00 00 04 D2 8A 5B3. 异常响应处理在实际通信中设备可能会返回异常响应。异常响应的格式是在正常功能码的基础上加0x80并附带异常码。常见的异常码有01非法功能码设备不支持该功能码02非法数据地址请求的地址不存在03非法数据值写入的值超出范围04从站设备故障设备内部错误例如如果向不存在的寄存器地址发送读取请求可能会收到如下异常响应01 83 02 C0 F101设备地址83异常功能码038002异常码非法数据地址C0 F1CRC校验我在调试一个温控器时遇到过这样的问题手册上写着温度值存放在30001-30010但实际发送读取请求后返回了异常码02。后来发现这个设备的寄存器地址是从30000开始的所以30001对应的相对地址是0x0001而不是0x0000。4. 实际应用案例让我们通过一个完整的案例来理解如何实现一次Modbus通信。假设要读取某电表的三相电压值已知设备地址1A相电压300010x0000B相电压300020x0001C相电压300030x0002步骤1构造读取请求要读取三个寄存器的值使用功能码04读输入寄存器请求报文01 04 00 00 00 03 F1 C901设备地址04功能码00 00起始地址00 03读取数量F1 C9CRC校验步骤2解析响应假设设备返回01 04 06 01 F4 01 F4 01 F4 7A 12解析01设备地址04功能码06返回字节数6字节01 F4A相电压500V01 F4B相电压500V01 F4C相电压500V7A 12CRC校验步骤3处理异常情况如果返回的是异常响应比如01 84 02 C0 F1表示非法数据地址需要检查设备地址是否正确寄存器地址映射是否正确设备是否支持该功能码在Python中使用pymodbus库实现上述功能的代码示例from pymodbus.client import ModbusSerialClient # 创建Modbus RTU客户端 client ModbusSerialClient( methodrtu, port/dev/ttyUSB0, baudrate19200, timeout3 ) # 连接设备 connection client.connect() if connection: try: # 读取输入寄存器30001-30003 result client.read_input_registers( address0, # 30001对应0 count3, # 读取3个寄存器 slave1 # 设备地址 ) if not result.isError(): # 解析响应数据 a_phase result.registers[0] / 10.0 # 假设实际值需要除以10 b_phase result.registers[1] / 10.0 c_phase result.registers[2] / 10.0 print(f三相电压A相{a_phase}V, B相{b_phase}V, C相{c_phase}V) else: print(f读取失败异常码{result.exception_code}) finally: client.close() else: print(连接设备失败)5. 常见问题与解决方案在实际项目中我遇到过各种Modbus通信问题这里分享几个典型问题及其解决方法问题1地址映射混乱症状设备返回异常码02非法数据地址 解决方法确认设备手册中的地址是绝对地址还是相对地址检查地址偏移量有些设备从0开始有些从1开始使用Modbus调试工具先测试单个寄存器读写问题2字节序问题症状读取到的数值明显不合理如温度值显示为-32768 解决方法确认设备的字节序大端/小端必要时交换字节顺序对于浮点数确认是否使用IEEE 754标准问题3CRC校验错误症状设备无响应或返回异常 解决方法检查串口参数波特率、数据位、停止位、校验位确认CRC计算是否正确检查线路是否有干扰RS485建议使用双绞线并加终端电阻问题4响应超时症状主站发送请求后长时间无响应 解决方法检查设备地址是否正确确认通信线路连接正常检查从站设备是否处于正常工作状态适当增加超时时间特别是长距离通信时记得第一次调试Modbus设备时我花了整整一天时间才搞明白为什么读取的数值总是错误。后来发现是设备的寄存器使用了小端字节序而我的程序默认按大端解析。这个教训让我明白仔细阅读设备手册的通信协议部分有多么重要。