STM32串口使用心得(一)——DMA+空閒中斷接收

想看正文的直接到 三,如何爲之

一,因何用之?

之前曾經寫過一篇《關於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接收中斷。。。

點擊下載DEMO

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