打印
[STM32F1]

STM32F103 串口DMA通信与环形缓冲区实现

[复制链接]
237|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2025-3-25 08:26 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一、功能概述
本教程基于STM32F103芯片,通过DMA(直接内存访问)实现高效串口通信,并结合**环形缓冲区(Circular Buffer)**解决数据接收时的溢出问题。代码核心功能如下:
USART1的DMA收发配置:自动传输数据,不占用CPU资源。
循环缓冲区管理:确保接收数据的连续性,避免丢失。
中断触发机制:通过空闲中断检测一帧数据结束,通过DMA中断管理缓冲区状态。

二、代码结构解析
1. 宏定义与全局变量
#define USART1_RX_DMA_CHANNEL DMA1_Channel5  // 接收DMA通道
#define USART1_TX_DMA_CHANNEL DMA1_Channel4  // 发送DMA通道
#define INTERCOM_RX_BUFF_SIZE 512            // 接收缓冲区大小
#define BUFFER_SIZE INTERCOM_RX_BUFF_SIZE    // 环形缓冲区大小

// 环形缓冲区结构体
typedef struct {
    volatile uint8_t buffer[BUFFER_SIZE];
    volatile uint16_t head;   // 数据起始位置
    volatile uint16_t tail;   // 数据结束位置
    volatile uint16_t count;  // 当前数据量
} CircularBuffer_T;

// 全局DMA通信管理结构体
TRANSFER_UART_DMA_T Transfer_UART_DMA;


关键点:

接收使用循环模式DMA,发送使用单次模式DMA。
环形缓冲区通过head和tail指针实现数据的循环覆盖,count记录有效数据量。
2. 环形缓冲区操作函数
初始化缓冲区

void CB_InitBuffer(CircularBuffer_T *cb) {
    cb->head = 0;
    cb->tail = 0;
    cb->count = 0;
    memset(cb->buffer, 0, BUFFER_SIZE);
}


数据出队(从缓冲区读取)

uint8_t CB_Dequeue(CircularBuffer_T *cb, uint8_t *data) {
    if (cb->count == 0) return 1; // 缓冲区为空
    *data = cb->buffer[cb->head];
    cb->head = (cb->head + 1) % BUFFER_SIZE;
    cb->count--;
    return 0;
}


3. USART1与DMA初始化
USART1配置(Transfer_USART1_Init_Config)

GPIO配置:
PB6(TX):复用推挽输出。
PB7(RX):浮空输入。
波特率设置:根据SystemParam.Uart_Baud_Rate_Num选择(2400~115200)。
中断配置:使能空闲中断(USART_IT_IDLE),用于检测一帧数据接收完成。
关键代码:

USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); // 空闲中断
USART_Cmd(USART1, ENABLE);                      // 启动USART1


4. 数据收发流程
发送数据(Transfer_USART1_DMA_Send)
检查发送缓冲区是否空闲。
将数据拷贝到发送缓冲区tx_buff。
设置DMA传输长度并启动发送。
代码逻辑:

memcpy(Transfer_UART_DMA.tx_buff, data_p, len); // 数据拷贝
DMA_SetCurrDataCounter(USART1_TX_DMA_CHANNEL, len); // 设置长度
DMA_Cmd(USART1_TX_DMA_CHANNEL, ENABLE);        // 启动发送


接收数据(中断处理)
空闲中断(USART1_IRQHandler):
触发条件:串口总线空闲(一帧数据接收完毕)。
计算接收到的数据长度,更新环形缓冲区指针。
DMA完成中断(DMA1_Channel5_IRQHandler):
标记缓冲区循环状态,处理数据覆盖逻辑。
// 空闲中断中更新缓冲区
temp = DMA_GetCurrDataCounter(USART1_RX_DMA_CHANNEL);
Transfer_UART_DMA.rx_buff.tail = BUFFER_SIZE - temp;


二、完整代码
#define USART1_RX_DMA_CHANNEL DMA1_Channel5
#define USART1_TX_DMA_CHANNEL DMA1_Channel4


#define INTERCOM_RX_BUFF_SIZE 512
#define INTERCOM_TX_BUFF_SIZE 512

// 定义环形缓冲区的大小
#define BUFFER_SIZE INTERCOM_RX_BUFF_SIZE

typedef struct
{
    volatile uint8_t buffer[BUFFER_SIZE]; // 存储数据的数组
    volatile uint16_t head;               // 指向第一个有效数据的索引
    volatile uint16_t tail;               // 指向下一个空闲位置的索引
    volatile uint16_t count;              // 当前缓冲区中有效数据的数量
} CircularBuffer_T;

typedef struct
{
    // volatile uint8_t rx_flag;
    // uint8_t rx_buff[INTERCOM_RX_BUFF_SIZE];
    // volatile uint16_t rx_len;

    CircularBuffer_T rx_buff;
    volatile uint8_t rx_buff_loopback;
    uint8_t tx_buff[INTERCOM_TX_BUFF_SIZE];

} TRANSFER_UART_DMA_T;


