新闻中心

EEPW首页 > 嵌入式系统 > 设计应用 > 基于 STM32F4 的串口通信驱动实现详解(环形缓冲区版)

基于 STM32F4 的串口通信驱动实现详解(环形缓冲区版)

作者:嵌入式芯视野 时间:2025-07-09 来源:今日头条 收藏

在嵌入式系统开发中,串口通信(UART)是最常用的基础通信方式之一。为了解决串口数据读写的不连续性问题,通常会配合环形缓冲区使用,以实现高效、稳定的数据收发缓存管理。

本文介绍一个基于 STM32F4 系列 MCU 编写的,采用中断方式配合发送/接收缓冲区,并封装为控制台接口,便于在系统中调用。

一、串口缓冲区定义与初始化

串口收发数据通常不直接读写寄存器,而是通过缓冲机制管理数据流。本例中使用了两个环形缓冲区 rxbuf 和 txbuf,分别用于接收和发送:

static unsigned char rxbuf[TTY_RXBUF_SIZE];  // 接收缓冲区static unsigned char txbuf[TTY_TXBUF_SIZE];  // 发送缓冲区static ring_buf_t rbsend, rbrecv;            // 缓冲区控制结构体

并在串口初始化时调用 ring_buf_init() 对缓冲区进行初始化。

二、串口初始化函数 uart_init

static void uart_init(int baudrate)

该函数用于初始化 USART1,并完成如下步骤:

  1. 初始化收发缓冲区控制结构体。

  2. 打开 GPIOA 和 USART1 时钟。

  3. 配置 USART1 的引脚复用功能(PA9 → TX,PA10 → RX)。

  4. 配置 GPIO 模式(复用模式,无上下拉)。

  5. 配置串口参数(波特率等)。

  6. 配置 NVIC 中断优先级并使能 USART1 中断。

通过将底层串口配置封装在 uart_init() 中,使得后续调用更加简洁。


三、串口写函数 uart_write

static unsigned int uart_write(const void *buf, unsigned int len)

此函数负责将要发送的数据写入发送缓冲区,并开启发送中断:

  • 通过 ring_buf_put() 将数据写入 rbsend。

  • 开启 USART_IT_TXE 发送中断。

  • 返回实际写入字节数。

由于发送在中断中进行,所以只需触发中断即可自动依次发送缓冲区数据。


四、串口读函数 uart_read

static unsigned int uart_read(void *buf, unsigned int len)

用于从接收缓冲区读取数据:

  • 通过 ring_buf_get() 从 rbrecv 中取出数据放入 buf。

  • 返回实际读取长度。

适用于非阻塞方式的读取调用。


五、缓冲区状态查询函数

代码中定义了以下缓冲区状态查询接口:

static bool tx_isfull(void);   // 判断发送缓冲区是否满bool tx_isempty(void);         // 判断发送缓冲区是否为空bool rx_isempty(void);         // 判断接收缓冲区是否为空

这些函数用于上层逻辑判断是否可以继续发送、是否有接收数据等,提升串口使用灵活性。


六、TTY 接口结构体封装

串口驱动最终以一个结构体 tty_t 形式暴露接口:

const tty_t tty = {
    uart_init,
    uart_write,
    uart_read,
    tx_isfull,
    tx_isempty,
    rx_isempty
};

这种方式便于统一管理串口控制接口,适合在大型项目中引入“控制台抽象层”统一管理多个串口。


七、串口中断处理函数 USART1_IRQHandler

这是串口驱动的核心部分,负责响应 USART1 的收发中断:

void USART1_IRQHandler(void)

主要包含以下处理逻辑:

  1. 接收中断 RXNE:

  2. 从接收寄存器读取数据。

  3. 存入接收缓冲区 rbrecv。

  4. 发送中断 TXE:

  5. 从发送缓冲区 rbsend 中取出下一个字节发送。

  6. 若无数据可发,关闭发送中断。

  7. 溢出错误中断 ORE_RX:

  8. 读取一次数据清除溢出标志位。

通过中断方式处理串口收发,不仅避免了阻塞操作,还能在高速数据传输中保持系统响应性。


八、总结

本驱动模块实现了一个完整的串口通信功能,具备如下特点:

  • 支持发送与接收双缓冲。

  • 使用中断驱动方式收发数据。

  • 提供状态判断接口,便于上层调用。

  • 封装为 tty_t 控制台结构,支持模块化应用。

