一、前言
最近在準備藍橋杯比賽(嵌入式賽道),研究了以下串口空閑中斷+DMA
接收不定長的數據,感覺這個方法的接收效率很高,十分好用。方法配置都成功了,但是有一個點需要進行考慮,就是一般我們需要對串口接收的數據進行處理,這個數據處理是在中斷的回調函數
里面處理還是在主函數
里面處理好呢?以下就這兩個方法進行分析:
二、方法分析
目前我想到的有兩種方法:
方法一
在回調函數里直接處理數據
優點:
- 實時性強:數據接收完成后立即處理,減少了數據處理的延遲。
- 代碼簡潔:數據接收和處理邏輯在同一個地方,代碼易于理解和維護。
缺點:
- 占用中斷處理時間:如果數據處理邏輯復雜或耗時,會影響中斷的響應速度,進而影響系統其他功能的實時性。
- 可維護性差:如果數據處理邏輯復雜,中斷處理函數會變得冗長,難以維護。
方法二
在回調函數中設置標志位,在主函數里讀取標志位再進行數據處理
優點:
- 保護中斷響應速度:中斷處理函數只負責設置標志位,數據處理在主循環中進行,保證了中斷的響應速度。
- 代碼結構清晰:中斷處理函數和數據處理邏輯分離,代碼結構更清晰,易于維護和擴展。
- 資源利用率高:可以在主循環中根據系統狀態靈活調度數據處理,避免在中斷中處理復雜邏輯造成的資源浪費。
缺點:
- 增加了一定的復雜性:需要額外管理標志位,以及同步數據接收和處理的邏輯。
- 可能引入延遲:數據處理被推遲到主循環中進行,可能會引入一定的處理延遲。
總結
- 數據處理的復雜度:如果數據處理邏輯復雜或耗時,建議采用方法二,以保護中斷響應速度。
- 系統的實時性要求:如果系統對實時性要求較高,且數據處理不是非常耗時,方法一可能更合適。但如果數據處理可能影響到系統的其他實時功能,方法二則更為穩妥。
- 代碼的可維護性和擴展性:如果希望代碼結構更清晰,易于維護和擴展,方法二通常是更好的選擇。
三、實際操作
配置的方法可以看之前寫的文章
鏈接: [STM32 HAL庫]串口空閑中斷+DMA接收不定長數據
實驗現象:將電腦發來的數據,原封不到的發送回去。特別注意
BUFF_SIZE
的大小,太小會造成接收數據的丟失。
方法一
在這個方法中,在中斷的回調函數
里直接發送回去數據,并手動開啟下一次的中斷。
#define BUFF_SIZE 128
uint8_t rx_buffer[BUFF_SIZE]; // 創建接收緩存,大小為BUF_SIZE
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUFF_SIZE);//手動開啟串口DMA模式接收數據__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); //手動關閉DMA_IT_HT中斷 while (1){}
}
void SystemClock_Config(void)
{//...
}
/* 串口接收完成回調函數 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{if (huart->Instance == USART1){HAL_UART_Transmit(&huart1, rx_buffer, Size, 0xffff);// 將接收到的數據再發出HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, BUFF_SIZE); // 接收完畢后重啟串口DMA模式接收數據__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);// 手動關閉DMA_IT_HT中斷memset(rx_buffer, 0, BUFF_SIZE);// 清除接收緩存 }
}
/* 串口錯誤回調函數 */
void HAL_UART_ErrorCallback(UART_HandleTypeDef * huart)
{if(huart->Instance == USART1){HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, BUFF_SIZE); // 接收完畢后重啟串口DMA模式接收數據__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);// 手動關閉DMA_IT_HT中斷memset(rx_buffer, 0, BUFF_SIZE);// 清除接收緩存}
}
方法二
在這個方法中,在串口接收完成的回調函數
置接收完成的標志位,然后在主函數
中進行判斷。判斷成立則進行數據的發送,并手動開啟下一次的中斷和清除標志位。
需要注意的是,不要在
回調函數
里面手動開啟下一次的中斷,因為有可能會出現主函數
數據還未處理完成,下一個串口數據就到來而覆蓋上一次的串口數據。
所以,這里程序的處理方法是:程序處理完本次數據,則開啟下一次中斷接收;程序未處理完本次數據,則不開啟下一次中斷接收。
#define BUFF_SIZE 128
uint8_t rx_buffer[BUFF_SIZE]; // 創建接收緩存,大小為BUF_SIZE
_Bool u1_rx_end_flag = 0; //USART1接收數據完成標志位 1:接收完成
uint16_t u1_rx_size; //USART1接收數據實際長度
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUFF_SIZE);//手動開啟串口DMA模式接收數據__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); //手動關閉DMA_IT_HT中斷 while (1){/* 判斷接收是否完成 */if(u1_rx_end_flag == 1){/* 對接收的數據進行處理 */HAL_UART_Transmit(&huart1, rx_buffer, u1_rx_size, 0xffff);// 將接收到的數據再發出memset(rx_buffer, 0, BUFF_SIZE); // 清除接收緩存/* 開啟下一次中斷 */HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUFF_SIZE);//手動開啟串口DMA模式接收數據__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);//手動關閉DMA_IT_HT中斷 /* 清除標志位 */u1_rx_end_flag = 0;}}
void SystemClock_Config(void)
{//...
}
/* 串口接收完成回調函數 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{if (huart->Instance == USART1){u1_rx_end_flag = 1; //置標志位u1_rx_size = Size; //獲取接收數據長度}
}
/* 串口錯誤回調函數 */
void HAL_UART_ErrorCallback(UART_HandleTypeDef * huart)
{if(huart->Instance == USART1){HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, BUFF_SIZE);//手動開啟串口DMA模式接收數據__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);// 手動關閉DMA_IT_HT中斷memset(rx_buffer, 0, BUFF_SIZE);// 清除接收緩存}
}
值得一提的是,若是沒有手動開啟
串口空閑中斷
,那么串口錯誤中斷
也不會被開啟,也就無法進入串口錯誤回調函數
。
四、實驗現象
兩個方法實現現象一致
應該還有更好的串口接收模式,現在來說,這個方法應該夠用了。