基于 STM32F4 的串口通信驱动实现详解(环形缓冲区版)
在嵌入式系统开发中,串口通信(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,并完成如下步骤:
初始化收发缓冲区控制结构体。
打开 GPIOA 和 USART1 时钟。
配置 USART1 的引脚复用功能(PA9 → TX,PA10 → RX)。
配置 GPIO 模式(复用模式,无上下拉)。
配置串口参数(波特率等)。
配置 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)
主要包含以下处理逻辑:
接收中断 RXNE:
从接收寄存器读取数据。
存入接收缓冲区 rbrecv。
发送中断 TXE:
从发送缓冲区 rbsend 中取出下一个字节发送。
若无数据可发,关闭发送中断。
溢出错误中断 ORE_RX:
读取一次数据清除溢出标志位。
通过中断方式处理串口收发,不仅避免了阻塞操作,还能在高速数据传输中保持系统响应性。
八、总结
本驱动模块实现了一个完整的串口通信功能,具备如下特点:
支持发送与接收双缓冲。
使用中断驱动方式收发数据。
提供状态判断接口,便于上层调用。
封装为 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);
}
}
评论