news 2026/10/27 1:06:18

工控人必知:字节与16进制详解及高低字节浮点数转换实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
工控人必知:字节与16进制详解及高低字节浮点数转换实战

1. 工控人绕不开的那道坎:字节与16进制到底在聊什么

干工控这行十几年,我越来越觉得,真正卡住新人的往往不是梯形图逻辑,也不是PID参数整定,而是最底层的那点东西——字节和16进制。你打开任何一个PLC的变量表,或者抓一段Modbus报文,满屏都是16#开头的数字、WORD、DWORD、BYTE这些数据类型。设备通讯不上、数据解析出来是乱码、浮点数读出来是个天文数字,十有八九都栽在这上面。

这篇文章我想把字节和16进制这件事彻底讲透。它不是什么高深理论,而是工控现场每天都在用的“普通话”。不管你是刚入行的电气助理,还是做了几年想补基础的工程师,只要涉及PLC通讯、仪表数据读取、报文解析、程序文件分析,这篇内容都能直接拿去用。我会从为什么工控偏爱16进制讲起,一路讲到高低字节、浮点数转换、bin文件翻译这些实操细节,中间穿插我自己踩过的坑和现场排查思路。

先把核心关键词摆出来:字节、16进制、高低字节、浮点数转换、报文解析。这几个词基本构成了工控数据处理的骨架。你把这几个概念理顺了,再看那些设备手册和通讯协议,会发现清晰很多。

我见过太多人,PLC编程逻辑写得飞起,但一让他解释为什么16#1234拆成两个字节是12和34,或者为什么浮点数0x42F60000对应的是123.0,就卡壳了。这不是能力问题,是没人系统讲过。下面我就按现场实战的顺序,一层层拆开。

2. 为什么工控领域对16进制情有独钟

2.1 二进制太长,十进制太“骗人”

要理解16进制,得先回到计算机的底层。所有数据在PLC、仪表、单片机里,本质都是二进制——一串0和1。但二进制写起来太要命了,一个16位的寄存器值,写成二进制是1011010001111000,你数位数都数半天,更别说核对。

十进制呢?看起来亲切,但它在描述底层数据时特别“骗人”。比如一个字节最大值是255,十进制写255,你根本看不出它的二进制结构。而16进制写FF,一眼就知道是8个1。这就是关键:16进制是二进制的“压缩写法”,每1位16进制数精确对应4位二进制。

我举个现场例子。Modbus RTU报文里,功能码03读保持寄存器,返回的数据经常是00 64这样的字节对。00 64用16进制看,转成十进制是100。如果你拿到的是十进制100,你没法直接判断它占几个字节、高位是什么。但00 64这个写法,高字节00、低字节64,结构清清楚楚。

2.2 一个字节等于两位16进制数,这是铁律

工控里最基础的一条对应关系,必须刻在脑子里:

单位位数16进制位数十进制范围
1个字节(Byte)8位2位0 ~ 255
1个字(Word)16位4位0 ~ 65535
1个双字(DWord)32位8位0 ~ 4294967295

1个字节 = 8个二进制位 = 2位16进制数。这条关系是所有换算的基石。为什么是2位?因为4位二进制刚好能表示0到15,也就是16进制的一位(0-9,A-F)。8位二进制自然就是2位16进制。

你在PLC里看到MW100(字地址),它占16位,也就是2个字节,写成16进制是4位,比如16#1A2B。看到MD100(双字地址),占32位,4个字节,16进制8位,比如16#1A2B3C4D。这个对应关系一旦建立,你看变量表的速度会快很多。

提示:很多新手把“字节”和“位”搞混。一个字节有8个位,位是最小单位,只能存0或1。PLC里的M0.0这种就是位地址,而MB0是字节地址,MW0是字地址。别小看这个区分,写通讯程序时搞错一个层级,数据全乱。

2.3 16进制让“位操作”变得直观

工控里经常要做位操作,比如判断某个字节的第3位是不是1,或者把某个位置1、清0。用16进制看,这件事特别直观。

假设一个字节的值是16#A5。转成二进制是1010 0101。你看,A对应1010,5对应0101。现在我要判断第0位(最低位)是不是1,直接看最后一位1,是1。要判断第7位(最高位),看最前面1,也是1。如果写成十进制165,你根本没法一眼看出这些位。

