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); // 回显
}
}
}
编译与运行
- 将
uart_drv.h、uart_drv.c、main.c加入工程,启动文件使用STM32F103标准启动文件。 - 编译命令示例(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处填入函数入口)。
验证方法与通过标准
- 硬件连接:将PA2(TX)与PA3(RX)短接,或连接USB-TTL模块至PC。
- 回环测试:烧录后,从PC串口助手发送任意字节(如
0x5A),应能立即收到相同字节0x5A。连续发送256字节不丢失,说明环形缓冲区与中断接收正常。 - 中断验证:在
UART_IRQHandler入口设断点,发送任意字节,应能触发中断并进入该函数。 - 波特率验证:若回显内容乱码,检查
USART2_BRR计算值是否匹配实际PCLK1频率(本案例按36MHz计算,若实际为72MHz需将BRR改为625)。
通过条件:回环数据与发送完全一致,且长时间(≥1分钟)连续传输无数据丢失或卡死。