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:中断分层处理时序

串口接收中断分层处理时序 硬件外设 驱动层(中断上下文) 应用层(主循环) RX引脚收到字节 RXC中断标志置位 CPU跳转到ISR 读取UDR0寄存器 获得1字节数据 rbuf_write()入队 head指针后移 中断返回(耗时极短) 主循环轮询 uart_read_byte() 从缓冲区取数据 协议解析/命令处理 耗时操作在此执行 中断触发 中断服务(微秒级) 主循环(毫秒级) 关键:中断中只做入队操作,不进行协议解析,保证中断响应实时性

编译与烧录

以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

验证方法

  1. 将串口TX(板载USB转串口)连接至PC,波特率9600。
  2. 打开串口助手,发送任意字节(如"ABC")。
  3. 观察串口助手接收区:应原样回显相同字节"ABC"。
  4. 连续快速发送大量数据(如1000字节),不应丢失字节——因为中断只入队,主循环持续读取。

判定标准:回显内容与发送完全一致,且快速发送时无数据丢失,即验证通过。若需进一步确认分层效果,可在主循环中人为添加延时(如10ms),仍能保证数据不丢失,说明中断入队与主循环读取解耦正确。