再比如,现场经常用“掩码”来提取某些位。16#0F这个掩码,二进制是0000 1111,意思就是“取低4位”。16#F0是1111 0000,取高4位。这种操作在解析仪表状态字、报警字的时候天天用。用16进制写掩码,逻辑一目了然;用十进制写,你得先换算半天。

我个人的习惯是,只要涉及位操作、字节拆分、报文解析,一律用16进制思考和记录。这不是装,是真的能减少出错。

3. 字节、字、双字:数据宽度与高低字节的实战拆解

3.1 高低字节到底谁在前,这是个要命的问题

“高低字节”这四个字,是工控通讯里最容易出事故的地方。同一个32位数据,不同厂家、不同协议,字节排列顺序可能完全相反。你按A厂家的顺序解析B厂家的设备,读出来的数就是错的。

先明确概念。一个16位的字,由高字节(High Byte)和低字节(Low Byte)组成。比如16#1234,高字节是12,低字节是34。这没问题。但问题是,这两个字节在传输时谁先发,在内存里谁在低地址,不同体系有不同规定。

这就引出了两个术语:大端(Big-Endian)和小端(Little-Endian)。

  • 大端模式:高字节存在低地址,传输时高字节先发。16#1234在内存里是12 34。
  • 小端模式:低字节存在低地址,传输时低字节先发。16#1234在内存里是34 12。

工控领域两种都有。Modbus协议本身规定是大端,但很多设备厂商在实现时,寄存器内部又用了小端。三菱PLC和西门子PLC在某些数据存储上习惯也不同。这就是为什么你抓到的报文,有时候需要“交换高低字节”才能得到正确值。

我现场遇到过一个典型案例:一台称重仪表通过Modbus RTU上传重量,寄存器地址40001,32位浮点数。我按大端解析,读出来是1.2e-38这种离谱的数。后来把两个寄存器的字节顺序调整了一下,才得到正确的123.45。问题就出在仪表内部用了小端存储,而Modbus传输时又按大端发,中间需要做字节交换。

3.2 字节交换的实操方法与判断技巧

怎么判断要不要交换?我的经验是:先看手册,手册没写就试。试的方法很简单,读一个你知道大概范围的物理量,比如温度、重量、电压,然后分别按大端和小端解析,哪个结果合理就是哪个。

具体操作上,在PLC里做字节交换有几种常见方式:

  • 西门子S7系列:用SWAP指令,或者用CAW(交换字)、CAD(交换双字)。比如CALL "SWAP"可以对一个字进行高低字节交换。
  • 三菱FX/Q系列:用SWAP指令,或者通过MOV配合位操作手动交换。
  • 通用方法:用移位和掩码。比如要把16#1234变成16#3412,可以(value & 16#00FF) << 8 | (value & 16#FF00) >> 8。

在高级语言或脚本里处理报文时,Python的struct模块很好用:

import struct # 假设收到两个寄存器,大端解析为16位无符号 reg1 = 0x1234 reg2 = 0x5678 # 组合成32位,大端 value_be = (reg1 << 16) | reg2 print(hex(value_be)) # 0x12345678 # 小端组合 value_le = (reg2 << 16) | reg1 print(hex(value_le)) # 0x56781234 # 浮点数解析,大端 bytes_be = struct.pack('>HH', reg1, reg2) float_be = struct.unpack('>f', bytes_be)[0] print(float_be) # 浮点数解析,小端 bytes_le = struct.pack('<HH', reg1, reg2) float_le = struct.unpack('<f', bytes_le)[0] print(float_le)

这段代码我经常用来快速验证报文。你把抓到的寄存器值填进去,四种组合都试一遍,哪个结果符合物理量范围,就是哪个。

注意:字节交换不是“随便换”,一定要结合协议和手册。有些设备是“寄存器内字节交换”,有些是“寄存器间顺序交换”,还有的是“两者都要”。搞错了,数据永远对不上。

3.3 4字节无符号与浮点数的范围陷阱

热词里提到“4字节无符号float数值范围”,这里有个常见误区。4字节无符号整数和4字节浮点数是两码事,虽然都占32位。

  • 4字节无符号整数(DWORD/UINT32):范围是0到4294967295。它表示的是精确的整数。
  • 4字节浮点数(FLOAT/REAL):遵循IEEE 754标准,范围大约是±3.4e38,但精度有限,不能表示所有整数。

为什么工控里要区分?因为有些仪表上传的是整数(比如累计流量),有些上传的是浮点数(比如瞬时温度)。你把浮点数当整数读,或者反过来,结果都是错的。

IEEE 754单精度浮点数的结构是:1位符号位,8位指数位,23位尾数位。比如0x42F60000,拆开看:

  • 符号位:0(正)
  • 指数位:10000101= 133,减去偏移127,得6
  • 尾数位:11101100000000000000000

计算:1.111011 × 2^6=1111011.0(二进制)=123.0(十进制)。所以0x42F60000就是123.0。

这个计算过程看起来复杂,但理解了结构,你就能手动验证。网上有很多“16进制浮点数转10进制在线转换器”,现场没网的时候,我就用Python的struct模块,一行代码搞定:

import struct hex_str = '42F60000' float_value = struct.unpack('>f', bytes.fromhex(hex_str))[0] print(float_value) # 123.0

这里>表示大端。如果设备是小端,改成<。这个技巧我用了无数次,比任何在线工具都快。

4. 从报文到程序:16进制解析的完整实操链路

4.1 抓一段真实报文,逐字节拆开看

光讲理论没意思,我拿一段典型的Modbus RTU报文来拆。假设主站读从站保持寄存器,从站返回:

01 03 04 00 64 00 C8 CRC_L CRC_H

逐字节解释:

  • 01:从站地址
  • 03:功能码,读保持寄存器
  • 04:数据字节数,4个字节,说明返回了2个寄存器
  • 00 64:第一个寄存器的值,16进制0064,十进制100
  • 00 C8:第二个寄存器的值,16进制00C8,十进制200
  • CRC_L CRC_H:校验码,低字节在前

你看,用16进制看报文,每个字节的职责清清楚楚。如果写成十进制1 3 4 0 100 0 200 ...,你根本分不清哪些是地址、哪些是数据。

现场排查时,我习惯把报文按字节列成表格:

字节位置16进制值含义十进制值
001从站地址1
103功能码3
204字节数4
3-400 64寄存器1100
5-600 C8寄存器2200
7-8CRC校验-

这个表格一列,问题一目了然。如果字节数对不上,或者寄存器值明显异常,马上就能定位。

4.2 浮点数报文的解析实战

再来看一个浮点数的例子。假设从站返回:

01 03 04 42 F6 00 00 CRC_L CRC_H

数据部分是42 F6 00 00,4个字节,一个浮点数。按大端解析:

import struct data = bytes.fromhex('42F60000') value = struct.unpack('>f', data)[0] print(value) # 123.0

如果设备是小端,数据可能是00 00 F6 42,解析时用<f:

data = bytes.fromhex('0000F642') value = struct.unpack('<f', data)[0] print(value) # 123.0

这里有个坑:Modbus传输时寄存器是大端,但寄存器内部可能是小端。也就是说,你收到的两个寄存器42F6和0000,组合成32位时,可能是42F60000,也可能是000042F6,还可能是F6420000。四种组合都要试。

我一般写个小脚本,把四种组合都打印出来:

import struct regs = [0x42F6, 0x0000] combinations = [ ('大端-寄存器顺序', struct.pack('>HH', regs[0], regs[1])), ('小端-寄存器顺序', struct.pack('<HH', regs[0], regs[1])), ('大端-交换寄存器', struct.pack('>HH', regs[1], regs[0])), ('小端-交换寄存器', struct.pack('<HH', regs[1], regs[0])), ] for name, data in combinations: try: value = struct.unpack('>f', data)[0] print(f'{name}: {value}') except: print(f'{name}: 解析失败')

跑一遍,哪个结果合理就是哪个。这个脚本我存在手机里,现场随时用。

4.3 如何把16进制的bin文件“翻译”出来

热词里有个问题:“如何将16进制的程序代码bin文件翻译出来”。这个问题在工控里很实际,比如你拿到一个PLC的固件bin文件,或者一个仪表的配置bin,想看看里面有什么。

bin文件本质就是一堆字节。用16进制编辑器(比如HxD、WinHex)打开,你会看到密密麻麻的16进制数。翻译它,分几个层次:

第一层:看文件头。很多bin文件开头有固定的标识。比如某些固件以55 AA开头,某些以7F 45 4C 46(ELF格式)开头。识别文件头,能判断文件类型。

第二层:找字符串。bin文件里往往嵌入了ASCII字符串,比如版本号、设备型号、错误信息。在16进制编辑器里切换到文本视图,或者用strings命令(Linux下)提取:

strings firmware.bin | head -50

这个命令能把文件中所有可打印字符串列出来,经常能发现有用信息。

