新闻中心

EEPW首页 > 牛人业话 > 妙用结构体 简化报文封装和解析

妙用结构体 简化报文封装和解析

作者:马步时间:2020-04-13来源:电子产品世界收藏

佛门里有句话:诸法无自性,尽随汝心转。就是说,同样一个东西,在不同的人眼中,呈现的是不同的印象。

本文引用地址:http://www.eepw.com.cn/article/202004/411937.htm

比如,同样是榴莲,有人视为美味,直流口水,有人却觉得闻起来臭秽,吃起来反胃,正所谓汝之蜜糖,彼之砒霜。

这一点倒是和“一千个读者的眼中就有一千个哈姆雷特”有点异曲同工之妙。

同样的东西,在不同使用者手中也能发挥不同的作用。比如倚天剑,张无忌拿它主持武林正义,护佑天下苍生,灭绝师太却拿它发泄更年期的怒火,切萝卜似地大杀四方。

比如中的,有的人轻车熟路,信手拈来,经常孔乙己似地“你可知和联合体有几种用法?”

有的人却笨手笨脚,不得章法。

说他不会用吧,他便涨红了脸,额上的青筋条条绽出,争辩道:“用得不好不能算不会用。。。不用。。。码农的事,能说不会用吗?”接着便是难懂的话,什么“不同的变量干嘛揉在一起”,什么“物以类聚人以群分”之类,引得众人都哄笑起来,办公室里充满了快活的空气。

洒家向来中规中矩,也没研究过结构体到底有几种用法,直到有一天,用它解决了工作中的一个大问题,才领会了它的妙用。

代码交接也许是码农最不愿意干的事情之一了,尤其是要接过一个要离职的“兄弟”的代码的时候。

当其时也,真个是左右为难。

卡得严一点吧,害怕伤交情,人都要走了,好说好散,何必在这个时候让人为难?放松一点吧,苦的是自己,人都要走了,一拍两散,他甩下的锅有那么容易刷完?

总之,一边是交情,一边是心情,左右都不是,为难了自己。该为他想吧,该为自己想吧,我已纠结地不可自拔。

当小韩离职、领导让我交接他的代码时,我陷入的便是这样的困境。

小韩交接给我的是一个半成品的电动防夹车窗控制器,虽然一时半会我还看不大懂他的防夹算法,但是应用部分还是比较容易理解的。待我沉下心去一看,我很快被代码中此起彼伏的位操作语句淹没了。

代码里到处涌现的这些位操作是干嘛的呢?原来,这个产品是个CAN节点,这些位操作是用来提取CAN报文里的某个“信号”,或者给CAN报文的某个“信号”赋值的。

这里面实际上牵扯到一个挺麻烦的事情,因为,传统的CAN信号读取和赋值方法以报文字节数组为操作对象,读取某个CAN信号或者对某个CAN信号赋值时,确实需要对报文字节数组中的某个字节进行位操作。

但是,根据具体应用不同,一个CAN节点需要读取和赋值的CAN信号可能多达数十个甚至上百个,这种位操作方式使得解析和赋值CAN信号的工作非常繁重,而且容易出错。当CAN节点功能升级造成网络矩阵表发生改变时,CAN信号解析和赋值操作也会随CAN信号位置或长度的变化而变化,这时又会造成大量的修改操作。

也就是说,且不说小韩的半成品代码里是不是埋了雷,就是以后要做一点修改时,也会很麻烦。

这可咋整?

进一步行文之前,还是有必要先给大家科普一下。

在CAN网路中,CAN报文是底层通信接收和发送的主体,每条CAN报文中的数据场为8个字节。在这八个字节里,有很多“信号”,这些信号一般是位形式,比如车窗命令可以用一个2位的信号表示-01上升10下降11停止。

这么说吧,从“通信”的角度,报文收发的操作对象是“字节形式”的报文数据,但是从“逻辑”的角度,应用的操作对象是“位形式”的CAN信号。

一边是字节形式的报文数据,一边是位形式的CAN信号,显然需要通过一种手段把它们联系起来。

位操作当然是手段之一。

就像小韩在代码里写的那样,先把CAN报文寄存器里的字节形式的数据赋值给具体报文中的某个字节,比如:

