本人最早从事 DSP 与 MCU 韧体开发,几经辗转后来到工业自动化撰写 PLC 程序。站在编程金字塔最底端的自动化产业,身边大部分工程师害怕的就是通讯,然而比起通讯,更可怕的就是浮点数值的数据通讯。为此希望透过这篇文章,能够让大家了解浮点数值如何通讯的疑问。
在了解如何传输浮点数值之前,数据类型(Data Type)、浮点数值(Floating Point)表示方式以及数据帧(Data Frame)是必须掌握的基础知识。
相信数据类型是所有从事编程的工程师第一个接触的知识点,不管何种数据类型,其可表示的数值范围皆是由不同长度的位(bit)以及相应规则所组合而成,在 PLC 中常见的数据类型及对应的位数如下:
类型 | 位数 |
---|---|
BOOL | 1 |
BYTE | 8 |
INT | 16 |
WORD | 16 |
DWORD | 32 |
REAL | 32 |
LREAL | 64 |
当然还有其他数据类型族繁不及备载,这里就不另外列出。
以上对于 CSDN 人均 30k 的工程师想必是伸手就来的知识,那浮点数又是如何进行通讯的呢?在这之前我们必须提一下浮点数的表示规则。
或称 IEEE 754 标准,百度百科介绍如下:
IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number)),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”;它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。
常用的浮点数有单精度(32位)与双精度(64位),其组成是由符号 s i g n sign sign、指数 e x p o n e n t exponent exponent 与尾数 f r a c t i o n fraction fraction 构成,方便起见以下就以 IEEE 754 单精度浮点数说明,浮点数 V a l u e Value Value 的表示如下:
V a l u e = ( − 1 ) s i g n × 2 e x p o n e n t − 127 × M Value = (-1)^{sign} \times 2^{exponent - 127}\times M Value=(−1)sign×2exponent−127×M
其中 M M M 的计算参考如下(C 语言为例):
float M = 0;
int fraction = 0x0049dfd0; //for example
for(int i=0; i<23; i++){
M = M + ((fraction & (1 << i)) >> i) * pow(2,i - 23);
}
若以位元展开 V a l u e Value Value 数值,其分别对应的 S i g n Sign Sign(s)、 e x p o n e n t exponent exponent 与 f r a c t i o n fraction fraction 各自占据在 32 位资料中的排序与位元数如下:
(手机观看请左右滑动表格)
s | exponent | fraction | |||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
- | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
假设浮点数值 V a l u e Value Value 存在一数值
V a l u e = 3.14159 Value = 3.14159 Value=3.14159
透过 IEEE 754 标准展开我们可以得到:
(手机观看请左右滑动表格)
s | exponent | fraction | |||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
数据帧是通讯实体传输的基本单位,位于 OSI 模型(或称七层网路架构)的第二层数据连路层(Data Link Layer),百度百科的解释如下:
数据链路层
数据链路层是在通信实体间建立数据链路联接,传输的基本单位为“帧”,并为网络层提供差错控制和流量控制服务。数据链路层由MAC(介质访问控制子层)和LLC(逻辑链路控制子层)组成。介质访问控制子层的主要任务是规定如何在物理线路上传输帧。逻辑链路控制子层对在同一条网络链路上的设备之间的通信进行管理。数据链路控制子层主要负责逻辑上识别不同协议类型,并对其进行封装。也就是说数据链路控制子层会接受网络协议数据、分组的数据报并且添加更多的控制信息,从而把这个分组传送到它的目标设备。
以上没看完也没关系,简单来说通讯之间就是靠一串串有头(帧头)、有内容(传输数据)加上校验(如 CRC 校验)的资料构成。帧头和校验的内容会根据通讯协议的不同有所区别,但传输数据其实都大同小异(不包含加密传输)。
而构成一个帧的最基本元素就是字节(BYTE),因此数据在透过帧传输前,势必会根据数据长度拆分成数个字节后再进行传输。
掌握以上数据类型、浮点数值表示方式与数据帧的基础知识后,我们便可以有效的将浮点数进行传输。
我们再以 V a l u e = 3.14159 Value = 3.14159 Value=3.14159 为例,将 V a l u e Value Value 拆分成字节后可以得到下表:
(手机观看请左右滑动表格)
s | exponent | fraction | |||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
16#40 | 16#49 | 16#0f | 16#d0 |
因此只要将浮点数 V a l u e Value Value 所对应的 4 个字节数据有序传输,即可实现浮点数通讯。同样的,将通讯所收到的 4 个字节有序组成,即可正确接收浮点数值。
上面已经介绍了浮点数如何透过字节的方式进行传输,但如何将浮点数拆解成字节又是个问题,以下介绍常见的两种方法进行拆解和包装浮点数所对应的字节。
IEC 61131-3 编程标准的 PLC 都具有联合体资料型态可让用户自行定义。联合体的功能是将变量分配在同一记忆体空间,例如我们定义一个联合体如下:
TYPE FloatData:
UNION
fValue : REAL;
wValue: Array [0..1] OF WORD;
bValue : ARRAY [0..3] OF BYTE;
END_UNION
END_TYPE
Data : FloatData;
联合体会以占用最大记忆体空间的内容进行记忆体划分,因此将 Data 定义为 FloatData 联合体后,便会划分一块 32 位的记忆体空间给 Data 使用,此时 Data 中包含了一个浮点数 fValue、字 wValue 数组以及字节 bValue 数组,然而这三种型态其实都对应了相同的 32 位资料,参考下表:
(手机观看请左右滑动表格)
fValue | |||||||||||||||||||||||||||||||
wValue[1] | wValue[0] | ||||||||||||||||||||||||||||||
bValue[3] | bValue[2] | bValue[1] | bValue[0] | ||||||||||||||||||||||||||||
- | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
因此不管我们操作了哪一种资料型态,其实都是在操作最底层的 32 位资料。现在我们将 V a l u e Value Value 所拆解的 4 个字节输入到 bValue 数组中
Data.bValue[0] := 16#D0;
Data.bValue[1] := 16#0F;
Data.bValue[2] := 16#49;
Data.bValue[3] := 16#40;
则我们可以同时得到 wValue 数组所对应的结果以及 fValue 的浮点数值。
AryByteTo 是将字节数组打包进某个资料型态内,相反的 ToAryByte 是将资料型态存放至字节数组中,因此透过这两种函数的操作,我们能将通讯所收到的字节打包成浮点数,也能将需要传输的浮点数拆分成字节后进行传输。