在 《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); //使能串口 }
中斷處理接收函數爲:ui
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則可在中斷裏做以下處理:spa
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,則就會出現錯誤的控制。又好比我要接收一串數據並進行處理,這樣就很差控制了。.net
這時咱們就要想着制定一套通訊協議來方便通訊。設計
在此介紹一種簡單通訊協議,是我在設計一款無人機數據鏈通訊時用到的一開源協議:MAVLink,另外加上CRC校驗,進一步保證接收數據的可靠性。code
其通訊數據格式以下:blog
紅色部分表明起始幀 STX 爲 0xFE ; LEN表示要發送的數據長度(PAYLOAD長度);SEQ表示數據的序列號,循環從0至255發送(能夠檢測是否丟包,並可能過此來判斷信號強度);SYS是用來表示區分同一網絡中不一樣飛行器號的,即系統ID;COMP表明組件ID,表示飛行器上各個組成部分,如飛控單元,GPS等;MSG則表明消息ID,即要發送不一樣控制命令ID;PAYLOAD表示此命令的內容;最後兩字節是自動生的的CRC校驗碼 。ip
從上圖也能夠看出PAYLOAD有效長度可爲0至255字節(由於LEN來表示,它們都是無符號8位數據類型),因此一條消息長度最小爲8字節,最大爲263字節。ci
至此一簡單通訊協議就介紹過了,說的有點多。下面就是如何對其解析,話很少說直接代碼說明:
#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檢測等,數據可靠性增長,固然軟件通訊可靠性加強通常是經過增長冗餘來實現,此也不例外。
稍微複雜的控制用此比較好,上面的例程用此只是做簡單原理性說明,有點大材小用的感受 。
另在此設計中,你會發現,當接收完一條消息,處理完成後才接收下一條。這樣,當處理過程較費時間,而且消息在不斷的快速發送時,就容易引發丟包現象 ,因此以上設計並非很好的。那麼這個又如何解決呢?
待我研究下,下篇將會做詳細介紹 。