4. UART通信驱动开发

任务目标

本案例实现AUTOSAR Bootloader中基于UART的底层通信驱动,完成MCU串口外设的初始化、单字节收发、中断处理以及环形缓冲区管理,为后续Bootloader刷写协议(如XCP或自定义帧协议)提供稳定的字节流传输通道。驱动以查询与中断两种模式工作,中断模式用于接收不定长数据,查询模式用于发送应答帧。

环境依赖

  • 硬件:任意集成UART外设的MCU(本案例以STM32F103的USART2为例,寄存器操作,不依赖HAL库)
  • 工具链:arm-none-eabi-gcc + Makefile,或任何支持寄存器级C编译的嵌入式工具链
  • 无第三方库依赖

驱动实现(uart_drv.c / uart_drv.h)

以下代码为完整可编译的UART驱动,包含寄存器定义、环形缓冲区、初始化、中断服务与收发API。代码直接操作USART2寄存器,时钟来自APB1(PCLK1=36MHz),配置为115200-8-N-1。

// uart_drv.h
#ifndef UART_DRV_H
#define UART_DRV_H
#include <stdint.h>

#define UART_RX_BUF_SIZE  256
#define UART_TX_BUF_SIZE  128

void     UART_Init(void);
void     UART_SendByte(uint8_t byte);
uint8_t  UART_ReceiveByte(void);       // 阻塞查询接收
uint16_t UART_RxBytesAvailable(void);  // 环形缓冲中待读字节数
void     UART_IRQHandler(void);        // 中断入口(USART2_IRQn)

#endif

// uart_drv.c
#include "uart_drv.h"
#include <stddef.h>

// ---- 寄存器映射(STM32F103 USART2)----
#define USART2_BASE   0x40004400UL
#define USART2_SR     (*(volatile uint32_t *)(USART2_BASE + 0x00))
#define USART2_DR     (*(volatile uint32_t *)(USART2_BASE + 0x04))
#define USART2_BRR    (*(volatile uint32_t *)(USART2_BASE + 0x08))
#define USART2_CR1    (*(volatile uint32_t *)(USART2_BASE + 0x0C))
#define USART2_CR2    (*(volatile uint32_t *)(USART2_BASE + 0x10))
#define USART2_CR3    (*(volatile uint32_t *)(USART2_BASE + 0x14))

// RCC寄存器(简化:使能USART2与GPIOA时钟)
#define RCC_APB1ENR   (*(volatile uint32_t *)0x4002101CUL)
#define RCC_APB2ENR   (*(volatile uint32_t *)0x40021018UL)
#define RCC_APB1ENR_USART2EN  (1 << 17)
#define RCC_APB2ENR_IOPAEN    (1 << 2)

// GPIOA寄存器(USART2_TX=PA2, USART2_RX=PA3)
#define GPIOA_CRL     (*(volatile uint32_t *)0x40010800UL)
#define GPIOA_CRH     (*(volatile uint32_t *)0x40010804UL)

// 状态位
#define SR_TXE   (1 << 7)
#define SR_RXNE  (1 << 5)

// ---- 环形缓冲区 ----
static volatile uint8_t  rx_buf[UART_RX_BUF_SIZE];
static volatile uint16_t rx_head = 0;  // 写入索引(ISR更新)
static volatile uint16_t rx_tail = 0;  // 读取索引(主循环更新)

static volatile uint8_t  tx_buf[UART_TX_BUF_SIZE];
static volatile uint16_t tx_head = 0;
static volatile uint16_t tx_tail = 0;

static void Buf_Push(volatile uint8_t *buf, uint16_t size,
                     volatile uint16_t *head, volatile uint16_t *tail, uint8_t byte)
{
    uint16_t next = (uint16_t)((*head + 1) % size);
    if (next != *tail) {          // 非满
        buf[*head] = byte;
        *head = next;
    }
}

static uint8_t Buf_Pop(volatile uint8_t *buf, uint16_t size,
                       volatile uint16_t *head, volatile uint16_t *tail)
{
    uint8_t byte = 0;
    if (*tail != *head) {         // 非空
        byte = buf[*tail];
        *tail = (uint16_t)((*tail + 1) % size);
    }
    return byte;
}

