
1. 搞懂Modbus功能码先弄明白它到底在解决什么问题干了十来年自动化现场调试最怕遇到什么不是PLC程序写得有多复杂也不是上位机界面有多花哨而是通讯不通。尤其是Modbus这种在工控圈里遍地都是的协议几乎每个搞电气、搞自控的人都绕不开它。你可以不懂OPC UA也可以不碰Profinet但只要你在现场摸过仪表、变频器、电表、温控器Modbus就一定会在某个时刻出现在你面前。Modbus功能码说白了就是主站告诉从站“我要干什么”的那串代码。就像你去饭店点菜服务员问你“吃什么”你说“来个宫保鸡丁”这个“宫保鸡丁”就是功能码。主站发一帧报文里面有个字节专门用来标识操作类型——是读线圈、读寄存器还是写单个寄存器、写多个寄存器。从站收到之后根据这个字节去执行对应动作然后把结果返回。很多人第一次接触Modbus觉得它简单得不像话不就是发几个字节收几个字节吗但真到了现场问题就来了为什么读回来的数据是乱的为什么写不进去为什么有时候通有时候不通这些问题的根源十有八九都跟功能码的理解不到位有关。比如你拿读保持寄存器的功能码去读线圈从站要么不响应要么返回异常码你还以为是接线问题查了半天线路最后发现是功能码用错了。这篇文章我打算把Modbus功能码从头到尾捋一遍不光是列个表告诉你01是读线圈、03是读保持寄存器而是把每个功能码的使用场景、报文结构、常见坑点都讲清楚。适合刚入行的电气小白也适合干了几年但一直没系统整理过的老手。你看完之后至少能做到拿到一个Modbus设备知道该用哪个功能码去读写报文发出去之后能看懂返回的数据对不对出了问题知道往哪个方向排查。2. Modbus功能码的整体分类与设计逻辑2.1 为什么功能码要分这么多类Modbus协议最早是Modicon公司搞出来的用来在PLC和现场设备之间传数据。当时的设计思路很朴素把设备里能读写的东西分成几类每类给一个编号主站想操作哪类就发对应的编号。这个思路一直沿用到现在成了工控通讯的“普通话”。功能码分四大类位操作、寄存器操作、文件记录操作、诊断和其他。位操作就是读写开关量一个位代表一个状态比如线圈通断、输入点高低电平。寄存器操作就是读写16位数据比如温度值、频率设定、电量参数。文件记录操作用得少一般用于批量读写历史数据。诊断类功能码主要用来查通讯质量、读设备状态。为什么这么分因为现场设备的数据类型就这几种。开关量是布尔值寄存器是数值分开处理效率最高。你想想如果一个功能码既能读位又能读寄存器那报文解析就乱了从站也不知道该返回什么格式。所以Modbus的设计者从一开始就把数据类型和功能码绑定简单粗暴但有效。2.2 常用功能码速查表先上一张表把最常用的功能码列出来后面再逐个拆解。功能码名称操作对象数据类型典型用途01读线圈线圈位读继电器状态、阀门开关02读离散输入离散输入位读限位开关、按钮状态03读保持寄存器保持寄存器16位读温度、频率、电量04读输入寄存器输入寄存器16位读传感器原始值05写单个线圈线圈位控制继电器通断06写单个寄存器保持寄存器16位设定频率、温度阈值15写多个线圈线圈位批量控制开关量16写多个寄存器保持寄存器16位批量下发参数23读写多个寄存器保持寄存器16位一次完成读和写这张表建议截图存手机里现场调试的时候随时能翻出来看。尤其是01、03、05、06这四个日常工作中出现频率最高先把这四个吃透基本能应付八成以上的场景。2.3 功能码的报文结构长什么样Modbus RTU模式下一帧报文的基本结构是从站地址 功能码 数据 CRC校验。从站地址占1字节功能码占1字节数据长度根据功能码不同而变化CRC占2字节。以读保持寄存器为例主站发送的报文是从站地址 03 起始地址高字节 起始地址低字节 寄存器数量高字节 寄存器数量低字节 CRC低字节 CRC高字节。从站返回的报文是从站地址 03 字节数 数据高字节 数据低字节 ... CRC。这里有个细节要注意寄存器地址在报文里是从0开始算的但很多设备手册上写的是从1开始。比如手册上写“温度值在40001寄存器”你发报文的时候起始地址要填0不是填40001。这个坑我踩过不止一次刚开始调的时候死活读不到数据后来才发现是地址偏移的问题。一般来说保持寄存器的地址范围是40001到49999对应报文里的0到9998输入寄存器是30001到39999对应报文里的0到9998线圈是00001到09999对应报文里的0到9998离散输入是10001到19999对应报文里的0到9998。注意不同厂家对地址的定义可能不一样有的直接用偏移量有的用绝对地址。拿到设备第一件事就是翻通讯手册确认地址映射关系别凭经验瞎猜。3. 常用功能码逐个拆解与实操要点3.1 功能码01和02读开关量功能码01用来读线圈也就是可读写的开关量。线圈在设备里通常对应继电器输出、电磁阀控制、指示灯状态这些。功能码02用来读离散输入也就是只读的开关量比如限位开关、按钮、接近开关。这两个功能码的报文格式一模一样区别只在于操作的对象不同。主站发送从站地址 功能码 起始地址高 起始地址低 数量高 数量低 CRC。从站返回从站地址 功能码 字节数 数据字节1 数据字节2 ... CRC。举个例子你要读从站地址为1的设备从线圈地址0开始的8个线圈状态。发送报文01 01 00 00 00 08 CRC。假设返回01 01 01 55 CRC。这里的55是十六进制转成二进制是01010101表示第0、2、4、6个线圈为ON第1、3、5、7个线圈为OFF。注意返回数据是从低位到高位对应起始地址的第一个线圈对应字节的最低位。实操中容易遇到的问题读回来的字节数和预期不符。比如你读8个线圈返回1个字节读9个线圈返回2个字节。因为Modbus是按字节打包的不够8位也要占一个字节。所以计算字节数的公式是(数量 7) / 8取整数部分。实操心得读线圈的时候如果只需要读一两个点建议单独读不要批量读一大片。因为批量读的时候如果某个地址不存在从站可能整个报文都不返回你连哪个点有问题都不知道。分开读虽然多几帧报文但排查问题方便得多。3.2 功能码03和04读寄存器功能码03读保持寄存器这是Modbus里用得最多的功能码没有之一。保持寄存器是可读写的16位寄存器用来存各种参数温度设定值、频率给定、PID参数、电量数据等等。功能码04读输入寄存器通常是只读的用来存传感器原始采集值。报文格式主站发送从站地址 03 起始地址高 起始地址低 数量高 数量低 CRC。从站返回从站地址 03 字节数 数据高 数据低 ... CRC。字节数等于寄存器数量乘以2因为每个寄存器占2个字节。这里重点讲一下数据解析。假设你读到一个寄存器的值是0x0064转成十进制是100。但这个100代表什么是100度还是10.0度还是1.00度这取决于设备手册里定义的缩放因子。有的设备温度值直接存整数单位是0.1度那100就是10.0度有的存的是实际值100就是100度。还有的设备用两个寄存器拼成一个32位浮点数这时候就要把两个16位寄存器合并再解析。我见过一个现场电表读回来的电压值是0x0898转十进制是2200。一开始以为是2200V吓了一跳后来查手册才知道缩放因子是0.1实际电压是220.0V。所以读寄存器一定要先确认数据格式是整数还是浮点有没有缩放字节序是大端还是小端注意32位数据在Modbus里跨两个寄存器字节序有ABCD、CDAB、BADC、DCBA四种可能。不同厂家用的不一样遇到浮点数读出来是乱码先换字节序试试。3.3 功能码05和06写单个量功能码05写单个线圈用来控制一个开关量的通断。发送报文从站地址 05 地址高 地址低 FF00 CRC表示置位0000表示复位。注意这里的数据域是固定的两个值FF00和0000其他值都是非法的。从站收到之后如果执行成功会把原报文原样返回。功能码06写单个寄存器用来设定一个参数值。发送报文从站地址 06 地址高 地址低 数据高 数据低 CRC。从站执行成功后也是原样返回。这两个功能码看起来简单但实际用的时候有几个坑。第一个坑是写进去没反应。很多时候是因为设备处于某种保护状态比如运行中不允许修改参数或者需要先写使能位。第二个坑是写进去的值被限制了。比如你写频率给定5000代表50.00Hz但设备最大频率只有4000那它可能自动截断到4000返回的报文还是5000但实际值变了。第三个坑是掉电不保存。很多设备的保持寄存器是RAM的断电就丢需要发特定的保存命令或者写EEPROM。实操心得写寄存器之前先读一遍当前值确认通讯正常、地址正确然后再写。写完再读一遍确认写入生效。这个“读-写-读”的流程看起来麻烦但能避免很多低级错误。3.4 功能码15和16批量写功能码15写多个线圈功能码16写多个寄存器。这两个功能码的存在是为了提高效率。比如你要设定10个参数用06要发10帧报文用16一帧就够了。功能码16的报文格式从站地址 10 起始地址高 起始地址低 寄存器数量高 寄存器数量低 字节数 数据1高 数据1低 ... CRC。从站返回从站地址 10 起始地址高 起始地址低 寄存器数量高 寄存器数量低 CRC。这里要注意字节数的计算寄存器数量乘以2。比如写10个寄存器字节数是20十六进制是0x14。数据域按顺序排列第一个寄存器的数据在前。批量写的时候如果其中一个寄存器地址非法或者值超范围从站可能整个报文都拒绝执行也可能部分执行。这取决于从站的实现。所以批量写之后最好逐个读回来确认。3.5 功能码23读写一把梭功能码23是读写多个寄存器这个功能码在需要同时读写的场景下很有用。比如你要调整PID参数同时想读回当前的实际值用23就可以一帧报文完成。发送格式从站地址 17 读起始地址高 读起始地址低 读数量高 读数量低 写起始地址高 写起始地址低 写数量高 写数量低 写字节数 写数据 CRC。这个功能码不是所有设备都支持用之前要确认从站是否实现了23功能码。如果不支持从站会返回异常码01非法功能。4. 异常码与常见故障排查4.1 从站返回异常码是什么意思当主站发送的功能码从站不支持或者地址、数据有问题时从站会返回一个异常响应。异常响应的格式是从站地址 功能码最高位置1 异常码 CRC。比如你发03从站返回8383就是03的最高位置1表示这是异常响应。常见的异常码有异常码名称含义排查方向01非法功能从站不支持该功能码确认设备手册支持的功能码列表02非法数据地址地址超出范围或不存在检查地址映射确认寄存器范围03非法数据值数据超出允许范围检查写入值是否在设备允许区间内04从站设备故障从站执行时出错检查设备状态可能需要复位05确认从站已接收正在处理等待后重试06从站设备忙从站暂时无法处理增加延时后重试看到异常码不要慌先看是哪个码。01和02最常见基本都是地址或功能码用错了。03一般是写入值超范围。04和06是设备本身的问题可能需要断电重启或者检查设备故障。4.2 通讯不通的排查思路通讯不通是现场最头疼的问题我一般按这个顺序排查第一步检查物理层。接线对不对A接A、B接B别接反了。终端电阻有没有长距离通讯两端要加120欧姆终端电阻。屏蔽线接地了没有屏蔽层单端接地不要两端都接否则会形成地环路。第二步检查通讯参数。波特率、数据位、停止位、校验位主从必须完全一致。最常见的是波特率不对比如主站9600从站19200那肯定通不了。校验位也容易忽略有的设备默认无校验有的默认偶校验。第三步检查从站地址。主站发的地址和从站实际地址要匹配。有的设备地址是通过拨码开关设置的有的通过参数设置。如果总线上有多个从站地址不能重复。第四步用工具抓包。如果以上都没问题就用串口调试助手或者Modbus调试工具抓一下报文看看主站发出去的是什么从站有没有返回。如果主站发了但从站没返回说明从站没收到或者收到了但不认识。如果从站返回了但主站不认可能是CRC校验错了或者格式不对。实操心得现场调试的时候我习惯先用一个已知能通的设备测试确认主站参数和接线没问题再接入目标设备。这样可以快速定位问题是在主站侧还是在从站侧。4.3 数据读回来不对怎么查数据读回来不对分几种情况。第一种是数值完全不对比如读温度读回来65535。这通常是地址错了读到了空地址或者保留地址。第二种是数值差一个倍数比如实际220V读回来2200。这是缩放因子的问题查手册确认。第三种是数值跳变一会儿对一会儿不对。这可能是通讯干扰检查屏蔽和接地或者降低波特率试试。第四种是32位数据解析错乱这是字节序的问题换一种字节序组合试试。我一般会准备一个Excel表格把设备手册里的寄存器地址、数据类型、缩放因子、字节序都整理进去调试的时候对着表格逐个核对。这个习惯帮我省了很多时间尤其是设备多、参数杂的项目。5. 实际项目中的功能码应用案例5.1 案例一读电表数据某项目要读一个三相电表的电压、电流、功率。电表手册上写电压在40001-40003电流在40004-40006功率在40007-40009都是32位浮点数字节序是CDAB。我的操作步骤第一步确认电表的从站地址和通讯参数。第二步用03功能码读40001开始的9个寄存器。第三步把返回的18个字节按CDAB的顺序解析成3个浮点数。第四步和电表面板显示的值对比确认解析正确。这里的关键是字节序。CDAB的意思是第一个寄存器的高字节是C低字节是D第二个寄存器的高字节是A低字节是B。实际浮点数的字节顺序是ABCD。所以解析的时候要把两个寄存器的字节重新排列。5.2 案例二控制变频器启停和频率某项目要控制一台变频器的启停和频率给定。变频器手册上写控制字在40100频率给定在40101都是16位整数频率缩放因子是0.01。操作步骤第一步用06功能码写40100值设为1表示启动。第二步用06功能码写40101值设为5000表示50.00Hz。第三步用03功能码读40102运行状态和40103实际频率确认变频器已启动并且频率正确。这里要注意的是有些变频器的控制字不是简单写1就启动可能需要按位操作。比如bit0是启动bit1是方向bit2是故障复位。这时候就要先读当前控制字修改对应位再写回去。直接写1可能会把其他位清零导致意外停机。5.3 案例三批量读取温控器参数某项目有8台温控器要读取每台的当前温度、设定温度、报警状态。每台温控器的寄存器地址相同但从站地址不同。操作步骤第一步写一个循环从站地址从1到8。第二步对每个从站用03功能码读起始地址0开始的3个寄存器。第三步解析数据当前温度和设定温度是16位整数缩放因子0.1报警状态是位数据需要单独用01功能码读。这个案例的难点在于轮询速度。8台设备如果每台都读一轮下来可能要几百毫秒。如果对实时性要求高可以只读关键数据或者提高波特率。但提高波特率会降低通讯距离需要权衡。6. 调试工具与实用技巧6.1 常用调试工具推荐调试Modbus手里没几个工具是不行的。我常用的有串口调试助手用来抓原始报文看主站发了什么、从站回了什么。Modbus Poll和Modbus Slave一个模拟主站一个模拟从站可以快速验证通讯参数和功能码。还有各种PLC的编程软件自带的Modbus调试功能比如某些品牌的PLC可以直接在线监控Modbus通讯状态。如果是以太网Modbus TCPWireshark是必备的可以抓包分析每一帧的细节。不过Wireshark用起来门槛稍高需要懂TCP/IP和Modbus TCP的报文结构。6.2 提高调试效率的几个习惯第一个习惯先离线测试再上线。用Modbus Slave模拟一个从站把地址、数据都配好主站先和模拟从站通讯确认报文格式和解析逻辑没问题再接到真实设备上。这样可以排除主站侧的问题。第二个习惯做好地址映射表。每个项目的设备寄存器地址都整理成表格标注功能码、数据类型、缩放因子、读写权限。调试的时候对着表格操作不容易出错。第三个习惯保存通讯日志。调试通了之后把正常的收发报文保存下来以后出问题可以对比。我一般会存一个“正常通讯报文.txt”里面记录每个操作的发送和返回报文。第四个习惯分段排查。通讯不通的时候不要一上来就怀疑设备坏了。先查物理层再查参数再查地址最后查设备。按这个顺序大部分问题都能快速定位。6.3 常见问题速查表现象可能原因解决方法从站无响应接线错误、参数不匹配、地址错误检查A/B线、波特率校验位、从站地址返回异常码01功能码不支持查手册确认支持的功能码返回异常码02地址非法检查地址映射和偏移量返回异常码03数据超范围检查写入值是否在允许区间数据乱码字节序错误、缩放因子错误调整字节序、确认缩放因子通讯时通时断干扰、终端电阻、线缆质量加终端电阻、检查屏蔽接地、换线写入不生效设备保护、需要使能、掉电丢失查手册确认写条件、发保存命令最后分享一个小技巧如果现场没有调试工具可以用PLC的在线监控功能强制读写Modbus地址观察从站反应。虽然不如专业工具方便但应急够用了。这个内容后续还可以这样扩展Modbus TCP和RTU的报文差异、Modbus ASCII模式的特点、多主站冲突的处理、以及如何用脚本批量测试Modbus设备。每个方向都够写一篇独立的文章后面有机会再慢慢聊。