在嵌入式系统开发中,UART(Universal Asynchronous Receiver/Transmitter)是最常用的串行通信接口之一。它通过两根数据线(TX和RX)实现全双工通信,不需要时钟信号,依靠双方预先约定的波特率进行数据传输。
UART通信有几个关键特性:
在STM32等常见MCU中,UART外设通常提供多种工作模式:
提示:对于实时性要求不高但数据量较大的应用,中断模式是最常用的折中方案,既能保证及时响应,又不会过度占用CPU资源。
在串口通信中,数据到达的时间是不确定的,而处理数据需要一定时间。如果没有缓冲区,当新数据到达时如果前一个数据还未处理完毕,就会导致数据丢失。环形缓冲区(Ring Buffer)解决了这个问题,它提供以下优势:
一个典型的环形缓冲区需要以下组件:
c复制#define BUF_SIZE 256
typedef struct {
uint8_t buffer[BUF_SIZE];
volatile uint16_t head; // 写入位置
volatile uint16_t tail; // 读取位置
} ring_buffer_t;
关键操作:
(head + 1) % BUF_SIZE注意:head和tail变量必须声明为volatile,因为它们会在中断和主程序中被同时访问,防止编译器优化导致的问题。
使用STM32 HAL库进行UART中断接收需要以下步骤:
c复制huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart1);
c复制HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
这个函数会:
数据接收的完整中断处理流程:
UART_Receive_IT中断服务程序HAL_UART_RxCpltCallback回调函数HAL_UART_Receive_IT典型回调函数实现:
c复制void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
ring_buffer_write(&rx_buf, rx_byte);
HAL_UART_Receive_IT(huart, &rx_byte, 1);
}
}
完整的环形缓冲区操作函数:
c复制void ring_buffer_init(ring_buffer_t *buf) {
buf->head = 0;
buf->tail = 0;
}
bool ring_buffer_write(ring_buffer_t *buf, uint8_t data) {
uint16_t next_head = (buf->head + 1) % BUF_SIZE;
if(next_head == buf->tail) // 缓冲区满
return false;
buf->buffer[buf->head] = data;
buf->head = next_head;
return true;
}
bool ring_buffer_read(ring_buffer_t *buf, uint8_t *data) {
if(buf->tail == buf->head) // 缓冲区空
return false;
*data = buf->buffer[buf->tail];
buf->tail = (buf->tail + 1) % BUF_SIZE;
return true;
}
在主循环中处理接收到的数据:
c复制while(1) {
uint8_t data;
if(ring_buffer_read(&rx_buf, &data)) {
// 处理接收到的数据
process_data(data);
}
// 其他任务
system_tasks();
}
缓冲区大小选择:
临界区保护:
c复制__disable_irq();
ring_buffer_write(&buf, data);
__enable_irq();
在写入/读取操作前后禁用中断,防止竞态条件
DMA结合环形缓冲区:
对于高速数据流,可以使用DMA将数据直接传输到环形缓冲区,减少中断频率
检查波特率设置:
缓冲区溢出诊断:
c复制uint16_t ring_buffer_avail(ring_buffer_t *buf) {
return (buf->head >= buf->tail) ?
(buf->head - buf->tail) :
(BUF_SIZE - buf->tail + buf->head);
}
定期检查缓冲区剩余空间,发现接近满时报警
中断优先级配置:
错误恢复机制:
c复制void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
// 重新初始化UART
HAL_UART_DeInit(huart);
HAL_UART_Init(huart);
HAL_UART_Receive_IT(huart, &rx_byte, 1);
}
数据校验:
流量控制:
在实际项目中,我通常会添加一个简单的统计功能,记录最大缓冲区使用量和溢出次数,这对后期性能调优非常有帮助。例如:
c复制typedef struct {
ring_buffer_t buf;
uint16_t max_usage;
uint32_t overflow_count;
} monitored_buffer_t;
void monitored_buffer_write(monitored_buffer_t *mbuf, uint8_t data) {
uint16_t usage = ring_buffer_avail(&mbuf->buf);
if(usage > mbuf->max_usage) mbuf->max_usage = usage;
if(!ring_buffer_write(&mbuf->buf, data)) {
mbuf->overflow_count++;
}
}
这种实现方式不仅解决了基本的数据接收问题,还提供了丰富的调试信息,当通信出现问题时可以快速定位原因。