第三层:分析结构。如果知道文件格式(比如Intel HEX、Motorola S-record),可以按格式解析。Intel HEX每行以:开头,后面是长度、地址、类型、数据和校验。写个脚本就能解析:

def parse_intel_hex(line): if not line.startswith(':'): return None byte_count = int(line[1:3], 16) address = int(line[3:7], 16) record_type = int(line[7:9], 16) data = bytes.fromhex(line[9:9+byte_count*2]) checksum = int(line[9+byte_count*2:11+byte_count*2], 16) return { 'length': byte_count, 'address': address, 'type': record_type, 'data': data, 'checksum': checksum } with open('firmware.hex', 'r') as f: for line in f: parsed = parse_intel_hex(line.strip()) if parsed: print(parsed)

第四层:反汇编。如果bin是某个处理器的机器码,需要对应的反汇编工具。这一步就比较专业了,一般工控现场用不到,除非你在做逆向或深度维护。

我的经验是,大部分工控bin文件,前两层就能解决80%的问题。你往往只需要确认版本号、校验和、或者某个配置参数,不需要完整反汇编。

提示:分析bin文件前,先备份。有些bin文件带校验,你改一个字节,整个文件就失效了。另外,注意文件的字节序,有些bin是小端存储,解析多字节数据时要交换。

5. 常见问题与排查技巧实录

5.1 数据解析错误的排查速查表

现场数据不对,原因就那么几类。我整理了一张速查表,按概率从高到低排:

现象可能原因排查方法解决方式
读出来是0或极大值字节序错误试四种字节组合调整解析顺序
浮点数变成整数数据类型搞错确认设备上传的是整数还是浮点改解析方式
数据跳变、不稳定寄存器地址错位核对地址偏移(0基还是1基)调整地址
通讯超时波特率/校验错核对串口参数统一参数
部分数据正确部分错误字节数不对检查功能码和字节数修正读取长度
校验失败CRC计算错用标准CRC工具验证修正CRC算法

这张表我贴在工位旁边,出问题先对照,能省很多时间。

5.2 地址偏移:0基和1基的坑

Modbus协议里,寄存器地址有“协议地址”和“PLC地址”之分。协议地址从0开始,PLC地址从1开始。比如协议地址0对应PLC地址40001。你按40001去读,实际发出去的地址是0。这个偏移搞错,读出来的就是隔壁寄存器的值。

我的习惯是:在脚本里统一用协议地址,在PLC编程时统一用PLC地址,中间做一次转换。转换公式很简单:协议地址 = PLC地址 - 40001(对于保持寄存器)。输入寄存器是30001,线圈是00001,离散输入是10001。

5.3 无效字符与多字节字符集的报错

热词里提到“出现无效字符。多字节字符集必须包含一前导字节且无结尾字节”。这个报错通常出现在处理字符串数据时。工控设备上传的字符串,可能是ASCII,也可能是GBK、UTF-8等多字节编码。

ASCII是单字节,每个字节对应一个字符。多字节字符集(如UTF-8)一个字符可能占2到4个字节。如果你按单字节解析多字节数据,就会出现“无效字符”或乱码。

处理方法:先确认设备用的编码。手册一般会写。如果没写,抓一段已知内容的字符串,用不同编码试。Python里:

data = bytes.fromhex('C4E3BAC3') # 假设收到这段 for encoding in ['ascii', 'gbk', 'utf-8']: try: print(f'{encoding}: {data.decode(encoding)}') except Exception as e: print(f'{encoding}: 失败 - {e}')

C4E3BAC3用GBK解码是“你好”,用UTF-8就失败。现场遇到乱码,先试GBK,国内设备用得多。

5.4 浮点数精度丢失的处理

浮点数在工控里有个烦人的问题:精度。比如0.1在IEEE 754里不能精确表示,累加多次后会有误差。如果你用浮点数做累计流量计算,跑几个月可能差出好几吨。

我的做法是:累计量用整数,瞬时量用浮点。累计流量用32位或64位整数,单位放大(比如乘以1000,用整数表示毫升级),显示时再除以1000。这样精度有保证。瞬时量用浮点没问题,因为不需要累加。

如果必须用浮点累加,定期做一次“归零校准”,或者用高精度库(如Python的decimal)。但在PLC里,一般还是整数方案更稳。

6. 工具与习惯:让字节和16进制不再成为障碍

6.1 我常用的几个工具

