妙用結(jié)構(gòu)體 簡化報(bào)文封裝和解析
佛門里有句話:諸法無自性,盡隨汝心轉(zhuǎn)。就是說,同樣一個(gè)東西,在不同的人眼中,呈現(xiàn)的是不同的印象。
本文引用地址:http://butianyuan.cn/article/202004/411937.htm比如,同樣是榴蓮,有人視為美味,直流口水,有人卻覺得聞起來臭穢,吃起來反胃,正所謂汝之蜜糖,彼之砒霜。
這一點(diǎn)倒是和“一千個(gè)讀者的眼中就有一千個(gè)哈姆雷特”有點(diǎn)異曲同工之妙。
同樣的東西,在不同使用者手中也能發(fā)揮不同的作用。比如倚天劍,張無忌拿它主持武林正義,護(hù)佑天下蒼生,滅絕師太卻拿它發(fā)泄更年期的怒火,切蘿卜似地大殺四方。
比如C語言中的結(jié)構(gòu)體,有的人輕車熟路,信手拈來,經(jīng)??滓壹核频亍澳憧芍?a class="contentlabel" href="http://butianyuan.cn/news/listbylabel/label/結(jié)構(gòu)體">結(jié)構(gòu)體和聯(lián)合體有幾種用法?”
有的人卻笨手笨腳,不得章法。
說他不會(huì)用結(jié)構(gòu)體吧,他便漲紅了臉,額上的青筋條條綻出,爭辯道:“用得不好不能算不會(huì)用。。。不用。。。碼農(nóng)的事,能說不會(huì)用嗎?”接著便是難懂的話,什么“不同的變量干嘛揉在一起”,什么“物以類聚人以群分”之類,引得眾人都哄笑起來,辦公室里充滿了快活的空氣。
灑家向來中規(guī)中矩,也沒研究過結(jié)構(gòu)體到底有幾種用法,直到有一天,用它解決了工作中的一個(gè)大問題,才領(lǐng)會(huì)了它的妙用。
一
代碼交接也許是碼農(nóng)最不愿意干的事情之一了,尤其是要接過一個(gè)要離職的“兄弟”的代碼的時(shí)候。
當(dāng)其時(shí)也,真?zhèn)€是左右為難。
卡得嚴(yán)一點(diǎn)吧,害怕傷交情,人都要走了,好說好散,何必在這個(gè)時(shí)候讓人為難?放松一點(diǎn)吧,苦的是自己,人都要走了,一拍兩散,他甩下的鍋有那么容易刷完?
總之,一邊是交情,一邊是心情,左右都不是,為難了自己。該為他想吧,該為自己想吧,我已糾結(jié)地不可自拔。
當(dāng)小韓離職、領(lǐng)導(dǎo)讓我交接他的代碼時(shí),我陷入的便是這樣的困境。
小韓交接給我的是一個(gè)半成品的電動(dòng)防夾車窗控制器,雖然一時(shí)半會(huì)我還看不大懂他的防夾算法,但是應(yīng)用部分還是比較容易理解的。待我沉下心去一看,我很快被代碼中此起彼伏的位操作語句淹沒了。
代碼里到處涌現(xiàn)的這些位操作是干嘛的呢?原來,這個(gè)產(chǎn)品是個(gè)CAN節(jié)點(diǎn),這些位操作是用來提取CAN報(bào)文里的某個(gè)“信號(hào)”,或者給CAN報(bào)文的某個(gè)“信號(hào)”賦值的。
這里面實(shí)際上牽扯到一個(gè)挺麻煩的事情,因?yàn)?,傳統(tǒng)的CAN信號(hào)讀取和賦值方法以報(bào)文字節(jié)數(shù)組為操作對象,讀取某個(gè)CAN信號(hào)或者對某個(gè)CAN信號(hào)賦值時(shí),確實(shí)需要對報(bào)文字節(jié)數(shù)組中的某個(gè)字節(jié)進(jìn)行位操作。
但是,根據(jù)具體應(yīng)用不同,一個(gè)CAN節(jié)點(diǎn)需要讀取和賦值的CAN信號(hào)可能多達(dá)數(shù)十個(gè)甚至上百個(gè),這種位操作方式使得解析和賦值CAN信號(hào)的工作非常繁重,而且容易出錯(cuò)。當(dāng)CAN節(jié)點(diǎn)功能升級(jí)造成網(wǎng)絡(luò)矩陣表發(fā)生改變時(shí),CAN信號(hào)解析和賦值操作也會(huì)隨CAN信號(hào)位置或長度的變化而變化,這時(shí)又會(huì)造成大量的修改操作。
也就是說,且不說小韓的半成品代碼里是不是埋了雷,就是以后要做一點(diǎn)修改時(shí),也會(huì)很麻煩。
這可咋整?
二
進(jìn)一步行文之前,還是有必要先給大家科普一下。
在CAN網(wǎng)路中,CAN報(bào)文是底層通信接收和發(fā)送的主體,每條CAN報(bào)文中的數(shù)據(jù)場為8個(gè)字節(jié)。在這八個(gè)字節(jié)里,有很多“信號(hào)”,這些信號(hào)一般是位形式,比如車窗命令可以用一個(gè)2位的信號(hào)表示-01上升10下降11停止。
這么說吧,從“通信”的角度,報(bào)文收發(fā)的操作對象是“字節(jié)形式”的報(bào)文數(shù)據(jù),但是從“邏輯”的角度,應(yīng)用的操作對象是“位形式”的CAN信號(hào)。
一邊是字節(jié)形式的報(bào)文數(shù)據(jù),一邊是位形式的CAN信號(hào),顯然需要通過一種手段把它們聯(lián)系起來。
位操作當(dāng)然是手段之一。
就像小韓在代碼里寫的那樣,先把CAN報(bào)文寄存器里的字節(jié)形式的數(shù)據(jù)賦值給具體報(bào)文中的某個(gè)字節(jié),比如:
WDW_CMD_REQ[0]=(unsigned char)(CANmsgReceiveNow[1]>>8);
再用位操作提取該報(bào)文該字節(jié)里面的信號(hào),比如:
Lf_cmd=WDW_CMD_REQ[0]&0x03;
在寫代碼和讀代碼時(shí),位操作畢竟比不得直接的加、減、邏輯、賦值操作那樣容易理解,但是,程序員的心一般都比較大,應(yīng)該不怕自己人讀時(shí)頭大。
而且,就算如前文所說,信號(hào)在字節(jié)中的位置發(fā)生了變化,無非是多費(fèi)點(diǎn)腦汁,重寫一下這個(gè)位操作語句就行了。
總之,位操作是麻煩了點(diǎn),但是好像也不是多大的硬傷。
好吧,如果你沒有覺得哪里有什么不對勁,請你思考一下這個(gè)問題:
每個(gè)報(bào)文對應(yīng)八個(gè)字節(jié),這八個(gè)字節(jié)里可能有二三十個(gè)信號(hào),如果每個(gè)信號(hào)都定義一個(gè)變量,一條報(bào)文就差不多消耗三十來個(gè)字節(jié)的RAM,如果報(bào)文多了,會(huì)消耗多少RAM資源呢?要知道,在MCU里面,RAM可算是寸土寸金吶!
三
信號(hào)對應(yīng)的RAM資源其實(shí)是可以省掉的!方法就是本文要說的聯(lián)合體和結(jié)構(gòu)體。
聯(lián)合體可以節(jié)省RAM空間,這是C語言里的常識(shí)??墒?,怎么把字節(jié)形式和位形式“聯(lián)合”為一體呢?
方法就是根據(jù)報(bào)文的信號(hào)矩陣設(shè)計(jì)信號(hào)組結(jié)構(gòu)體。即根據(jù)網(wǎng)絡(luò)矩陣表給出的每個(gè)報(bào)文的所有CAN信號(hào)的名稱、起始位和長度信息,每個(gè)報(bào)文都設(shè)計(jì)一個(gè)由一組位信號(hào)組成的結(jié)構(gòu)體。
這里首先要注意你所用處理器的大小端模式,看看是先定義第一個(gè)字節(jié)里的信號(hào),還是先定義最后一個(gè)字節(jié)里的信號(hào)。然后根據(jù)信號(hào)的占位(第幾個(gè)字節(jié)的哪幾位),把它定義在相應(yīng)的位置,如果在報(bào)文里有空位,比如說第二個(gè)字節(jié)的0-3位沒有定義,這時(shí)也要以占位符信號(hào)的形式把它定義出來。
然后為每個(gè)報(bào)文定義一個(gè)聯(lián)合體類型。聯(lián)合體有兩個(gè)成員變量,一個(gè)是數(shù)組變量,一個(gè)是上述信號(hào)組結(jié)構(gòu)體變量。根據(jù)聯(lián)合體的定義,數(shù)組變量和信號(hào)組結(jié)構(gòu)體變量的尺寸相同,存放于相同的地址空間(同一個(gè)RAM地址)里,無論誰發(fā)生了變化,另一方也會(huì)同步發(fā)生改變。
在這個(gè)聯(lián)合體中,數(shù)組變量是以單字節(jié)類型定義的數(shù)組,存儲(chǔ)字節(jié)形式的報(bào)文數(shù)據(jù),它用于報(bào)文的收發(fā),對應(yīng)于底層通信。
信號(hào)組結(jié)構(gòu)體變量是以上述信號(hào)組結(jié)構(gòu)體類型定義的結(jié)構(gòu)體,存儲(chǔ)信號(hào)組形式的報(bào)文數(shù)據(jù),它用于信號(hào)的解析和封裝,對應(yīng)于上層應(yīng)用。
報(bào)文聯(lián)合體和信號(hào)組結(jié)構(gòu)體內(nèi)存空間示意圖
CAN節(jié)點(diǎn)接收報(bào)文并放入報(bào)文緩沖區(qū)后,進(jìn)行報(bào)文解析時(shí),首先根據(jù)報(bào)文ID,找出對應(yīng)的報(bào)文,然后將報(bào)文緩沖區(qū)當(dāng)前報(bào)文的數(shù)據(jù)寫入對應(yīng)的報(bào)文聯(lián)合體變量的數(shù)組變量中,由于聯(lián)合體字節(jié)數(shù)組和聯(lián)合體信號(hào)組結(jié)構(gòu)體位于相同的地址空間上,數(shù)組變量的內(nèi)容更新直接更新了該聯(lián)合體變量中的信號(hào)組結(jié)構(gòu)體變量的內(nèi)容,當(dāng)提取和解析CAN信號(hào)時(shí),直接讀取該報(bào)文聯(lián)合體中的結(jié)構(gòu)體中定義的信號(hào)即可。
當(dāng)需要發(fā)送報(bào)文時(shí),如果需要賦值信號(hào),直接賦值該報(bào)文聯(lián)合體中的結(jié)構(gòu)體中定義的信號(hào),不需要進(jìn)行位操作對報(bào)文數(shù)據(jù)進(jìn)行封裝,然后將該報(bào)文聯(lián)合體中的字節(jié)數(shù)組填充到CAN控制器的發(fā)送寄存器中,啟動(dòng)CAN控制器的發(fā)送就可以完成報(bào)文的發(fā)送。
這種實(shí)現(xiàn)方案是不是很酷?Super Cool!
四
這種對結(jié)構(gòu)體和聯(lián)合體的妙用,只需要在定義結(jié)構(gòu)體的時(shí)候膽大心細(xì)一些,便可以將CAN信號(hào)的解析及封裝畢其功于一役,之后的工作就像那橋邊姑娘,風(fēng)華模樣,落落大方了。
如果是底層報(bào)文收發(fā),你就對聯(lián)合體中的數(shù)組形式的字節(jié)變量進(jìn)行操作,如果是應(yīng)用層CAN信號(hào)的讀取和賦值,你就對聯(lián)合體中信號(hào)組結(jié)構(gòu)體形式的位變量進(jìn)行操作。
有了聯(lián)合體,報(bào)文里字節(jié)形式的數(shù)據(jù)變化后,位形式的信號(hào)量自動(dòng)發(fā)生變化,直接拿來用即可。反之,在應(yīng)用中對某個(gè)位形式的信號(hào)更新賦值了以后,報(bào)文里的字節(jié)數(shù)據(jù)自動(dòng)發(fā)生變化,該發(fā)送時(shí)直接發(fā)送即可!
再也沒有惱人的位操作了,是不是非常地神清氣爽?
總之,假輿馬者,非利足也,而致千里;假舟楫者,非能水也,而絕江河。君子生非異也,善假于物也!
就在于這個(gè)善假于物也!
評論