3. FIFO 原理与作用:FIFO 深度、触发阈值、FIFO 在 UART 中的角色、降低 CPU 中断频率
在 UART 通信中,传统的中断模式每接收或发送一个字节就会触发一次 CPU 中断。当波特率较高或数据量较大时,频繁的中断会严重消耗 CPU 资源,导致系统响应变慢。FIFO(First In First Out,先进先出)缓冲区的引入,从根本上解决了这一问题。
3.1 FIFO 在 UART 中的角色
FIFO 是一种硬件缓冲区,位于 UART 控制器内部,用于暂存接收和发送的数据。它允许 CPU 以“批量”方式处理数据,而不是逐字节处理。FIFO 在 UART 中的核心角色包括:
- 数据缓冲:在接收路径中,FIFO 暂存从串行线路接收到的字节,等待 CPU 或 DMA 读取;在发送路径中,FIFO 暂存 CPU 写入的待发送数据,由 UART 硬件自动逐字节发送。
- 速率匹配:桥接 CPU 总线速度与串行线路速度之间的差异,允许 CPU 以突发方式写入/读取数据,而 UART 以恒定波特率处理串行数据。
- 中断聚合:通过设置触发阈值,将多个字节的中断合并为一次中断,显著降低中断频率。
- 防止数据溢出:在接收路径中,FIFO 提供额外的缓冲深度,防止 CPU 未能及时读取时数据丢失。
3.2 FIFO 深度
FIFO 深度是指 FIFO 能够存储的数据字节数。常见的 UART FIFO 深度有 16 字节、32 字节、64 字节,部分高端芯片支持 128 字节或 256 字节。FIFO 深度直接影响系统的实时性和中断频率。
| FIFO 深度 | 典型应用场景 | 中断频率(相对) | 实时性 |
|---|---|---|---|
| 16 字节 | 低波特率、简单传感器通信 | 高 | 较高 |
| 32 字节 | 通用嵌入式 UART 通信 | 中 | 适中 |
| 64 字节 | 高速数据采集、批量传输 | 低 | 较低 |
| 128 字节 | DMA 协同、大数据量场景 | 极低 | 低 |
3.3 触发阈值
触发阈值(Trigger Threshold)是 FIFO 中数据量达到某个水平时,UART 硬件向 CPU 发送中断的设定值。当 FIFO 中的数据字节数达到或超过该阈值时,硬件自动触发中断。触发阈值是可编程的,通常支持 1、4、8、14 等不同级别(具体取决于芯片)。
触发阈值的工作原理如下:
- 接收 FIFO 触发阈值:当 RX FIFO 中的数据字节数达到设定阈值时,触发接收中断。CPU 在中断服务程序中一次性读取阈值数量的字节。
- 发送 FIFO 触发阈值:当 TX FIFO 中的空余空间达到设定阈值时,触发发送中断。CPU 在中断服务程序中一次性写入多个字节,填满 FIFO。
3.4 降低 CPU 中断频率
FIFO 降低 CPU 中断频率的核心机制是“中断聚合”。在没有 FIFO 的传统 UART 中,每接收或发送一个字节就产生一次中断。假设波特率为 115200 bps,每字节 10 位(1 起始位 + 8 数据位 + 1 停止位),则每秒产生 11520 次中断。CPU 需要频繁进入和退出中断上下文,开销巨大。
引入 FIFO 后,通过设置触发阈值,中断频率可以降低为原来的 1/N(N 为触发阈值)。例如,设置接收触发阈值为 8,则每接收 8 个字节才产生一次中断,中断频率降低为原来的 1/8。
3.5 FIFO 与中断模式的协同工作
在中断模式下,FIFO 与中断控制器协同工作,形成高效的数据传输路径。下图展示了典型的 UART FIFO 中断处理流程:
3.6 FIFO 配置参数对比
在实际工程中,FIFO 的配置需要根据应用场景进行调整。下表总结了不同场景下的推荐配置:
| 应用场景 | FIFO 深度 | 接收触发阈值 | 发送触发阈值 | 中断频率 |
|---|---|---|---|---|
| 低功耗传感器采集 | 16 字节 | 14 | 1 | 极低 |
| 实时控制指令 | 16 字节 | 1 | 1 | 高 |
| 批量数据传输 | 64 字节 | 56 | 8 | 低 |
| DMA 协同模式 | 128 字节 | 120 | 16 | 极低 |
uart_port 结构体的 fifosize 和 rx_trig 字段配置。例如,设置接收触发阈值为 FIFO 深度的一半,可以在中断频率和数据实时性之间取得平衡。
3.7 FIFO 配置代码示例
以下代码展示了在 Linux 内核 UART 驱动中配置 FIFO 触发阈值的典型实现:
/* 设置 UART FIFO 触发阈值 */
static void uart_set_fifo_threshold(struct uart_port *port, int rx_trig, int tx_trig)
{
unsigned int fifo_size = port->fifosize;
unsigned int reg_val;
/* 检查阈值是否合法 */
if (rx_trig < 1 || rx_trig > fifo_size)
rx_trig = fifo_size / 2; /* 默认使用一半深度 */
if (tx_trig < 1 || tx_trig > fifo_size)
tx_trig = fifo_size / 4; /* 发送阈值通常小于接收阈值 */
/* 读取当前 FIFO 控制寄存器 */
reg_val = readl(port->membase + UART_FCR);
/* 清除旧的触发阈值位 */
reg_val &= ~(UART_FCR_RX_TRIG_MASK | UART_FCR_TX_TRIG_MASK);
/* 设置新的接收触发阈值 */
reg_val |= (rx_trig << UART_FCR_RX_TRIG_SHIFT);
/* 设置新的发送触发阈值 */
reg_val |= (tx_trig << UART_FCR_TX_TRIG_SHIFT);
/* 使能 FIFO */
reg_val |= UART_FCR_FIFO_ENABLE;
/* 写入寄存器 */
writel(reg_val, port->membase + UART_FCR);
/* 更新端口结构体 */
port->rx_trig = rx_trig;
port->tx_trig = tx_trig;
}
3.8 FIFO 与 DMA 的协同
在更高性能的场景中,FIFO 可以与 DMA(直接存储器访问)协同工作,进一步降低 CPU 负载。DMA 控制器可以直接在 FIFO 和内存之间传输数据,仅在传输完成或发生错误时才中断 CPU。
FIFO 与 DMA 的协同模式包括:
- 接收路径:DMA 从 RX FIFO 读取数据并写入内存缓冲区,当达到预设的传输长度或 FIFO 超时时,DMA 发送完成中断。
- 发送路径:DMA 从内存缓冲区读取数据并写入 TX FIFO,当所有数据发送完毕时,DMA 发送完成中断。
- 阈值联动:DMA 请求信号通常与 FIFO 触发阈值绑定,当 FIFO 数据量达到阈值时,DMA 自动启动传输。