☰
Modbus功能码详解:从01到16的工业通信原理与实战
2026/10/31 3:50:14 网站建设 项目流程

简介:面向工业自动化与嵌入式开发初学者及现场调试人员,这份PDF围绕Modbus协议的功能码体系展开,帮助读者理解主从设备之间如何构造与解析报文,解决数字量、模拟量读写及通信诊断中的实际问题。包内仅含1个PDF文件,压缩包约10KB,篇幅精炼,便于随查随用。目前已有2330人学习下载,说明其在Modbus入门与查阅场景中具有一定参考价值。文档先以表格形式梳理01至22号功能码的名称与作用,并补充保留与异常应答区间,随后重点讲解最常用的01、02、03、04、05、06号功能码,逐一给出读线圈、读输入状态、读保持寄存器、读输入寄存器、强置单线圈、预置单寄存器的命令帧与响应帧实例,逐字段说明设备地址、起始地址、寄存器数量及CRC校验的高低字节顺序,并提示写单线圈时FF00与0000的合法取值。读者可据此快速对照报文结构,排查通信异常,也可作为开发调试时的速查手册。

1. 搞懂 Modbus 功能码:为什么工业设备之间能“对上话”

如果你拆开过配电柜或者看过产线上的 PLC,大概率会看到一根两芯的绿色线把一堆设备串在一起,它们之间没有网线、没有 Wi-Fi,却能把温度、开关状态、设定值传来传去。这背后经常跑的就是 Modbus 协议,而让这些设备“说同一句话”的核心,就是功能码。通俗地说,功能码就是报文里的一个字节,它告诉对方“我这次来是要读你、写你,还是问你身体怎么样”。很多人第一次看到01、03、05这些数字时一头雾水,其实只要把功能码理解成“动作指令”,整件事就顺了。搞懂它,你才能看懂一条报文到底在干什么,也才能在设备不响应时判断是地址错了、功能码用错了,还是数据格式不对。

2. 功能码到底在解决什么问题:一次请求的完整分工

2.1 主站和从站:谁问谁答

Modbus 采用的是“一问一答”的模式。总线上通常只有一个主站(比如上位机、触摸屏、PLC),其余都是从站(传感器、变频器、继电器模块)。主站发一条报文,从站收到后判断地址是不是自己,是就执行并回复,不是就沉默。功能码就放在这条报文的第二个字节,紧跟在设备地址后面。打个比方,设备地址像是门牌号,功能码像是敲门时喊的那句话——“我来抄表”还是“我来送东西”。没有功能码,从站只知道有人在敲门,却不知道要干什么。

2.2 四类数据区:功能码为什么有读有写

Modbus 把从站里的数据分成四个区,这是理解功能码分类的关键。线圈是可读可写的开关量,输入状态是只读的开关量,保持寄存器是可读可写的模拟量(16 位),输入寄存器是只读的模拟量。功能码 01 读线圈、02 读输入状态、03 读保持寄存器、04 读输入寄存器,正好对应四个区的读操作;05 写单个线圈、06 写单个保持寄存器、15 写多个线圈、16 写多个保持寄存器,对应写操作。你会发现读和写是分开的,只读区没有写功能码,这是协议设计上的保护——输入状态和输入寄存器反映的是外部真实信号,不允许主站随意覆盖。

2.3 一条报文长什么样:以 03 功能码为例

读保持寄存器是最常见的操作。主站发送的报文结构是:设备地址、功能码 03、起始地址高字节、起始地址低字节、寄存器数量高字节、寄存器数量低字节、CRC 低字节、CRC 高字节。假设设备地址 17(十六进制 11),从地址 107 开始读 3 个寄存器,报文就是11 03 00 6B 00 03 CRC低 CRC高。从站回复:设备地址、功能码 03、返回字节数、数据 1 高、数据 1 低、数据 2 高、数据 2 低……最后跟 CRC。一个寄存器占两个字节,所以读 3 个寄存器返回 6 个数据字节。下面这段 Python 用最小代码模拟了组包过程,帮助理解字节顺序:

