ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

工控调试:字节、十六进制与Modbus报文换算实践

工控调试:字节、十六进制与Modbus报文换算实践 干工控这行的十有八九都见过这种场面调试现场设备不动作打开监控报文一看满屏01 03 00 00 00 01 84 0A这种十六进制数据或者仪表手册上写着“地址0x1000数据类型UINT数值高位在前”译完这一行字新人基本就卡住了。工控调试的尽头绕不开字节和16进制这一关。这篇文章不跟你讲教科书上的定义就按现场干活的路子把位、字节、字、进制换算、Modbus报文、模拟量工程量换算、大小端、负数补码这些事一次性捋清楚。适合刚入行的PLC调试人员、做上位机开发的工程师以及所有被“寄存器数据怎么读都对不上”折磨过的朋友。1. 先搞清楚字节和16进制到底是个啥1.1 位、字节、字工控人必须会换算的“尺寸”计算机的世界只有0和1一个0或1就是一个位bit。8个位组成一个字节Byte2个字节组成一个字Word4个字节也就是2个字组成一个双字DWord。工控里最常打交道的PLC寄存器多数以“字”为单位一个寄存器16位也就是2个字节。模拟量采集通道的原始值、Modbus保持寄存器里的数据基本都是按16位1个字来存的。这套关系必须烂熟于心因为后面所有换算都是在这几个尺寸里打转名称位宽字节数范围无符号位bit11/80 或 1字节Byte810~255字Word1620~65535双字DWord3240~4294967295我见过不少调试现场的兄弟把0xFF当成255没问题但看到0xFFFF脑袋里还是255这就出事了——一个255一个65535差了257倍。读温度、读压力差257倍是什么概念直接炸表。1.2 为什么工控的世界里到处都是16进制有人可能会问PLC内部明明跑的是二进制仪表手册上写的也应该是十进制为什么报文、地址、寄存器值全是十六进制核心原因就四个字方便人看。一个字节8位写成二进制是10101010写出来8个字符写成十六进制就是0xAA2个字符。一个16位寄存器二进制要写16个字符还容易看串行十六进制只要4个字符0x1A2B整齐清楚。更关键的是十六进制和二进制之间的换算极其机械一位十六进制数正好对应四位二进制数。十六进制二进制00000100012001030011401005010160110701118100091001A1010B1011C1100D1101E1110F1111所以报文里的0xA5我扫一眼就能在脑子里拆成1010 0101哪一位是0哪一位是1清清楚楚。工控里大量所谓的“状态字”就是拿这16个位来表示设备的各种状态——运行、停止、报警、故障——你看到0x0003直接拆成0000 0000 0000 0011就知道bit0和bit1都是1对应两个状态同时有效了。2. 手把手教你把进制换算吃到肚子里2.1 二进制、十六进制、十进制互转的基本功先看十六进制转十进制。规则叫“位权展开”每一位乘以16的n次方n从0开始从右往左数然后累加。以0x1A2B为例从右往左B是第0位权重16⁰2是第1位权重16¹A是第2位权重16²1是第3位权重16³。A等于10B等于11所以十进制就是11×16⁰ 2×16¹ 10×16² 1×16³ 11 32 2560 4096 6699这里有个从右往左的记法特别容易搞错。我教新人的土办法右边第一位是个位16⁰往左走一位就乘以一次16。记牢这个怎么都不会错。再看十进制转十六进制。规则是“除16取余倒着写”。比如2000转十六进制2000 ÷ 16 125余0这是最低位125 ÷ 16 7余13也就是D7 ÷ 16 0余7从下往上写结果是0x7D0。验证一下7×256 13×16 0 1792 208 2000没错。二进制转十六进制更简单从右往左每4位二进制数转成一位十六进制数。比如1111 0101从右往左分成两组0101是51111是F所以是0xF5。反过来十六进制转二进制就是把每一位十六进制数展开成4位二进制然后拼起来。2.2 与门控场景里最高频的换算套路工控现场经常看到一个寄存器值比如0x3E要速算成十进制。其实不需要按位权慢慢展开我平时是这么干的先用熟记的半字节心算0x3E的高半字节是3低半字节是14E等于14。十六进制数0xAB换算成十进制的通用公式是A×16 B所以0x3E就是 3×16 14 62。这个方法用熟了16位以内的数字基本一眼出结果。0xFF 2550x100 2560x8000 32768这种高频值我建议直接背下来模数转换里经常用。另一个高频场景是字节和十进制的对应。一个字节最大255这个前面说过。但很多人容易把字节范围和有符号数混起来这里先记一条一个字节无符号范围0~255有符号范围-128~127。后面第五章详细讲负数现在只需要有个概念。场景数值心得单字节上限0xFF 255字节型数据最大就到这里单字上限0xFFFF 65535无符号16位寄存器的天花板半字高频0x8000 3276816位有符号数的“零点偏置”很常用模拟量满量程0x6C00 27648常见PLC模拟量模块的量程上限比如某模拟量采集模块量程对应0~27648你读到0x6C00几秒内就该反应出来这是满量程。如果不知道这些高频值对应的十进制现场翻计算器效率就低了。2.3 字节里边哪些位在捣乱位编号和权重一个字节8个位从右往左依次是bit0、bit1……bit7。bit0的权重是1bit1的权重是2bit2是4bit3是8一路翻倍到bit7的128。也就是常见的128 64 32 16 8 4 2 1。这个权重表在工控里太常用了。比如设备手册上写“bit7为1表示超温报警”你从仪表读回来一个字节0x80二进制1000 0000立刻就知道bit7置1了超温。如果读回来是0x81那就是超温加另一个bit0的状态同时成立。PLC里判断某一位是否置1通常就是拿读回来的值和掩码做“与”操作。伪代码常见写法是IF 寄存器值 AND 16#0001 16#0001 THEN // bit0为1设备运行中 END_IF;这种位运算的思路本质就是十六进制、二进制、字节、位这四层东西在心里瞬间打通的结果。很多老工程师扫一眼报文就能说出哪几个状态报警靠的不是记性好是这套二进制位权重拆得很熟。3. Modbus报文里的字节和16进制3.1 一帧RTU报文拆给你看工控通信绕不开Modbus尤其是RTU模式。一个标准Modbus RTU请求帧长这样以读保持寄存器为例01 03 00 00 00 01 84 0A逐字节拆解01从站地址。你要访问的设备的站号。03功能码。03表示读保持寄存器04是读输入寄存器06写单个寄存器16也就是0x10写多个寄存器。00 00寄存器起始地址这里表示从地址0x0000开始。注意这个地址本身是十六进制数占2个字节。00 01读取寄存器个数。请求读1个寄存器。84 0ACRC16校验码由前面所有字节计算得出。对应的响应帧01 03 02 1A 2B 30 5101从站地址原样返回。03功能码原样返回。02数据字节数。因为只读1个寄存器寄存器是16位也就是2个字节所以这里是02。1A 2B读回来的寄存器值。这是一个16位无符号数按十六进制表示。30 51响应帧的CRC16校验码。一个现场很常见的误会把1A 2B直接当成十进制数来算工程量结果怎么算都不对。这里要把0x1A2B先转成十进制6699再代入量程换算公式。十六进制、十进制的转换在这里就是生死线。3.2 CRC校验到底在算什么CRC16是Modbus RTU帧的“指纹”。它的计算对象是整个帧里除CRC外的所有字节算法上是对这些字节做多项式除法取余数。收到帧后接收方会重新计算一次CRC跟帧尾的CRC比对不一致就说明通信出错或数据被污染。对绝大多数工控工程师来说CRC的完整计算过程不需要手推但是你得懂两件事第一CRC是全帧字节的线性组合只要中间任何一个字节变了CRC就会变。所以调试时改了地址或者数据CRC必须重新算不然从站直接丢弃报文。第二如果通信不稳定、偶发丢包优先怀疑的其实不是CRC算法对不对而是波特率、奇偶校验、总线终端电阻这些物理层参数。CRC只是个“最后把关”的校验它只能发现错误不能纠正错误。实际开发中CRC计算一般用PLC自带的功能块或者上位机里现成的库函数。网上的在线计算工具也很多自己搜索就能找到。但有个原则测试CRC时用一条已知正确的标准报文来验证别拿自己拍的脑袋编的报文算。3.3 寄存器地址和数据值的进制陷阱Modbus的寄存器地址在报文层是十六进制数比如0x0000、0x000A但到了设备手册或组态软件里地址显示可能是十进制。不少触摸屏的Modbus地址写法就是40001、40002这种这里的4开头代表保持寄存器后面0001是对应报文地址0x0000。这中间有一个偏移问题常常坑人PLC里地址是40001但报文里发的是0000。组态软件帮你把偏移处理掉了你直接填40001就行但如果你是自己写上位机、自己拼报文你必须清楚地知道报文里的地址是数据地址而不是屏上显示的“40001”。差了1读写就可能落到错误的位置。另外一个坑是数据值和地址长度。读回来的寄存器值是16位的但某些仪表手册会把数据描述成“32位浮点数占2个寄存器”。这时候地址发00 00读回4个字节就涉及下一章要讲的双字和大小端问题。先把“寄存器里的数据是十六进制、但它的真实含义和长度要看手册”这条刻在脑子里后面就顺畅很多。4. 从字节到真实物理量模拟量换算全流程4.1 模拟量原始值怎么进到寄存器里的压力变送器输出4~20mA电流信号接到PLC模拟量模块的AI通道上。模块内部有AD转换器把电流信号按比例变成数字量这个数字量就存在模块对应的输入寄存器里以十六进制形式呈现。这个原始值本身不是工程单位只是个“代码值”。要得到压力、温度、流量这些真实物理量必须做一次线性换算。换算公式是工控人最熟悉的“斜率直线”当前工程量 (原始值 / 量程上限) × 物理量程这里的量程上限跟AD转换的位数和模块配置有关。常见的模拟量模块配置4~20mA时量程上限是0~27648默认设置配置0~20mA时量程上限也是0~27648但对应关系会变。还有一些仪表内部做了标定原始值范围可能是0~4000或0~65535。4.2 一条压力测点的完整换算计算假设现场有一条压力变送器量程0~1.6MPa输出4~20mA接入模块配置4~20mA对应原始值0~27648。调试时读到寄存器值0x1A2B。第一步转十进制0x1A2B 11 2×16 10×256 1×4096 6699。第二步代入公式压力 6699 ÷ 27648 × 1.6MPa ≈ 0.388MPa也就是说当前管道压力大约0.39MPa接近满量程的1/4。这里面有几个雷要点破如果量化程上限用错了比如拿4000当上限6699 ÷ 4000已经大于1算出来压力超过1.6MPa这明显不合理通常是量程配置和模块不一致。如果模块配置成0~20mA而变送器输出4~20mA那么4mA对应原始值不是0而是约552927648 × 4/20你按0对应去换算读到的零位压力会变成0.32MPa左右误报警。读回来的十六进制值要先确认是无符号还是有符号。负数的情况后面第五章讲一旦按错符号压力直接变负几兆帕。正确的换算在现场经常直接写进PLC里梯形图或结构化文本里一般是这样工程量 : (二进制原始值 / 27648) * 1.6;这一行代码里单位、量程、数据格式全都得对齐任何一个错了显示值就是错的。4.3 量程配置错了会看到什么现象如果模块量程配置和传感器量程不一致常见两种现象第一种原始值偏小。比如传感器4~20mA模块误配成0~20mA4mA时模块读到的原始值在5200左右而不是0。换算出来的工程量会整体偏大约0.3倍满量程肉眼可见的离谱。第二种是模块读数到上限后不再变化。比如变送器输出24mA超出模块配置的20mA量程读到0xFFFF或卡在0x6C00附近不再上升这就是“顶量程”了。这种问题排查时看原始值是不是满值一分钟就能定位。在组态屏上看到压力值一直偏高、或到某个值不动了不要急着怀疑硬件先把寄存器原始值调出来用十六进制看一眼确认原始值本身是否符合预期再做下一步判断。这招在现场排故障时救了我很多次。5. 大小端、双字和浮点数工控数据最想坑你的地方5.1 什么叫大小端8/16/32位数据怎么排顺序大小端说的是多字节数据在内存或通信线里的字节排列顺序。大端高字节在前。比如32位数0x12345678先发/先存12再34再56再78。小端低字节在前。0x12345678先发/先存78再56再34再12。Modbus协议本身规定多字节数值是高位字节在前这个叫大端序。所以读一个16位整数报文里先出现的字节是高位后出现的是低位。比如寄存器值是1A 2B那就是大端序低位在后组合成0x1A2B没有问题。但现实很骨感。很多智能传感器、仪表内部用的是小端序单片机出厂固件写寄存器时把数据倒着塞进去。协议是大端序设备却按小端序发送读回来就成了2B 1A。这种差异表现在数值上就是“高低字节对调”数值变得非常奇怪但又不是完全乱码。5.2 读32位浮点数和双字时最经典的乱码现场32位浮点数在Modbus里占2个寄存器也就是4个字节。假如某台仪表手册写着“数据长度32位传输顺序为低字在前”但你按默认的高字在前去解析读回来的数值就会大变。举个例子流量计里的32位浮点数实际值是0x3F9DF3B6大约1.234。如果发送顺序是“高字在前、低字在后”接收端正确解析就是1.234但仪表的实际顺序是“低字在前”你按高字在前拼得到的是0xF3B63F9D这个浮点数的数值可能是一个-4.5e35左右的离谱数字。解决这种问题有两个办法第一看手册严格按手册规定的字节顺序组装。这个是最靠谱的但调试现场未必能找到手册兄弟设备可能连铭牌都磨没了。第二用“交换”指令/函数处理。多数PLC都提供字节交换Byte Swap或字交换Word Swap指令。遇到高低字节反了先做字节交换遇到高低字反了先做字交换。动手之前先拿一个已知数值测试读回来比较一下确认是哪种反了再处理。数值乱码不是故障本身而是解析规则错了。工控老手判断这个有个土办法如果读回来的数值极其离谱比如温度变成几百万度、压力变成负几十兆帕而原始十六进制看起来有规律十有八九是字节序或字序错了。5.3 负数在寄存器里长什么样有符号数与补码寄存器的16位数据如果按无符号数读范围是0~65535如果按有符号数读范围是-32768~32767。同一个0xFF9C按无符号读是65436按有符号读是-100。计算机里负数用补码存储。补码的计算规则是正数按原值存负数等于原值的二进制按位取反再加1。计算-100在16位寄存器里的存储值100的二进制补码无符号是0x0064。取反0xFF9B。再加10xFF9C。所以-100在寄存器里实际存的就是0xFF9C。PLC程序里如果把这个寄存器定义为无符号整数看到的就是65436定义为整数看到的就是-100。数据类型定义错同样一个十六进制值会得到完全不同的物理含义。工控现场经常遇到变送器输出带负值的场景比如液位测的是“负压”流量可能出现反向。调试时看到大正数不要直接报故障先确认数据类型和符号位是否正确。技巧是看读回来的值是不是接近65535或32768附近这种“顶到边界”的数值大概率是负数被按无符号解析了。6. 现场调试最容易踩的坑含排查思路6.1 常见现象与对应原因速查表我把自己这些年调试碰到的典型问题整理成了一张速查表遇到类似问题可以直接对着排查现象可能原因排查思路数值一直偏大或偏小固定倍数量程上限配置错如27648混用了4000读原始值代入正确量程核模块配置数值在“很大”和“很小”之间跳变字节序反了高低字节对调断电再看数值尝试字节交换测试明明应该是负值读出来是五六万有符号数被当无符号解析确认数据类型是INT有符号16位浮点数数值极其离谱字序反了或浮点数长度不对查手册的寄存器顺序做字交换测试某个位总是置不上或置不上掩码写错或位编号算错用二进制监视器看着位值一位一位核对通信偶发无响应报文里CRC没更新改报文后务必重新计算CRC两个设备明明同型号读值差一半小组的传感器量程和模块量程不一致同时看两路原始值对比AD代码值这张表的价值在于它把“数据不对”这个模糊的问题拆成了几种具体可能。排查时不要大海捞针按表里顺序快速排除法基本十分钟能定位。6.2 我自己调试时的几个排错习惯最后分享几个我在现场养成的习惯都是踩过坑之后总结出来的。第一所有仪表、模块到手第一件事是把原始值读出来写成一个十六进制数不要先转十进制。比如读回0x00C8心里应该立刻对应200而不是先去想“这代表温度200度还是压力200帕”。先确认代码值是不是合理再谈工程量。第二组态调试时在PLC或上位机监控界面里同时显示三列原始十六进制值、无符号十进制、有符号十进制。三列摆一起看是不是符号问题一眼就穿。第三写进程序里的模拟量换算公式不要写死量程上限。把量程、偏移、上下限都做成可在线修改的参数。现场改一次量程就知道这习惯多救命了。你不需要重新下装程序光在触摸屏上改参数压力值就对了。第四碰到任何一次通信异常先怀疑CRC、再怀疑数据格式、最后才怀疑硬件。这个顺序能让你少走很多弯路。早期我做项目一遇到通信不通就直接换线换模块折腾半天最后发现是报文地址算错了纯属冤枉硬件。字节和16进制在工控这个圈子里的重要性怎么说都不过分。它不只是报文里写的那些字符更是你跟设备之间沟通的语言基础。把这套换算练成本能反应调试效率真的能翻一倍。我也是这么一路走过来的今天这篇就当是交个底希望能帮你在现场少踩几个坑。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进