一、空閑中斷
STM32
的串口具有空閑中斷,什么叫做空閑呢?如何觸發空閑中斷呢?
- 空閑:串口發送的兩個字符之間間隔非常短,所以在兩個字符之間不叫空閑。空閑的定義是總線上在一個字節的時間內沒有再接收到數據。
- 觸發條件:空閑中斷是檢測到有數據被接收后,總線上在一個字節的時間內沒有再接收到數據的時候發生的。而總線在什么情況時,會有一個字節時間內沒有接收到數據呢?一般就只有一個數據幀發送完成的情況,所以串口的空閑中斷也叫幀中斷。
開啟空閑中斷后,要重寫對應的回調函數HAL_UARTEx_RxEventCallback
,在函數里做些處理。
二、實驗內容
使用USART1
外設,接收電腦發來的數據,然后將接收到的數據發送給電腦。
二、STM32CUBEMX配置
USART1
的模式為異步通信
、115200波特率
、數據長度8位
、無校驗位
、停止位1位
。
DMA Settings
的配置,開啟串口接收的DMA
。
NVIC Settings
的配置,開啟USART1
的全局中斷。
這里兩個中斷優先級我都給了1,根據不同情況修改中斷優先級
三、keil代碼
首先確保魔術棒中的Use MicroLIB
這個選項勾選上,不然串口發送數據會不正常
添加頭文件,因為要使用memset
函數
#include "string.h"
定義串口接收數據數組
#define BUFF_SIZE 128 //接收緩存大小
uint8_t rx_buffer[BUFF_SIZE]; // 創建接收緩存,大小為BUFF_SIZE
手動在usart.h
外部聲明hdma_usart1_rx
,在main
函數中要使用
extern UART_HandleTypeDef huart1;/* USER CODE BEGIN Private defines */
extern DMA_HandleTypeDef hdma_usart1_rx; //手動外部聲明
/* USER CODE END Private defines */void MX_USART1_UART_Init(void);
在main
函數初始化添加這兩個函數,不然串口首次無法進入中斷
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUFF_SIZE); //手動開啟串口DMA模式接收數據
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); //手動關閉DMA_IT_HT中斷
重定向HAL_UARTEx_RxEventCallback
串口接收完成回調函數
/* 串口接收完成回調函數 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{if (huart->Instance == USART1){HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, BUFF_SIZE); // 接收完畢后重啟串口DMA模式接收數據HAL_UART_Transmit(&huart1, rx_buffer, Size, 0xffff); // 將接收到的數據再發出__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); // 手動關閉DMA_IT_HT中斷memset(rx_buffer, 0, BUFF_SIZE); // 清除接收緩存}
}
重定向HAL_UART_ErrorCallback
串口錯誤回調函數
/* 串口錯誤回調函數 */
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); // 清除接收緩存}
}
以下是main
函數完整代碼
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"#include "stdio.h"
#include "string.h"void SystemClock_Config(void);#define BUFF_SIZE 128 //接收緩存大小
uint8_t rx_buffer[BUFF_SIZE]; // 創建接收緩存,大小為BUFF_SIZEint 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)
{//...
}
/* USER CODE BEGIN 4 */
/* 串口接收完成回調函數 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{if (huart->Instance == USART1){HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, BUFF_SIZE); /// 接收完畢后重啟串口DMA模式接收數據HAL_UART_Transmit(&huart1, rx_buffer, Size, 0xffff); // 將接收到的數據再發出__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); // 清除接收緩存}
}
/* USER CODE END 4 */
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}
四、原理講解
我們使用串口空閑中斷的目的是為了獲取完整的數據,并且是由軟件自動判斷是否接收完整。當軟件判斷接收到完整的數據時,就會產生中斷進入回調函數HAL_UARTEx_RxEventCallback
。
軟件判斷接收到完整數據有兩種情況:
- 1、數據接收后,一個字節未接收到數據;
- 2、當前的
數據接收長度
與預設接收長度
相等;預設接收長度
為HAL_UARTEx_ReceiveToIdle_DMA
的參數BUFF_SIZE
;
預設接收長度
要考慮好,若數據接收長度
>預設接受長度
,就會出現數據的丟失情況。因為數據接收長度
是隨著接收不斷累加的,其大小等于預設接收長度
時就會觸發中斷,程序就會判斷成接收到完整數據,就是第二情況。
1、HAL_UARTEx_ReceiveToIdle_DMA
在main
函數里我們調用了HAL_UARTEx_ReceiveToIdle_DMA
,該函數會開啟USART1
的空閑中斷,并啟用串口以DMA
方式接收數據,當數據接收完成之后進入HAL_UARTEx_RxEventCallback
回調函數。隨后,我們在回調函數里進行數據處理即可。
2、__HAL_DMA_DISABLE_IT
為什么要手動關閉這個中斷,如果不關閉這個中斷,程序在接收一串完整的數據時會進入執行兩次HAL_UARTEx_RxEventCallback
。一次是數據傳輸一半時,一次數數據傳輸完成時,這與我們預期的不符。我們只希望在數據傳輸完成時進入回調函數進行數據處理。
__HAL_DMA_DISABLE_IT
的回調函數是UART_DMARxHalfCplt
,該回調函數會執行一次HAL_UARTEx_RxEventCallback
3、HAL_UARTEx_RxEventCallback
在該回調函數里,需要手動開啟中斷,才能進入下一次的中斷。
4、HAL_UART_ErrorCallback
重寫這個回調函數主要是為了防止一些錯誤情況的發生。若產生錯誤中斷進入HAL_UART_ErrorCallback
,則無法進入HAL_UARTEx_RxEventCallback
回調函數,也就無法開啟下一次空閑中斷。所以,這里在HAL_UART_ErrorCallback
里手動開啟了空閑中斷,做了一些恢復處理。
錯誤示例:開發板設置115200波特率,電腦串口用9600波特率,電腦發送數據之后,程序會進入串口錯誤中斷,而進入不了空閑中斷。若程序當中未在錯誤中斷進行錯誤處理,即使電腦串口修改成115200波特率進行通信,程序也無法進入空閑中斷。
五、參考文章
配置和代碼鏈接,里面詳解了相關一些函數的源碼
鏈接: 【STM32 HAL庫實戰】串口DMA + 空閑中斷 實現不定長數據接收
空閑中斷講解
鏈接: STM32串口之空閑中斷