佛门里有句话:诸法无自性,尽随汝心转。便是说,相同一个东西,在不同的人眼中,出现的是不同的形象。
比方,相同是榴莲,有人视为甘旨,直流口水,有人却觉得闻起来臭秽,吃起来反胃,正所谓汝之蜜糖,彼之砒霜。
这一点却是和“一千个读者的眼中就有一千个哈姆雷特”有点异曲同工之妙。
相同的东西,在不同使用者手中也能发挥不同的效果。比方倚天剑,张无忌拿它掌管武林正义,护佑全国苍生,灭绝师太却拿它宣泄更年期的怒火,切萝卜似地大杀四方。
比方C言语中的结构体,有的人驾轻就熟,信手拈来,常常孔乙己似地“你可知结构体和联合体有几种用法?”
有的人却笨手笨脚,不得规矩。
说他不会用结构体吧,他便涨红了脸,额上的青筋条条绽出,争论道:“用得欠好不能算不会用。。。不必。。。码农的事,能说不会用吗?”接着便是难明的话,什么“不同的变量干嘛揉在一起”,什么“物以类聚人以群分”之类,引得世人都哄笑起来,作业室里充满了快活的空气。
洒家历来中规中矩,也没研讨过结构体到底有几种用法,直到有一天,用它处理了作业中的一个大问题,才领会了它的妙用。
一
代码交代也许是码农最不乐意干的作业之一了,尤其是要接过一个要离任的“兄弟”的代码的时分。
当当时也,端的是左右尴尬。
卡得严一点吧,惧怕伤友谊,人都要走了,好说好散,何须在这个时分让人尴尬?放松一点吧,苦的是自己,人都要走了,一拍两散,他甩下的锅有那么简单刷完?
总归,一边是友谊,一边是心境,左右都不是,尴尬了自己。该为他想吧,该为自己想吧,我已纠结地不可自拔。
当小韩离任、领导让我交代他的代码时,我堕入的便是这样的窘境。
小韩交代给我的是一个半成品的电动防夹车窗控制器,尽管一时半会我还看不大懂他的防夹算法,但是使用部分仍是比较简单了解的。待我沉下心去一看,我很快被代码中此伏彼起的位操作句子淹没了。
代码里处处出现的这些位操作是干嘛的呢?本来,这个产品是个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空间,这是C言语里的知识。但是,怎样把字节方式和位方式“联合”为一体呢?
办法便是依据报文的信号矩阵规划信号组结构体。即依据网络矩阵表给出的每个报文的一切CAN信号的称号、开始位和长度信息,每个报文都规划一个由一组位信号组成的结构体。
这儿首要要注意你所用处理器的巨细端方式,看看是先界说第一个字节里的信号,仍是先界说最终一个字节里的信号。然后依据信号的占位(第几个字节的哪几位),把它界说在相应的方位,假如在报文里有空位,比方说第二个字节的0-3位没有界说,这时也要以占位符信号的方式把它界说出来。
然后为每个报文界说一个联合体类型。联合体有两个成员变量,一个是数组变量,一个是上述信号组结构体变量。依据联合体的界说,数组变量和信号组结构体变量的尺度相同,存放于相同的地址空间(同一个RAM地址)里,无论谁产生了改动,另一方也会同步产生改动。
在这个联合体中,数组变量是以单字节类型界说的数组,存储字节方式的报文数据,它用于报文的收发,对应于底层通讯。
信号组结构体变量是以上述信号组结构体类型界说的结构体,存储信号组方式的报文数据,它用于信号的解析和封装,对应于上层使用。
报文联合体和信号组结构体内存空间示意图
CAN节点接纳报文并放入报文缓冲区后,进行报文解析时,首要依据报文ID,找出对应的报文,然后将报文缓冲区当时报文的数据写入对应的报文联合体变量的数组变量中,因为联合体字节数组和联合体信号组结构体坐落相同的地址空间上,数组变量的内容更新直接更新了该联合体变量中的信号组结构体变量的内容,当提取和解析CAN信号时,直接读取该报文联合体中的结构体中界说的信号即可。
当需求发送报文时,假如需求赋值信号,直接赋值该报文联合体中的结构体中界说的信号,不需求进行位操刁难报文数据进行封装,然后将该报文联合体中的字节数组填充到CAN控制器的发送寄存器中,发动CAN控制器的发送就能够完结报文的发送。
这种完成计划是不是很帅?Super Cool!
四
这种对结构体和联合体的妙用,只需求在界说结构体的时分胆大心细一些,便能够将CAN信号的解析及封装毕其功于一役,之后的作业就像那桥边姑娘,风华容貌,雍容大方了。
假如是底层报文收发,你就对联合体中的数组方式的字节变量进行操作,假如是使用层CAN信号的读取和赋值,你就对联合体中信号组结构体方式的位变量进行操作。
有了联合体,报文里字节方式的数据改动后,位方式的信号量主动产生改动,直接拿来用即可。反之,在使用中对某个位方式的信号更新赋值了今后,报文里的字节数据主动产生改动,该发送时直接发送即可!
再也没有恼人的位操作了,是不是十分地神清气爽?
总归,假舆马者,非利足也,而致千里;假舟楫者,非能水也,而绝江河。正人生非异也,善假于物也!
就在于这个善假于物也!