一条 8 字节的指令发下去,回来 12 个字节,其中只有 4 个真正有用。把这 4 个字节解成一个 IEEE754 浮点数,温度就出来了。听着简单,可每一处字节顺序出了错,结果都会面目全非。
预计阅读约 4 分钟
01 先认识这台设备
这次的主角是 KELLER HCW 的 PA 系列高温计------一台红外非接触测温仪。
它内部不是单一测温通道,而是三路:λ1、λ2 两个波长通道,再加一路由两者推算出来的比值通道(Quotient)。两个波长的发射率还能各自独立调节,范围 10%~110%。配套的对象表里,常用的量有这么几个:0x3101 是仪表内部温度,0x3201 是 λ1 通道温度,0x3401 是 λ2 通道温度,0x3601 是比值通道温度,0x3604 是 Total Epsilon(总发射率信号,可以拿来判断有没有对准目标),0x2102 是设备型号编码,0x4103 是序列号。
这里先记住一件事:对象号的高 4 位不是编号的一部分,而是数据类型标志。 0 是 U8、1 是 U16、2 是 U32、3 是 Float、4 是 String。所以 0x3101 拆开读是「Float 类型 + 编号 0x101」。这意味着你从对象表里查到什么号就照抄什么号,不用自己去拼类型位------后面还会再提到这一点。
通信接口是 RS485 半双工,参数有点特别:57600 波特率、8 位数据、偶校验、1 位停止位(8E1),地址范围 1~32,默认地址是 1,255 为广播。测温仪本体后面还带一个 USB 口,短距离调试直接插上就能用。
02 请求帧:8 个字节,每个都有出处
要读内部温度,发出去的指令是:
08 01 00 30 31 01 E8 CC
八个字节,逐位都有来源。第 1 字节 0x08 是整帧长度;第 2 字节 0x01 是目标地址,指向从站(也就是测温仪);第 3 字节 0x00 是源地址,也就是主站;第 4 字节 0x30 要拆成两半看------高 4 位的 3 表示这个对象字段占 3 个字节,低 4 位的 0 表示「读」;第 5、6 字节 0x31 0x01 是对象号;最后两字节 0xE8 0xCC 是校验。
最后那个校验值得单独说。生成多项式是 0xA001------和 Modbus 用的那个一模一样,初值 0xFFFF,但发送时低字节在前。对前 6 个字节算一遍得到 0xCCE8,所以帧尾写成 E8 CC。
我用厂家资料里的两个例子分别验算过:08 01 00 30 31 01 算出来 0xCCE8,帧尾 E8 CC;08 01 00 30 36 04 算出来 0xFF2A,帧尾 2A FF。两处都能对上,说明这个算法没有变体,可以放心直接用。
03 响应帧:12 个字节里只有 4 个有用
指令发出去,回来的是:
0C 00 01 70 31 01 98 C0 B4 41 A1 BE
照样逐字节拆。0x0C 是整帧长度 12;0x00 是目标地址,这次指向主站;0x01 是源地址,也就是测温仪;0x70 的高 4 位 7 表示这个对象字段占 7 个字节,低 4 位 0 表示读取成功;0x31 0x01 把对象号原样回显一遍;中间那四个字节 98 C0 B4 41 才是温度值;最后 A1 BE 是校验。
那四个字节怎么变成温度?它是一个 IEEE754 单精度浮点数,而且字节序是小端------低字节在前。把这四个字节还原成 32 位数值是 0x41B4C098,按 IEEE754 解开就是 22.594 ℃。
这里有个 LabVIEW 的天然红利。 x86 本身就是小端机器,所以拿到这四个字节,直接 Type Cast 成 SGL 就是正确答案,一个字节都不用翻。反倒是你用在线转换器手动算的时候,必须记得「按 10→7 的顺序倒着输进去」------厂家中文说明里专门提醒了这一点,原因就是手动转换和程序解析的方向正好相反。