其设计适用于嵌入式系统中多个串口同时工作的场景,也适合作为 CLI 控制台、调试口或上位机通信口的底层驱动支撑。结合环形缓冲区,可有效避免数据丢失或阻塞,是一种常用、稳定的串口通信实现方式。

开源代码:

#include "stm32f4xx.h"#include "ringbuffer.h"#include "tty.h"#include "public.h"   #include <string.h>#if (TTY_RXBUF_SIZE & (TTY_RXBUF_SIZE - 1)) != 0 
    #error "TTY_RXBUF_SIZE must be power of 2!"#endif#if (TTY_TXBUF_SIZE & (TTY_TXBUF_SIZE - 1)) != 0 
    #error "TTY_RXBUF_SIZE must be power of 2!"#endifstatic unsigned char rxbuf[TTY_RXBUF_SIZE];         /*接收缓冲区 */static unsigned char txbuf[TTY_TXBUF_SIZE];         /*发送缓冲区 */static ring_buf_t rbsend, rbrecv;                   /*收发缓冲区管理*//*
 * @brief     串口初始化
 * @param[in]   baudrate - 波特率
 * @return      none
 */static void uart_init(int baudrate){
    ring_buf_init(&rbsend, txbuf, sizeof(txbuf));/*初始化环形缓冲区 */
    ring_buf_init(&rbrecv, rxbuf, sizeof(rxbuf)); 
    
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA , ENABLE);
    
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);  
    
    gpio_conf(GPIOA, GPIO_Mode_AF, GPIO_PuPd_NOPULL, 
              GPIO_Pin_9 | GPIO_Pin_10);
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    uart_conf(USART1, baudrate);                    /*串口配置*/
    
    nvic_conf(USART1_IRQn, 1, 1);
    
}/*
 * @brief     向串口发送缓冲区内写入数据并启动发送
 * @param[in]   buf       -  数据缓存
 * @param[in]   len       -  数据长度
 * @return      实际写入长度(如果此时缓冲区满,则返回len)
 */static unsigned int uart_write(const void *buf, unsigned int len){   
    unsigned int ret;
    ret = ring_buf_put(&rbsend, (unsigned char *)buf, len);  
    USART_ITConfig(USART1, USART_IT_TXE, ENABLE);    return ret; 
}/*
 * @brief     读取串口接收缓冲区的数据
 * @param[in]   buf       -  数据缓存
 * @param[in]   len       -  数据长度
 * @return      (实际读取长度)如果接收缓冲区的有效数据大于len则返回len否则返回缓冲
 *              区有效数据的长度
 */static unsigned int uart_read(void *buf, unsigned int len){    return ring_buf_get(&rbrecv, (unsigned char *)buf, len);
}/*发送缓冲区满*/static bool tx_isfull(void){    return ring_buf_len(&rbsend) == TTY_TXBUF_SIZE;
}/*发送缓冲区空*/bool tx_isempty(void){    return ring_buf_len(&rbsend) == 0;
}/*接收缓冲区空*/bool rx_isempty(void){    return ring_buf_len(&rbrecv) == 0;
}/*控制台接口定义 -------------------------------------------------------------*/const tty_t tty = {
    uart_init,
    uart_write,
    uart_read,
    tx_isfull,
    tx_isempty,
    rx_isempty
};/*
 * @brief     串口1收发中断
 * @param[in]   none
 * @return      none
 */void USART1_IRQHandler(void){     
    unsigned char data;    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        data = USART_ReceiveData(USART1);
        ring_buf_put(&rbrecv, &data, 1);           /*将数据放入接收缓冲区*/             
    }    if (USART_GetITStatus(USART1, USART_IT_TXE) != RESET) {        if (ring_buf_get(&rbsend, &data, 1))      /*从缓冲区中取出数据---*/
            USART_SendData(USART1, data);            
        else{
            USART_ITConfig(USART1, USART_IT_TXE, DISABLE);    
        }
    }    if (USART_GetITStatus(USART1, USART_IT_ORE_RX) != RESET) {
        data = USART_ReceiveData(USART1);        
    }
}



关键词: 串口通信驱动

评论


技术专区

关闭