STM32F103_LL庫+寄存器學習筆記12.3 - 串口DMA高效收發實戰3:支持多實例化的版本

導言


《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打開文檔

在這里插入圖片描述
在這里插入圖片描述
要快速了解怎樣使用這個模塊,請認真閱讀使用文檔。

2.3、在gitee上閱讀使用文檔

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/87892.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/87892.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/87892.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

跨平臺的重構版Notepad++文本編輯器

跨平臺 Notepad 替代方案 Notepad 是 Windows 平臺上的流行文本編輯器&#xff0c;但其原生版本不支持跨平臺。以下是功能相似且支持多平臺的替代工具&#xff1a; Notepadqq&#xff08;Linux/macOS/Windows&#xff09; Notepadqq 是 Notepad 的開源跨平臺版本&#xff0c…

意法STM32F103C8T6 單片機ARM Cortex-M3 國民MCU 電機控制到物聯網專用

STM32F103C8T6 單片機全面解析 1. 產品定位 STM32F103C8T6 是意法半導體&#xff08;ST&#xff09;推出的 經典ARM Cortex-M3內核單片機&#xff0c;采用 LQFP48封裝&#xff0c;以 高性能、豐富外設和超高性價比 成為嵌入式開發領域的"國民MCU"。 2. 核心功能特…

Sui 技術如何助力 Claynosaurz 成功推出 Popkins NFT

像 Claynosaurz 這樣的品牌利用 Sui 推動鏈上創新的邊界&#xff0c;展示了 Web3 如何結合互動娛樂并帶來獨特全新的體驗。Claynosaurz 最近在 Sui 上推出的 Popkins NFT 系列及其大型抽獎活動&#xff0c;不僅在社區參與度上取得了成功&#xff0c;也有力地展示了 Sui 獨特的技…

OpenCV CUDA模塊設備層-----反正弦運算函數asin()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 對一個 uchar 類型的像素值&#xff08;范圍 [0, 255]&#xff09;&#xff0c;先歸一化到浮點范圍 [0.0, 1.0]&#xff0c;然后計算其反正弦值 …

PixiJS 中 HTMLText 富文本渲染問題探究與優化思考?

起因 在使用 PixiJS 進行富文本渲染開發過程中&#xff0c;遭遇 HTMLText 組件處理中文字體加載時出現顯著卡頓現象。針對此問題&#xff0c;在開源社區提交 issue 并附上相關技術細節與運行表現&#xff0c;期望獲得解決方案。 提issues 從 issue 反饋內容來看&#xff0c;項目…

五、Redis的IO模型

簡介 在談及Redis為什么快的時候&#xff0c;很多人都只能回答redis是基于內存&#xff0c;所以快。但他們往往不知道&#xff0c;決定redis快的因素&#xff0c;還有它的IO模型-Reactor模型。談及Redis的IO模型之前&#xff0c;先補充一下IO模型的基礎知識。 IO模型演化 1.堵塞…

Cesium、ThreeWebGL詳解(二)渲染引擎向GPU傳數據、性能優化、引擎對比

下面從 API 定位、坐標體系、性能表現、面試常問點幾個維度詳細對比 Cesium、Three.js 與原生 WebGL 的繪制差異。 &#x1f9ed; 1. API 定位與典型應用 Cesium 聚焦全地球 GIS 場景&#xff0c;支持地形、影像、時空動態等地理信息功能&#xff0c;是專業級地圖應用首選。 T…

單點登錄(SSO)系統

設計一個 Java 單點登錄&#xff08;SSO&#xff09;系統需要解決跨系統認證和會話共享問題。以下是核心設計和實現方案&#xff0c;包含關鍵組件和代碼示例&#xff1a; 一、核心概念 認證中心 (Auth Center)&#xff1a;中央身份驗證服務令牌 (Token)&#xff1a;用戶身份憑…

《信息技術》科技核心期刊推薦

【科研必看】《信息技術》——科技核心期刊&#xff0c;助力你發表高影響力論文&#xff01; 如果你是一位科研工作者、學者或者在校學生&#xff0c;正在為發表論文而努力&#xff0c;那么《信息技術》期刊無疑是你不可錯過的選擇&#xff01;這本期刊以其卓越的學術影響力&am…

界面組件DevExpress WPF中文教程:Grid - 如何遍歷節點?

DevExpress WPF擁有120個控件和庫&#xff0c;將幫助您交付滿足甚至超出企業需求的高性能業務應用程序。通過DevExpress WPF能創建有著強大互動功能的XAML基礎應用程序&#xff0c;這些應用程序專注于當代客戶的需求和構建未來新一代支持觸摸的解決方案。 無論是Office辦公軟件…

