基於STM32的串口與DMA的完美組合(下)

上次聊過STM32串口及DMA在實際開發中的應用,今天將主要從C開發的角度來談談本人實際開發中的一些理解!如有不合理的地方還希望讀者可以指出和賜教。

關於串口及DMA底層的驅動將參照上一篇的文章不做任何改動,然後我們來添加具體協議。例如定義協議格式如下(這裡只是為了講解下述操作方法,所以對內容長度做了定長設定),並規定數據傳輸按小端模式:

幀頭 (2字節)

ID (1字節)

命令(1字節)

內容(10字節)

校驗(1字節)

幀尾(2字節)

0xFF 0xAA

ID號

命令碼(所有應答命令碼都是由其對應的詢問命令碼最高位置位)

數據內容

加和校驗(ID、命令、內容)

0xFF 0x55

開發中可能用到的串口設備有很多,如串口液晶屏、串口藍牙模塊、串口WIFI模塊、串口無線模塊。本人最近剛用過一款型號為SV611的串口無線數傳模塊,假如我們與外部通信使用的是串口3,現在就以此為例。

首先:定義一個結構體指針 USART_DATA_STRUCT * sv611DataHandler;

然後在初始化的時候將sv611DataHandler指向串口3的數據句柄結構體,即

sv611DataHandler=&UsartDataHandle3; 這樣後續我們操作無線模塊的數據直接對sv611DataHandler操作就好了,增強代碼的可讀性。同樣的,我們重新封裝一個函數實現無線發送數據功能,如下:

void SV611_Send(uint8_t *data,uint16_t len)

{

USART3_Send(data,len);

}

這樣準備工作基本上就差不多了。然後,需要說明很重要的一點,如何按照我們定義的協議格式去解析收到的數據內容,這是開發中的關鍵。我們應當知道:當設備收到一幀數據,會進入空閒中斷,然後 sv611DataHandler->RxStatus 標誌字節會由0變成1,而數據是存放在sv611DataHandler->RxBuf緩衝區裡面的。我們可以在主任務中輪詢 sv611DataHandler->RxStatus狀態,若為1則解析數據包,完成後再將sv611DataHandler->RxStatus 清為0。

那麼按照上述協議,有沒有什麼好的解析方法呢?當然有。需先按照協議格式定義一個結構體及結構體變量,如下:

typedef struct

{

uint16_t dataHead;

uint8_t devId;

uint8_t msgCmd;

uint8_t data[10];

uint8_t checkSum;

uint16_t dataEnd;

}MSG_DATA_STRUCT;

MSG_DATA_STRUCT MsgDataHandler;

接下來我們就可以把我們接收到的數據包強制轉換成MSG_DATA_STRUCT類型,我們想要的結果是直接得到MsgDataHandler正確的成員變量,讓sv611DataHandler->RxBuf中的數據按照我們結構體中成員的順序自動組合,如將sv611DataHandler->RxBuf[0]和sv611DataHandler->RxBuf[1]自動按小端模式組合後變成MsgDataHandler.dataHead。

如果你用編譯器是IAR或者Keil,那麼結果肯定會讓你失望!!!因為這兩種編譯器中數據默認的對齊方式是int類型,在這裡是4字節對齊,即MSG_DATA_STRUCT 中成員數據是4字節對齊的。當我們使用強制轉換功能:

memcpy(&MsgDataHandler , (MSG_DATA_STRUCT *)sv611DataHandler->RxBuf,siezof(MSG_DATA_STRUCT );

這樣它會依次從sv611DataHandler->RxBuf中取出4個字節按小端方式匹配到我們的MSG_DATA_STRUCT 結構體中,最後不足4個字節的部分自動補0,這樣我們的數據就錯亂了,除非我們結構體中的成員變量數據類型全是單字節類型。

那麼該怎麼解決呢?百度一下,發現有方法可以讓我們在定義結構體的時候改變其對齊方式為單字節。在keil中,我們可以這樣定義結構體:

#pragma pack(1)

typedef struct

{

uint16_t dataHead;

uint8_t devId;

uint8_t msgCmd;

uint8_t data[10];

uint8_t checkSum;

uint16_t dataEnd;

}MSG_DATA_STRUCT;

#pragma pack()

即在首尾加上#pragma pack(1) , #pragma pack() 。這樣,我們將sv611DataHandler->RxBuf強轉為MSG_DATA_STRUCT類型後,MsgDataHandler 中的成員就變成了我們想要的數據。我們在解析過程中就可以直接做數據解析。

最後,參考實際代碼大致如下:

uint8_t MsgDecode(void);

void ()

void main(void)

{

sv611Init();

systemInit();

uint8_t sta= DECODE_IRRELEVANT;

while(1)

{

if(sv611DataHandler->RxStatus == 1)

{

sta=MsgDecode();

sv611DataHandler->RxStatus = 0;

}

/*

MsgHandleTask(sta,MsgDataHandler.data);

//根據MsgDecode()的返回值及MsgDataHandler結構體中的數據內容做相應的處理

*/

}

}

uint8_t MsgDecode(void)

{

memcpy(&MsgDataHandler , (MSG_DATA_STRUCT *)sv611DataHandler- >RxBuf,siezof(MSG_DATA_STRUCT );

uint8_t checkVal=MsgDataHandler.devId + msgCmd ;

for(uint8_t i=0;i<10;i++)

checkVal += MsgDataHandler.data[i];

if( (MsgDataHandler.dataHead!=0xAAFF)&&

(MsgDataHandler.dataEnd!=0x55FF) &&

(MsgDataHandler.checkSum!=checkVal) )

{

return DECODE_DATA_ERROR; //數據解析錯誤

}

if( MsgDataHandler.devId != MyId )

{

return DECODE_IRRELEVANT; //與本機無關的通信

}

return MsgDataHandler.msgCmd; //相關的數據包,直接返回指令類型,再在main()函數後面根據類型MsgDataHandler.msgCmd和數據內容MsgDataHandler.data做處理任務。

}

到此,串口數據處理流程就基本結束了。本文只介紹了處理過程及思想,實際項目中可能會有所不同,還需要大家靈活應用!


分享到:


相關文章: