STM32串口如何代碼實現更穩定的接收消息 原 薦

在 《STM32串口向世界問好》介紹過如何發送消息,那麼又如何接收消息呢?

也很簡單,只需要配置好串口接收,配置好中斷,並在串口中斷函數裏面進行數據接收就可以了。通用配置代碼如下:

/**
  * @brief  初始化IO 串口1
  * @param  bound:波特率
  * @retval None
  */
void USART1_Debug_Init(u32 bound)
{
    //GPIO端口設置
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    assert_param(bound >0 && bound <= 256000);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    USART_DeInit(USART1);  //復位串口1
    //USART1_TX   PA.9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//複用推輓輸出
    GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9
    //USART1_RX	  PA.10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入
    GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化PA10

    //USART 初始化設置
    USART_InitStructure.USART_BaudRate = bound;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長爲8位數據格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;//一個停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;//無奇偶校驗位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收發模式
    USART_Init(USART1, &USART_InitStructure); //初始化串口

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2 ; 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			
    NVIC_Init(&NVIC_InitStructure);	

    USART_ClearFlag(USART1, USART_FLAG_TC);//防止第一個數據被覆蓋
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//開啓中斷

    USART_Cmd(USART1, ENABLE);                    //使能串口
}

中斷處理接收函數爲:

 void USART1_IRQHandler(void)
{
    u8 res;
    
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷 有數據爲 1 SET
    {
        res = (u8)USART_ReceiveData(USART1);
        res = res ;
    }
}

如果此時需要判斷當接收的數據爲1時點亮LED1,當接收數據爲2時熄滅LED1則可在中斷裏作如下處理:

 void USART1_IRQHandler(void)
{
    u8 res;
    
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷 有數據爲 1 SET
    {
        res = (u8)USART_ReceiveData(USART1); 

        if(0x01 == res)
        {
            LED1 = ON ; 

        }
        if(0x02 == res)
        {
            LED1 = OFF ; 

        }

    }
}

但這種接收控制方法是不夠穩定與靈活的,比如在傳輸的過程中受到干擾,0x01 變成 0x02,則就會出現錯誤的控制。又比如我要接收一串數據並進行處理,這樣就不好控制了。

這時我們就要想着制定一套通信協議來方便通信。

在此介紹一種簡單通信協議,是我在設計一款無人機數據鏈通信時用到的一開源協議:MAVLink,另外加上CRC校驗,進一步保證接收數據的可靠性。

其通信數據格式如下:

紅色部分代表起始幀 STX 爲 0xFE ; LEN表示要發送的數據長度(PAYLOAD長度);SEQ表示數據的序列號,循環從0至255發送(可以檢測是否丟包,並可能過此來判斷信號強度);SYS是用來表示區分同一網絡中不同飛行器號的,即系統ID;COMP代表組件ID,表示飛行器上各個組成部分,如飛控單元,GPS等;MSG則代表消息ID,即要發送不同控制命令ID;PAYLOAD表示此命令的內容;最後兩字節是自動生的的CRC校驗碼 。

從上圖也可以看出PAYLOAD有效長度可爲0至255字節(因爲LEN來表示,它們都是無符號8位數據類型),所以一條消息長度最小爲8字節,最大爲263字節。

至此一簡單通信協議就介紹過了,說的有點多。下面就是如何對其解析,話不多說直接代碼說明:

#define MavlinkLenMin  8
#define MavlinkLenMax  263
#define STX      0xFE//MAVLINK HEAD
#define Add_STX  0x00
#define Add_LEN  0x01
#define Add_SEQ  0x02
#define Add_SYS  0x03
#define Add_COMP 0x04
#define Add_MSG  0x05
#define Add_PAYLOAD  0x06//PAYLOAD start from 0x06


typedef enum {BEEN_RECEIVED = 0, RECEIVING = !BEEN_RECEIVED} Receive_Status;

typedef struct
{
    boolean Get ; 
    u16 Len ; 
    u8 Cache[MavlinkLenMax];
}MAVLink_Data_Struct , * MAVLink_Data_Struct_p ; 

MAVLink_Data_Struct Msg_Rev ; 

void Msg_Recv_Data_Analyse_Irq(u8 data)
{
    if(RECEIVING == Msg_Rev.Get){
        Msg_Rev.Cache[Msg_Rev.Len++] = data; 
        if(STX != Msg_Rev.Cache[Add_STX]){
            Msg_Rev.Len = 0 ;
        }

        if(((u16)Msg_Rev.Cache[Add_LEN] + MavlinkLenMin)==Msg_Rev.Len){
            Msg_Rev.Get = BEEN_RECEIVED ;
        }

    }
}

可在串口中斷接收函數裏調用此函數用作協議數據接收解析

void USART1_IRQHandler(void)
{
    u8 res;

    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷 有數據爲 1 SET
    {
        res = (u8)USART_ReceiveData(USART1);
        Msg_Recv_Data_Analyse_Irq(res); 
    }
}

當一條消息接收完成後,Msg_Rev.Get的狀態就會被設置成BEEN_RECEIVED ,這時就可在相關函數中對此條消息進行處理。

另外爲了消息的更可靠,還可加入CRC校驗,如下函數就是一簡單通用的CRC16校驗碼生成函數:

u16 crc_chk_value(u8 *data_value)   
{ 
    u16 crc_value = 0xFFFF;     

    u16 length = (uint16_t)data_value[1] + 6;  
    u16  i;

    while(length--)     
    { 
        crc_value ^= *data_value++;     
        for(i=0;i<8;i++)     
        { 
            if(crc_value & 0x0001)     
                crc_value = (crc_value >>1) ^ 0xa001;     
            else     
                crc_value=crc_value >> 1;     
        }     
    }     
    return(crc_value);     
}

如上述的對LED燈控制,我們可以作如下簡單設計,設定發送操控數據的設備SYS ID爲1,假定組件串口1的ID爲1,消息ID也爲1,另外發送的數據長度也爲1,則解析控制函數如:

void Msg_Control_Process(void)
{
    u16 checksum;
    if(BEEN_RECEIVED == Msg_Rev.Get){
        checksum = crc_chk_value(Msg_Rev.Cache);
        if( (Msg_Rev.Cache[Msg_Rev.Cache[Add_LEN] + 6 + 1] == (checksum & 0xFF)) &&
                (Msg_Rev.Cache[Msg_Rev.Cache[Add_LEN] + 6 + 2] == ((checksum >> 8) & 0xFF)) ){

            if( (0x01 == Msg_Rev.Cache[Add_SYS]) && (0x01 == Msg_Rev.Cache[Add_COMP]) ){
                if(0x01 == Msg_Rev.Cache[Msg_Rev.Cache[Add_LEN] + 6]){
                    LED1 = ON ; 
                }
                if(0x02 ==  Msg_Rev.Cache[Msg_Rev.Cache[Add_LEN] + 6]){
                    LED1 = OFF ; 
                }
            }

        }
        Msg_Rev.Get = RECEIVING ;
        Msg_Rev.Len = 0;
    }
}

此函數可在主輪詢裏調用,當中斷里正常接收到一串消息後,就可以根據條件判斷及加處控制處理,處理完成後再繼續接收。因加入了CRC校驗及消息和組件ID檢測等,數據可靠性增加,當然軟件通信可靠性增強一般是通過增加冗餘來實現,此也不例外。

稍微複雜的控制用此比較好,上面的例程用此只是作簡單原理性說明,有點大材小用的感覺 。

另在此設計中,你會發現,當接收完一條消息,處理完成後才接收下一條。這樣,當處理過程較費時間,並且消息在不斷的快速發送時,就容易引起丟包現象 ,所以以上設計並不是很好的。那麼這個又如何解決呢?

待我研究下,下篇將會作詳細介紹 。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章