2D寫實交互數字人:讓AI形象擁有“真人溫度“的技術革命

在人工智能技術日新月異的今天&#xff0c;數字人已不再是科幻電影中的概念&#xff0c;而是逐步滲透到我們日常生活的各個領域。然而&#xff0c;市場上大多數數字人產品仍停留在"能說會動"的初級階段&#xff0c;缺乏真正的情感交互能力&#xff0c;這種"機械…

2025 年拓客系統排行榜

在數字化營銷時代&#xff0c;拓客系統成為企業獲取客戶資源、提升銷售效率的關鍵工具。以下為您盤點 2025 年表現出色的中文名字拓客系統&#xff0c;其中 微拓客 憑借強大功能脫穎而出&#xff0c;成為眾多從業者的首選。 一、微拓客&#xff1a;精準拓客的全能王者 微拓客堪…

TikTok 矩陣如何快速漲粉

在社交媒體的廣袤天地里&#xff0c;TikTok 以其強大的影響力和龐大的用戶基礎&#xff0c;成為眾多創作者和品牌競相角逐的舞臺。構建 TikTok 矩陣&#xff0c;是擴大影響力、實現快速漲粉的有效策略。那么&#xff0c;究竟如何讓 TikTok 矩陣快速漲粉呢&#xff1f;下面將為您…

基于微信小程序和云開發的企業綠色融資平臺的設計與實現

文章目錄 摘要前言緒論1. 課題背景2. 國內外現狀與趨勢2.1 國內研究現狀2.2 國外研究現狀2.3 發展趨勢3. 課題內容相關技術與方法介紹1. 微信小程序開發技術2. 騰訊云開發平臺3. 綠色項目評估模型4. 智能匹配算法5. 碳核算方法系統分析1. 需求分析1.1 用戶需求1.2 功能需求1.3 …

如何自建服務器并開啟公網IP:本地內網網址讓外網訪問詳細教學

本地內網環境自建服務器后&#xff0c;如何讓外網訪問&#xff0c;提供互聯網連接服務呢&#xff1f;有不少方法都可以實現&#xff0c;常見的有如公網IP、DDNS動態域名、nat123內網穿透等&#xff0c;下面詳細教學。 一、申請開公網IP-----------------公網IP篇-------------…

企業公用電腦登錄安全管控的終極方案:ASP操作系統安全登錄管控方案

一、引言&#xff1a;公用電腦——企業安全管理的“灰色地帶” 在企業辦公場景中&#xff0c;公用電腦&#xff08;如會議室電腦、生產線終端、客服工位&#xff09;因多用戶共用、權限復雜&#xff0c;往往成為安全管理的薄弱環節。員工隨意登錄、弱密碼泛濫、敏感數據泄露事…

HarmonyOS-ArkTS開發指南:從基礎到實戰

目錄 一、基礎語法 二、聲明式 UI 開發 基本組件結構 三、狀態管理 四、生命周期鉤子 五、組件化開發 1. 創建自定義組件 2. 組件嵌套 六、事件處理 七、布局系統 八、樣式設置 九、條件渲染與列表渲染 十、異步操作 十一、路由導航 開發建議 一、基礎語法 ArkT…

算法-Day04

今天還是給大家分享幾道題目&#xff0c;希望大家可以好好理解。 第一題 問題描述 小藍有一天誤入了一個混境之地。 好消息是&#xff1a;他誤打誤撞拿到了一張地圖&#xff0c;并從中獲取到以下信息&#xff1a; 混境之地是一個 n?m大小的矩陣&#xff0c;這個地圖中一共…

Git版本控制詳細資料

Git安裝基本配置 下載安裝(一路next) 打開bash終端&#xff08;git專用&#xff09; 命令: git -v(查看版本號) 配置: 用戶名和郵箱,應用在每次提交代碼版本時表明自己身份 命令: git config --global user.name "FT" git config --global user.email "F…

利用井云平臺把Coze工作流接入小程序/網站封裝變現 | 詳細步驟→

今天來看看怎么把Coze工作流接入井云生成你的專屬網站/小程序&#xff01; 當前已支持三大模塊接入&#xff1a;? 工作流 ? 智能體 ? 外部網頁 本文所用工具 1、扣子&#xff1a;www.coze.cn 2、井云智能體&#xff1a;jingyun.center 為什么選擇井云平臺&#xff1f; …