串口空闲中断+DMA接收+环形缓冲区 串口DMA发送+环形缓冲区

张开发
2026/4/21 21:20:13 15 分钟阅读

分享文章

串口空闲中断+DMA接收+环形缓冲区  串口DMA发送+环形缓冲区
实测串口3PB10 TXPB11 RX,115200波特率每包12字节一万多包回显测试没有丢包代码main.cintmain(void){/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();/* USER CODE BEGIN 2 */USART3_Init(115200);USART3_Send((uint8_t*)USART3 Echo Test Ready\r\n,24);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while(1){/* USER CODE END WHILE *//* ----- USART3 回显把接收缓冲区中所有数据原样发回 ----- */{uint8_tch;while(USART3_RecvByte(ch)){USART3_Send(ch,1);}}/* LED 闪烁保持原来功能 */// HAL_Delay(50);// HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);// HAL_Delay(50);// HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);/* USER CODE BEGIN 3 */}/* USER CODE END 3 */}usart3.c/** * file usart3.c * brief USART3 驱动实现 * 接收IDLE 中断 DMA1_Ch3 循环模式 → 接收 RingBuf * 发送发送 RingBuf → DMA1_Ch2 普通模式TC 中断继续发送 * * 时钟STM32F103C8T6APB136MHzUSART3 挂 APB1 * 引脚PB10TX(AF_PP)PB11RX(Input Floating) */#includeusart3.h#includestring.h/* memcpy *//* ----------------------------------------------------------------------- * HAL 句柄 * -----------------------------------------------------------------------*/UART_HandleTypeDef huart3;DMA_HandleTypeDef hdma_usart3_rx;DMA_HandleTypeDef hdma_usart3_tx;/* ----------------------------------------------------------------------- * DMA 原始缓冲区DMA 直接写入用于计算新数据位置 * -----------------------------------------------------------------------*/staticuint8_ts_rxDmaBuf[USART3_RX_DMA_BUF_SIZE];staticuint8_ts_txDmaBuf[USART3_TX_DMA_BUF_SIZE];/*! DMA 发送中间缓冲 *//* ----------------------------------------------------------------------- * 环形缓冲区 * -----------------------------------------------------------------------*/staticRingBuf_t s_rxRing;staticuint8_ts_rxRingData[USART3_RX_RING_SIZE];staticRingBuf_t s_txRing;staticuint8_ts_txRingData[USART3_TX_RING_SIZE];/* ----------------------------------------------------------------------- * DMA 上一次处理到的位置用于计算增量 * -----------------------------------------------------------------------*/staticvolatileuint32_ts_rxDmaLastPos0;/* DMA 发送是否正在进行 */staticvolatileuint8_ts_txBusy0;/* ----------------------------------------------------------------------- * 内部函数启动一次 DMA 发送 * -----------------------------------------------------------------------*/staticvoid_TxStart(void){uint32_tavailRingBuf_Used(s_txRing);if(avail0){s_txBusy0;return;}/* 每次最多搬 USART3_TX_DMA_BUF_SIZE 字节到线性 DMA 缓冲区 */uint32_ttxLen(availUSART3_TX_DMA_BUF_SIZE)?USART3_TX_DMA_BUF_SIZE:avail;RingBuf_Get(s_txRing,s_txDmaBuf,txLen);s_txBusy1;HAL_UART_Transmit_DMA(huart3,s_txDmaBuf,(uint16_t)txLen);}/* ----------------------------------------------------------------------- * USART3_Init * -----------------------------------------------------------------------*/voidUSART3_Init(uint32_tbaudrate){/* 1. 初始化环形缓冲区 */RingBuf_Init(s_rxRing,s_rxRingData,USART3_RX_RING_SIZE);RingBuf_Init(s_txRing,s_txRingData,USART3_TX_RING_SIZE);s_rxDmaLastPos0;s_txBusy0;/* 2. 配置 UART 参数 */huart3.InstanceUSART3;huart3.Init.BaudRatebaudrate;huart3.Init.WordLengthUART_WORDLENGTH_8B;huart3.Init.StopBitsUART_STOPBITS_1;huart3.Init.ParityUART_PARITY_NONE;huart3.Init.ModeUART_MODE_TX_RX;huart3.Init.HwFlowCtlUART_HWCONTROL_NONE;huart3.Init.OverSamplingUART_OVERSAMPLING_16;if(HAL_UART_Init(huart3)!HAL_OK){/* 初始化失败 — 挂起 */while(1){}}/* 3. 启动 DMA 循环接收HAL_UART_Receive_DMA 内部会启动 DMA */s_rxDmaLastPosUSART3_RX_DMA_BUF_SIZE;/* 初始化为缓冲区末尾 */HAL_UART_Receive_DMA(huart3,s_rxDmaBuf,USART3_RX_DMA_BUF_SIZE);/* 4. 使能 UART 空闲中断HAL 不会自动开启 */__HAL_UART_ENABLE_IT(huart3,UART_IT_IDLE);}/* ----------------------------------------------------------------------- * USART3_Send * -----------------------------------------------------------------------*/uint32_tUSART3_Send(constuint8_t*data,uint32_tlen){uint32_twrittenRingBuf_Put(s_txRing,data,len);/* 若 DMA 当前空闲立即启动发送 */if(!s_txBusy){_TxStart();}returnwritten;}/* ----------------------------------------------------------------------- * USART3_RecvByte / USART3_Recv / USART3_RecvAvail * -----------------------------------------------------------------------*/uint8_tUSART3_RecvByte(uint8_t*out){returnRingBuf_GetByte(s_rxRing,out);}uint32_tUSART3_Recv(uint8_t*buf,uint32_tmaxLen){returnRingBuf_Get(s_rxRing,buf,maxLen);}uint32_tUSART3_RecvAvail(void){returnRingBuf_Used(s_rxRing);}/* ----------------------------------------------------------------------- * USART3_IDLE_Callback * 在 USART3_IRQHandler 中调用 * -----------------------------------------------------------------------*/voidUSART3_IDLE_Callback(void){/* 清除 IDLE 标志必须先读 SR 再读 DR */__HAL_UART_CLEAR_IDLEFLAG(huart3);/* 计算 DMA 当前写到哪里了 *//* DMA_CNDTR 寄存器 还剩多少字节未写 * 已写到位置 总大小 - 剩余 */uint32_tdmaRemain(uint32_t)(__HAL_DMA_GET_COUNTER(hdma_usart3_rx));uint32_tdmaCurPosUSART3_RX_DMA_BUF_SIZE-dmaRemain;if(dmaCurPoss_rxDmaLastPos){/* 无新数据 */return;}if(dmaCurPoss_rxDmaLastPos){/* 无回绕正常顺序写入 */RingBuf_Put(s_rxRing,s_rxDmaBuf[s_rxDmaLastPos],dmaCurPos-s_rxDmaLastPos);}else{/* 发生回绕先取末尾部分再取头部部分 */RingBuf_Put(s_rxRing,s_rxDmaBuf[s_rxDmaLastPos],USART3_RX_DMA_BUF_SIZE-s_rxDmaLastPos);RingBuf_Put(s_rxRing,s_rxDmaBuf[0],dmaCurPos);}s_rxDmaLastPosdmaCurPos;}/* ----------------------------------------------------------------------- * USART3_TxDMA_Callback * 在 DMA1_Channel2_IRQHandler 调用TC 中断 * -----------------------------------------------------------------------*/voidUSART3_TxDMA_Callback(void){/* HAL 处理 DMA 中断清标志、调 TxCpltCallback */HAL_DMA_IRQHandler(hdma_usart3_tx);}/* ----------------------------------------------------------------------- * HAL 发送完成回调被 HAL_DMA_IRQHandler 内部调用 * -----------------------------------------------------------------------*/voidHAL_UART_TxCpltCallback(UART_HandleTypeDef*huart){if(huart-InstanceUSART3){/* 继续发送环形缓冲区中剩余数据 */_TxStart();}}usart3.h/** * file usart3.h * brief USART3 驱动头文件 * - 接收空闲中断 DMA1_Channel3循环模式→ 接收环形缓冲区 * - 发送DMA1_Channel2普通模式→ 发送环形缓冲区驱动 * * 硬件映射STM32F103C8T6 * USART3_TX → PB10AF_PP * USART3_RX → PB11Input Floating * DMA1_Ch3 → USART3_RX * DMA1_Ch2 → USART3_TX * * 使用示例 * USART3_Init(115200); * * // 发送 * USART3_Send((uint8_t*)Hello\r\n, 7); * * // 主循环接收/回显 * uint8_t ch; * while (USART3_RecvByte(ch)) { * USART3_Send(ch, 1); * } */#ifndef__USART3_H__#define__USART3_H__#ifdef__cplusplusexternC{#endif#includemain.h/* 包含 stm32f1xx_hal.h */#includeringbuf.h/* ----------------------------------------------------------------------- * 可配置参数 * -----------------------------------------------------------------------*/#defineUSART3_RX_DMA_BUF_SIZE128U/*! DMA 原始接收缓冲区字节 */#defineUSART3_RX_RING_SIZE256U/*! 接收环形缓冲区字节 */#defineUSART3_TX_RING_SIZE256U/*! 发送环形缓冲区字节 */#defineUSART3_TX_DMA_BUF_SIZE128U/*! DMA 发送临时缓冲区字节 *//* ----------------------------------------------------------------------- * 对外暴露的句柄中断文件需要用到 * -----------------------------------------------------------------------*/externUART_HandleTypeDef huart3;externDMA_HandleTypeDef hdma_usart3_rx;externDMA_HandleTypeDef hdma_usart3_tx;/* ----------------------------------------------------------------------- * 接口函数 * -----------------------------------------------------------------------*//** * brief 初始化 USART3含 GPIO / DMA / NVIC * param baudrate 波特率如 115200 */voidUSART3_Init(uint32_tbaudrate);/** * brief 将数据写入发送环形缓冲区并启动 DMA 发送 * param data 数据指针 * param len 字节数 * retval 实际写入字节数缓冲区满时截断 */uint32_tUSART3_Send(constuint8_t*data,uint32_tlen);/** * brief 从接收环形缓冲区取出一字节 * param out 输出字节 * retval 1有数据 0空 */uint8_tUSART3_RecvByte(uint8_t*out);/** * brief 从接收环形缓冲区取出多字节 * retval 实际读取字节数 */uint32_tUSART3_Recv(uint8_t*buf,uint32_tmaxLen);/** brief 接收缓冲区当前已有数据字节数 */uint32_tUSART3_RecvAvail(void);/** * brief 空闲中断回调在 USART3_IRQHandler 中调用 * 负责把 DMA 接收到的新数据搬入接收环形缓冲区 */voidUSART3_IDLE_Callback(void);/** * brief DMA 发送完成回调在 DMA1_Channel2_IRQHandler 中调用 * 负责检查发送环形缓冲区并继续发送剩余数据 */voidUSART3_TxDMA_Callback(void);#ifdef__cplusplus}#endif#endif/* __USART3_H__ */ringbuf.c/** * file ringbuf.c * brief 通用字节环形缓冲区实现 */#includeringbuf.h/* ----------------------------------------------------------------------- * 辅助宏 * -----------------------------------------------------------------------*//* 将索引折叠到 [0, size) 范围size 任意非必须 2^N */#defineRB_MASK(rb,idx)((idx)%(rb)-size)/* ----------------------------------------------------------------------- * 接口实现 * -----------------------------------------------------------------------*/voidRingBuf_Init(RingBuf_t*rb,uint8_t*mem,uint32_tsize){rb-bufmem;rb-sizesize;rb-head0;rb-tail0;}voidRingBuf_Flush(RingBuf_t*rb){rb-head0;rb-tail0;}uint32_tRingBuf_Used(constRingBuf_t*rb){return(rb-head-rb-tail);}uint32_tRingBuf_Free(constRingBuf_t*rb){returnrb-size-RingBuf_Used(rb);}uint8_tRingBuf_Empty(constRingBuf_t*rb){return(rb-headrb-tail)?1U:0U;}uint8_tRingBuf_Full(constRingBuf_t*rb){return(RingBuf_Used(rb)rb-size)?1U:0U;}uint8_tRingBuf_PutByte(RingBuf_t*rb,uint8_tbyte){if(RingBuf_Full(rb)){return0;}rb-buf[RB_MASK(rb,rb-head)]byte;rb-head;return1;}uint32_tRingBuf_Put(RingBuf_t*rb,constuint8_t*data,uint32_tlen){uint32_ti;for(i0;ilen;i){if(!RingBuf_PutByte(rb,data[i])){break;}}returni;}uint8_tRingBuf_Peek(constRingBuf_t*rb,uint8_t*out){if(RingBuf_Empty(rb)){return0;}*outrb-buf[RB_MASK(rb,rb-tail)];return1;}uint8_tRingBuf_GetByte(RingBuf_t*rb,uint8_t*out){if(RingBuf_Empty(rb)){return0;}*outrb-buf[RB_MASK(rb,rb-tail)];rb-tail;return1;}uint32_tRingBuf_Get(RingBuf_t*rb,uint8_t*buf,uint32_tmaxLen){uint32_ti;for(i0;imaxLen;i){if(!RingBuf_GetByte(rb,buf[i])){break;}}returni;}ringbuf.h/** * file ringbuf.h * brief 通用字节环形缓冲区无动态内存线程安全于单生产者/单消费者场景 * * 用法 * 1. 定义缓冲区实例 static RingBuf_t rxBuf; * 2. 定义数据存储空间static uint8_t rxBufData[256]; * 3. 初始化 RingBuf_Init(rxBuf, rxBufData, sizeof(rxBufData)); */#ifndef__RINGBUF_H__#define__RINGBUF_H__#ifdef__cplusplusexternC{#endif#includestdint.h#includestddef.h/* ----------------------------------------------------------------------- * 数据结构 * -----------------------------------------------------------------------*/typedefstruct{uint8_t*buf;/*! 数据缓冲区指针 */uint32_tsize;/*! 缓冲区总容量建议为 2^N */volatileuint32_thead;/*! 写指针生产者推进 */volatileuint32_ttail;/*! 读指针消费者推进 */}RingBuf_t;/* ----------------------------------------------------------------------- * 接口函数声明 * -----------------------------------------------------------------------*//** * brief 初始化环形缓冲区 * param rb 缓冲区控制块 * param mem 外部数据数组 * param size 数组字节数建议 2^N最大 2^31 */voidRingBuf_Init(RingBuf_t*rb,uint8_t*mem,uint32_tsize);/** brief 清空缓冲区将 head/tail 复位 */voidRingBuf_Flush(RingBuf_t*rb);/** brief 返回当前已用字节数 */uint32_tRingBuf_Used(constRingBuf_t*rb);/** brief 返回当前空闲字节数 */uint32_tRingBuf_Free(constRingBuf_t*rb);/** brief 缓冲区是否为空 */uint8_tRingBuf_Empty(constRingBuf_t*rb);/** brief 缓冲区是否已满 */uint8_tRingBuf_Full(constRingBuf_t*rb);/** * brief 写入单字节 * retval 1成功 0缓冲区满 */uint8_tRingBuf_PutByte(RingBuf_t*rb,uint8_tbyte);/** * brief 写入多字节 * retval 实际写入字节数 */uint32_tRingBuf_Put(RingBuf_t*rb,constuint8_t*data,uint32_tlen);/** * brief 读取单字节不移动指针仅查看 * param out 输出字节 * retval 1成功 0缓冲区空 */uint8_tRingBuf_Peek(constRingBuf_t*rb,uint8_t*out);/** * brief 取出单字节 * param out 输出字节 * retval 1成功 0缓冲区空 */uint8_tRingBuf_GetByte(RingBuf_t*rb,uint8_t*out);/** * brief 取出多字节 * retval 实际读取字节数 */uint32_tRingBuf_Get(RingBuf_t*rb,uint8_t*buf,uint32_tmaxLen);#ifdef__cplusplus}#endif#endif/* __RINGBUF_H__ */stm32f1xx_it.cvoidUSART3_IRQHandler(void){/* 检测到空闲中断标志 */if(__HAL_UART_GET_FLAG(huart3,UART_FLAG_IDLE)__HAL_UART_GET_IT_SOURCE(huart3,UART_IT_IDLE)){USART3_IDLE_Callback();/* 搬数据进 RingBuf */}/* 调用 HAL 通用 UART 中断处理处理 DMA RX 等事件 */HAL_UART_IRQHandler(huart3);}

更多文章