# 构造一条读保持寄存器的 Modbus RTU 请求(不含 CRC 计算细节) device_addr = 0x11 # 从站地址 17 func_code = 0x03 # 读保持寄存器 start_addr = 0x006B # 起始地址 107 reg_count = 0x0003 # 读 3 个寄存器 frame = bytes([ device_addr, func_code, (start_addr >> 8) & 0xFF, start_addr & 0xFF, # 地址高字节在前 (reg_count >> 8) & 0xFF, reg_count & 0xFF # 数量高字节在前 ]) print(frame.hex()) # 输出 1103006b0003,后面还需追加 CRC

这段代码只拼了前 6 个字节,CRC 需要另外按多项式计算并低字节在前追加。注意地址和数量都是高字节在前,这是 Modbus 的大端约定,和很多单片机内部的小端存储相反,初学者在这里翻车的不少。参数上,起始地址从 0 开始计,寄存器数量不能超过从站支持的上限,常见设备一次最多允许读 125 个寄存器,超过会返回异常码。

3. 常用功能码逐个拆:01 到 06 分别管什么

3.1 01 和 02:读开关量,位和字节的换算最容易错

功能码 01 读线圈,02 读输入状态,报文结构完全一样,只是功能码字节不同。主站指定起始地址和要读的开关数量,从站返回的每个数据字节包含 8 个开关的状态,最低位对应起始地址那个开关。比如返回CD,二进制是1100 1101,从低位往高位读就是:第 1 个闭合、第 2 个断开、第 3 个闭合、第 4 个闭合、第 5 个断开、第 6 个断开、第 7 个闭合、第 8 个闭合。如果读的数量不是 8 的整数倍,最后一个字节的高位补 0,解析时不要把它当成有效开关。这里最常见的误区是把位序读反,或者把返回字节数当成开关数量——返回字节数是数据字节的个数,不是开关个数。

3.2 03 和 04:读模拟量,一个值占两个字节

03 读保持寄存器,04 读输入寄存器,区别只在数据区是否可写。返回的数据每两个字节组成一个 16 位值,高字节在前。比如返回02 2B 00 00 00 64,三个值分别是 0x022B=555、0x0000=0、0x0064=100。很多传感器会把温度放大 10 倍存放,读到 555 实际表示 55.5,具体换算要看设备手册。这里容易混淆的是“寄存器地址”和“寄存器编号”:有些手册写 40001,那是 Modbus 数据模型里的编号,实际报文里的起始地址要减 1 变成 0,再转十六进制。不确认这一点,读出来的数据会整体偏移一个寄存器。

3.3 05 和 06:写单个值,为什么 05 的数据只有两种合法值

05 写单个线圈,报文里的数据域只有FF 00表示闭合、00 00表示断开,其他值都非法。这是协议刻意设计的:写开关不需要连续量,只给两个明确状态,避免歧义。06 写单个保持寄存器则允许任意 16 位值,比如把地址 1 的寄存器写成 3,报文是11 06 00 01 00 03 CRC。这两个功能码成功后从站会原样返回请求报文,失败则不响应或返回异常码。实际调试时,如果发了 05 但设备没动,先检查数据域是不是写成了00 01之类的值,这种错误很隐蔽,报文长度对、CRC 也对,但设备就是不执行。

3.4 15 和 16:批量写的效率账

当需要连续写多个线圈或寄存器时,用 15 和 16 比循环发 05、06 快得多。以 16 为例,报文包含起始地址、寄存器数量、字节数,然后是按顺序排列的数据。一次最多写 123 个寄存器(受报文长度限制)。批量写在参数下发场景很常见,比如一次性给变频器写频率、加减速时间、运行命令。需要注意的是,部分老设备对批量写支持不完整,或者要求地址连续且不跨区,遇到写一半成功一半失败的情况,优先怀疑从站对批量写的边界处理,而不是 CRC。

4. 容易搞混的几个概念:功能码、地址、异常码不是一回事

4.1 功能码和寄存器地址:一个管动作,一个管位置

初学者常把功能码当成“地址”的一部分,其实功能码回答“做什么”,地址回答“对哪里做”。同一条报文里两者同时存在,缺一不可。比如 03 功能码配起始地址 0x006B,意思是“读保持寄存器区从 107 开始的位置”。如果功能码写错成 04,读的就是输入寄存器区同一个偏移,可能读到完全不同的值,甚至因为该区不存在而返回异常。

4.2 功能码和异常码:回复里的 0x83 是什么

当从站无法执行请求时,会在回复的功能码最高位置 1,比如 03 变成 0x83,后面跟一个异常码字节。异常码 02 表示非法数据地址,03 表示非法数据值,04 表示从站设备故障。很多人看到 0x83 以为是另一个功能码,其实是“03 的失败回复”。区分方法很简单:正常回复的功能码和请求一致,异常回复的功能码等于请求功能码加 0x80。

4.3 01 读线圈和 02 读输入状态:都是开关量,为什么分两个码

两者返回格式一模一样,区别在数据来源和可写性。线圈区可以被 05、15 改写,输入状态区只读,反映的是物理输入点的真实电平。如果把本该读输入状态的传感器接到线圈区去读,可能读到的是上次写入的缓存值,而不是现场信号。判断依据是设备手册里该点位属于哪个区,不能只看“它是个开关”就随便选功能码。

4.4 功能码 08 和 11、12:诊断类功能码不是用来读数据的

08 回送诊断校验、11 读事件计数、12 读通信事件记录,这些属于诊断功能,返回的是通信质量信息,不是工艺数据。初学者容易把它们和读寄存器混为一谈,试图用 08 去读设备参数,结果报文格式对不上。诊断功能码的报文结构有专门的子功能码字段,和 01 到 06 的“地址+数量”模式不同,用之前要单独查手册。

4.5 保留功能码和厂商自定义:看到 65 以上不要硬猜

功能码 22 到 64 保留扩展,65 到 72 留给用户自定义,73 到 119 属于非法功能,128 到 255 用于异常应答。有些设备厂商会在自定义区实现特殊功能,但这些没有统一标准,不能按通用 Modbus 的格式去解析。遇到不认识的的功能码,先确认设备手册是否声明了扩展定义,否则贸然发过去大概率收到异常码 01(非法功能)。

5. 什么时候该用哪个功能码:一份现场判断清单

选功能码的本质是回答三个问题:要动的是开关量还是模拟量?是读还是写?目标区允不允许写?把这三个问题按顺序过一遍,基本不会选错。下面这张表把常见需求直接映射到功能码,现场调试时可以对照:

现场需求数据类型方向选用功能码
读继电器输出状态开关量读01
读限位开关实际电平开关量读02
读温度、压力等测量值模拟量读03 或 04
控制一个继电器吸合开关量写05
修改一个设定值模拟量写06
一次下发多组参数模拟量写16
一次控制多个输出开关量写15

需要提醒的是,03 和 04 的选择取决于设备手册把测量值放在保持寄存器还是输入寄存器,不是所有传感器都放在 03。有些设备两个区映射同一份数据,读哪个都行,但写操作只能对 03 对应的区做。另外,05 和 06 虽然叫“强置”和“预置”,实际效果就是写单个值,不要被名字吓到。

什么时候别用某个功能码?当你不确定目标区是否存在时,不要用写功能码去“试探”,因为写操作可能改变设备状态,造成误动作。调试阶段优先用读功能码确认地址映射,再用写功能码。还有,批量写 15、16 在跨区或不连续地址时不要用,老老实实拆成单条 05、06,虽然慢一点,但排错容易。我曾经在一个项目里为了省事用 16 一次性写 20 个寄存器,结果其中三个地址在从站里不连续,设备返回异常码 02,查了半天才发现是地址跳空。从那以后我每次下发批量写之前,都强制把地址表逐条核对一遍连续性,再决定用 16 还是拆成 06。希望这些能帮你在下一次接线调试时少走一点弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询