1. UART 基础:通信协议概述与帧格式

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是嵌入式系统中最基础、最广泛使用的串行通信接口之一。它不需要时钟线,仅通过两根数据线(TX 发送、RX 接收)即可实现全双工通信。理解 UART 的协议层次、帧结构和参数配置,是后续开发 DMA 传输与错误处理流程的前提。

核心要点:UART 是异步、全双工、点对点的串行通信协议,依赖双方预先约定的波特率、数据位、停止位和校验位来保证数据正确解析。

1.1 UART 通信协议概述

UART 通信的物理层非常简单:发送端通过 TX 线逐位发送数据帧,接收端通过 RX 线逐位采样。由于没有独立的时钟线,收发双方必须使用相同的波特率(Baud Rate)来同步位时序。协议层定义了帧的起始、数据、校验和停止边界,确保接收端能从连续的比特流中正确提取每个字节。

UART 通信系统架构 发送端 (TX) CPU / DMA 写入 接收端 (RX) CPU / DMA 读取 TX 数据线 RX 数据线 通信参数约定 • 波特率:双方必须一致 • 数据位:5/6/7/8 位 • 停止位:1/1.5/2 位 • 校验位:无/奇/偶/强制

1.2 UART 帧格式

一个完整的 UART 数据帧由以下字段按顺序组成:

字段 位宽 电平状态 说明
起始位 1 位 逻辑 0(低电平) 标志帧开始,接收端据此同步采样时钟
数据位 5~9 位(常用 8 位) LSB 优先 实际传输的有效数据,低位在前
校验位 0 或 1 位 奇/偶/强制 0/强制 1 可选,用于检错
停止位 1/1.5/2 位 逻辑 1(高电平) 帧结束标志,同时给接收端处理时间
UART 帧格式(8N1 典型配置) 起始 1 位 D0 LSB D1 D2 D3 D4 D5 D6 D7 MSB 校验 可选 停止 1/2 位 空闲状态为高电平,起始位下降沿触发接收采样

1.3 波特率

波特率定义每秒传输的符号数(单位:baud)。在 UART 中,1 波特 = 1 位/秒。常用波特率包括:

  • 9600:传统低速设备,如 GPS 模块、工业传感器
  • 115200:嵌入式调试串口最常用,兼顾速度与稳定性
  • 921600:高速传输,适用于短距离、低干扰场景
  • 3000000(3Mbps):部分 SoC 支持,需配合 DMA 使用
驱动开发提示:波特率由 UART 外设时钟分频得到。Linux 内核中通过 uart_get_baud_rate() 和 uart_update_timeout() 管理波特率与超时时间。波特率误差应小于 2%,否则接收端可能采样错误。

1.4 数据位

数据位决定每个帧传输的有效数据位数。常见配置:

数据位 典型用途 备注
5 位 老式电传打字机、特殊工业协议 极少使用
6 位 某些 ASCII 子集通信 不常见
7 位 纯 ASCII 字符传输(0x00~0x7F) 部分 Modbus 变体使用
8 位 最通用,字节流传输 Linux 串口驱动默认配置
9 位 多机通信地址/数据标志 需硬件支持 9 位模式

1.5 停止位

停止位位于帧末尾,为高电平,用于:

  • 保证帧间有足够的时间间隔
  • 给接收端处理已接收数据的时间
  • 检测帧边界:若停止位不是高电平,则产生帧错误
停止位长度 应用场景
1 位 标准配置,平衡效率与可靠性
1.5 位 某些 5 位数据模式下的特殊要求
2 位 低速或高噪声环境,增加容错

1.6 校验位

校验位提供简单的检错能力。Linux 内核通过 termios 结构中的 c_cflag 控制校验模式:

校验类型 c_cflag 标志 校验位计算 检错能力
无校验 (None) 清除 PARENB 无校验位 无
奇校验 (Odd) PARENB | PARODD 数据位中 1 的个数为奇数 检测奇数个位翻转
偶校验 (Even) PARENB 数据位中 1 的个数为偶数 检测奇数个位翻转
强制 0 (Space) PARENB | CMSPAR 校验位恒为 0 无检错,用于特殊协议
强制 1 (Mark) PARENB | PARODD | CMSPAR 校验位恒为 1 无检错,用于特殊协议
驱动开发注意:校验错误(Parity Error)在 Linux 串口驱动中通过 ASYNC_PARITY_ERROR 标志上报,并在 tty_ldisc 层转换为 TTY_PARITY 错误字符。DMA 传输模式下,校验错误需要从 UART 状态寄存器中读取并记录到 DMA 描述符的错误标志位。

1.7 常见配置组合与传输效率

以下表格列出典型配置下的有效数据传输率(假设波特率 115200):

配置 帧总位数 有效数据率 效率
8N1(8 数据位 + 无校验 + 1 停止位) 10 11520 字节/秒 80%
8E1(8 数据位 + 偶校验 + 1 停止位) 11 10472 字节/秒 72.7%
8N2(8 数据位 + 无校验 + 2 停止位) 11 10472 字节/秒 72.7%
7E2(7 数据位 + 偶校验 + 2 停止位) 11 10472 字节/秒 63.6%
UART 参数配置与驱动数据结构关系 用户空间 termios / stty 内核串口核心 uart_port / uart_ops UART 硬件 波特率发生器 / 移位寄存器 参数传递路径 1. stty 设置波特率、数据位、停止位、校验位 → termios → uart_set_options() 2. uart_change_speed() → 调用 ops->set_termios() → 配置硬件寄存器

1.8 驱动开发中的参数配置示例

在 Linux UART 驱动中,参数配置通过 uart_ops->set_termios() 回调实现。以下是一个典型的配置代码片段:

static void my_uart_set_termios(struct uart_port *port,
                                struct ktermios *termios,
                                const struct ktermios *old)
{
    unsigned int baud, cflag;
    unsigned int data_bits, stop_bits, parity;

    cflag = termios->c_cflag;

    /* 1. 提取数据位 */
    switch (cflag & CSIZE) {
    case CS5: data_bits = 5; break;
    case CS6: data_bits = 6; break;
    case CS7: data_bits = 7; break;
    case CS8: data_bits = 8; break;
    default:  data_bits = 8;
    }

    /* 2. 提取停止位 */
    stop_bits = (cflag & CSTOPB) ? 2 : 1;

    /* 3. 提取校验位 */
    if (cflag & PARENB) {
        if (cflag & PARODD)
            parity = 'O';   /* 奇校验 */
        else
            parity = 'E';   /* 偶校验 */
    } else {
        parity = 'N';       /* 无校验 */
    }

    /* 4. 计算并设置波特率 */
    baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16);
    port->uartclk = baud * 16;

    /* 5. 写入硬件寄存器 */
    writel(data_bits - 5, port->membase + UART_LCR);   /* 数据位 */
    writel(stop_bits - 1, port->membase + UART_LCR);   /* 停止位 */
    writel(parity,        port->membase + UART_LCR);   /* 校验位 */
    writel(baud,          port->membase + UART_BAUD);  /* 波特率 */

    /* 6. 更新超时时间 */
    uart_update_timeout(port, cflag, baud);
}
关键点:波特率计算使用 uart_get_baud_rate() 进行范围检查和自动降级;uart_update_timeout() 用于更新 FIFO 超时和 DMA 传输超时参数,直接影响 DMA 传输完成中断的触发时机。