導言
《STM32F103_LL庫+寄存器學習筆記12.2 - 串口DMA高效收發實戰2:進一步提高串口接收的效率》基于上一個版本,進一步提升代碼的模塊化水平,支持多實例化。
如上所示,收發大量的數據,沒有丟包。
項目地址:
github:
- LL庫: https://github.com/q164129345/MCU_Develop/tree/main/stm32f103_ll_library12_3_usart_multi-instantiation
gitee(國內):
- LL庫: https://gitee.com/wallace89/MCU_Develop/tree/main/stm32f103_ll_library12_3_usart_multi-instantiation
一、代碼
1.1、bsp_usart_drive.c
/*** @file bsp_usart_drive.c* @brief STM32F1系列 USART + DMA + RingBuffer LL底層驅動實現,支持多實例化* @author Wallace.zhang* @version 1.0.0* @date 2025-01-10*/#include "bsp_usart_drive/bsp_usart_drive.h"/* ============================= 私有函數聲明 ============================= *//*** @brief 配置DMA接收通道(循環模式)* @param usart 指向USART驅動結構體的指針* @note 就像設置一個"自動傳送帶",持續將串口數據搬運到內存* @retval 無*/
static void USART_LL_DMA_RX_Configure(USART_LL_Driver_t *usart);/*** @brief 使用DMA發送字符串(非阻塞方式)* @param usart 指向USART驅動結構體的指針* @param data 指向發送數據的緩沖區* @param len 發送數據長度* @note 就像啟動一個"快遞員",自動把數據送到目的地* @retval 無*/
static void USART_LL_SendString_DMA(USART_LL_Driver_t *usart, const uint8_t *data, uint16_t len);/*** @brief 將DMA接收到的數據復制到RingBuffer中(支持環形)* @param usart 指向USART驅動結構體的指針* @note 就像將貨物從"臨時倉庫"搬到"長期儲存倉庫"* @retval 無*/
static void USART_LL_DMA_RX_Copy(USART_LL_Driver_t *usart);/*** @brief 將數據寫入接收RingBuffer* @param usart 指向USART驅動結構體的指針* @param data 指向要寫入的數據緩沖區* @param len 要寫入的數據長度* @retval 返回寫入狀態(同USART_LL_Put_TxData_To_Ringbuffer)* @note 智能緩沖區管理:滿了就擠掉老數據,騰出空間給新數據*/
static uint8_t USART_LL_Put_RxData_Into_Ringbuffer(USART_LL_Driver_t *usart, const void* data, uint16_t len);/*** @brief 處理DMA發送通道錯誤* @param usart 指向USART驅動結構體的指針* @retval 1 檢測到并處理了錯誤,0 無錯誤* @note 就像"故障檢測器",發現問題立即修復*/
static uint8_t USART_LL_DMA_TX_Error_Handler(USART_LL_Driver_t *usart);/*** @brief 處理DMA接收通道錯誤* @param usart 指向USART驅動結構體的指針* @retval 1 檢測到并處理了錯誤,0 無錯誤* @note 就像"故障檢測器",發現問題立即修復*/
static uint8_t USART_LL_DMA_RX_Error_Handler(USART_LL_Driver_t *usart);/*** @brief 處理USART硬件錯誤(ORE、NE、FE、PE等)* @param usart 指向USART驅動結構體的指針* @retval 1 檢測到并處理了錯誤,0 無錯誤* @note 清除各種串口硬件錯誤標志*/
static uint8_t USART_LL_Hardware_Error_Handler(USART_LL_Driver_t *usart);/*** @brief 獲取TX DMA忙碌狀態* @param usart 指向USART驅動結構體的指針* @retval 0 空閑,1 忙碌* @note 檢查"快遞員"是否還在工作*/
static uint8_t USART_LL_Get_TX_DMA_Busy(USART_LL_Driver_t *usart);/* ============================= 公有函數實現 ============================= *//*** @brief 阻塞方式發送以 NUL 結尾的字符串* @param usart 指向USART驅動結構體的指針* @param str 指向以'\0'結尾的字符串* @note 逐字節輪詢發送,就像用"老式打字機"一個字一個字地敲*/
void USART_LL_SendString_Blocking(USART_LL_Driver_t* usart, const char* str)
{if (!usart || !str) return;while (*str) {while (!LL_USART_IsActiveFlag_TXE(usart->USARTx)); // 等待發送寄存器空LL_USART_TransmitData8(usart->USARTx, *str++); // 發送一個字節}
}/*** @brief 用戶數據寫入發送 RingBuffer* @param usart 指向USART驅動結構體的指針* @param data 指向要寫入的數據緩沖區* @param len 要寫入的數據長度(字節)* @retval 0-3 寫入狀態碼* @note 智能緩沖區:就像一個"智能郵箱",滿了會自動清理舊郵件*/
uint8_t USART_LL_Put_TxData_To_Ringbuffer(USART_LL_Driver_t *usart, const void* data, uint16_t len)
{if (!usart || !data) return 3; // 輸入參數無效lwrb_t *rb = &usart->txRB;uint16_t capacity = usart->txBufSize;lwrb_sz_t freeSpace = lwrb_get_free(rb);uint8_t ret = 0;// 情況1:數據長度小于ringbuffer容量if (len < capacity) {if (len <= freeSpace) {lwrb_write(rb, data, len); // 剩余空間夠,直接寫入} else {// 空間不足,擠掉一些舊數據lwrb_sz_t used = lwrb_get_full(rb);lwrb_sz_t skip_len = len - freeSpace;if (skip_len > used) skip_len = used;lwrb_skip(rb, skip_len);lwrb_write(rb, data, len);ret = 1;}} else if (len == capacity) { // 情況2:數據長度等于ringbuffer容量if (freeSpace < capacity) { // 清空ringbufferlwrb_reset(rb);ret = 1;}lwrb_write(rb, data, len);} else { // 情況3:數據長度大于ringbuffer容量,截斷保留 capacity 字節const uint8_t *ptr = (const uint8_t*)data + (len - capacity);lwrb_reset(rb);lwrb_write(rb, ptr, capacity);ret = 2;}return ret;
}/*** @brief USART模塊主運行函數* @param usart 指向USART驅動結構體的指針* @note 就像一個"郵局管理員",定期檢查是否有郵件要發送*/
void USART_LL_Module_Run(USART_LL_Driver_t *usart)
{if (!usart) return;/* 檢查發送隊列,如果有數據且DMA空閑,就啟動發送 */uint16_t available = lwrb_get_full(&usart->txRB);if (available && USART_LL_Get_TX_DMA_Busy(usart) == 0) {uint16_t len = (available > usart->txBufSize) ? usart->txBufSize : available;lwrb_read(&usart->txRB, usart->txDMABuffer, len); // 從RingBuffer讀取到DMA緩沖區usart->txMsgCount += len; // 統計發送數據USART_LL_SendString_DMA(usart, usart->txDMABuffer, len); // 啟動DMA發送}
}/*** @brief 獲取接收RingBuffer中的可讀字節數*/
uint32_t USART_LL_Get_Available_RxData_Length(USART_LL_Driver_t *usart)
{if (!usart) return 0;return lwrb_get_full(&usart->rxRB);
}/*** @brief 從接收RingBuffer中讀取一個字節*/
uint8_t USART_LL_Read_A_Byte_Data(USART_LL_Driver_t *usart, uint8_t* data)
{if (!usart || !data) return 0;return lwrb_read(&usart->rxRB, data, 1);
}/* ========================= 中斷處理函數 ========================= *//*** @brief USART發送DMA中斷處理函數* @param usart 指向USART驅動結構體的指針* @note DMA發送完成后調用,就像"快遞員完成投遞后報告"*/
void USART_LL_DMA_TX_Interrupt_Handler(USART_LL_Driver_t *usart)
{if (!usart) return;if (USART_LL_DMA_TX_Error_Handler(usart)) { // 處理錯誤// 錯誤已被處理} else if (LL_DMA_IsActiveFlag_TC(usart->DMAx, usart->dmaTxChannel)) { // 傳輸完成// 清除傳輸完成標志LL_DMA_ClearFlag_TC(usart->DMAx, usart->dmaTxChannel);// 關閉DMA通道,確保下次傳輸前已經完全停止LL_DMA_DisableChannel(usart->DMAx, usart->dmaTxChannel);// 關閉USART的DMAT位(關閉DMA發送請求)LL_USART_DisableDMAReq_TX(usart->USARTx);// 清除DMA忙碌狀態usart->txDMABusy = 0;}
}/*** @brief USART接收DMA中斷處理函數* @param usart 指向USART驅動結構體的指針* @note 處理DMA接收的半傳輸和全傳輸中斷*/
void USART_LL_DMA_RX_Interrupt_Handler(USART_LL_Driver_t *usart)
{if (!usart) return;if (USART_LL_DMA_RX_Error_Handler(usart)) { // 處理錯誤失敗// 錯誤已被處理} else if (LL_DMA_IsActiveFlag_HT(usart->DMAx, usart->dmaRxChannel)) { // 半傳輸中斷// 清除半傳輸標志LL_DMA_ClearFlag_HT(usart->DMAx, usart->dmaRxChannel);USART_LL_DMA_RX_Copy(usart); // 將DMA接收數據放到ringbuffer} else if (LL_DMA_IsActiveFlag_TC(usart->DMAx, usart->dmaRxChannel)) { // 全傳輸中斷// 清除傳輸完成標志LL_DMA_ClearFlag_TC(usart->DMAx, usart->dmaRxChannel);USART_LL_DMA_RX_Copy(usart); // 將DMA接收數據放到ringbuffer}
}/*** @brief USART全局中斷處理函數* @param usart 指向USART驅動結構體的指針* @note 處理IDLE中斷和各種錯誤中斷*/
void USART_LL_RX_Interrupt_Handler(USART_LL_Driver_t *usart)
{if (!usart) return;if (USART_LL_Hardware_Error_Handler(usart)) { // 處理硬件錯誤usart->errorRX++; // 有錯誤,記錄計數} else if (LL_USART_IsActiveFlag_IDLE(usart->USARTx)) { // 檢查 USART 是否空閑中斷/* 清除IDLE標志:必須先讀SR,再讀DR */volatile uint32_t tmp = usart->USARTx->SR;tmp = usart->USARTx->DR;(void)tmp;USART_LL_DMA_RX_Copy(usart); // 將接收數據放到ringbuffer}
}/* ========================== 初始化和配置 ========================== *//*** @brief 初始化USART驅動實例* @param usart 指向USART驅動結構體的指針* @param USARTx USART寄存器基地址* @param DMAx DMA控制器基地址* @param dmaTxChannel DMA發送通道* @param dmaRxChannel DMA接收通道* @param rxDMABuffer DMA接收緩沖區指針* @param rxRBBuffer 接收RingBuffer緩沖區指針* @param rxBufSize 接收緩沖區大小* @param txDMABuffer DMA發送緩沖區指針* @param txRBBuffer 發送RingBuffer緩沖區指針* @param txBufSize 發送緩沖區大小* @note 就像"搭建一個完整的郵政系統",配置所有必要組件*/
void USART_LL_Config(USART_LL_Driver_t *usart,USART_TypeDef *USARTx, DMA_TypeDef *DMAx,uint32_t dmaTxChannel, uint32_t dmaRxChannel,uint8_t *rxDMABuffer, uint8_t *rxRBBuffer, uint16_t rxBufSize,uint8_t *txDMABuffer, uint8_t *txRBBuffer, uint16_t txBufSize)
{if (!usart) return;/* 硬件實例配置 */usart->USARTx = USARTx;usart->DMAx = DMAx;usart->dmaTxChannel = dmaTxChannel;usart->dmaRxChannel = dmaRxChannel;/* RX緩沖區配置 */usart->rxDMABuffer = rxDMABuffer;usart->rxRBBuffer = rxRBBuffer;usart->rxBufSize = rxBufSize;lwrb_init(&usart->rxRB, usart->rxRBBuffer, usart->rxBufSize);/* TX緩沖區配置 */usart->txDMABuffer = txDMABuffer;usart->txRBBuffer = txRBBuffer;usart->txBufSize = txBufSize;lwrb_init(&usart->txRB, usart->txRBBuffer, usart->txBufSize);/* 狀態初始化 */usart->txDMABusy = 0;usart->dmaRxLastPos = 0;usart->rxMsgCount = 0;usart->txMsgCount = 0;usart->errorDMATX = 0;usart->errorDMARX = 0;usart->errorRX = 0;/* 配置USART和DMA */LL_USART_EnableDMAReq_RX(usart->USARTx); // 使能USART_RX的DMA請求LL_USART_EnableIT_IDLE(usart->USARTx); // 開啟USART空閑中斷LL_DMA_EnableIT_HT(usart->DMAx, usart->dmaRxChannel); // 使能DMA接收半傳輸中斷LL_DMA_EnableIT_TC(usart->DMAx, usart->dmaRxChannel); // 使能DMA接收傳輸完成中斷LL_DMA_EnableIT_TC(usart->DMAx, usart->dmaTxChannel); // 使能DMA發送傳輸完成中斷USART_LL_DMA_RX_Configure(usart); // 配置DMA接收
}/*** @brief DMA錯誤恢復處理*/
void USART_LL_DMA_Error_Recover(USART_LL_Driver_t *usart, uint8_t dir)
{if (!usart) return;if (dir == 0) { // RX錯誤usart->errorDMARX++; // DMA接收錯誤計數 */LL_DMA_DisableChannel(usart->DMAx, usart->dmaRxChannel);while(LL_DMA_IsEnabledChannel(usart->DMAx, usart->dmaRxChannel)); // 等待完全關閉// 重新配置并啟動DMA接收USART_LL_DMA_RX_Configure(usart);} else { // TX錯誤usart->errorDMATX++; // DMA發送錯誤計數 */LL_DMA_DisableChannel(usart->DMAx, usart->dmaTxChannel);LL_USART_DisableDMAReq_TX(usart->USARTx);usart->txDMABusy = 0; // 清除忙碌標志// 一般等待主循環重新發送}
}/* ============================= 私有函數實現 ============================= *//*** @brief 配置DMA接收通道* @param usart 指向USART驅動結構體的指針* @note 設置DMA循環模式,從串口持續接收數據到緩沖區*/
static void USART_LL_DMA_RX_Configure(USART_LL_Driver_t *usart)
{if (!usart) return;/* 配置DMA接收:從USART_DR到內存緩沖區 */LL_DMA_SetMemoryAddress(usart->DMAx, usart->dmaRxChannel, (uint32_t)usart->rxDMABuffer);LL_DMA_SetPeriphAddress(usart->DMAx, usart->dmaRxChannel, LL_USART_DMA_GetRegAddr(usart->USARTx));LL_DMA_SetDataLength(usart->DMAx, usart->dmaRxChannel, usart->rxBufSize);LL_DMA_EnableChannel(usart->DMAx, usart->dmaRxChannel);
}/*** @brief 使用DMA發送字符串* @param usart 指向USART驅動結構體的指針* @param data 指向發送數據的緩沖區* @param len 發送數據長度* @note 啟動DMA非阻塞發送*/
static void USART_LL_SendString_DMA(USART_LL_Driver_t *usart, const uint8_t *data, uint16_t len)
{if (!usart || !data || len == 0 || len > usart->txBufSize) return;// 等待上一個DMA傳輸完成while(usart->txDMABusy);usart->txDMABusy = 1; // 設置DMA正在發送// 如果DMA通道x正在使用,先關閉以便重新配置if (LL_DMA_IsEnabledChannel(usart->DMAx, usart->dmaTxChannel)) {LL_DMA_DisableChannel(usart->DMAx, usart->dmaTxChannel);while(LL_DMA_IsEnabledChannel(usart->DMAx, usart->dmaTxChannel));}// 配置DMA通道:從內存到USART_DRLL_DMA_SetMemoryAddress(usart->DMAx, usart->dmaTxChannel, (uint32_t)usart->txDMABuffer);LL_DMA_SetPeriphAddress(usart->DMAx, usart->dmaTxChannel, LL_USART_DMA_GetRegAddr(usart->USARTx));LL_DMA_SetDataLength(usart->DMAx, usart->dmaTxChannel, len);// 啟用USART的DMA發送請求LL_USART_EnableDMAReq_TX(usart->USARTx); // 啟用USART的DMA發送請求// 啟用DMA通道,開始DMA傳輸LL_DMA_EnableChannel(usart->DMAx, usart->dmaTxChannel);
}/*** @brief 將DMA接收位置數據復制到RingBuffer中* @param usart 指向USART驅動結構體的指針* @note 處理DMA環形接收緩沖區的數據搬移*/
static void USART_LL_DMA_RX_Copy(USART_LL_Driver_t *usart)
{if (!usart) return;uint16_t bufsize = usart->rxBufSize;uint16_t curr_pos = bufsize - LL_DMA_GetDataLength(usart->DMAx, usart->dmaRxChannel); // 計算當前寫指針位置uint16_t last_pos = usart->dmaRxLastPos; // 上一次讀指針位置if (curr_pos != last_pos) {if (curr_pos > last_pos) {// 正常情況,未環繞USART_LL_Put_RxData_Into_Ringbuffer(usart, usart->rxDMABuffer + last_pos, curr_pos - last_pos);usart->rxMsgCount += (curr_pos - last_pos);} else {// 環繞,分兩段處理USART_LL_Put_RxData_Into_Ringbuffer(usart, usart->rxDMABuffer + last_pos, bufsize - last_pos);USART_LL_Put_RxData_Into_Ringbuffer(usart, usart->rxDMABuffer, curr_pos);usart->rxMsgCount += (bufsize - last_pos) + curr_pos;}}usart->dmaRxLastPos = curr_pos; // 更新讀指針位置
}/*** @brief 將數據寫入接收RingBuffer* @param usart 指向USART驅動結構體的指針* @param data 指向要寫入的數據緩沖區* @param len 要寫入的數據長度* @retval 返回寫入狀態* @note 與發送RingBuffer的寫入邏輯相同*/
static uint8_t USART_LL_Put_RxData_Into_Ringbuffer(USART_LL_Driver_t *usart, const void* data, uint16_t len)
{uint8_t ret = 0;if (!usart || !data) return 3;lwrb_t *rb = &usart->rxRB;uint16_t rb_size = usart->rxBufSize;lwrb_sz_t freeSpace = lwrb_get_free(rb); // ringbuffer剩余空間if (len < rb_size) { // 數據長度小于ringbuffer容量if (len <= freeSpace) { // 足夠的剩余空間lwrb_write(rb, data, len); // 將數據放入ringbuffer} else { // 沒有足夠的空間,需要丟棄舊數據lwrb_sz_t used = lwrb_get_full(rb); // 使用了多少空間lwrb_sz_t skip_len = len - freeSpace;if (skip_len > used) { // 跳過的數據長度不能超過已使用的長度(例如,緩存58bytes,想接收59bytes,只能丟棄58bytes)skip_len = used;}lwrb_skip(rb, skip_len); // 為了接收新數據,丟棄舊數據lwrb_write(rb, data, len); // 將數據放入ringbufferret = 1;}} else if (len == rb_size) { // 數據長度等于ringbuffer容量if (freeSpace < rb_size) {lwrb_reset(rb); // 清空ringbufferret = 1;}lwrb_write(rb, data, len); // 將數據放入ringbuffer} else { // 數據長度大于ringbuffer容量,數據太大,僅保存最后RX_BUFFER_SIZE字節const uint8_t* byte_ptr = (const uint8_t*)data;data = (const void*)(byte_ptr + (len - rb_size)); // 指針偏移lwrb_reset(rb);lwrb_write(rb, data, rb_size);ret = 2;}return ret;
}/*** @brief 處理DMA發送通道錯誤* @param usart 指向USART驅動結構體的指針* @retval 1 檢測到并處理了錯誤,0 無錯誤*/
static uint8_t USART_LL_DMA_TX_Error_Handler(USART_LL_Driver_t *usart)
{if (!usart) return 0;// 檢查通道是否有傳輸錯誤(TE)if (LL_DMA_IsActiveFlag_TE(usart->DMAx, usart->dmaTxChannel)) {// 清除傳輸錯誤標志LL_DMA_ClearFlag_TE(usart->DMAx, usart->dmaTxChannel);// 關閉DMA通道,停止當前傳輸LL_DMA_DisableChannel(usart->DMAx, usart->dmaTxChannel);// 關閉USART的DMA發送請求(DMAT位)LL_USART_DisableDMAReq_TX(usart->USARTx);// 清除發送標志變量usart->txDMABusy = 0;usart->errorDMATX++;return 1;} else {return 0;}
}/*** @brief 處理DMA接收通道錯誤* @param usart 指向USART驅動結構體的指針* @retval 1 檢測到并處理了錯誤,0 無錯誤*/
static uint8_t USART_LL_DMA_RX_Error_Handler(USART_LL_Driver_t *usart)
{if (!usart) return 0;// 檢查通道是否有傳輸錯誤(TE)if (LL_DMA_IsActiveFlag_TE(usart->DMAx, usart->dmaRxChannel)) {// 清除傳輸錯誤標志LL_DMA_ClearFlag_TE(usart->DMAx, usart->dmaRxChannel);// 關閉DMA通道,停止當前傳輸LL_DMA_DisableChannel(usart->DMAx, usart->dmaRxChannel);// 重新配置傳輸長度,恢復到初始狀態LL_DMA_SetDataLength(usart->DMAx, usart->dmaRxChannel, usart->rxBufSize);// 重新使能DMA通道,恢復接收LL_DMA_EnableChannel(usart->DMAx, usart->dmaRxChannel);usart->errorDMARX++;return 1;} else {return 0;}
}/*** @brief 處理USART硬件錯誤標志* @param usart 指向USART驅動結構體的指針* @retval 1 檢測到并處理了錯誤,0 無錯誤*/
static uint8_t USART_LL_Hardware_Error_Handler(USART_LL_Driver_t *usart)
{if (!usart) return 0;// 檢查是否有USART錯誤標志(ORE、NE、FE、PE)if (LL_USART_IsActiveFlag_ORE(usart->USARTx) ||LL_USART_IsActiveFlag_NE(usart->USARTx) ||LL_USART_IsActiveFlag_FE(usart->USARTx) ||LL_USART_IsActiveFlag_PE(usart->USARTx)){// 通過讀SR、DR來清除錯誤標志volatile uint32_t tmp = usart->USARTx->SR;tmp = usart->USARTx->DR;(void)tmp;return 1;} else {return 0;}
}/*** @brief 獲取TX DMA忙碌狀態* @param usart 指向USART驅動結構體的指針* @retval 0 空閑,1 忙碌*/
static uint8_t USART_LL_Get_TX_DMA_Busy(USART_LL_Driver_t *usart)
{if (!usart) return 1; // 參數無效時認為忙碌return usart->txDMABusy;
}
1.2、bsp_usart_drive.h
/*** @file bsp_usart_drive.h* @brief STM32F1系列 USART + DMA + RingBuffer LL底層驅動接口,支持多實例化* @author Wallace.zhang* @version 1.0.0* @date 2025-01-10* * @note 基于LL庫開發,支持多個USART實例(USART1、USART2、USART3等)* 參考bsp_usart_hal的多實例化設計,但使用LL庫實現更高的性能*/
#ifndef __BSP_USART_DRIVE_H
#define __BSP_USART_DRIVE_H#ifdef __cplusplus
extern "C" {
#endif#include "main.h"
#include "lwrb/lwrb.h"/* ============================= DMA通道函數映射宏 ============================= */
/*** @brief STM32F1 LL庫DMA標志檢查和清除函數映射宏* @note STM32F1的LL庫沒有通用的LL_DMA_IsActiveFlag_TC()函數,* 而是按通道編號提供具體函數,如LL_DMA_IsActiveFlag_TC1()等*//* DMA傳輸完成標志檢查宏 */
#define LL_DMA_IsActiveFlag_TC(DMAx, Channel) \((Channel == LL_DMA_CHANNEL_1) ? LL_DMA_IsActiveFlag_TC1(DMAx) : \(Channel == LL_DMA_CHANNEL_2) ? LL_DMA_IsActiveFlag_TC2(DMAx) : \(Channel == LL_DMA_CHANNEL_3) ? LL_DMA_IsActiveFlag_TC3(DMAx) : \(Channel == LL_DMA_CHANNEL_4) ? LL_DMA_IsActiveFlag_TC4(DMAx) : \(Channel == LL_DMA_CHANNEL_5) ? LL_DMA_IsActiveFlag_TC5(DMAx) : \(Channel == LL_DMA_CHANNEL_6) ? LL_DMA_IsActiveFlag_TC6(DMAx) : \(Channel == LL_DMA_CHANNEL_7) ? LL_DMA_IsActiveFlag_TC7(DMAx) : 0)/* DMA半傳輸標志檢查宏 */
#define LL_DMA_IsActiveFlag_HT(DMAx, Channel) \((Channel == LL_DMA_CHANNEL_1) ? LL_DMA_IsActiveFlag_HT1(DMAx) : \(Channel == LL_DMA_CHANNEL_2) ? LL_DMA_IsActiveFlag_HT2(DMAx) : \(Channel == LL_DMA_CHANNEL_3) ? LL_DMA_IsActiveFlag_HT3(DMAx) : \(Channel == LL_DMA_CHANNEL_4) ? LL_DMA_IsActiveFlag_HT4(DMAx) : \(Channel == LL_DMA_CHANNEL_5) ? LL_DMA_IsActiveFlag_HT5(DMAx) : \(Channel == LL_DMA_CHANNEL_6) ? LL_DMA_IsActiveFlag_HT6(DMAx) : \(Channel == LL_DMA_CHANNEL_7) ? LL_DMA_IsActiveFlag_HT7(DMAx) : 0)/* DMA傳輸錯誤標志檢查宏 */
#define LL_DMA_IsActiveFlag_TE(DMAx, Channel) \((Channel == LL_DMA_CHANNEL_1) ? LL_DMA_IsActiveFlag_TE1(DMAx) : \(Channel == LL_DMA_CHANNEL_2) ? LL_DMA_IsActiveFlag_TE2(DMAx) : \(Channel == LL_DMA_CHANNEL_3) ? LL_DMA_IsActiveFlag_TE3(DMAx) : \(Channel == LL_DMA_CHANNEL_4) ? LL_DMA_IsActiveFlag_TE4(DMAx) : \(Channel == LL_DMA_CHANNEL_5) ? LL_DMA_IsActiveFlag_TE5(DMAx) : \(Channel == LL_DMA_CHANNEL_6) ? LL_DMA_IsActiveFlag_TE6(DMAx) : \(Channel == LL_DMA_CHANNEL_7) ? LL_DMA_IsActiveFlag_TE7(DMAx) : 0)/* DMA傳輸完成標志清除宏 */
#define LL_DMA_ClearFlag_TC(DMAx, Channel) \do { \if (Channel == LL_DMA_CHANNEL_1) LL_DMA_ClearFlag_TC1(DMAx); \else if (Channel == LL_DMA_CHANNEL_2) LL_DMA_ClearFlag_TC2(DMAx); \else if (Channel == LL_DMA_CHANNEL_3) LL_DMA_ClearFlag_TC3(DMAx); \else if (Channel == LL_DMA_CHANNEL_4) LL_DMA_ClearFlag_TC4(DMAx); \else if (Channel == LL_DMA_CHANNEL_5) LL_DMA_ClearFlag_TC5(DMAx); \else if (Channel == LL_DMA_CHANNEL_6) LL_DMA_ClearFlag_TC6(DMAx); \else if (Channel == LL_DMA_CHANNEL_7) LL_DMA_ClearFlag_TC7(DMAx); \} while(0)/* DMA半傳輸標志清除宏 */
#define LL_DMA_ClearFlag_HT(DMAx, Channel) \do { \if (Channel == LL_DMA_CHANNEL_1) LL_DMA_ClearFlag_HT1(DMAx); \else if (Channel == LL_DMA_CHANNEL_2) LL_DMA_ClearFlag_HT2(DMAx); \else if (Channel == LL_DMA_CHANNEL_3) LL_DMA_ClearFlag_HT3(DMAx); \else if (Channel == LL_DMA_CHANNEL_4) LL_DMA_ClearFlag_HT4(DMAx); \else if (Channel == LL_DMA_CHANNEL_5) LL_DMA_ClearFlag_HT5(DMAx); \else if (Channel == LL_DMA_CHANNEL_6) LL_DMA_ClearFlag_HT6(DMAx); \else if (Channel == LL_DMA_CHANNEL_7) LL_DMA_ClearFlag_HT7(DMAx); \} while(0)/* DMA傳輸錯誤標志清除宏 */
#define LL_DMA_ClearFlag_TE(DMAx, Channel) \do { \if (Channel == LL_DMA_CHANNEL_1) LL_DMA_ClearFlag_TE1(DMAx); \else if (Channel == LL_DMA_CHANNEL_2) LL_DMA_ClearFlag_TE2(DMAx); \else if (Channel == LL_DMA_CHANNEL_3) LL_DMA_ClearFlag_TE3(DMAx); \else if (Channel == LL_DMA_CHANNEL_4) LL_DMA_ClearFlag_TE4(DMAx); \else if (Channel == LL_DMA_CHANNEL_5) LL_DMA_ClearFlag_TE5(DMAx); \else if (Channel == LL_DMA_CHANNEL_6) LL_DMA_ClearFlag_TE6(DMAx); \else if (Channel == LL_DMA_CHANNEL_7) LL_DMA_ClearFlag_TE7(DMAx); \} while(0)/*** @brief USART驅動實例結構體(基于LL庫 + DMA + RingBuffer + 統計)* * 這個結構體就像一個"工具箱",每個USART都有自己的一套工具:* - 就像每個郵遞員都有自己的郵包(緩沖區)* - 自己的計數器(統計信息)* - 自己的工作狀態(忙碌標志)*/
typedef struct
{/* USART硬件實例 */USART_TypeDef *USARTx; /**< USART寄存器基地址(如USART1、USART2等) */DMA_TypeDef *DMAx; /**< DMA控制器基地址(如DMA1、DMA2等) */uint32_t dmaTxChannel; /**< DMA發送通道(如LL_DMA_CHANNEL_4) */uint32_t dmaRxChannel; /**< DMA接收通道(如LL_DMA_CHANNEL_5) *//* 狀態和統計信息 */volatile uint8_t txDMABusy; /**< DMA發送忙碌標志:1=正在發送,0=空閑 */volatile uint64_t rxMsgCount; /**< 統計接收字節總數 */volatile uint64_t txMsgCount; /**< 統計發送字節總數 */volatile uint16_t dmaRxLastPos; /**< DMA接收緩沖區上次讀取的位置 *//* 錯誤統計 */volatile uint32_t errorDMATX; /**< DMA發送錯誤統計 */volatile uint32_t errorDMARX; /**< DMA接收錯誤統計 */volatile uint32_t errorRX; /**< 串口接收錯誤統計 *//* RX相關緩沖區 */uint8_t *rxDMABuffer; /**< DMA接收緩沖區數組 */uint8_t *rxRBBuffer; /**< 接收RingBuffer緩沖區數組 */uint16_t rxBufSize; /**< DMA緩沖區/RX Ringbuffer緩沖區大小 */lwrb_t rxRB; /**< 接收RingBuffer句柄 *//* TX相關緩沖區 */uint8_t *txDMABuffer; /**< DMA發送緩沖區數組 */uint8_t *txRBBuffer; /**< 發送RingBuffer緩沖區數組 */uint16_t txBufSize; /**< DMA緩沖區/TX Ringbuffer緩沖區大小 */lwrb_t txRB; /**< 發送RingBuffer句柄 */} USART_LL_Driver_t;/* ================================ 核心API ================================ *//*** @brief 阻塞方式發送以 NUL 結尾的字符串(調試用,不走DMA)* @param usart 指向USART驅動結構體的指針* @param str 指向以'\0'結尾的字符串* @note 通過LL庫API逐字節發送,底層輪詢TXE標志位(USART_SR.TXE)。* @retval 無*/
void USART_LL_SendString_Blocking(USART_LL_Driver_t* usart, const char* str);/*** @brief 用戶數據寫入指定USART實例的發送 RingBuffer 中* @param usart 指向USART驅動結構體的指針* @param data 指向要寫入的數據緩沖區* @param len 要寫入的數據長度(字節)* @retval 0 數據成功寫入,不丟數據* @retval 1 ringbuffer 空間不足,丟棄了舊數據后成功寫入* @retval 2 數據長度超過 ringbuffer 容量,截斷后保留最新數據* @retval 3 輸入參數指針為空* @note* - 使用 lwrb 庫管理的 RingBuffer(usart->txRB)。* - 當 len > ringbuffer 容量時自動截斷,僅保存最新的數據。* - 當空間不足,會調用 lwrb_skip() 丟棄舊數據。*/
uint8_t USART_LL_Put_TxData_To_Ringbuffer(USART_LL_Driver_t *usart, const void* data, uint16_t len);/*** @brief USART模塊定時運行函數(數據處理主循環,1ms內調用)* @param usart 指向USART驅動結構體的指針* @note 在主循環中定時調用(1ms間隔推薦)* - 檢查發送RingBuffer是否有待發送數據,通過DMA異步發送* - 自動維護已發送數據統計* @retval 無*/
void USART_LL_Module_Run(USART_LL_Driver_t *usart);/*** @brief 獲取USART接收RingBuffer中的可讀字節數* @param usart 指向USART驅動結構體的指針* @retval uint32_t 可讀取的緩沖字節數* @note 通過主循環調用,在數據處理前先判斷是否需要讀取數據。*/
uint32_t USART_LL_Get_Available_RxData_Length(USART_LL_Driver_t *usart);/*** @brief 從USART接收RingBuffer中讀取一個字節數據* @param usart 指向USART驅動結構體的指針* @param data 指向存放讀取數據的緩沖區指針* @retval 1 讀取成功,數據存放在 *data* @retval 0 讀取失敗(無數據或data為NULL)* @note 如果緩沖區為空,則直接返回0。*/
uint8_t USART_LL_Read_A_Byte_Data(USART_LL_Driver_t *usart, uint8_t* data);/* ========================= 中斷處理函數 ========================= *//*** @brief USART發送DMA中斷處理函數* @param usart 指向USART驅動結構體的指針* @note 在對應的DMA中斷服務程序中調用*/
void USART_LL_DMA_TX_Interrupt_Handler(USART_LL_Driver_t *usart);/*** @brief USART接收DMA中斷處理函數* @param usart 指向USART驅動結構體的指針* @note 在對應的DMA中斷服務程序中調用,處理HT/TC中斷*/
void USART_LL_DMA_RX_Interrupt_Handler(USART_LL_Driver_t *usart);/*** @brief USART全局中斷處理函數(支持DMA+RingBuffer)* @param usart 指向USART驅動結構體的指針* @note 在USARTx_IRQHandler中調用,處理IDLE中斷和錯誤中斷*/
void USART_LL_RX_Interrupt_Handler(USART_LL_Driver_t *usart);/* ========================== 初始化和配置 ========================== *//*** @brief 初始化USART驅動實例(包括DMA、RingBuffer、中斷)* @param usart 指向USART驅動結構體的指針* @param USARTx USART寄存器基地址(如USART1、USART2等)* @param DMAx DMA控制器基地址(如DMA1、DMA2等)* @param dmaTxChannel DMA發送通道(如LL_DMA_CHANNEL_4)* @param dmaRxChannel DMA接收通道(如LL_DMA_CHANNEL_5)* @param rxDMABuffer DMA接收緩沖區指針* @param rxRBBuffer 接收RingBuffer緩沖區指針* @param rxBufSize 接收緩沖區大小* @param txDMABuffer DMA發送緩沖區指針* @param txRBBuffer 發送RingBuffer緩沖區指針* @param txBufSize 發送緩沖區大小* @retval 無* @note 必須通過CubeMX完成串口、DMA硬件配置后調用此函數*/
void USART_LL_Config(USART_LL_Driver_t *usart,USART_TypeDef *USARTx, DMA_TypeDef *DMAx,uint32_t dmaTxChannel, uint32_t dmaRxChannel,uint8_t *rxDMABuffer, uint8_t *rxRBBuffer, uint16_t rxBufSize,uint8_t *txDMABuffer, uint8_t *txRBBuffer, uint16_t txBufSize);/*** @brief DMA錯誤恢復處理(自動重新初始化并更新統計)* @param usart 指向USART驅動結構體* @param dir 方向:0=RX, 1=TX* @note 檢測到DMA傳輸錯誤(TE)時調用,自動清除統計并恢復* RX錯誤:自動重啟DMA;TX錯誤:等待主循環重新發送* @retval 無*/
void USART_LL_DMA_Error_Recover(USART_LL_Driver_t *usart, uint8_t dir);#ifdef __cplusplus
}
#endif#endif /* __BSP_USART_DRIVE_H */
1.3、main.c
1.4、stm32f1xx_it.c
二、bsp_usart_deive的使用文檔
要快速了解怎樣使用這個模塊,請認真閱讀使用文檔。
2.1、文檔位置
2.2、使用vscode打開文檔
要快速了解怎樣使用這個模塊,請認真閱讀使用文檔。