1. UART 基础:通信协议概述与帧格式
UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是嵌入式系统中最基础、最广泛使用的串行通信接口之一。它不需要时钟线,仅通过两根数据线(TX 发送、RX 接收)即可实现全双工通信。理解 UART 的协议层次、帧结构和参数配置,是后续开发 DMA 传输与错误处理流程的前提。
核心要点:UART 是异步、全双工、点对点的串行通信协议,依赖双方预先约定的波特率、数据位、停止位和校验位来保证数据正确解析。
1.1 UART 通信协议概述
UART 通信的物理层非常简单:发送端通过 TX 线逐位发送数据帧,接收端通过 RX 线逐位采样。由于没有独立的时钟线,收发双方必须使用相同的波特率(Baud Rate)来同步位时序。协议层定义了帧的起始、数据、校验和停止边界,确保接收端能从连续的比特流中正确提取每个字节。
1.2 UART 帧格式
一个完整的 UART 数据帧由以下字段按顺序组成:
| 字段 | 位宽 | 电平状态 | 说明 |
|---|---|---|---|
| 起始位 | 1 位 | 逻辑 0(低电平) | 标志帧开始,接收端据此同步采样时钟 |
| 数据位 | 5~9 位(常用 8 位) | LSB 优先 | 实际传输的有效数据,低位在前 |
| 校验位 | 0 或 1 位 | 奇/偶/强制 0/强制 1 | 可选,用于检错 |
| 停止位 | 1/1.5/2 位 | 逻辑 1(高电平) | 帧结束标志,同时给接收端处理时间 |
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% |
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 传输完成中断的触发时机。