// ---- 初始化 ----
void UART_Init(void)
{
    // 1. 使能时钟
    RCC_APB2ENR |= RCC_APB2ENR_IOPAEN;
    RCC_APB1ENR |= RCC_APB1ENR_USART2EN;

    // 2. PA2=TX(复用推挽50MHz), PA3=RX(浮空输入)
    GPIOA_CRH &= ~(0xFFUL << 0);          // 清CRH[7:0](PA2-PA3在CRH低字节)
    GPIOA_CRH |=  (0x0BUL << 0);          // PA2: AF_PP_50MHz = 1011
    GPIOA_CRH |=  (0x04UL << 4);          // PA3: 浮空输入 = 0100

    // 3. 设置波特率 115200 @ PCLK1=36MHz → BRR=36000000/115200=312.5≈313(0x139)
    USART2_BRR = 313;

    // 4. 8数据位、无校验、1停止位(默认复位值满足),使能发送、接收、RXNE中断
    USART2_CR1 = (1 << 13) |   // UE
                 (1 << 3)  |   // TE
                 (1 << 2)  |   // RE
                 (1 << 5);     // RXNEIE

    // 5. 清空缓冲区索引
    rx_head = rx_tail = 0;
    tx_head = tx_tail = 0;
}

// ---- 发送(查询模式,带超时保护)----
void UART_SendByte(uint8_t byte)
{
    // 等待TXE置位(超时约1ms @ 115200)
    uint32_t timeout = 115200 / 10;
    while (!(USART2_SR & SR_TXE)) {
        if (--timeout == 0) return;
    }
    USART2_DR = byte;
}

// ---- 接收(查询模式,阻塞)----
uint8_t UART_ReceiveByte(void)
{
    while (!(USART2_SR & SR_RXNE)) { }
    return (uint8_t)(USART2_DR & 0xFF);
}

// ---- 中断接收(推荐用于Bootloader帧接收)----
void UART_IRQHandler(void)
{
    if (USART2_SR & SR_RXNE) {
        uint8_t byte = (uint8_t)(USART2_DR & 0xFF);
        Buf_Push(rx_buf, UART_RX_BUF_SIZE, &rx_head, &rx_tail, byte);
    }
}

uint16_t UART_RxBytesAvailable(void)
{
    // 注意:head由ISR写,tail由主循环写,此处读取时关闭中断保证一致性
    __disable_irq();
    uint16_t count = (uint16_t)((rx_head + UART_RX_BUF_SIZE - rx_tail) % UART_RX_BUF_SIZE);
    __enable_irq();
    return count;
}

// 供主循环调用的非阻塞读取
uint8_t UART_RxReadByte(void)
{
    return Buf_Pop(rx_buf, UART_RX_BUF_SIZE, &rx_head, &rx_tail);
}

中断向量接入

在启动文件(startup_stm32f103.s)中,将USART2_IRQHandler弱符号替换为本驱动的强符号定义。若使用STM32标准外设库的启动文件,默认已声明该弱符号,直接链接即可。若使用其他MCU,需将UART_IRQHandler映射到对应UART的中断向量表。

最小验证代码(main.c)

#include "uart_drv.h"

// 回环测试:将接收到的字节原样发送回去
int main(void)
{
    UART_Init();

    while (1) {
        if (UART_RxBytesAvailable() > 0) {
            uint8_t byte = UART_RxReadByte();
            UART_SendByte(byte);   // 回显
        }
    }
}

编译与运行

  1. 将uart_drv.h、uart_drv.c、main.c加入工程,启动文件使用STM32F103标准启动文件。
  2. 编译命令示例(Makefile核心片段):
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c uart_drv.c main.c
arm-none-eabi-ld -T stm32f103.ld uart_drv.o main.o startup.o -o uart_test.elf
arm-none-eabi-objcopy -O binary uart_test.elf uart_test.bin

链接脚本需包含USART2_IRQHandler向量地址(0x08000000 + 39*4 = 0x0800009C处填入函数入口)。

验证方法与通过标准

  1. 硬件连接:将PA2(TX)与PA3(RX)短接,或连接USB-TTL模块至PC。
  2. 回环测试:烧录后,从PC串口助手发送任意字节(如0x5A),应能立即收到相同字节0x5A。连续发送256字节不丢失,说明环形缓冲区与中断接收正常。
  3. 中断验证:在UART_IRQHandler入口设断点,发送任意字节,应能触发中断并进入该函数。
  4. 波特率验证:若回显内容乱码,检查USART2_BRR计算值是否匹配实际PCLK1频率(本案例按36MHz计算,若实际为72MHz需将BRR改为625)。

通过条件:回环数据与发送完全一致,且长时间(≥1分钟)连续传输无数据丢失或卡死。