前言
大家好,這里是 Hello_Embed。上一篇我們用中斷方式實現了 UART 收發,但發現一個關鍵問題:若 CPU 在處理其他任務時未及時重新使能接收中斷,新數據會覆蓋舊數據,導致丟失。本篇的核心改進方案是 ——“中斷接收 + 環形緩沖區”:中斷中實時將接收的數據存入緩沖區,主程序從緩沖區按需讀取,徹底解決 “接收不及時” 的痛點。下一篇我們將進一步學習更高效的 DMA 方式,現在先聚焦這個經典的中斷優化方案。
本篇筆記所提及的環形緩沖區相關知識可在同系列筆記14-15中找到
一、改進核心思路
要解決數據丟失,關鍵是讓 “接收” 和 “處理” 解耦 —— 接收端用中斷快速存數據,處理端(主程序)慢慢讀數據,無需同步等待。具體思路有兩點:
- 提前使能接收中斷:程序啟動時就開啟 UART 接收中斷,確保任何時候有數據都能被捕獲;
- 中斷中存環形緩沖區:每次接收中斷觸發時,立即將數據存入環形緩沖區,避免數據在寄存器中被覆蓋,主程序從緩沖區讀取數據時不影響接收。
二、代碼實現:從緩沖區定義到中斷處理
我們基于上一篇的工程修改,核心是在usart.c
中集成環形緩沖區,實現 “中斷存數據、主程序讀數據” 的流程。
1. 準備工作:包含頭文件與定義核心變量
首先在usart.c
的開頭包含環形緩沖區頭文件(需確保路徑正確),并定義接收相關的變量:
#include <circle_buffer.h> // 包含環形緩沖區頭文件/* USER CODE BEGIN 1 */
// 1. 發送完成標志(沿用上篇)
static volatile int g_tx_cplt = 0;
// 2. 接收暫存變量:每次中斷接收1字節,先存在這里
static uint8_t g_RecvChar;
// 3. 環形緩沖區存儲數組:容量100字節,可存100個接收數據
static uint8_t g_RecvBuf[100];
// 4. 環形緩沖區結構體:管理讀寫指針和長度
static circle_buf g_uart1_rx_bufs;
/* USER CODE END 1 */
2. 步驟 1:初始化環形緩沖區 + 啟動接收中斷
定義StartUART1Recv
函數,作用是初始化環形緩沖區并提前使能接收中斷—— 程序啟動時調用一次,后續無需手動開啟中斷。
/* USER CODE BEGIN 1 */
// 啟動UART1接收:初始化緩沖區+使能接收中斷
void StartUART1Recv(void)
{// 初始化環形緩沖區:綁定結構體、容量100、存儲數組g_RecvBufcircle_buf_init(&g_uart1_rx_bufs, 100, g_RecvBuf);// 使能接收中斷:接收1字節到g_RecvChar,觸發中斷后進入回調函數HAL_UART_Receive_IT(&huart1, &g_RecvChar, 1);
}
/* USER CODE END 1 */
circle_buf_init
參數說明:&g_uart1_rx_bufs
(緩沖區結構體)、100
(容量)、g_RecvBuf
(存儲數組);HAL_UART_Receive_IT
:使能 RXNE 中斷(RDR 寄存器非空時觸發),接收的 1 字節暫存到g_RecvChar
。
3. 步驟 2:接收中斷回調 —— 數據存入緩沖區
重寫HAL_UART_RxCpltCallback
(接收完成回調函數),核心邏輯是:將暫存的字節存入環形緩沖區,并重新使能接收中斷(確保下一個數據能被捕獲)。
/* USER CODE BEGIN 1 */
// 接收完成回調函數:中斷觸發后執行
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{if (huart == &huart1) // 確認是USART1的中斷{// 1. 將接收的字節(g_RecvChar)寫入環形緩沖區circle_buf_write(&g_uart1_rx_bufs, g_RecvChar);// 2. 重新使能接收中斷:準備接收下一個字節(關鍵!避免中斷斷連)HAL_UART_Receive_IT(&huart1, &g_RecvChar, 1);}
}
/* USER CODE END 1 */
- 為什么要 “重新使能中斷”?
HAL_UART_Receive_IT
是 “一次性” 的 —— 接收 1 字節后會自動關閉中斷,必須重新調用才能繼續接收下一字節。
4. 步驟 3:封裝緩沖區讀取函數
定義UART1GetChar
函數,供主程序調用,從環形緩沖區讀取 1 字節數據(成功返回 0,失敗返回 - 1,對應緩沖區空)。
/* USER CODE BEGIN 1 */
// 從環形緩沖區讀取1字節數據
int UART1GetChar(uint8_t *pVal)
{// 調用環形緩沖區讀函數,將數據存入pVal指向的地址return circle_buf_read(&g_uart1_rx_bufs, pVal);
}
/* USER CODE END 1 */
pVal
:主程序傳入的 “數據存儲地址”,讀取成功后,緩沖區的數據會存在這里;- 返回值:0 表示讀取成功(有數據),-1 表示緩沖區空(無數據)。
5. 沿用發送相關函數
若需要保留中斷發送功能,可沿用上篇的發送完成回調和等待函數(確保發送流程正常):
/* USER CODE BEGIN 1 */
// 發送完成回調函數(沿用)
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{if (huart == &huart1)g_tx_cplt = 1;
}// 等待發送完成(沿用,主程序調用)
void Wait_Tx_Complete(void)
{while (g_tx_cplt == 0); // 等待發送完成標志置位g_tx_cplt = 0; // 復位標志
}
/* USER CODE END 1 */
三、主程序調用:實現 “接收 - 處理 - 返回” 完整流程
在main.c
中,先啟動接收中斷,再通過 “發送提示→讀取緩沖區→數據加 1 返回” 的邏輯,驗證改進方案是否有效。
1. 聲明外部函數
在main.c
的/* USER CODE BEGIN PV */
區域,聲明usart.c
中定義的函數:
/* USER CODE BEGIN PV */
// 聲明外部函數:啟動接收中斷、等待發送完成、讀取緩沖區數據
extern void StartUART1Recv(void);
extern void Wait_Tx_Complete(void);
extern int UART1GetChar(uint8_t *pVal);
/* USER CODE END PV */
2. 主程序核心邏輯
/* USER CODE BEGIN 2 */
// 1. 啟動UART1接收:初始化緩沖區+使能中斷(程序啟動時執行一次)
StartUART1Recv();// 2. 定義發送的提示信息和接收變量
char *str1 = "Please enter a char : \r\n";
uint8_t c; // 存儲從緩沖區讀取的字節
/* USER CODE END 2 *//* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{/* USER CODE BEGIN 3 */// 步驟1:發送提示信息(中斷方式)HAL_UART_Transmit_IT(&huart1, (uint8_t *)str1, strlen(str1));Wait_Tx_Complete(); // 等待發送完成// 步驟2:從環形緩沖區讀取1字節(循環等待,直到有數據)while (0 != UART1GetChar(&c)); // 返回0表示讀取成功,退出循環// 步驟3:數據加1后返回(查詢方式,簡單場景可用)c += 1;HAL_UART_Transmit(&huart1, &c, 1, 1000); // 發送加1后的字符HAL_UART_Transmit(&huart1, (uint8_t *)"\r\n", 2, 1000); // 換行
}
/* USER CODE END 3 */
四、實驗驗證:數據丟失問題解決
燒錄程序后,用串口工具一次性發送 “12345”(模擬快速連續發送),結果如下:
可以看到,單片機正確接收了所有字符,并返回 “23456”—— 證明環形緩沖區成功暫存了所有數據,即使主程序在處理發送,也不會丟失接收的數據。
五、核心流程梳理(為什么能解決丟失?)
整個改進方案的閉環流程如下,關鍵是 “接收” 和 “處理” 的解耦:
- 啟動階段:
StartUART1Recv
初始化緩沖區→使能接收中斷; - 接收階段:電腦發數據→RXNE 中斷觸發→
HAL_UART_RxCpltCallback
將數據存入緩沖區→重新使能中斷(準備下一次接收); - 處理階段:主程序通過
UART1GetChar
從緩沖區讀數據→加 1 后返回→即使主程序耗時,緩沖區也會暫存新數據,不會被覆蓋。
結尾
“中斷 + 環形緩沖區” 是 UART 通信中解決數據丟失的經典方案,它兼顧了中斷的高效性和緩沖區的可靠性,適合中低速、數據量不大的場景。下一篇筆記,我們將學習更高級的 “DMA 方式”—— 讓 DMA 硬件替 CPU 完成 “數據搬運”,徹底解放 CPU,適合高速、大數據量的通信場景。
Hello_Embed 繼續帶你探索 UART 通信的高效實現方式,敬請期待~