KELLER 高温计 LabVIEW 读取链路:打开端口 → 发送请求 → 读取响应 → 校验 CRC → 解析显示
04 LabVIEW 里怎么实现
第一步,把串口配对。 57600 波特率、8 位数据、偶校验、1 位停止位、无流控,一个 VISA Configure Serial Port 节点就能搞定。厂家说明里还有一句提醒值得抄进注释:用 USB 转 485 盒子时要注意关闭 DTR,否则会出现一直在收发、没法「发一条回一条」的情况。
第二步,发指令。 用 U8 数组常量比字符串常量更稳妥------字节就是字节,不会被编码方式悄悄改写。
第三步,读响应。 这个协议有个很好用的设计:帧的第一个字节就是帧长。所以最稳的读法是「先读 1 个字节拿到 n,再读 n-1 个字节」,而不是取一次 Bytes at Port 就往下走。前一种写法对仪表的响应延迟完全不敏感,后一种在仪表还没来得及回话时会直接读到 0,紧接着就是一个 VISA 超时。
第四步,校验。 对前 n-2 个字节算 CRC16(多项式 0xA001、初值 0xFFFF),把结果按低字节在前与帧尾比对。校验不过就丢弃重读,不要拿去解析。
第五步,解析。 从响应里定位到对象值那四个字节,Type Cast 成 SGL,温度就出来了。再往后是显示、存 TDMS、画曲线,都是常规动作。
第六步,想一趟多读几个量?对象字段是可以串联的。 比如一条指令同时读 λ1 温度和 Total Epsilon:
0B 01 00 30 32 01 30 36 04 93 A8
帧长 11 = 3 字节帧头 + 3 字节对象字段 + 3 字节对象字段 + 2 字节 CRC,响应里两个对象字段依次排开。对轮询周期敏感的场合,这一招能把通信往返次数直接砍掉一半。
写参数也是同一套结构。比如把 λ1 的发射率设成 100.0%,对象字段变成 71 32 10 00 00 C8 42------0x71 表示这个字段占 7 字节、状态是「写」,中间是对象号,后面四字节是 100.0 的小端浮点表示。整帧就是:
0C 01 00 71 32 10 00 00 C8 42 CE 8F
写完之后建议立刻回读一次确认,尤其是发射率这类会直接影响温度换算结果的参数。
05 五条能直接带走的经验
■ 串口参数是 8E1,不是 8N1 57600 配偶校验,跟大多数国产仪表的 9600-8N1 完全不同,参数配错就是满屏乱码
■ CRC 低字节在前 多项式 0xA001、初值 0xFFFF,帧尾先低后高;上线前用官方两处示例各验一遍,比猜快得多
■ 先读 1 字节取帧长 协议首字节就是长度,先读 1 再读 n-1,比轮询 Bytes at Port 稳得多,对响应延迟完全不敏感
■ 浮点是小端,别手动翻 x86 直接 Type Cast 成 SGL 即可;只有用在线转换器手算时才需要按 10→7 倒序输入
■ 对象号高 4 位是类型 0x3201 就是 Float + 编号 0x201,照抄对象表即可,别自己去拼类型位
06 写在最后
这类工业仪表的串口协议,结构其实都很朴素:一个帧长、两个地址、若干对象字段、末尾一个 CRC。真正的门槛不在协议本身,而在那些「手册上写了、但你不一定注意到」的地方------偶校验而不是无校验、校验值低字节在前、浮点数小端存放、DTR 要关掉。
它们不会在实验室调试的时候出问题,只会在现场连续跑了几十个小时之后,才慢慢露出獠牙。
把这一套跑通之后,你会发现自己拿到任何一台带 RS485 的仪表,都只需要问三个问题:帧长怎么定、校验怎么算、数值怎么解。剩下的事情,LabVIEW 的框图会替你说清楚。
你项目里的串口仪表,最头疼的是超时、跳数,还是校验对不上?欢迎在评论区聊聊。如果这篇对你有用,也欢迎转给正在调串口的同事。