想看正文的直接到 三,如何爲之
一,因何用之?
之前曾經寫過一篇《關於CubeMX的串口全雙工接收發送鎖死的問題》的文章,討論了STM32的串口在全雙工模式下會出現鎖死問題的現象。當時的解決辦法是在串口接收中斷中加入解鎖機制,貌似臨時解決了這個問題。但這幾天程序不知道怎麼回事,又開始頻繁地出現死機現象,而且仿真的時候會進入HardFault()。
二,緣何致之?
最讓人頭疼的問題就是大部分時間裏沒有問題。
———— mickey35
死機的現象總是在意想不到的情形下發生,可能剛下載完程序就出現了;或者正常跑了好幾天纔出現。所以,爲了尋找死機的原因,採取了以下兩種方式:
1,縮短任務執行的延時,加重MCU負擔。此舉可使死機的概率大幅提高。(仔細想來,之前貌似解決問題的時候,MCU的負擔並不是很重,之後可能任務加多了,矛盾也就凸顯出來了)
2,在網上找到的調試HardFault()問題的辦法。除去檢查數組越界和堆棧溢出之外,最簡單的辦法就是在HardFault()處添加斷點,之後在監視窗查看調用代碼就可以找到調用HardFault()的代碼了。(經過博主實驗,這個方法並非百試百靈,大部分情況下都跳到“MOVS r0,r0”去了,多試幾次纔可能找到罪魁禍首)
總之,經過一番調試之後,發現最可能的調用函數是HAL_UART_Receive_IT
個人猜測,因爲串口數據來的非常快(115200),串口中斷調用頻繁,導致出現數據溢出,串口鎖死,串口狀態異常等等各種情況,最終進入硬件錯誤中斷,然後導致死機。順便一提,博主的板子上跑的是Freertos,其中一個任務負責LCD的顯示,LCD的默認刷新率是50Hz,但有時候會出現明顯的掉幀,甚至穩定只有十幾Hz,這從側面印證存在某個極其佔用資源的任務,MCU負擔過重。
三,如何爲之?
單字節接收,中斷保存數據,關鍵字節開始處理數據是從51時代就延續下來,專門處理串口接收數據的辦法,但似乎在STM32上已經不好適應了。對於STM32這種提供了豐富外設和功能的芯片,串口接收也可以嘗試一下一些新的方式,比如DMA+空閒中斷接收。
仍然採用STM32CubeMX來創建工程,請忽略掉我的板子上那幾個LED燈的配置。
在main.c裏新建全局變量
uint8_t usart1_rx_flag = 0;
uint8_t usart1_rx_buffer[128];
uint8_t usart1_tx_buffer[128];
uint16_t usart1_tx_len = 0;
main函數中開啓DMA接收和空閒中斷:
void main(void)
{
...
/* USART1接收 */
if(HAL_UART_Receive_DMA(&huart1,(uint8_t *)&usart1_rx_buffer,128) != HAL_OK) Error_Handler();
/* 開啓空閒接收中斷 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
...
}
在usart.c文件中添加處理空閒中斷的回掉函數:
/* 串口接收空閒中斷 */
void UsartReceive_IDLE(UART_HandleTypeDef *huart)
{
uint16_t i = 0;
if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
{
if(huart->Instance == USART1)
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
i = huart->Instance->SR;
i = huart->Instance->DR;
i = hdma_usart1_rx.Instance->CNDTR;
HAL_UART_DMAStop(huart);
/* 此處處理數據,主要是拷貝和置位標誌位 */
if(usart1_rx_flag == 0)
{
memcpy(usart1_tx_buffer,usart1_rx_buffer,(128 - i));
usart1_rx_flag = 1;
}
/* 清空緩存,重新接收 */
memset(usart1_rx_buffer,0x00,128);
HAL_UART_Receive_DMA(huart,(uint8_t *)&usart1_rx_buffer,128);
}
}
}
在stm32f1xx_it.c中的中斷入口點處添加空閒中斷檢查:
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* HAL庫好像沒有處理空閒中斷的代碼,需自己添加 */
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))
{
UsartReceive_IDLE(&huart1);
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
在主循環中加入檢查標誌位和發送數據的代碼:
/* USART_Task function */
void USART_Task(void const * argument)
{
/* USER CODE BEGIN USART_Task */
/* Infinite loop */
for(;;)
{
if(usart1_rx_flag == 1)
{
dma_send(usart1_tx_buffer,usart1_tx_len);
usart1_rx_flag = 0;
}
osDelay(1);
}
/* USER CODE END USART_Task */
}
最終測試結果如下:
此處只是一個DEMO,單純做了一個迴環測試,但在實際項目使用中確實極大減輕了MCU的負擔,目前暫時沒有因爲這種方式的串口接收導致的死機。但在接收一些超長的數據時,需要極大的緩存區。正在考慮是否可以同時開啓串口接收中斷和DMA接收中斷。。。