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 未能及时读取时数据丢失。
关键概念: FIFO 是 UART 硬件内部的存储阵列,通常分为接收 FIFO(RX FIFO)和发送 FIFO(TX FIFO),两者独立工作,深度可配置。

3.2 FIFO 深度

FIFO 深度是指 FIFO 能够存储的数据字节数。常见的 UART FIFO 深度有 16 字节、32 字节、64 字节,部分高端芯片支持 128 字节或 256 字节。FIFO 深度直接影响系统的实时性和中断频率。

FIFO 深度 典型应用场景 中断频率(相对) 实时性
16 字节 低波特率、简单传感器通信 高 较高
32 字节 通用嵌入式 UART 通信 中 适中
64 字节 高速数据采集、批量传输 低 较低
128 字节 DMA 协同、大数据量场景 极低 低
工程建议: 选择 FIFO 深度时,需平衡中断频率与数据实时性。深度越大,中断频率越低,但数据延迟增加。对于实时性要求高的控制类通信,建议使用较小的 FIFO 深度或较低的触发阈值。

3.3 触发阈值

触发阈值(Trigger Threshold)是 FIFO 中数据量达到某个水平时,UART 硬件向 CPU 发送中断的设定值。当 FIFO 中的数据字节数达到或超过该阈值时,硬件自动触发中断。触发阈值是可编程的,通常支持 1、4、8、14 等不同级别(具体取决于芯片)。

触发阈值的工作原理如下:

  • 接收 FIFO 触发阈值:当 RX FIFO 中的数据字节数达到设定阈值时,触发接收中断。CPU 在中断服务程序中一次性读取阈值数量的字节。
  • 发送 FIFO 触发阈值:当 TX FIFO 中的空余空间达到设定阈值时,触发发送中断。CPU 在中断服务程序中一次性写入多个字节,填满 FIFO。
注意: 触发阈值不能超过 FIFO 深度。例如,对于 16 字节的 FIFO,触发阈值通常只能设置为 1、4、8 或 14。设置过高的阈值可能导致数据溢出(接收)或发送停顿(发送)。

3.4 降低 CPU 中断频率

FIFO 降低 CPU 中断频率的核心机制是“中断聚合”。在没有 FIFO 的传统 UART 中,每接收或发送一个字节就产生一次中断。假设波特率为 115200 bps,每字节 10 位(1 起始位 + 8 数据位 + 1 停止位),则每秒产生 11520 次中断。CPU 需要频繁进入和退出中断上下文,开销巨大。

引入 FIFO 后,通过设置触发阈值,中断频率可以降低为原来的 1/N(N 为触发阈值)。例如,设置接收触发阈值为 8,则每接收 8 个字节才产生一次中断,中断频率降低为原来的 1/8。

FIFO 降低中断频率原理对比 无 FIFO:逐字节中断 CPU 中断服务程序 每字节触发一次中断 中断频率高 INT INT INT INT INT ... 有 FIFO:批量中断 CPU 中断服务程序 FIFO 阈值触发中断 中断频率低 接收 FIFO(深度 16) D0 D1 D2 D3 D4 D5 D6 D7 触发阈值 = 8 字节 中断频率对比(波特率 115200) 无 FIFO:每秒 11520 次中断 → CPU 频繁上下文切换 有 FIFO(阈值=8):每秒 1440 次中断 → CPU 负载降低 87.5%

3.5 FIFO 与中断模式的协同工作

在中断模式下,FIFO 与中断控制器协同工作,形成高效的数据传输路径。下图展示了典型的 UART FIFO 中断处理流程:

UART FIFO 中断处理流程 串行数据输入 接收移位寄存器 接收 FIFO 缓冲区 FIFO 数据量 ≥ 阈值? 否:继续接收 是:触发中断 中断控制器 CPU 中断服务程序 读取 FIFO 数据处理

3.6 FIFO 配置参数对比

在实际工程中,FIFO 的配置需要根据应用场景进行调整。下表总结了不同场景下的推荐配置:

应用场景 FIFO 深度 接收触发阈值 发送触发阈值 中断频率
低功耗传感器采集 16 字节 14 1 极低
实时控制指令 16 字节 1 1 高
批量数据传输 64 字节 56 8 低
DMA 协同模式 128 字节 120 16 极低
工程实践: 在 Linux 内核 UART 驱动中,FIFO 触发阈值通常通过 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;
}
注意事项: 不同芯片的 UART FIFO 控制寄存器布局不同,触发阈值的编码方式也各异。例如,STM32 的 USART_CR3 寄存器使用 2 位编码阈值,而 NXP i.MX 系列使用 3 位编码。移植驱动时务必参考芯片参考手册。

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 自动启动传输。
FIFO 与 DMA 协同架构 CPU DMA 控制器 UART FIFO 内存缓冲区 配置 DMA 请求 数据传输 传输完成中断
总结: FIFO 是 UART 驱动开发中不可或缺的硬件特性。合理配置 FIFO 深度和触发阈值,可以在中断频率、数据实时性和 CPU 负载之间取得最佳平衡。在 Linux 内核驱动中,FIFO 配置是中断模式优化的第一步,也是后续 DMA 协同的基础。