WDW_CMD_REQ[0]=(unsigned char)(CANmsgReceiveNow[1]>>8);

再用位操作提取该报文该字节里面的信号,比如:

Lf_cmd=WDW_CMD_REQ[0]&0x03;

在写代码和读代码时,位操作毕竟比不得直接的加、减、逻辑、赋值操作那样容易理解,但是,程序员的心一般都比较大,应该不怕自己人读时头大。

而且,就算如前文所说,信号在字节中的位置发生了变化,无非是多费点脑汁,重写一下这个位操作语句就行了。

总之,位操作是麻烦了点,但是好像也不是多大的硬伤。

好吧,如果你没有觉得哪里有什么不对劲,请你思考一下这个问题:

每个报文对应八个字节,这八个字节里可能有二三十个信号,如果每个信号都定义一个变量,一条报文就差不多消耗三十来个字节的RAM,如果报文多了,会消耗多少RAM资源呢?要知道,在MCU里面,RAM可算是寸土寸金呐!

信号对应的RAM资源其实是可以省掉的!方法就是本文要说的联合体和结构体。

联合体可以节省RAM空间,这是里的常识。可是,怎么把字节形式和位形式“联合”为一体呢?

方法就是根据报文的信号矩阵设计信号组结构体。即根据网络矩阵表给出的每个报文的所有CAN信号的名称、起始位和长度信息,每个报文都设计一个由一组位信号组成的结构体。

这里首先要注意你所用处理器的大小端模式,看看是先定义第一个字节里的信号,还是先定义最后一个字节里的信号。然后根据信号的占位(第几个字节的哪几位),把它定义在相应的位置,如果在报文里有空位,比如说第二个字节的0-3位没有定义,这时也要以占位符信号的形式把它定义出来。

然后为每个报文定义一个联合体类型。联合体有两个成员变量,一个是数组变量,一个是上述信号组结构体变量。根据联合体的定义,数组变量和信号组结构体变量的尺寸相同,存放于相同的地址空间(同一个RAM地址)里,无论谁发生了变化,另一方也会同步发生改变。

在这个联合体中,数组变量是以单字节类型定义的数组,存储字节形式的报文数据,它用于报文的收发,对应于底层通信。

信号组结构体变量是以上述信号组结构体类型定义的结构体,存储信号组形式的报文数据,它用于信号的解析和封装,对应于上层应用。

1586754731303299.png

报文联合体和信号组结构体内存空间示意图

CAN节点接收报文并放入报文缓冲区后,进行报文解析时,首先根据报文ID,找出对应的报文,然后将报文缓冲区当前报文的数据写入对应的报文联合体变量的数组变量中,由于联合体字节数组和联合体信号组结构体位于相同的地址空间上,数组变量的内容更新直接更新了该联合体变量中的信号组结构体变量的内容,当提取和解析CAN信号时,直接读取该报文联合体中的结构体中定义的信号即可。

当需要发送报文时,如果需要赋值信号,直接赋值该报文联合体中的结构体中定义的信号,不需要进行位操作对报文数据进行封装,然后将该报文联合体中的字节数组填充到CAN控制器的发送寄存器中,启动CAN控制器的发送就可以完成报文的发送。

这种实现方案是不是很酷?Super Cool!

这种对结构体和联合体的妙用,只需要在定义结构体的时候胆大心细一些,便可以将CAN信号的解析及封装毕其功于一役,之后的工作就像那桥边姑娘,风华模样,落落大方了。

如果是底层报文收发,你就对联合体中的数组形式的字节变量进行操作,如果是应用层CAN信号的读取和赋值,你就对联合体中信号组结构体形式的位变量进行操作。

有了联合体,报文里字节形式的数据变化后,位形式的信号量自动发生变化,直接拿来用即可。反之,在应用中对某个位形式的信号更新赋值了以后,报文里的字节数据自动发生变化,该发送时直接发送即可!

再也没有恼人的位操作了,是不是非常地神清气爽?

总之,假舆马者,非利足也,而致千里;假舟楫者,非能水也,而绝江河。君子生非异也,善假于物也!

就在于这个善假于物也!



关键词: C语言 结构体

评论


相关推荐

技术专区

关闭