导言


《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; …