TRANSFER_UART_DMA_T Transfer_UART_DMA;

CircularBuffer_T Rx_CircularBuffer;

// 初始化环形缓冲区
void CB_InitBuffer(CircularBuffer_T *cb)
{
    cb->head = 0;
    cb->tail = 0;
    cb->count = 0;
    // 初始化数组为0
    for (int i = 0; i < BUFFER_SIZE; i++)
        cb->buffer = 0;
}

// 检查环形缓冲区是否为空
uint8_t CB_IsEmpty(CircularBuffer_T *cb)
{
    return cb->count == 0;
}

// 检查环形缓冲区是否已满
uint8_t CB_IsFull(CircularBuffer_T *cb)
{
    return cb->count == BUFFER_SIZE;
}

/**
* @brief  从环形缓冲区取出数据
* @param  无
* @retval 0 有数据,1 无数据
*/
uint8_t CB_Dequeue(CircularBuffer_T *cb, uint8_t *data)
{
    if (CB_IsEmpty(cb))
        return 1; // 缓冲区为空

    if (data == NULL)
        return 1;

    *data = cb->buffer[cb->head];
    cb->head = (cb->head + 1) % BUFFER_SIZE;
    cb->count--;
    return 0;
}


/**
* @brief  USART1配置
* @param  无
* @retval 无
*/
void Transfer_USART1_Init_Config(void)
{
    uint8_t clear = clear;
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStruct;

    // 打开时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);

    // 将USART Tx的GPIO配置为推挽复用模式
    GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 将USART Rx的GPIO配置为浮空输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 配置串口的工作参数
    // 配置波特率
    switch (SystemParam.Uart_Baud_Rate_Num)
    {
    case 0:
        USART_InitStructure.USART_BaudRate = 2400;
        break;
    case 1:
        USART_InitStructure.USART_BaudRate = 4800;
        break;
    case 2:
        USART_InitStructure.USART_BaudRate = 9600;
        break;
    case 3:
        USART_InitStructure.USART_BaudRate = 19200;
        break;
    case 4:
        USART_InitStructure.USART_BaudRate = 38400;
        break;
    case 5:
        USART_InitStructure.USART_BaudRate = 115200;
        break;
    default:
        USART_InitStructure.USART_BaudRate = 115200;
        break;
    }
    // USART_InitStructure.USART_BaudRate = UART_TRANSFER_BAUD_RATE;
    // 配置帧数据字长
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    // 配置停止位
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    // 配置校验位
    USART_InitStructure.USART_Parity = USART_Parity_No;
    // 配置硬件流控制
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    // 配置工作模式,收发一起
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    // 完成串口的初始化配置
    USART_Init(USART1, &USART_InitStructure);

    NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = UART1_PRIORITY;
    NVIC_Init(&NVIC_InitStruct);

    // //使能发送完成中断
    // USART_ITConfig(USART1,USART_IT_TC,ENABLE);
    // 使能空闲中断
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
    USART_ClearFlag(USART1, USART_IT_IDLE);
    clear = USART1->SR;
    clear = USART1->DR;
    // 使能串口
    USART_Cmd(USART1, ENABLE);
}

/**
* @brief  USART1 DMA配置
* @param  无
* @retval 无
*/
void Transfer_USART1_DMA_Init_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStruct;

    // 开启DMA时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    DMA_DeInit(USART1_TX_DMA_CHANNEL); // TX
    DMA_DeInit(USART1_RX_DMA_CHANNEL); // RX

    // Rx到接收缓冲区
    DMA_StructInit(&DMA_InitStructure);
    //  设置DMA源地址:串口数据寄存器地址*/
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(USART1->DR));
    // 内存地址(要传输的变量的指针)
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)Transfer_UART_DMA.rx_buff.buffer;
    // 方向:从外设到内存
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    // 传输大小
    DMA_InitStructure.DMA_BufferSize = INTERCOM_RX_BUFF_SIZE;
    // 外设地址不增
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    // 内存地址自增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    // 外设数据单位
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    // 内存数据单位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    // DMA模式,循环
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // DMA_Mode_Circular DMA_Mode_Normal
    // 优先级:中
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    // 禁止内存到内存的传输
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    // 配置DMA通道
    DMA_Init(USART1_RX_DMA_CHANNEL, &DMA_InitStructure);

    // 开启传输完成中断
    DMA_ITConfig(USART1_RX_DMA_CHANNEL, DMA_IT_TC, ENABLE);
    DMA_ClearFlag(DMA1_FLAG_TC5);

    NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel5_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = DMA_PRIORITY;
    NVIC_Init(&NVIC_InitStruct);

    // 发送缓冲区到TX
    DMA_StructInit(&DMA_InitStructure);

    //  设置DMA源地址:串口数据寄存器地址*/
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(USART1->DR));
    // 内存地址(要传输的变量的指针)
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)Transfer_UART_DMA.tx_buff;
    // 方向:从外设到内存
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    // 传输大小
    DMA_InitStructure.DMA_BufferSize = 0;
    // 外设地址不增
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    // 内存地址自增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    // 外设数据单位
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    // 内存数据单位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    // DMA模式,循环
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // DMA_Mode_Circular DMA_Mode_Normal
    // 优先级:中
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    // 禁止内存到内存的传输
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

    DMA_Init(USART1_TX_DMA_CHANNEL, &DMA_InitStructure);

    // 使能DMA
    DMA_Cmd(USART1_RX_DMA_CHANNEL, ENABLE);
    DMA_Cmd(USART1_TX_DMA_CHANNEL, ENABLE);

    USART_DMACmd(USART1, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE); // 使能DMA串口发送和接受请求
}

