演示界面
视频演示
Modbus Encoding for Floating Point Values
IEEE 754 单精度浮点格式是表示浮点数的最流行方法,但需要 32 位。制造商已决定使用 2 个连续的 Modbus 寄存器来编码浮点值。这听起来很容易……但是,没那么快。尽管同意使用 2 个连续的寄存器对浮点值进行编码,但对于数据发送的顺序没有标准方法。
示例:对于浮点值 50.123,IEE 754 单精度浮点表示如下所示。
Decimal | 50.1230049133 |
Binary | 01000010010010000111110111110100 |
Hexidecimal | 0x42487df4 |
分成两个 16 位字(寄存器):高 0x4248 低 0x7df4。Modbus 保持寄存器 40001 和 40002 的编码。一些制造商先发送高位字,然后发送低位字。
Modbus Reg | Contents Hex | Contents Decimal |
400001 | 0x7df4 | 32244 |
400002 | 0x4248 | 16968 |
其他制造商先发送低位字,然后发送高位字。
Modbus Reg | Contents Hex | Contents Decimal |
400001 | 0x4248 | 16968 |
400002 | 0x7df4 | 32244 |
How to read Modbus floating point values
大多数 Modbus 主站提供了一种交换浮点值字序的方法。请查阅相关设备的通信点列表,以确定用于对浮点值进行编码的字(寄存器)顺序。根据该设置配置主设备。
如果没有提供有关如何发送数据的信息,则它将成为一个试错过程。选择一种格式并配置主设备以读取该格式的值。如果读回的值非常不准确,则交换单词,这通常会更正读回的值。
如何在 MODBUS RTU 消息中编码真实(浮点)和 32 位数据
本文讨论了通过 Modbus RTU 处理 32 位数据类型时遇到的一些典型困难,并为解决这些问题提供了实际帮助。
点对点 Modbus 协议是 RTU 通信的流行选择,如果没有其他原因,它是基本的便利。该协议本身控制 Modbus 网络上每个设备的交互,设备如何建立已知地址,每个设备如何识别其消息以及如何从数据中提取基本信息。本质上,协议是整个 Modbus 网络的基础。
然而,这种便利并非没有一些复杂性,Modbus RTU 消息协议也不例外。该协议本身是基于具有 16 位寄存器长度的设备设计的。因此,在实现 32 位数据元素时需要特别考虑。这个实现决定使用两个连续的 16 位寄存器来表示 32 位数据或基本上 4 字节的数据。正是在这 4 个字节的数据中,可以将单精度浮点数据编码为 Modbus RTU 消息。
The Importance of Byte Order
Modbus 本身没有定义浮点数据类型,但人们普遍认为它使用 IEEE-754 标准实现 32 位浮点数据。然而,IEEE 标准对数据有效载荷的字节顺序没有明确的定义。因此,在处理 32 位数据时,最重要的考虑因素是以正确的顺序寻址数据。
例如,IEEE 754 标准中为单精度 32 位浮点数定义的数字 123456.00 如下所示:
各种字节顺序的影响是显着的。例如,将表示 123456.00 的 4 字节数据按“B A D C”序列排序,称为“字节交换”。当解释为 IEEE 744 浮点数据类型时,结果完全不同:
在“C D A B”序列中对相同字节进行排序称为“字交换”。同样,结果与 123456.00 的原始值有很大不同:
此外,“字节交换”和“字交换”本质上都会完全颠倒字节顺序以产生另一个结果:
显然,在使用 Modbus 等网络协议时,必须严格注意内存字节在传输时是如何排序的,也称为“字节顺序”。
Determining Byte Order
根据 Modbus 应用协议规范 V1.1.b,Modbus 协议本身被声明为“大端”协议:
“Modbus 使用“big-Endian”表示地址和数据项。这意味着当传输大于单个字节的数字量时,首先发送最高有效字节。”
Big-Endian 是网络协议最常用的格式——事实上,它非常普遍,也被称为“网络顺序”。
鉴于 Modbus RTU 消息协议是 big-Endian,为了通过 Modbus RTU 消息成功交换 32 位数据类型,必须考虑主从机的字节序。许多 RTU 主设备和从设备允许特定的字节顺序选择,特别是在软件模拟单元的情况下。只需确保所有单元都设置为相同的字节顺序。
根据经验,设备微处理器的家族决定了它的字节序。通常,大端风格(首先存储高位字节,然后是低位字节)通常出现在使用摩托罗拉处理器设计的 CPU 中。little-Endian 风格(首先存储低位字节,然后是高位字节)通常出现在使用 Intel 架构的 CPU 中。至于哪种风格被认为是“倒退”,这是个人观点的问题。
但是,如果字节顺序和字节顺序不是可配置的选项,您将必须确定如何解释字节。这可以通过从从站请求一个已知的浮点值来完成。如果返回不可能的值,即具有两位数指数等的数字,则很可能需要修改字节顺序。
Practical Help
FieldServer Modbus RTU 驱动程序提供了几个处理 32 位整数和 32 位浮点值的函数移动。更重要的是,这些函数移动考虑了所有不同形式的字节排序。下表显示了 FieldServer 函数将两个相邻的 16 位寄存器复制到一个 32 位整数值的移动。
Function Keyword | Swap Mode | Source Bytes | Target Bytes |
2.i16-1.i32 | N/A | [ a b ] [ c d ] | [ a b c d ] |
2.i16-1.i32-s | byte and word swap | [ a b ] [ c d ] | [ d c b a ] |
2.i16-1.i32-sb | byte swap | [ a b ] [ c d ] | [ b a d c ] |
2.i16-1.i32-sw | word swap | [ a b ] [ c d ] | [ c d a b ] |
下表显示了将两个相邻 16 位寄存器复制到 32 位浮点值的 FieldServer 函数移动:
Function Keyword | Swap Mode | Source Bytes | Target Bytes |
2.i16-1.ifloat | N/A | [ a b ] [ c d ] | [ a b c d ] |
2.i16-1.ifloat-s | byte and word swap | [ a b ] [ c d ] | [ d c b a ] |
2.i16-1.ifloat-sb | byte swap | [ a b ] [ c d ] | [ b a d c ] |
2.i16-1.ifloat-sw | word swap | [ a b ] [ c d ] | [ c d a b ] |
下表显示了将单个 32 位浮点值复制到两个相邻 16 位寄存器的 FieldServer 函数移动:
Function Keyword | Swap Mode | Source Bytes | Target Bytes |
1.float-2.i16 | N/A | [ a b c d ] | [ a b ][ c d ] |
1.float-2.i16-s | byte and word swap | [ a b c d ] | [ d c ][ b a ] |
1.float-2.i16-sb | byte swap | [ a b c d ] | [ b a ][ d c ] |
1.float-2.i16-sw | word swap | [ a b c d ] | [ c d ][ a b ] |
鉴于各种 FieldServer 功能移动,正确处理 32 位数据取决于选择正确的。观察这些 FieldServer 函数在已知单精度十进制浮点值 123456.00 上移动的以下行为:
16-bit Values | Function Move | Result | Function Move | Result |
0x2000 0x47F1 | 2.i16-1.float | 123456.00 | 1.float-2.i16 | 0x2000 0x47F1 |
0xF147 0x0020 | 2.i16-1.float-s | 123456.00 | 1.float-2.i16-s | 0xF147 0X0020 |
0x0020 0xF147 | 2.i16-1.float-sb | 123456.00 | 1.float-2.i16-sb | 0x0020 0xF147 |
0x47F1 0x2000 | 2.i16-1.float-sw | 123456.00 | 1.float-2.i16-sw | 0x47F1 0x2000 |
请注意,不同的字节和字顺序需要使用适当的 FieldServer 函数移动。一旦选择了正确的功能移动,数据就可以双向转换。
在 Internet 上可用的许多十六进制到浮点数转换器和计算器中,很少有人真正允许对字节和字序进行操作。一个这样的实用程序可以在这里找到(https://www.h-schmidt.net/FloatConverter/IEEE754.html)。该实用程序显示十进制浮点值 123456.00,如下所示:
然后可以交换字节和/或字以分析 Modbus RTU 主设备和从设备之间可能存在哪些潜在的字节顺序问题。
参考:
1>https://store.chipkin.com/articles/how-real-floating-point-and-32-bit-data-is-encoded-in-modbus-rtu-messages
2>https://www.cnblogs.com/wjx-blog/p/13419909.html
3>https://blog.csdn.net/qq_34699535/article/details/111658342
4>https://blog.csdn.net/XUMENGCAS/article/details/122305152
5>https://www.thepyroscope.com/blog/modbus-comms-how-to-set-floating-point-values/
6>https://www.h-schmidt.net/FloatConverter/IEEE754.html
附部分源码
private void btn_write_Click(object sender, EventArgs e)
{
float value;
bool ok = float.TryParse(txt_float.Text, out value);
if (ok)
{
ushort lowOrderValue = BitConverter.ToUInt16(BitConverter.GetBytes(value), 2);// 后16bit
ushort highOrderValue = BitConverter.ToUInt16(BitConverter.GetBytes(value), 0);//前16bit
ushort[] data = new ushort[2];
data[0] = lowOrderValue;//低16位 存入前一寄存器
data[0 + 1] = highOrderValue;//高16位 存入后一寄存器
try
{
master.WriteMultipleRegisters(1, 0, data);
Thread.Sleep(2000);
txt_float.Text = "NULL";
}
catch (Exception)
{
throw;
}
}
}
private void btn_read_Click(object sender, EventArgs e)
{
ushort[] res = master.ReadHoldingRegisters(1, 0, 2);
float ret = GetFloatFormUshortArray(res);
txt_float.Text = ret.ToString();
}
The End