4. UART驱动与调试接口

UART,说白了就是串口。做嵌入式开发的人,没人离得开它。我入行那会儿,第一个调试手段就是拿串口线怼上去,看打印信息。直到现在,UART 依然是我最信赖的调试接口——简单、可靠、哪里都能用。

这一章,咱们把 UART 从协议到驱动,再到调试工具链,彻底捋一遍。

4.1 UART协议基础

UART 是异步串行通信。什么叫异步?就是没有单独的时钟线。收发双方靠约定的波特率来同步。

一个完整的数据帧长这样:

起始位 数据位(5~8位) 校验位(可选) 停止位(1/1.5/2位)
0(低电平) LSB 先发 奇/偶/无 1(高电平)

我最常用的配置是:115200 波特率、8 数据位、无校验、1 停止位,也就是常说的 115200-8N1。这个组合在大多数 MCU 上都能稳定工作。

经验之谈: 波特率误差要控制在 ±2% 以内。我遇到过一块板子,晶振精度不够,115200 实际跑出来差了 3%,偶尔丢字节。后来换了外部晶振,问题解决。

4.2 轮询模式驱动

轮询模式最简单。CPU 死等,直到数据收发完成。适合裸机调试,或者对实时性要求不高的场景。

下面是一个典型的轮询发送函数:

void uart_send_byte(uint8_t data) {
    // 等待发送缓冲区空
    while (!(UART->SR & UART_SR_TXE));
    // 写入数据
    UART->DR = data;
    // 等待发送完成
    while (!(UART->SR & UART_SR_TC));
}

接收也类似:

uint8_t uart_recv_byte(void) {
    // 等待接收数据就绪
    while (!(UART->SR & UART_SR_RXNE));
    return (uint8_t)(UART->DR & 0xFF);
}

嗯,这里要注意。轮询模式有个大坑:如果对方不发数据,你的程序就卡死在 while 循环里了。我早期做 Bootloader 时,就因为这个原因,导致整个系统在等待串口数据时无法响应看门狗,直接复位了。

避坑指南: 轮询接收一定要加超时机制。用 SysTick 或者硬件定时器做超时判断,别让 while 死等。

4.3 中断模式驱动

中断模式才是正经的驱动写法。CPU 不用傻等,数据来了自动触发中断。

中断接收的典型流程:

  1. 使能 UART 接收中断(RXNEIE)
  2. 在中断服务函数里读取 DR 寄存器
  3. 把数据存入缓冲区
  4. 清除中断标志

代码骨架:

void UART_IRQHandler(void) {
    if (UART->SR & UART_SR_RXNE) {
        uint8_t data = (uint8_t)(UART->DR & 0xFF);
        // 把 data 存入环形缓冲区
        ringbuf_put(&rx_ring, data);
    }
}

中断发送稍微讲究一点。我习惯用 发送缓冲区空中断(TXEIE) 来驱动发送。初始化时先发第一个字节,然后使能 TXEIE,后续字节在中断里逐个发送。全部发完后,关闭 TXEIE。

关键点: 中断服务函数要短、快、不阻塞。千万别在中断里做复杂处理,比如解析协议、写 Flash。把数据扔进缓冲区,回到主循环再处理。

4.4 环形缓冲区实现

环形缓冲区,也叫循环队列。它是中断驱动和主循环之间的桥梁。

为什么用环形缓冲区?说白了,就是解决 生产者和消费者速度不匹配 的问题。中断是生产者,主循环是消费者。中断随时可能来数据,主循环不能保证立刻处理。

一个简单的环形缓冲区结构体:

typedef struct {
    uint8_t *buffer;
    uint32_t head;   // 写指针
    uint32_t tail;   // 读指针
    uint32_t size;   // 缓冲区大小
} ringbuf_t;

核心操作就两个:

  • 写操作: 把数据放到 head 位置,head 递增。如果 head 追上 tail,说明缓冲区满了——要么覆盖旧数据,要么丢弃新数据。
  • 读操作: 从 tail 位置取数据,tail 递增。如果 tail 追上 head,说明缓冲区空了。

我见过很多人把环形缓冲区写复杂了。其实关键就一点:判断满和空。我的做法是:

  • 缓冲区空:head == tail
  • 缓冲区满:(head + 1) % size == tail

这种实现会浪费一个字节的空间,但判断逻辑极其清晰,不容易出 bug。

我的习惯: 缓冲区大小用 2 的幂次,比如 64、128、256。这样取模可以用位运算替代:head = (head + 1) & (size - 1),效率高不少。

4.5 串口调试工具链

工具用对了,调试效率翻倍。我这些年攒了一套串口调试工具链,分享给你。

工具 用途 我的评价
PuTTY 基础串口终端 轻量、稳定,适合快速看打印
SecureCRT 专业串口终端 支持脚本、日志、多会话,收费但值
Serial Port Monitor 抓取串口数据 调试协议栈时必备,能看到原始字节流
逻辑分析仪 硬件级抓波形 波特率不对、电平不匹配时,一抓就知

我个人最常用的组合是:SecureCRT + 逻辑分析仪。SecureCRT 看应用层数据,逻辑分析仪看物理层波形。两个视角一结合,大部分串口问题都能定位。

举个例子。有一次 Bootloader 下载固件总是校验失败。用 SecureCRT 看,数据好像没问题。但用逻辑分析仪一抓,发现停止位宽度不对。原来是时钟分频算错了,导致实际波特率偏差。这种问题,光靠软件是看不出来的。

硬件排查清单:
  • TX/RX 有没有接反?——我犯过至少三次这个错
  • 电平是否匹配?3.3V 和 5V 不能直连
  • 共地了吗?——不共地,数据全是乱码
  • 波特率误差在范围内吗?——用逻辑分析仪量一下实际波形

4.6 本章知识结构

下面这张图,把 UART 驱动与调试的核心脉络串起来了:

UART 驱动与调试接口知识结构 UART 协议基础 轮询模式驱动 中断模式驱动 环形缓冲区 串口终端工具 数据抓取工具 逻辑分析仪 硬件排查清单 从协议 → 驱动 → 缓冲区 → 工具链,形成完整闭环

从协议到驱动,再到缓冲区和工具链,这是一条完整的链路。每个环节都踩过坑,也都有优化空间。掌握了这些,你在 Bootloader 开发中遇到串口相关的问题,基本都能快速定位。