STM32系列的M0內(nèi)核剛出來(lái)的時(shí)候,為了降本,我有很多項(xiàng)目都需要移植到M0平臺(tái),本以為只是把底層驅(qū)動(dòng)看一下就可以,卻不想總是遇到莫名其妙的死機(jī),著實(shí)折騰過(guò)一段時(shí)間的。今天在DIY遙控器的時(shí)候,突然又發(fā)現(xiàn)了一個(gè)當(dāng)時(shí)遇到的問(wèn)題,記錄下來(lái)給大家提個(gè)醒,萬(wàn)一遇到的死機(jī)的情況,不妨思考一下這個(gè)問(wèn)題。
布置場(chǎng)景
因?yàn)槲业倪b控器是基于無(wú)線RF通信的,因此我需要定義了兩個(gè)幀結(jié)構(gòu),分別用數(shù)組來(lái)存放,然后再發(fā)送和接收函數(shù)中直接處理數(shù)組。幀結(jié)構(gòu)的前四個(gè)字節(jié)作為通信的地址,也就是一個(gè)過(guò)濾ID,不符合這個(gè)ID的幀直接放棄掉。為了讓系統(tǒng)中兼容更多的設(shè)備對(duì),我把這個(gè)通信地址定義為4個(gè)字節(jié),也就是一個(gè)uint32_t
類型。數(shù)組及其索引值定義如下:
// 接收緩沖區(qū)長(zhǎng)度定義
#define DW_RX_MSG_LEN 18
// 接收包中的地址索引
#define RX_ADDRESS_LL 0
#define RX_ADDRESS_LH 1
#define RX_ADDRESS_HL 2
#define RX_ADDRESS_HH 3
...
uint8_t g_dw_rx_msg[DW_RX_MSG_LEN] = {0}; //接收緩存定義
接收緩沖器如上定義完成后,我將在接收函數(shù)中處理這個(gè)接收到的幀,第一件事情肯定是比對(duì)地址是否匹配,假設(shè)我宏定義的地址為:
#define ADDRESS 0x12345678
最簡(jiǎn)單的寫法肯定是這樣判斷:
if((g_dw_rx_msg[RX_ADDRESS_LL] == (ADDRESS >> 0) & 0xFF) &&
(g_dw_rx_msg[RX_ADDRESS_LH] == (ADDRESS >> 8) & 0xFF) &&
(g_dw_rx_msg[RX_ADDRESS_HL] == (ADDRESS >> 16) & 0xFF) &&
(g_dw_rx_msg[RX_ADDRESS_HH] == (ADDRESS >> 24) & 0xFF)
)
{
}
上面這種寫法很明了,我們比對(duì)每一個(gè)字節(jié)是否相等,如果都相等,我們認(rèn)為這個(gè)幀是發(fā)送給我們的,但是這樣的寫法比較啰嗦,字?jǐn)?shù)比較多,并且并不直觀呀,好好的一個(gè)32bit的數(shù)據(jù),非要拆分成4次比較,干起來(lái)效率一點(diǎn)都不高。
問(wèn)題是怎么發(fā)生的?
C語(yǔ)言中一定要用指針才顯得高級(jí),這里很明顯可以使用指針來(lái)取出數(shù)組的前四個(gè)字節(jié),然后和我們的宏定義地址相比較,這樣是非常簡(jiǎn)單高效的,大牛們常常都是這么寫的。
if(*(uint32_t *)g_dw_rx_msg == ADDRESS)
{
}
你看多簡(jiǎn)單,數(shù)組的名稱就是數(shù)組的地址,也是第一個(gè)元素的地址,我們?nèi)〕鰜?lái)轉(zhuǎn)換成32bit整型,然后取出來(lái)直接和我們的地址比較。既能清楚的表達(dá)這是一個(gè)地址的比對(duì),又能省不少敲字符的力氣。這樣也許在CortexM3上面大概率不會(huì)出現(xiàn)問(wèn)題,但是在M0內(nèi)核上大概率會(huì)出問(wèn)題,會(huì)死機(jī)的。為什么總說(shuō)是概率呢?我先來(lái)描述一下你可能遇到的現(xiàn)象。我按照指針的寫法編譯通過(guò)了,運(yùn)行也沒(méi)有問(wèn)題,性能杠杠的,賊穩(wěn)定。后來(lái),突然增加了一個(gè)需求,于是我又定義了一些變量和數(shù)組,再編譯,運(yùn)行….
我擦,死機(jī)了!我就一直在新增的功能那里不斷地查找問(wèn)題,怎么也找不到那里寫的有問(wèn)題,只要我新定一個(gè)變量,并且在程序中使用它(防止編譯器優(yōu)化掉),就會(huì)死機(jī),去掉就沒(méi)事了。調(diào)試過(guò)程中,你一定遇到過(guò)這樣的場(chǎng)景。當(dāng)我們Debug一步一步的跟蹤時(shí),就會(huì)發(fā)現(xiàn),程序死機(jī)之前執(zhí)行了我們的判斷語(yǔ)句。
原因是什么呢?
我們都知道,CortexM0內(nèi)核是一個(gè)32位總線的,它的硬件無(wú)法處理32位未對(duì)齊的內(nèi)存訪問(wèn),那么他對(duì)于內(nèi)存的訪問(wèn)地址一定是4的整數(shù)倍,否則就會(huì)出現(xiàn)無(wú)法訪問(wèn)內(nèi)存的問(wèn)題。這里,我們的判斷語(yǔ)句中使用了指針的強(qiáng)制類型轉(zhuǎn)換(uint3_t*)
,那么如果我們數(shù)組中的元素地址恰好不是4的整數(shù)倍的時(shí)候,在轉(zhuǎn)化成uint3_t進(jìn)行訪問(wèn)時(shí),處理器就只能報(bào)錯(cuò)了。
為什么出錯(cuò)是概率性的呢?編譯器會(huì)根據(jù)我們定義的全局變量和靜態(tài)變量來(lái)安排變量的地址,并且數(shù)組這東西,如果定義成uint8_t類型的,他中間的某個(gè)元素地址對(duì)齊的可能性只有四分之一。也就是說(shuō),我們第一次編譯成功,運(yùn)行可靠的時(shí)候,恰好我們定義的全局變量合適,編譯器把我們的數(shù)組起始地址正好安排在32位對(duì)齊的地址上。當(dāng)我們?cè)俣x一個(gè)變量的時(shí)候,一定是定義了非32位對(duì)齊的,比如uint8_t,或者一個(gè)數(shù)組或者結(jié)構(gòu)體,總之,它不是4字節(jié)的整數(shù)倍。這樣,我們?cè)倬幾g,編譯器就會(huì)重新給我們安排所有變量的地址,我們的數(shù)組的地址就恰好給擠到了一個(gè)地址不對(duì)齊的地方。我們?cè)儆?2bit的指針類型去訪問(wèn)時(shí),就會(huì)死機(jī)了。
如何處理呢?
有三種方法處理。第一種,要承認(rèn)傻人有傻福,笨辦法有笨辦法的優(yōu)點(diǎn),有些事情投機(jī)取巧可能會(huì)賺,但大概率還是賠的。就好比我們的大A,你以為是投資,后來(lái)認(rèn)為是投機(jī),再后來(lái),你以為是詐騙,現(xiàn)在你可能認(rèn)為是搶劫了,其實(shí),我們的大A本質(zhì)上是捐款!跑題了,所以,我們老老實(shí)實(shí)按字節(jié)比較就可以了。
if((g_dw_rx_msg[RX_ADDRESS_LL] == (ADDRESS >> 0) & 0xFF) &&
(g_dw_rx_msg[RX_ADDRESS_LH] == (ADDRESS >> 8) & 0xFF) &&
(g_dw_rx_msg[RX_ADDRESS_HL] == (ADDRESS >> 16) & 0xFF) &&
(g_dw_rx_msg[RX_ADDRESS_HH] == (ADDRESS >> 24) & 0xFF)
)
{
}
第二種,如果你實(shí)在忍受不了這個(gè)樣式,想讓程序更直觀的體現(xiàn)出來(lái),它是一個(gè)地址的比對(duì),那么就自己寫一個(gè)函數(shù),將四個(gè)字節(jié)取出來(lái)組成一個(gè)32bit的整型數(shù)?;蛘?,你可以直接利用memcpy函數(shù)來(lái)實(shí)現(xiàn)。
uint32_t my_address = 0;
memcpy(&my_address, &((uint32_t*)g_dw_rx_msg), sizeof(uint32_t));
第三種,如果你還是糾結(jié)于優(yōu)雅和效率,那我們就不撞南墻不回頭,編譯器自身提供了一些指令,來(lái)強(qiáng)制我們定義的變量地址對(duì)齊。比如:__attribute__((aligned(4)))
?如下定義:
__attribute__((aligned(4))) uint8_t g_dw_rx_msg[DW_RX_MSG_LEN] = {0}; //接收緩存
按照上面的定義方法,我們的數(shù)組其實(shí)地址就會(huì)被安排在32位地址對(duì)齊的位置上了,指針就可以大顯身手了,不過(guò)要注意,如果我們的地址索引在數(shù)組內(nèi)部不對(duì)齊也是不行的。比如,我們的地址是從數(shù)組的第1個(gè)地址開始的。(前面有一個(gè)第0個(gè))
// 接收緩沖區(qū)長(zhǎng)度定義
#define DW_RX_MSG_LEN 18
// 接收包中的地址索引
#define RX_ADDRESS_LL 1 //這里地址從數(shù)組的第1個(gè)元素開始
#define RX_ADDRESS_LH 2
#define RX_ADDRESS_HL 3
#define RX_ADDRESS_HH 4
...
__attribute__((aligned(4))) uint8_t g_dw_rx_msg[DW_RX_MSG_LEN] = {0}; //接收緩存
if(*(uint32_t *)g_dw_rx_msg == ADDRESS)
{
// 我還得死?。。。。?/code>
}