工控现场,我手机和电脑里常备这几个工具:

  • 16进制编辑器:HxD(Windows)、iHex(Mac)。看bin文件、改配置必备。
  • 串口调试助手:SSCOM、ComAssistant。抓报文、发测试帧。
  • Modbus调试工具:Modbus Poll、QModMaster。快速验证寄存器。
  • Python脚本:自己写的解析脚本,处理批量数据。
  • 在线转换器:16进制浮点数转换、CRC计算。没网时用本地脚本替代。

工具不在多,在于顺手。我见过有人装了一堆软件,结果现场还是手算。关键是理解原理,工具只是加速。

6.2 养成“16进制思维”的习惯

最后说个习惯问题。我刚开始干工控时,也习惯用十进制。后来发现,凡是涉及底层数据,用16进制思考效率高得多。

具体做法:

  • 看变量表时,心里默念16进制值。
  • 写报文时,直接写16进制,不写十进制。
  • 做位操作时,用16进制掩码。
  • 记录问题时,用16进制记录原始数据。

这个习惯养成后,你看数据的速度会快一个档次。别人还在换算,你已经看出问题了。

提示:16进制里A-F是大写还是小写,不影响值,但团队里最好统一。我习惯用大写,因为手册里通常是大写。

6.3 一个真实案例的完整复盘

去年有个项目,一台流量计通过Modbus RTU上传瞬时流量和累计流量。瞬时流量是浮点数,累计流量是32位无符号整数。调试时,瞬时流量读出来是0.0,累计流量读出来是1.2e9这种离谱值。

排查过程:

  1. 抓报文,确认数据有返回。
  2. 按大端解析浮点数,得到0.0,不对。
  3. 试小端,得到123.45,对了。说明瞬时流量是小端。
  4. 累计流量按大端解析,得到1.2e9,明显不对。
  5. 试小端,得到123456,合理。说明累计流量也是小端。
  6. 但手册写的是大端。联系厂家,确认固件版本有bug,实际用了小端。

这个案例告诉我:手册不一定对,实测才是王道。遇到数据不对,先试四种组合,再联系厂家。别死磕手册。

7. 从字节到系统:16进制思维在工控中的延伸

7.1 报文解析只是起点

字节和16进制的应用,远不止报文解析。你去看PLC的存储器布局、IO映射、诊断缓冲区,全是16进制。比如西门子的诊断缓冲区,每条记录都有16进制的错误代码,查手册对应表才能知道含义。三菱的缓冲存储器(BFM)也是16进制地址。

再往深了说,工控安全、固件升级、设备逆向,都离不开字节级操作。你理解了字节和16进制,这些领域的大门就打开了。

7.2 给新人的学习路径建议

如果你刚入行,我建议按这个顺序学:

  1. 先搞懂二进制和16进制的对应关系。背下0-F的二进制,做到看到16进制能秒转二进制。
  2. 理解字节、字、双字的宽度。知道1字节=8位=2位16进制。
  3. 搞懂高低字节和大端小端。这是通讯的核心。
  4. 动手抓报文、解析报文。用串口助手和Python脚本。
  5. 学习浮点数的IEEE 754结构。理解为什么浮点数有精度问题。
  6. 实践bin文件分析。从简单的配置文件开始。

这个路径走下来,大概两三个月,你就能独立处理大部分工控数据问题。

7.3 一个值得长期坚持的练习

我有个习惯,每次遇到新的设备或协议,就抓一段报文,手动解析一遍,然后写个脚本自动化。这个练习坚持了几年,现在看到任何报文,基本能条件反射地拆开。

你也可以这样做:找一个Modbus模拟器,自己发请求、收响应,然后手动解析。解析对了,再用脚本验证。反复几次,字节和16进制就刻在脑子里了。

工控这行,底层的东西越扎实,上层越轻松。字节和16进制就是那个底层。花点时间搞透,后面省下的时间是以倍计的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 1:06:13

滤波电路设计详解:RC/LC/EMI滤波与实际电感并联谐振实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/27 1:04:37

芯片时序路径分析实战:从PrimeTime到SDC签核

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/27 1:02:55

Matlab滤波器设计实战:从需求分析到Simulink部署的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/27 1:02:09

嵌入式I2C调试实战:从物理层到设备树的全栈排障指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/27 1:02:02

STM32+IMU实时姿态解算实战:Mahony滤波与上位机闭环调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/27 1:01:47

Qt大屏监控系统:动态表格与过渡动画实战优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华