这是系列第五篇。前四天你已经学会了读、写、轮询、处理浮点数、做 WinForms 界面。
但你可能一直有个困惑:Modbux 里那些 INT16、UINT16、FLOAT 到底是什么意思?为什么有的读 1 个寄存器,有的读 2 个?
今天专门把这件事讲清楚,并且把遇到问题时怎么排查也一并写全。
一、先理解一个核心事实
Modbus 协议本身,只认识一种东西:16 位的寄存器。
它不知道什么是整数、小数、正数、负数。它只知道“这里有一个 16 位的格子”。
所有的类型,都是人给它赋予的含义。
二、Modbux 里的类型 vs C# 里的类型
你在 Modbux 里看到的类型,和 C# 里写的类型,是一一对应的,只是叫法不同。
| Modbux 里选 | C# 里写 | 位数 | 占几个寄存器 | 范围 |
|---|---|---|---|---|
UINT16 |
ushort |
16 位 | 1 个 | 0 ~ 65535 |
INT16 |
short |
16 位 | 1 个 | -32768 ~ 32767 |
UINT32 |
uint |
32 位 | 2 个 | 0 ~ 42 亿 |
INT32 |
int |
32 位 | 2 个 | -21 亿 ~ 21 亿 |
FLOAT |
float |
32 位 | 2 个 | 小数 |
DOUBLE |
double |
64 位 | 4 个 | 小数 |
INT64 |
long |
64 位 | 4 个 | 很大 |
UINT64 |
ulong |
64 位 | 4 个 | 很大 |
核心规律:
- 16 位的类型,占 1 个寄存器
- 32 位的类型,占 2 个寄存器
- 64 位的类型,占 4 个寄存器
一个寄存器 = 16 位 = 2 个字节。
三、为什么有 INT 和 UINT
INT 和 UINT 的区别,是“能不能存负数”。
| 类型 | 全称 | 能存负数吗 |
|---|---|---|
INT16 |
有符号 16 位整数 | ✅ 能 |
UINT16 |
无符号 16 位整数 | ❌ 不能 |
例子:
-
UINT16的范围是0 ~ 65535 -
INT16的范围是-32768 ~ 32767
同样是 16 位,UINT16 把一半的范围分给了正数,INT16 把一半分给了负数。
所以:
- 数据不可能为负(比如温度、数量)→ 用
UINT16 - 数据可能为负(比如偏移量、温差)→ 用
INT16
四、16 位 vs 32 位
4.1 16 位能存多大
UINT16 最大 65535。超过这个数,就装不下。
比如你要存 100000:
-
UINT16最大65535,装不下 - 必须用
UINT32(32 位),它最大能到 42 亿
4.2 32 位为什么占 2 个寄存器
因为一个寄存器只有 16 位,32 位需要 2 个寄存器拼起来。
- 寄存器 1:前 16 位
- 寄存器 2:后 16 位
拼起来,才能表示一个 32 位数。
4.3 64 位占 4 个寄存器
同理,64 位需要 4 个寄存器。
实际项目中,64 位很少用。 大部分数据 16 位或 32 位就够了。
五、FLOAT 是什么
FLOAT 就是小数。
- 占 32 位 = 2 个寄存器
- 用 IEEE 754 标准编码
- 能存
25.6、101.325这种小数
在 C# 里对应 float 类型。
为什么叫浮点数: 因为小数点可以“浮动”,能表示很大或很小的数。
六、怎么知道用哪个类型
看设备手册。
设备手册会写清楚:
| 手册写什么 | C# 里用什么 | 读几个寄存器 |
|---|---|---|
| 温度是 16 位无符号整数 | ushort |
1 个 |
| 温度是 16 位有符号整数 | short |
1 个 |
| 累计产量是 32 位无符号整数 | uint |
2 个 |
| 温度是浮点数 | float |
2 个 |
没有手册,就问厂家,或者试。
七、读出来不对,怎么排查
这是实际工作中一定会遇到的。按下面四步来。
第一步:打印原始寄存器值
不要直接看拼出来的结果,先看寄存器里的原始值。
ushort[] registers = master.ReadHoldingRegisters(2, 0, 2);
Console.WriteLine("寄存器 0:" + registers[0]);
Console.WriteLine("寄存器 1:" + registers[1]);
对比:
- 打印的值和 Modbux(或设备)里设置的一样 → 读取没问题,问题在拼装
- 打印的值不一样 → 读取有问题,检查 Unit ID、地址
第二步:确认类型对不对
- 你代码里用的类型,和设备手册说的一致吗?
-
float对应FLOAT,ushort对应UINT16 - 如果手册说是
UINT32,你却用ushort去读,只有一个寄存器,肯定不对
类型错了,读出来的值一定不对。
第三步:试四种字节顺序
如果寄存器值对了,类型也对了,但拼出来还是不对,那就是字节顺序问题。
把 ConvertToFloat 里的两个寄存器,按四种顺序都试一遍:
顺序 A(大端):
bytes[0] = (byte)(registers[0] >> 8);
bytes[1] = (byte)(registers[0] & 0xFF);
bytes[2] = (byte)(registers[1] >> 8);
bytes[3] = (byte)(registers[1] & 0xFF);
顺序 B(小端):
bytes[0] = (byte)(registers[1] >> 8);
bytes[1] = (byte)(registers[1] & 0xFF);
bytes[2] = (byte)(registers[0] >> 8);
bytes[3] = (byte)(registers[0] & 0xFF);
顺序 C(字节交换):
bytes[0] = (byte)(registers[0] & 0xFF);
bytes[1] = (byte)(registers[0] >> 8);
bytes[2] = (byte)(registers[1] & 0xFF);
bytes[3] = (byte)(registers[1] >> 8);
顺序 D(全交换):
bytes[0] = (byte)(registers[1] & 0xFF);
bytes[1] = (byte)(registers[1] >> 8);
bytes[2] = (byte)(registers[0] & 0xFF);
bytes[3] = (byte)(registers[0] >> 8);
四种都试一遍,总有一个对的。
第四步:确认比例系数
如果拼出来的值合理,但单位不对(比如温度显示 256 而不是 25.6),检查设备手册的比例系数。
常见情况:
| 手册说 | 转换方法 |
|---|---|
| 单位是 0.1℃ | 值 × 0.1 |
| 单位是 0.01℃ | 值 × 0.01 |
| 单位是 1℃ | 值 × 1 |
这一步很容易漏,但很关键。
八、一个提醒:REAL 是什么
有些设备手册会用 REAL 这个词。
REAL 和 FLOAT 是一个意思,都是 32 位浮点数,占 2 个寄存器。
九、完整排查流程
遇到读出来不对,按这个顺序走:
1. 打印原始寄存器值
↓
和 Modbux / 设备里设置的一样吗?
├── 不一样 → 检查 Unit ID、地址
└── 一样 → 继续
↓
2. 检查类型
↓
代码里的类型,和手册一致吗?
├── 不一致 → 改类型
└── 一致 → 继续
↓
3. 试四种字节顺序
↓
哪个拼出来合理,就用哪个
↓
4. 检查比例系数
↓
值合理但单位不对,就乘比例
十、核心总结
| 你只需要记住 | 说明 |
|---|---|
| 16 位 = 1 个寄存器 |
UINT16 / INT16
|
| 32 位 = 2 个寄存器 |
UINT32 / INT32 / FLOAT
|
| 64 位 = 4 个寄存器 |
DOUBLE / INT64 / UINT64
|
INT 能存负数,UINT 不能 |
看数据会不会为负 |
| 用哪个类型 | 看设备手册 |
| 读出来不对 | 按四步排查:寄存器值 → 类型 → 字节顺序 → 比例 |