实测,串口3,PB10 = TX,PB11 = RX,115200波特率,每包12字节,一万多包回显测试没有丢包

代码
main.c
c
int main(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_t ch;
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
c
/**
* @file usart3.c
* @brief USART3 驱动实现
* 接收:IDLE 中断 + DMA1_Ch3 循环模式 → 接收 RingBuf
* 发送:发送 RingBuf → DMA1_Ch2 普通模式(TC 中断继续发送)
*
* 时钟:STM32F103C8T6,APB1=36MHz,USART3 挂 APB1
* 引脚:PB10=TX(AF_PP),PB11=RX(Input Floating)
*/
#include "usart3.h"
#include <string.h> /* memcpy */
/* -----------------------------------------------------------------------
* HAL 句柄
* -----------------------------------------------------------------------*/
UART_HandleTypeDef huart3;
DMA_HandleTypeDef hdma_usart3_rx;
DMA_HandleTypeDef hdma_usart3_tx;
/* -----------------------------------------------------------------------
* DMA 原始缓冲区(DMA 直接写入,用于计算新数据位置)
* -----------------------------------------------------------------------*/
static uint8_t s_rxDmaBuf[USART3_RX_DMA_BUF_SIZE];
static uint8_t s_txDmaBuf[USART3_TX_DMA_BUF_SIZE]; /*!< DMA 发送中间缓冲 */
/* -----------------------------------------------------------------------
* 环形缓冲区
* -----------------------------------------------------------------------*/
static RingBuf_t s_rxRing;
static uint8_t s_rxRingData[USART3_RX_RING_SIZE];
static RingBuf_t s_txRing;
static uint8_t s_txRingData[USART3_TX_RING_SIZE];
/* -----------------------------------------------------------------------
* DMA 上一次处理到的位置(用于计算增量)
* -----------------------------------------------------------------------*/
static volatile uint32_t s_rxDmaLastPos = 0;
/* DMA 发送是否正在进行 */
static volatile uint8_t s_txBusy = 0;
/* -----------------------------------------------------------------------
* 内部函数:启动一次 DMA 发送
* -----------------------------------------------------------------------*/
static void _TxStart(void)
{
uint32_t avail = RingBuf_Used(&s_txRing);
if (avail == 0) {
s_txBusy = 0;
return;
}
/* 每次最多搬 USART3_TX_DMA_BUF_SIZE 字节到线性 DMA 缓冲区 */
uint32_t txLen = (avail > USART3_TX_DMA_BUF_SIZE) ? USART3_TX_DMA_BUF_SIZE : avail;
RingBuf_Get(&s_txRing, s_txDmaBuf, txLen);
s_txBusy = 1;
HAL_UART_Transmit_DMA(&huart3, s_txDmaBuf, (uint16_t)txLen);
}
/* -----------------------------------------------------------------------
* USART3_Init
* -----------------------------------------------------------------------*/
void USART3_Init(uint32_t baudrate)
{
/* 1. 初始化环形缓冲区 */
RingBuf_Init(&s_rxRing, s_rxRingData, USART3_RX_RING_SIZE);
RingBuf_Init(&s_txRing, s_txRingData, USART3_TX_RING_SIZE);
s_rxDmaLastPos = 0;
s_txBusy = 0;
/* 2. 配置 UART 参数 */
huart3.Instance = USART3;
huart3.Init.BaudRate = baudrate;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
huart3.Init.Mode = UART_MODE_TX_RX;
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart3) != HAL_OK) {
/* 初始化失败 --- 挂起 */
while (1) {}
}
/* 3. 启动 DMA 循环接收(HAL_UART_Receive_DMA 内部会启动 DMA) */
s_rxDmaLastPos = USART3_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_t USART3_Send(const uint8_t *data, uint32_t len)
{
uint32_t written = RingBuf_Put(&s_txRing, data, len);
/* 若 DMA 当前空闲,立即启动发送 */
if (!s_txBusy) {
_TxStart();
}
return written;
}
/* -----------------------------------------------------------------------
* USART3_RecvByte / USART3_Recv / USART3_RecvAvail
* -----------------------------------------------------------------------*/
uint8_t USART3_RecvByte(uint8_t *out)
{
return RingBuf_GetByte(&s_rxRing, out);
}
uint32_t USART3_Recv(uint8_t *buf, uint32_t maxLen)
{
return RingBuf_Get(&s_rxRing, buf, maxLen);
}
uint32_t USART3_RecvAvail(void)
{
return RingBuf_Used(&s_rxRing);
}
/* -----------------------------------------------------------------------
* USART3_IDLE_Callback
* 在 USART3_IRQHandler 中调用
* -----------------------------------------------------------------------*/
void USART3_IDLE_Callback(void)
{
/* 清除 IDLE 标志(必须先读 SR 再读 DR) */
__HAL_UART_CLEAR_IDLEFLAG(&huart3);
/* 计算 DMA 当前写到哪里了 */
/* DMA_CNDTR 寄存器 = 还剩多少字节未写
* 已写到位置 = 总大小 - 剩余
*/
uint32_t dmaRemain = (uint32_t)(__HAL_DMA_GET_COUNTER(&hdma_usart3_rx));
uint32_t dmaCurPos = USART3_RX_DMA_BUF_SIZE - dmaRemain;
if (dmaCurPos == s_rxDmaLastPos) {
/* 无新数据 */
return;
}
if (dmaCurPos > s_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_rxDmaLastPos = dmaCurPos;
}
/* -----------------------------------------------------------------------
* USART3_TxDMA_Callback
* 在 DMA1_Channel2_IRQHandler 调用(TC 中断)
* -----------------------------------------------------------------------*/
void USART3_TxDMA_Callback(void)
{
/* HAL 处理 DMA 中断(清标志、调 TxCpltCallback) */
HAL_DMA_IRQHandler(&hdma_usart3_tx);
}
/* -----------------------------------------------------------------------
* HAL 发送完成回调(被 HAL_DMA_IRQHandler 内部调用)
* -----------------------------------------------------------------------*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART3) {
/* 继续发送环形缓冲区中剩余数据 */
_TxStart();
}
}
usart3.h
c
/**
* @file usart3.h
* @brief USART3 驱动头文件
* - 接收:空闲中断 + DMA1_Channel3(循环模式)→ 接收环形缓冲区
* - 发送:DMA1_Channel2(普通模式)→ 发送环形缓冲区驱动
*
* 硬件映射(STM32F103C8T6):
* USART3_TX → PB10(AF_PP)
* USART3_RX → PB11(Input 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 __cplusplus
extern "C" {
#endif
#include "main.h" /* 包含 stm32f1xx_hal.h */
#include "ringbuf.h"
/* -----------------------------------------------------------------------
* 可配置参数
* -----------------------------------------------------------------------*/
#define USART3_RX_DMA_BUF_SIZE 128U /*!< DMA 原始接收缓冲区(字节) */
#define USART3_RX_RING_SIZE 256U /*!< 接收环形缓冲区(字节) */
#define USART3_TX_RING_SIZE 256U /*!< 发送环形缓冲区(字节) */
#define USART3_TX_DMA_BUF_SIZE 128U /*!< DMA 发送临时缓冲区(字节) */
/* -----------------------------------------------------------------------
* 对外暴露的句柄(中断文件需要用到)
* -----------------------------------------------------------------------*/
extern UART_HandleTypeDef huart3;
extern DMA_HandleTypeDef hdma_usart3_rx;
extern DMA_HandleTypeDef hdma_usart3_tx;
/* -----------------------------------------------------------------------
* 接口函数
* -----------------------------------------------------------------------*/
/**
* @brief 初始化 USART3(含 GPIO / DMA / NVIC)
* @param baudrate 波特率,如 115200
*/
void USART3_Init(uint32_t baudrate);
/**
* @brief 将数据写入发送环形缓冲区,并启动 DMA 发送
* @param data 数据指针
* @param len 字节数
* @retval 实际写入字节数(缓冲区满时截断)
*/
uint32_t USART3_Send(const uint8_t *data, uint32_t len);
/**
* @brief 从接收环形缓冲区取出一字节
* @param out 输出字节
* @retval 1=有数据 0=空
*/
uint8_t USART3_RecvByte(uint8_t *out);
/**
* @brief 从接收环形缓冲区取出多字节
* @retval 实际读取字节数
*/
uint32_t USART3_Recv(uint8_t *buf, uint32_t maxLen);
/** @brief 接收缓冲区当前已有数据字节数 */
uint32_t USART3_RecvAvail(void);
/**
* @brief 空闲中断回调(在 USART3_IRQHandler 中调用)
* 负责把 DMA 接收到的新数据搬入接收环形缓冲区
*/
void USART3_IDLE_Callback(void);
/**
* @brief DMA 发送完成回调(在 DMA1_Channel2_IRQHandler 中调用)
* 负责检查发送环形缓冲区并继续发送剩余数据
*/
void USART3_TxDMA_Callback(void);
#ifdef __cplusplus
}
#endif
#endif /* __USART3_H__ */
ringbuf.c
c
/**
* @file ringbuf.c
* @brief 通用字节环形缓冲区实现
*/
#include "ringbuf.h"
/* -----------------------------------------------------------------------
* 辅助宏
* -----------------------------------------------------------------------*/
/* 将索引折叠到 [0, size) 范围(size 任意,非必须 2^N) */
#define RB_MASK(rb, idx) ((idx) % (rb)->size)
/* -----------------------------------------------------------------------
* 接口实现
* -----------------------------------------------------------------------*/
void RingBuf_Init(RingBuf_t *rb, uint8_t *mem, uint32_t size)
{
rb->buf = mem;
rb->size = size;
rb->head = 0;
rb->tail = 0;
}
void RingBuf_Flush(RingBuf_t *rb)
{
rb->head = 0;
rb->tail = 0;
}
uint32_t RingBuf_Used(const RingBuf_t *rb)
{
return (rb->head - rb->tail);
}
uint32_t RingBuf_Free(const RingBuf_t *rb)
{
return rb->size - RingBuf_Used(rb);
}
uint8_t RingBuf_Empty(const RingBuf_t *rb)
{
return (rb->head == rb->tail) ? 1U : 0U;
}
uint8_t RingBuf_Full(const RingBuf_t *rb)
{
return (RingBuf_Used(rb) >= rb->size) ? 1U : 0U;
}
uint8_t RingBuf_PutByte(RingBuf_t *rb, uint8_t byte)
{
if (RingBuf_Full(rb)) {
return 0;
}
rb->buf[RB_MASK(rb, rb->head)] = byte;
rb->head++;
return 1;
}
uint32_t RingBuf_Put(RingBuf_t *rb, const uint8_t *data, uint32_t len)
{
uint32_t i;
for (i = 0; i < len; i++) {
if (!RingBuf_PutByte(rb, data[i])) {
break;
}
}
return i;
}
uint8_t RingBuf_Peek(const RingBuf_t *rb, uint8_t *out)
{
if (RingBuf_Empty(rb)) {
return 0;
}
*out = rb->buf[RB_MASK(rb, rb->tail)];
return 1;
}
uint8_t RingBuf_GetByte(RingBuf_t *rb, uint8_t *out)
{
if (RingBuf_Empty(rb)) {
return 0;
}
*out = rb->buf[RB_MASK(rb, rb->tail)];
rb->tail++;
return 1;
}
uint32_t RingBuf_Get(RingBuf_t *rb, uint8_t *buf, uint32_t maxLen)
{
uint32_t i;
for (i = 0; i < maxLen; i++) {
if (!RingBuf_GetByte(rb, &buf[i])) {
break;
}
}
return i;
}
ringbuf.h
c
/**
* @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 __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
/* -----------------------------------------------------------------------
* 数据结构
* -----------------------------------------------------------------------*/
typedef struct {
uint8_t *buf; /*!< 数据缓冲区指针 */
uint32_t size; /*!< 缓冲区总容量(建议为 2^N) */
volatile uint32_t head; /*!< 写指针(生产者推进) */
volatile uint32_t tail; /*!< 读指针(消费者推进) */
} RingBuf_t;
/* -----------------------------------------------------------------------
* 接口函数声明
* -----------------------------------------------------------------------*/
/**
* @brief 初始化环形缓冲区
* @param rb 缓冲区控制块
* @param mem 外部数据数组
* @param size 数组字节数(建议 2^N,最大 2^31)
*/
void RingBuf_Init (RingBuf_t *rb, uint8_t *mem, uint32_t size);
/** @brief 清空缓冲区(将 head/tail 复位) */
void RingBuf_Flush (RingBuf_t *rb);
/** @brief 返回当前已用字节数 */
uint32_t RingBuf_Used (const RingBuf_t *rb);
/** @brief 返回当前空闲字节数 */
uint32_t RingBuf_Free (const RingBuf_t *rb);
/** @brief 缓冲区是否为空 */
uint8_t RingBuf_Empty (const RingBuf_t *rb);
/** @brief 缓冲区是否已满 */
uint8_t RingBuf_Full (const RingBuf_t *rb);
/**
* @brief 写入单字节
* @retval 1=成功 0=缓冲区满
*/
uint8_t RingBuf_PutByte(RingBuf_t *rb, uint8_t byte);
/**
* @brief 写入多字节
* @retval 实际写入字节数
*/
uint32_t RingBuf_Put(RingBuf_t *rb, const uint8_t *data, uint32_t len);
/**
* @brief 读取单字节(不移动指针,仅查看)
* @param out 输出字节
* @retval 1=成功 0=缓冲区空
*/
uint8_t RingBuf_Peek(const RingBuf_t *rb, uint8_t *out);
/**
* @brief 取出单字节
* @param out 输出字节
* @retval 1=成功 0=缓冲区空
*/
uint8_t RingBuf_GetByte(RingBuf_t *rb, uint8_t *out);
/**
* @brief 取出多字节
* @retval 实际读取字节数
*/
uint32_t RingBuf_Get(RingBuf_t *rb, uint8_t *buf, uint32_t maxLen);
#ifdef __cplusplus
}
#endif
#endif /* __RINGBUF_H__ */
stm32f1xx_it.c
c
void USART3_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);
}