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

实测,串口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);
}
相关推荐
LCG元3 小时前
STM32实战:基于STM32F103的4G模块(EC20)HTTP通信
stm32·嵌入式硬件·http
送外卖的CV工程师4 小时前
STM32+Makefile编译+OpenOCD 烧录调试
stm32·单片机·嵌入式硬件·makefile·调试·烧录·openocd
豆包公子4 小时前
程序流监控:AUTOSAR CP 功能安全在裸机 MCU 上的实现(理论篇)
运维·单片机·嵌入式硬件·安全·车载系统·autosar
NQBJT8 小时前
嵌入式从零开始(第十二篇):调试与工具链 —— 从 IDE 到逻辑分析仪
ide·stm32·单片机·嵌入式硬件·c#
豆包公子9 小时前
程序流监控 —— AUTOSAR CP 功能安全在裸机 MCU 上的实现:实践篇
单片机·嵌入式硬件·学习
cici158749 小时前
C# 五子棋小游戏源码(人机对战)
开发语言·单片机·c#
iCxhust9 小时前
51单片机定时器PWM发生
stm32·单片机·51单片机
LCG元9 小时前
STM32实战:基于STM32F103的智能宠物喂食器(定时+定量)
stm32·嵌入式硬件·宠物
水云桐程序员10 小时前
用C语言写LED灯嵌入式系统案例|STM32 LED控制与按键输入系统
c语言·stm32·单片机