4. 串口接收中断的分层处理
任务说明
本案例实现串口接收中断的分层处理:底层驱动在RX中断中将字节写入环形缓冲区,上层应用通过独立接口读取数据。核心目标是让中断处理时间极短(仅入队),而数据解析、协议处理等耗时操作全部放在主循环中完成,避免中断阻塞。
分层结构
- 驱动层(ringbuf.c/h):环形缓冲区实现,提供初始化、写入、读取接口。
- 驱动层(uart_drv.c/h):串口初始化与中断服务函数,中断中调用ringbuf写入。
- 应用层(app.c/h):主循环中通过接口读取数据并处理。
代码实现
ringbuf.h
#ifndef RINGBUF_H
#define RINGBUF_H
#include <stdint.h>
#include <stdbool.h>
#define RBUF_SIZE 256
typedef struct {
uint8_t buffer[RBUF_SIZE];
volatile uint16_t head; // 写入位置
volatile uint16_t tail; // 读取位置
} ringbuf_t;
void rbuf_init(ringbuf_t *rb);
bool rbuf_write(ringbuf_t *rb, uint8_t data);
bool rbuf_read(ringbuf_t *rb, uint8_t *data);
uint16_t rbuf_count(const ringbuf_t *rb);
#endif
ringbuf.c
#include "ringbuf.h"
void rbuf_init(ringbuf_t *rb)
{
rb->head = 0;
rb->tail = 0;
}
bool rbuf_write(ringbuf_t *rb, uint8_t data)
{
uint16_t next = (uint16_t)((rb->head + 1) % RBUF_SIZE);
if (next == rb->tail) {
return false; // 缓冲区满
}
rb->buffer[rb->head] = data;
rb->head = next;
return true;
}
bool rbuf_read(ringbuf_t *rb, uint8_t *data)
{
if (rb->head == rb->tail) {
return false; // 缓冲区空
}
*data = rb->buffer[rb->tail];
rb->tail = (uint16_t)((rb->tail + 1) % RBUF_SIZE);
return true;
}
uint16_t rbuf_count(const ringbuf_t *rb)
{
if (rb->head >= rb->tail) {
return (uint16_t)(rb->head - rb->tail);
}
return (uint16_t)(RBUF_SIZE - rb->tail + rb->head);
}
uart_drv.h
#ifndef UART_DRV_H
#define UART_DRV_H
#include "ringbuf.h"
void uart_init(void);
void uart_isr_handler(void); // 由中断向量表调用
#endif
uart_drv.c
#include "uart_drv.h"
#include <avr/io.h>
#include <avr/interrupt.h>
static ringbuf_t rx_ring;
void uart_init(void)
{
rbuf_init(&rx_ring);
// 以ATmega328P为例:9600bps, 8N1
UBRR0H = 0;
UBRR0L = 103; // 16MHz / (16*9600) - 1
UCSR0B = (1 << RXEN0) | (1 << RXCIE0); // 使能接收和接收中断
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); // 8数据位
}
ISR(USART_RX_vect)
{
uint8_t byte = UDR0;
rbuf_write(&rx_ring, byte); // 中断中仅入队,快速返回
}
void uart_isr_handler(void)
{
// 备用入口:若使用非AVR平台,可在统一中断入口中调用
}
// 上层读取接口
bool uart_read_byte(uint8_t *data)
{
return rbuf_read(&rx_ring, data);
}
uint16_t uart_available(void)
{
return rbuf_count(&rx_ring);
}
app.c(主循环处理)
#include "uart_drv.h"
#include <avr/io.h>
// 简单示例:将收到的字节原样从TX发回(回环测试)
static void uart_send_byte(uint8_t data)
{
while (!(UCSR0A & (1 << UDRE0)));
UDR0 = data;
}
int main(void)
{
uart_init();
sei(); // 全局中断使能
uint8_t rx_data;
while (1) {
if (uart_read_byte(&rx_data)) {
uart_send_byte(rx_data); // 上层处理:回显
}
// 此处可添加其他协议解析、命令处理等耗时逻辑
}
}
SVG:中断分层处理时序
编译与烧录
以ATmega328P(Arduino Uno板,16MHz)为例:
# 使用avr-gcc编译
avr-gcc -mmcu=atmega328p -DF_CPU=16000000UL -Os -o uart_demo.elf ringbuf.c uart_drv.c app.c
avr-objcopy -O ihex uart_demo.elf uart_demo.hex
# 使用avrdude烧录
avrdude -c arduino -p m328p -P /dev/ttyUSB0 -U flash:w:uart_demo.hex
验证方法
- 将串口TX(板载USB转串口)连接至PC,波特率9600。
- 打开串口助手,发送任意字节(如"ABC")。
- 观察串口助手接收区:应原样回显相同字节"ABC"。
- 连续快速发送大量数据(如1000字节),不应丢失字节——因为中断只入队,主循环持续读取。
判定标准:回显内容与发送完全一致,且快速发送时无数据丢失,即验证通过。若需进一步确认分层效果,可在主循环中人为添加延时(如10ms),仍能保证数据不丢失,说明中断入队与主循环读取解耦正确。