void Transfer_USART1_DMA_Start(void)
{
    DMA_Cmd(USART1_RX_DMA_CHANNEL, DISABLE);
    // 重新写入要传输的数据数量
    DMA_SetCurrDataCounter(USART1_RX_DMA_CHANNEL, INTERCOM_RX_BUFF_SIZE);
    // 启动DMA
    DMA_Cmd(USART1_RX_DMA_CHANNEL, ENABLE);
}

/**
* @brief  USART1 DMA 发送数据
* @param  无
* @retval 0 正常,1 异常
*/
uint8_t Transfer_USART1_DMA_Send(uint8_t *data_p, uint16_t len)
{
    // /* 发送一个字节数据到USART */
    // USART_SendData(USART1,*data_p);

    // /* 等待发送数据寄存器为空 */
    // while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

    if (len == 0 || len > INTERCOM_TX_BUFF_SIZE)
        return 1;

    // if (DMA_GetCurrDataCounter(USART1_TX_DMA_CHANNEL))
    //     return 1;
    while (DMA_GetCurrDataCounter(USART1_TX_DMA_CHANNEL))
        ;

    if (data_p)
        memcpy(Transfer_UART_DMA.tx_buff, data_p, len);

    DMA_Cmd(USART1_TX_DMA_CHANNEL, DISABLE);
    DMA_SetCurrDataCounter(USART1_TX_DMA_CHANNEL, len); // 重新写入要传输的数据数量
    DMA_Cmd(USART1_TX_DMA_CHANNEL, ENABLE);             // 启动DMA发送

    return 0;
}

void Transfer_USART1_DMA_Send_Test(void)
{
    // if (Transfer_USART1_DMA_Send(Transfer_UART_DMA.rx_buff, Transfer_UART_DMA.rx_len) == 0)
    //     ;
    // Transfer_UART_DMA.rx_len = 0;

    uint8_t rx_data = 0;
    if (CB_Dequeue(&(Transfer_UART_DMA.rx_buff), &rx_data) == 0)
        Transfer_USART1_DMA_Send(&rx_data, 1);
}

/**
* @brief  USART1 中断
* @param  无
* @retval 无
*/
void USART1_IRQHandler(void)
{
    uint16_t temp;

    // 空闲中断
    if (USART_GetITStatus(USART1, USART_IT_IDLE))
    {
        USART_ClearITPendingBit(USART1, USART_IT_IDLE);
        temp = USART1->SR;
        temp = USART1->DR;

        // 接收完成
        temp = DMA_GetCurrDataCounter(USART1_RX_DMA_CHANNEL);

        // 计算新增数据
        if (Transfer_UART_DMA.rx_buff_loopback == 0)
            Transfer_UART_DMA.rx_buff.count += BUFFER_SIZE - temp - Transfer_UART_DMA.rx_buff.tail;
        else
        {
            Transfer_UART_DMA.rx_buff.count += BUFFER_SIZE - Transfer_UART_DMA.rx_buff.tail + BUFFER_SIZE - temp;
            Transfer_UART_DMA.rx_buff_loopback = 0;
        }

        Transfer_UART_DMA.rx_buff.tail = BUFFER_SIZE - temp;

        // 数据溢出,覆盖
        if (Transfer_UART_DMA.rx_buff.count > BUFFER_SIZE)
        {
            // 更新头部位置
            Transfer_UART_DMA.rx_buff.count = BUFFER_SIZE;
            Transfer_UART_DMA.rx_buff.head = Transfer_UART_DMA.rx_buff.tail + 1 % BUFFER_SIZE;
        }
    }
}

void DMA1_Channel5_IRQHandler(void)
{
    if (DMA_GetITStatus(DMA1_IT_TC5))
    {
        DMA_ClearITPendingBit(DMA1_IT_TC5);
        Transfer_UART_DMA.rx_buff_loopback = 1;
    }
}




————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/hallo8888/article/details/146337620

使用特权

评论回复
沙发
xuanhuanzi| | 2025-3-25 11:20 | 只看该作者
使用缓冲区能提高程序运行流畅度

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2180

主题

16468

帖子

17

粉丝