微信硬件平台提供的demo中傳輸數據格式如下。每次數據傳輸時,都將有效數據打包,然後再添加上固定包頭包尾後發送。
官網提供的demo是實現點燈發送消息什麼的,所以為了區分這些消息以及一些其他附加功能又在有效數據(也就是上面的protoalbuf打包的變長包體) 裡面定義了一個包頭
typedef struct
{
uint8_tm_magicCode[2];
uint16_tm_version;
uint16_tm_totalLength;
uint16_tm_cmdid;
uint16_tm_seq;
uint16_tm_errorCode;
}BlueDemoHead;
比如通過 m_cmdid來區分點燈還是亮燈什麼的。這個頭只是為了點燈這個demo做的而已,我們使用的時候完全可以不用這個頭,只有固定包頭才是必須的
PS:blueDemoHead 剛連接時的Auth,和init過程是沒有這個包頭的。
在使用微信demo二次開發時,為了可擴展我們保留了這個 為了點燈demo而做的內部包頭。於是有效數據為 blueDemoHead+跟隨數據 這部分打包然後再加上固定包頭。最終發給微信。同樣從微信接收到的數據也是這樣的類型。
問題也就是出現在這裡,微信demo內部的對微信push過來的數據的解包代碼是直接將 uint8_t類型指針強轉成BlueDemoHead 類型指針,這樣就可以通過結構體的形式來方便的訪問數據。
mpbledemo2.c文件中的解包代碼:只看解push包的解包部分
//data為收到的微信發過來的所有數據
int mpbledemo2_data_consume_func(uint8_t*data,uint32_t len){
…………….
switch(ntohs(fix_head->nCmdId))
{
caseECI_resp_auth:
……….……….
Break;
caseECI_resp_init:
……………………
break;
caseECI_push_recvData:
RecvDataPush *recvDatPush;
//這裡從固定頭後面開始解包。
recvDatPush= epb_unpack_recv_data_push(data+fix_head_len, len-fix_head_len);
…………………….
//解包後recvDatPush->data.data就是指向有效數據了即(blueDemoHead+負載 //數據)
//就是這裡的強制轉換存在問題!!!!
BlueDemoHead *bledemohead =(BlueDemoHead*)recvDatPush->data.data;
……………………
//原因在於解包函數實際上是將recvDatPush->data.data指向有效數據部分
// 也就是指向了mpbledemo2_data_consume_func的參數data 後的某個偏移
//從這個偏移開始到recvDatPush->data.len就是有效數據(blueDemoHead+負載 //數據)
//與是下面的代碼就可能存在問題了,因為recvDatPush->data.data指向的偏
//移的物理地址可能是非 半字對齊,於是bledemohead也是非半字對齊而
//m_cmdid成員在結構體中的偏移為6,所以該成員的地址也可能是非半字對
//齊,於是如果你使用的BLE芯片MCU數據訪問需要地址對齊,那麼下面的
//訪問就出問題,而51822 ble芯片是M0的處理器要求對 半字的訪問需地址
//半字對齊,於是執行下面這句代碼就可能出現硬件錯誤異常。
if(ntohs(bledemohead->m_cmdid ) == openLightPush){
綜上: 可能發生錯誤的根本原因在於
該demo跑在需要數據訪問為地址對齊的MCU上時,這時候如果bledemohead被賦值後如果不是半字對齊,那麼導致通過指針訪問其偏移為6字節的m_cmdid成員時變出現 硬件錯誤異常。
之所以說可能發生錯誤是因為recvDatPush->data.data 最終指向的地址可能是半字對齊可能不是,如果是就不會發生錯誤,如果不是那麼執行那一句指針訪問成員就會發生錯誤了。
我們在使用過程中,之前都是一直正常運行,後來傳輸一次較多數據時 150多字節,發生了死機現象,最終才定位到這個非對齊訪問的問題。
上面的解釋有點抽象:我抓了一下數據處理過程方便大家理解:
下面是正常時的情況, data存放了微信發過來的所有數據,存放地址有下圖所示為 0x20004388, 前面說過解包函數就是將recvDatPush->data.data 指向有效數據的起始位置,有下圖所示,除去固定包頭fe 0100 53 7531 0 00 以及protoalbuf打包函數打包時加在前面的0a 0012 44 ,則真正有效的數據從偏移12開始 就是從地址0x20004388+12 = 0x20004394開始,也就是下圖打印的。改地址是半字對齊的,所以將其轉換成 結構體指針 bledemohead是也是半字對齊,有因為bledemohead 中成員m_cmdid偏移為6所以其地址也是半字對齊,於是bledemohead->m_cmdid 訪問就不會出現問題
下面是一個異常情況:
數據的存放地址還是0x20004388,但是這次有效數據的偏移是8字節固定包頭+5字節protoalbuf打包時額外添加的,於是偏移為13字節
0x20004388+13 = 0x20004395,該地址非半字對齊,於是在訪問bledemohead的成員m_cmdid時就進行了一次非對齊訪問,於是出現硬件錯誤異常。
有上面分析可知,問題的出現是因為偏移的問題,測試發現數據量比較小時 打包時在有效數據前面額外添加的數據只有4字節,而固定包頭有8字節,於是有效數據的偏移就是12字節。
Data的地址滿足半字對齊,於是偏移12字節的位置的地址也滿足半字對齊,所以就不會出現問題。
而數據量較多時從上圖可以發現除了 8字節固定包頭 protoalbuf打包函數額外添加的數據有5字節,這就最終導致有效數據的偏移為13 也就導致了地址非半字對齊。
目前並未測試當數據量更多時,protoalbuf打包時添加的數據是否又會變多比如說為6個,那麼這時候又可以”正常”運行了。不過這樣終究是存在bug隱患。
解決的辦法有很多,比如不使用demo中的這個bledemohead,自己定義一個更加單簡單的格式,比如在數據前面加2個字節來區分每次數據表示的不同意思,並且解析的時候通過數據一字節一字節訪問。 recvDatPush->data.data[0] == aa&&recvDatPush->data.data[1]==0xBB表示一種數據,而recvDatPush->data.data[0]==0xAA&&recvDatPush->data.data[1]==0xCC時表示另一種數據。。 或者依舊使用這個bledemohead頭,但是解包後的數據訪問不要通過轉換成結構體指針然後訪問成員的方式去訪問,而是直接通過解包後的uint8_t型指針一個一個字節去訪問,比如recvDatPush->data.data[0]是否等於什麼,recvDatPush->data.data[1]是否等於什麼,recvDatPush->data.data[2]是否等於什麼 …….. 這種方式來區分各種不同的數據