
简介这份PDF资料面向工业自动化、嵌入式开发及现场调试人员系统梳理Modbus协议的功能码体系与报文实例帮助读者理解主从设备间的数据交互逻辑。内容覆盖01至19号常用功能码逐一说明读取线圈、输入状态、保持寄存器、输入寄存器以及强置线圈、预置寄存器等操作的作用与差异并给出读可读写数字量、读只可读数字量、写数字量、读可读写模拟量、写单个模拟量等典型场景的完整命令帧与响应帧示例逐字段解析设备地址、命令号、起始地址、寄存器数量与CRC校验的构造方式。资源包为1个PDF文件约10KB轻量便携适合随时查阅。目前已有2326人学习下载可作为Modbus通信入门与报文排错的速查手册。1. Modbus 通讯功能码从一份 PDF 到能跑通的读写链路很多人第一次接触 Modbus是从一份叫《Modbus通讯功能码及实例.pdf》的文档开始的。打开一看功能码 01、02、03、04、05、06、0F、10 排成一列每个后面跟着请求帧和响应帧的十六进制示例看着挺清楚合上文档却不知道从哪下手。问题不在文档在于它只告诉你「帧长什么样」没告诉你「这些帧在真实设备上怎么发出去、怎么收回来、错了怎么查」。Modbus 通讯功能码本质是一套约定主站用哪个数字告诉从站「我要读你的线圈」还是「我要写你的保持寄存器」从站按约定回什么。它跑在 Modbus RTU串口或 Modbus TCP以太网之上前者靠 CRC 校验后者靠 MBAP 头。搞懂功能码等于拿到了读写 PLC、传感器、数控机床运行状态数据的钥匙。这篇面向的是要动手接线、写代码、调通一条 Modbus 链路的工程师不是背协议概念的考生。2. 功能码到底在读写什么四类数据区与请求帧结构2.1 线圈、离散输入、保持寄存器、输入寄存器不是一回事Modbus 把从站的数据分成四个区功能码就是针对这四个区的读写动作。搞混这四个区是新手翻车的头号原因。数据区读写属性位/字常用功能码典型用途线圈 Coil读写位01 读、05 写单个、0F 写多个继电器输出、启停控制离散输入 Discrete Input只读位02限位开关、按钮状态保持寄存器 Holding Register读写字03 读、06 写单个、10 写多个参数设定值、PLC 寄存器输入寄存器 Input Register只读字04温度、电流等采集量线圈和离散输入都是「位」区别在读写权限保持寄存器和输入寄存器都是「字」16 位区别同样在权限。热搜里常出现的「modbus 线圈 寄存器 的区别」答案就在这张表里线圈是位、可写寄存器是字、保持寄存器可写而输入寄存器只读。2.2 请求帧的字段拆解从站地址、功能码、起始地址、数量、CRC以 Modbus RTU 读保持寄存器功能码 03为例主站发出的请求帧固定是 8 个字节[从站地址 1B][功能码 1B][起始地址高 1B][起始地址低 1B][数量高 1B][数量低 1B][CRC低 1B][CRC高 1B]假设从站地址 1读起始地址 0 开始的 2 个保持寄存器帧就是01 03 00 00 00 02 C4 0B逐字段看01是从站地址03是功能码00 00是起始地址 000 02是读 2 个寄存器C4 0B是 CRC16 校验注意低字节在前。响应帧则是01 03 04 XX XX YY YY CRC04是字节数2 个寄存器 4 字节后面 4 字节是数据最后 2 字节 CRC。看懂这个结构任何功能码的帧你都能自己推。2.3 功能码 01/02/03/04/05/06/0F/10 的适用边界01、02 读位响应里用字节数 位打包读 9 个线圈会返回 2 字节第 2 字节高位补 0。03、04 读字响应里字节数 寄存器数 × 2。05 写单个线圈数据字段固定FF 00表示 ON00 00表示 OFF写别的值从站会拒绝。06 写单个保持寄存器直接把 2 字节值放进数据字段。0F 写多个线圈、10 写多个保持寄存器请求里带字节数响应里回显起始地址和数量。选功能码的原则很简单能读单个就别读一片能写单个就别写一片减少一次交互的数据量链路更稳。但批量采集场景下10 和 0F 能显著减少轮询次数该用还得用。3. 用 Python 跑通第一条 Modbus RTU 读写链路3.1 环境准备与最小可跑代码Python 里最省事的是pymodbus串口依赖pyserial。先装pip install pymodbus pyserial下面是一段读保持寄存器 写单个线圈的最小代码串口参数按你的设备改from pymodbus.client import ModbusSerialClient import time # 串口参数波特率、数据位、校验、停止位必须和从站一致 client ModbusSerialClient( portCOM3, # Linux 下是 /dev/ttyUSB0 baudrate9600, bytesize8, parityN, # 无校验 stopbits1, timeout1 # 超时 1 秒太短会误判掉线 ) if not client.connect(): print(串口打开失败检查端口号和占用情况) exit() # 读从站 1 的保持寄存器起始地址 0读 2 个 rr client.read_holding_registers(address0, count2, slave1) if not rr.isError(): print(寄存器值:, rr.registers) else: print(读失败:, rr) # 写从站 1 的线圈 0 为 ON wr client.write_coil(address0, valueTrue, slave1) print(写线圈结果:, wr) client.close()逻辑说明ModbusSerialClient负责串口收发和 CRC 计算你不用自己拼帧。read_holding_registers对应功能码 03write_coil对应功能码 05。slave参数就是从站地址必须和从站实际设置一致否则从站不响应表现为超时。参数说明baudrate常见 9600 或 19200parity常见N无、E偶、O奇这三个参数和stopbits只要有一个和从站不一致通信就全乱。timeout建议 1 秒串口链路本身慢设 0.1 秒容易误报。3.2 用 Modbus Poll / Modbus Slave 先验证再写代码血泪经验不要一上来就写代码调。先用 Modbus Slave 模拟一个从站用 Modbus Poll 当主站把功能码、地址、数量点一遍确认帧能通再换成自己的代码。这样能把「协议问题」和「代码问题」分开。Modbus Slave 里设置从站地址、功能码区域比如 Holding Registers、起始地址然后在对应格子里填值。Modbus Poll 里设同样的从站地址、功能码 03、起始地址 0、数量 2点连接。如果 Poll 里能看到 Slave 里填的值说明链路和参数都对。这时候再跑上面的 Python 代码成功率会高很多。热搜里「modbus slave 密钥」「modbus poll 密钥」这类词本质是找工具授权但验证链路用试用版或免费替代工具比如diagslave、modbuspal完全够用不必卡在授权上。3.3 地址映射为什么你读的 40001 和代码里的 0 对不上这是最高频的翻车点。设备手册上写「温度在 40001」你代码里写address40001结果报非法数据地址。原因是 Modbus 有两种地址表示法协议地址PDU 地址从 0 开始代码里用的就是这个。数据模型地址PLC 地址40001 表示保持寄存器区第 1 个对应协议地址 0。换算规则40001 → 040002 → 130001 → 0输入寄存器00001 → 0线圈10001 → 0离散输入。所以手册写 40001代码里写address0手册写 40100代码里写address99。不同设备的 Modbus 地址映射表不一定一样有的厂商直接给协议地址有的给 PLC 地址还有的偏移量自己定。拿到手册先确认它用的是哪种别想当然。4. Modbus TCP 与 RTU 的差异功能码一样封装不一样4.1 MBAP 头TCP 比 RTU 多出来的 7 个字节功能码在 TCP 和 RTU 里完全一样03 还是 0310 还是 10。区别在帧的封装。Modbus TCP 在 PDU 前面加了 7 字节的 MBAP 头[事务标识 2B][协议标识 2B][长度 2B][单元标识 1B][功能码...][数据...]事务标识用来匹配请求和响应协议标识固定 0长度表示后面还有多少字节单元标识在 TCP 里通常当从站地址用网关场景下有意义。因为 TCP 本身有校验Modbus TCP 不再带 CRC。用 Python 跑 TCP 只需换客户端类from pymodbus.client import ModbusTcpClient # TCP 默认端口 502 client ModbusTcpClient(host192.168.1.10, port502, timeout1) client.connect() rr client.read_holding_registers(address0, count2, slave1) print(rr.registers if not rr.isError() else rr) client.close()逻辑说明ModbusTcpClient自动处理 MBAP 头slave参数对应单元标识。TCP 链路比串口快得多timeout可以设小一点但网络抖动时仍建议留 1 秒。4.2 端口、单元标识与网关场景Modbus TCP 默认端口 502但很多设备或网关会改成别的端口连不上先确认端口。单元标识在直连设备时经常被忽略填 0 或 1 都行但在 Modbus RTU 转 TCP 的网关后面单元标识就是真实从站的地址填错就读不到。热搜里「fx5u modbus tcp 主站功能」「汇川 easy 怎么做 modbus 从站」这类问题本质都是设备侧的角色配置谁当主站、谁当从站、地址怎么映射。三菱 FX5U 做 TCP 主站时用内置以太网口配置里指定对方 IP 和端口功能码用 03/10 读写汇川 Easy 系列做从站时要在软件里使能从站功能并设定从站地址和寄存器映射表。这些配置各厂商界面不同但底层功能码和帧结构不变。4.3 用 Wireshark 抓 Modbus TCP 报文定位问题TCP 的好处是可以抓包。Wireshark 里过滤modbus或tcp.port 502能看到完整的请求和响应。如果请求发出去了但没响应看是不是从站地址错了如果响应是异常帧功能码最高位置 1比如 03 变成 83看异常码01 非法功能从站不支持这个功能码。02 非法数据地址地址超出从站范围。03 非法数据值写入的值不合法。04 从站设备故障从站内部错误。异常码是排查的黑匣子比盲猜快得多。RTU 场景下没有 Wireshark 这么方便但可以用串口助手看原始字节或者用 Modbus Poll 的报文监视窗口。5. 避坑与排查功能码调不通时先看这几条5.1 现象请求发出去从站完全不响应原因从站地址不对、串口参数不一致、接线 A/B 反了、从站没上电或没使能。RTU 里 A/B 线接反是最隐蔽的因为有时候反了也能通但误码率高。解决先用 Modbus Poll 扫从站地址很多工具支持地址扫描确认地址核对波特率、校验、停止位用万用表量 A/B 线电压或者直接对调试试确认从站设备处于运行状态。5.2 现象响应是异常帧异常码 02原因读写的地址超出从站实际支持的范围。常见于把 PLC 地址 40001 直接填进代码或者从站只映射了 0 到 99 而你要读 200。解决查设备手册的地址映射表确认协议地址范围用 Modbus Poll 从地址 0 开始小批量试读逐步加数量找到边界。5.3 现象读回来的寄存器值明显不对比如温度是 65000原因字节序或字序问题。Modbus 寄存器是 16 位但 32 位数据浮点、长整型要占两个寄存器高字低字谁在前不同厂商不一样。还有的厂商在一个寄存器内高低字节也反。解决先读单个寄存器确认原始值再判断是字节交换还是字交换。pymodbus里可以用payload手动处理或者用struct按厂商文档解。常见组合有 ABCD、CDAB、BADC、DCBA 四种试一遍就知道。5.4 现象写单个线圈用 05写 0x0100 被拒绝原因功能码 05 的数据字段只认FF 00ON和00 00OFF写其他值从站返回异常码 03。解决写线圈用布尔值不要自己拼十六进制。pymodbus的write_coil(valueTrue/False)会自动处理。5.5 现象轮询几十个寄存器时偶尔超时原因串口链路带宽有限一次读太多寄存器响应帧变长加上从站处理时间容易超时。或者轮询间隔太短从站还没处理完下一个请求就来了。解决把大批量读拆成多次小批量每次读 10 到 20 个寄存器轮询间隔留足RTU 下建议至少 100 毫秒timeout适当放大到 2 秒。TCP 下可以并发但要注意从站是否支持多连接。6. 进阶用功能码 10 批量写 校验码在线计算自检批量写保持寄存器功能码 10是参数下发场景的常用手段。下面这段代码一次写 4 个寄存器from pymodbus.client import ModbusSerialClient client ModbusSerialClient(portCOM3, baudrate9600, parityN, stopbits1, timeout1) client.connect() # 从地址 10 开始写 4 个寄存器值分别是 100,200,300,400 values [100, 200, 300, 400] wr client.write_registers(address10, valuesvalues, slave1) print(批量写结果:, wr) client.close()逻辑说明write_registers对应功能码 10请求帧里会带起始地址、寄存器数量、字节数和数据响应帧回显起始地址和数量。参数上values列表长度就是寄存器数量地址是起始协议地址。注意一次写太多寄存器可能超过从站缓冲区一般不超过 123 个。调试时如果手头没有工具可以用在线 CRC 计算器自检。把请求帧去掉 CRC 的字节输进去算出 CRC 和实际抓到的对比。比如01 03 00 00 00 02的 CRC 应该是C4 0B低字节在前。对不上说明帧拼错了或者字节序理解反了。我自己的习惯是每调一个新设备先用 Modbus Poll 把要用的功能码全点一遍把请求帧抄下来再用代码复现同样的帧。这样代码跑不通时能确定是代码问题而不是协议理解问题。功能码这东西看十遍 PDF 不如亲手发一帧、收一帧。希望帮到你。本文还有配套的精品资源点击获取