UART FIFO触发水平与DMA模式配置详解:提升嵌入式串口通信效率

1. 项目概述与核心价值

搞嵌入式开发,尤其是涉及到串口通信,UART寄存器配置绝对是绕不开的“硬骨头”。很多朋友可能觉得,串口嘛,不就是配置个波特率、数据位、停止位,然后读写数据就完事了。但当你真正面对一个复杂的工业级MCU,尤其是像TI这类厂商的芯片,其UART模块的功能之丰富、寄存器之复杂,常常会让人望而生畏。今天,我们就来啃一啃这块硬骨头,聚焦于两个直接影响通信效率和系统性能的核心机制:FIFO触发水平DMA模式配置

为什么这两个点如此重要?想象一下,你的设备需要通过UART以115200的波特率接收来自传感器的连续数据流。如果每收到一个字节就产生一次中断让CPU来处理,CPU很快就会淹没在中断的海洋里,什么主任务都干不了。FIFO(先进先出缓冲区)就是为了解决这个问题而生的,它像一个蓄水池,可以暂存多个数据。而FIFO触发水平,就是这个蓄水池的“水位报警线”。当数据积累到预设的“水位”(比如8个字节),才通知CPU或DMA来一次性搬运,这极大地减少了中断频率。更进一步,DMA(直接内存访问)则像一个自动化的搬运工,可以在不打扰CPU的情况下,直接把FIFO里的数据搬到指定的内存区域,实现“零CPU开销”的数据搬运。这两者结合,是构建高效、可靠、实时串口通信系统的基石。

本文将以TI某款芯片的UART模块寄存器手册为蓝本,但原理是通用的。我会带你深入寄存器位域,拆解UART_TLR(触发水平寄存器)和SCR(辅助控制寄存器)等关键寄存器,不仅告诉你每个位该怎么设,更会解释为什么这么设,以及在实际项目中如何权衡和避坑。无论你是正在调试UART驱动的嵌入式工程师,还是想深入理解外设工作原理的学生,这篇文章都能给你带来实实在在的干货。

2. UART寄存器体系与FIFO机制深度解析

在深入FIFO触发和DMA之前,我们必须对UART的寄存器体系有一个整体的认识。UART的寄存器大致可以分为几类:控制类(如LCR配置数据格式,FCR控制FIFO)、状态类(如LSR查看线路状态,IIR标识中断源)、数据类(如RHR接收保持寄存器,THR发送保持寄存器)以及我们今天重点关注的高级功能类(如TLR、SCR等)。

2.1 FIFO:从单字节到数据缓冲区的进化

早期的UART或“UART模式”通常只有一个字节的缓冲区(RHR/THR)。CPU必须频繁查询或响应中断来读取每一个到达的字节或发送下一个字节,效率极低。现代UART普遍集成了硬件FIFO,深度通常是16、64甚至128字节。

FIFO的工作流程可以这样理解:

  • 接收端(RX):串行移位寄存器将接收到的比特流组装成字节,然后压入RX FIFO队列尾部。RX FIFO有一个读指针(由CPU或DMA读取RHR寄存器操作推进)和一个写指针(由硬件接收推进)。
  • 发送端(TX):CPU或DMA将待发送字节写入THR寄存器,实质是压入TX FIFO队列尾部。发送逻辑从TX FIFO头部取出字节,送入串行移位寄存器发送出去。

有了FIFO,通信的实时性要求就从“每个字节”放宽到了“每N个字节”,给了系统更大的调度弹性。

2.2 触发水平:中断与DMA请求的“发令枪”

FIFO的存在引出了一个新的问题:我们什么时候应该去处理FIFO里的数据?是等到它完全满了吗?那样可能会造成数据溢出(Overrun)。是只要有一个字节就处理吗?那FIFO就失去了意义。

这就是FIFO触发水平(Trigger Level)要解决的问题。它为RX FIFO和TX FIFO分别设定了一个阈值。

  • RX FIFO触发水平:当RX FIFO中累积的数据量达到或超过这个阈值时,硬件会触发一个“接收数据可用”事件。这个事件可以配置为产生一个中断(IRQ)给CPU,或者拉高一个DMA请求信号(DMAREQ),通知DMA控制器来搬运数据。
  • TX FIFO触发水平:当TX FIFO中剩余空间(空位置)达到或超过这个阈值时,硬件会触发一个“发送缓冲区空”事件。同样,它可以产生中断通知CPU来填充更多数据,或者拉高DMA请求信号,让DMA从内存搬运数据到TX FIFO。

关键点在于:触发水平是一个“阈值”,而非“精确值”。例如,设置RX触发水平为8,并不意味着FIFO里一有8个字节就立刻触发,而是“当数据量从7增加到8时”触发。触发后,即使你还没取走数据,只要数据量不低于阈值(比如保持在8或以上),通常不会重复触发,直到数据被取走到低于阈值,然后再次累积到阈值时才会触发下一次。这种机制避免了事件的频繁抖动。

3. 核心寄存器详解:UART_TLR与触发水平配置

理解了原理,我们来看具体如何配置。UART_TLR寄存器是控制FIFO触发水平的核心。

3.1 UART_TLR寄存器位域精讲

根据手册,UART_TLR寄存器偏移地址为0x1C。它的结构非常清晰:

  • 位[15:8]:保留位,只读为0。
  • 位[7:4] (RX_FIFO_TRIG_DMA):接收FIFO触发水平设置位。
  • 位[3:0] (TX_FIFO_TRIG_DMA):发送FIFO触发水平设置位。

这个寄存器的访问有一个前提条件:必须同时使能增强功能寄存器(EFR[4] = 1)并置位调制解调器控制寄存器的某个位(MCR[6] = 1)。这是TI UART的一个常见设计,将一些高级功能(如TLR、自动流控)放在一个“增强功能模式”下,需要通过MCR[6]来解锁。不满足条件时,对该寄存器的读写可能无效或访问的是其他寄存器(如XOFF2)。

触发水平的设定并非直接写入字符数,而是受SCR寄存器和FCR寄存器的联合控制,形成了一个灵活的配置矩阵。这是最容易让人困惑的地方。

3.2 接收触发水平(RX_FIFO_TRIG_DMA)配置逻辑

手册给出了一个清晰的决策表,我们将其翻译成更易懂的配置步骤:

条件判断取决于SCR[7](RXTRIGGRANU1) 和TLR[7:4]的值:

  1. 模式A:使用FCR的预设档位

    • 条件SCR[7] = 0TLR[7:4] = 0000
    • 行为:RX触发水平由FCR[7:6]这两位决定。这是一个简化的“预设”模式,通常提供几个固定档位,例如:
      • 00: 触发水平 = 1 字节 (注意:有些芯片可能是8字节,需查具体手册)
      • 01: 触发水平 = 4 字节
      • 10: 触发水平 = 8 字节
      • 11: 触发水平 = 14 字节
    • 适用场景:对触发水平精度要求不高,希望快速配置的场合。
  2. 模式B:使用TLR的4字符步进档位

    • 条件SCR[7] = 0TLR[7:4] != 0000
    • 行为:RX触发水平直接由TLR[7:4]这4位二进制值决定,但步进(粒度)为4个字符
    • 计算公式触发水平 = TLR[7:4]的十进制值 × 4
    • 范围TLR[7:4]0001(1)到1111(15),对应的触发水平为4到60字节。
    • 示例:若TLR[7:4] = 0101(十进制5),则触发水平 = 5 × 4 = 20字节。
    • 适用场景:需要中等精度调整,且触发水平是4的倍数的应用。
  3. 模式C:使用TLR+FCR的1字符步进精细档位

    • 条件SCR[7] = 1
    • 行为:无论TLR[7:4]为何值,RX触发水平由TLR[7:4]FCR[7:6]拼接成一个6位的值来决定(TLR[7:4]为高4位,FCR[7:6]为低2位),步进为1个字符
    • 计算公式触发水平 = (TLR[7:4] << 2 | FCR[7:6])的十进��值。
    • 范围:从1到63字节(因为6位最大值为63)。
    • 重要禁忌:手册明确警告,TLR[7:4]FCR[7:6]不能同时为0(即拼接后的值为0),因为最小触发水平是1字节。全零会导致不可预测的行为。
    • 适用场景:需要最精细控制触发水平的场景,例如FIFO深度为64,你想精确设置在32字节触发。

配置流程示例(假设需要设置RX触发水平为16字节):

  1. 确定使用模式C(1字节粒度)以实现精确控制。
  2. 设置SCR[7] = 1
  3. 计算TLR[7:4]FCR[7:6]的值:16的二进制是010000。高4位0100(4)放入TLR[7:4],低2位00放入FCR[7:6]
  4. 确保EFR[4]=1MCR[6]=1
  5. 写入TLR寄存器,RX_FIFO_TRIG_DMA字段写入0100
  6. 写入FCR寄存器,确保FCR[7:6]00

3.3 发送触发水平(TX_FIFO_TRIG_DMA)配置逻辑

TX触发水平的配置逻辑与RX完全对称,只是控制的寄存器位不同:

  • 它由SCR[6](TXTRIGGRANU1) 和TLR[3:0]控制。
  • 同样有三种模式,决策取决于SCR[6]TLR[3:0]
  • 模式ASCR[6]=0TLR[3:0]=0000,触发水平由FCR[5:4]决定(预设档位,如8, 16, 32, 56字符)。
  • 模式BSCR[6]=0TLR[3:0]!=0000,触发水平 =TLR[3:0]值 × 4(4字符步进)。
  • 模式CSCR[6]=1,触发水平 = (TLR[3:0]<< 2 |FCR[5:4])(1字符步进,范围1-63)。

一个关键区别:TX触发水平指的是TX FIFO空余空间的阈值。例如,TX FIFO深度为64,设置触发水平为8。意思是当TX FIFO中已发送的数据被移出,导致空闲位置达到或超过8个时,就会触发“TX缓冲区空”事件,通知系统可以继续写入数据了。

实操心得:触发水平设置的权衡艺术设置触发水平不是越大越好,也不是越小越好,需要权衡:

  • 高触发水平(如接近FIFO深度):优点是可以最大化单次中断/DMA传输的数据量,减少系统开销。缺点是数据在FIFO中停留时间变长,增加了接收端的处理延迟(Latency)。对于实时性要求高的控制指令传输,这可能不可接受。
  • 低触发水平(如1-4字节):优点是响应快,延迟低。缺点是中断/DMA请求非常频繁,在高速波特率下会严重消耗CPU或总线资源。
  • 经验值:对于115200及以下的中低速通信,且数据包较小(如几十字节),触发水平设为FIFO深度的1/4到1/2是个不错的起点(例如64深度的FIFO,设16或32)。对于高速通信(如921600)或大数据块传输,应倾向于更高的触发水平,并搭配DMA使用。务必结合你的实际应用场景(数据包大小、波特率、系统负载)进行测试和调整。

4. DMA模式配置:解放CPU的关键

当数据量很大或系统实时性要求极高时,即使优化了触发水平,频繁的中断处理仍然是负担。此时,DMA就成为必选项。DMA允许外设(这里是UART)直接与内存交换数据,无需CPU参与每次字节的搬运。

4.1 SCR寄存器:DMA模式的控制中心

UART_SCR寄存器(偏移0x40)是配置DMA模式的核心。我们关注其低3位:

  • 位[2:1] (DMAMODE2):这两位具体定义了DMA的工作模式。
  • 位[0] (DMAMODECTL):这是一个模式选择开关,决定了由哪个寄存器来定义DMA模式。
    • DMAMODECTL = 0:DMA模式由FCR[3]控制。这是传统或简化模式。
    • DMAMODECTL = 1:DMA模式由SCR[2:1](即DMAMODE2)控制。这提供了更灵活的模式选择。

为了使配置清晰且功能强大,我们通常设置DMAMODECTL = 1,以便使用SCR[2:1]定义的四种模式。

4.2 四种DMA模式详解(当SCR[0]=1时)

根据手册,DMAMODE2(SCR[2:1]) 定义了以下模式:

DMAMODE2 值模式名称UARTnDMAREQ[0] 功能UARTnDMAREQ[1] 功能工作描述
00DMA Mode 0未使用未使用无DMA模式。所有传输依赖CPU中断。
01DMA Mode 1TX DMA 请求RX DMA 请求经典独立模式。这是最常用、最直观的模式。TX和RX有各自独立的DMA请求线,可以同时进行全双工DMA传输。
10DMA Mode 2RX DMA 请求未使用仅接收DMA模式。只有RX FIFO可以产生DMA请求,TX仍需CPU或中断处理。适用于纯数据采集场景。
11DMA Mode 3TX DMA 请求未使用仅发送DMA模式。只有TX FIFO可以产生DMA请求,RX仍需CPU或中断处理。适用于纯数据发送场景。

UARTnDMAREQ[0]和[1]是UART模块输出给DMA控制器的硬件请求信号线。DMA控制器会监听这些信号。当RX FIFO数据达到触发水平,DMAREQ[1](Mode 1)或DMAREQ[0](Mode 2)会拉高,DMA控制器随即发起一次从UART RHR寄存器到指定内存地址的传输。TX同理。

4.3 完整DMA配置流程与代码示例

假设我们需要配置UART0在Mode 1(全双工DMA)下工作,RX触发水平为16字节,TX触发水平为8字节(指空闲空间)。

步骤1:基础与FIFO使能

// 1. 解锁增强功能寄存器 (EFR) UART0->MCR |= (1 << 6); // 设置MCR[6] = 1 // 2. 使能FIFO并设置其复位/触发控制 (假设使用FCR预设值作为初始值,后续会被TLR覆盖) UART0->FCR = (1 << 0) | // FIFO使能 (1 << 1) | // 接收FIFO复位 (1 << 2); // 发送FIFO复位 // 等待FIFO复位完成(通常需要几个时钟周期)

步骤2:配置触发水平(使用精细的1字节粒度)

// 3. 设置SCR[7]和SCR[6]为1,启用RX/TX的1字节粒度触发模式 UART0->SCR |= (1 << 7) | (1 << 6); // 4. 计算并设置TLR寄存器 // RX触发水平 = 16字节。16的二进制: 010000。高4位0100(4)给TLR[7:4],低2位00给FCR[7:6]。 // TX触发水平 = 8字节。8的二进制: 001000。高4位0010(2)给TLR[3:0],低2位00给FCR[5:4]。 uint16_t tlr_value = (4 << 4) | (2 << 0); // TLR[7:4]=4, TLR[3:0]=2 UART0->TLR = tlr_value; // 5. 确保FCR[7:6]和FCR[5:4]为00(与TLR组合成16和8) UART0->FCR &= ~((0x3 << 6) | (0x3 << 4)); // 清零FCR[7:6]和[5:4]

步骤3:配置DMA模式

// 6. 设置SCR[0]=1,使用SCR[2:1]定义DMA模式 UART0->SCR |= (1 << 0); // 7. 设置SCR[2:1] = 01,选择DMA Mode 1 (全双工) UART0->SCR |= (1 << 1); // SCR[2:1] = 01b // SCR[2]保持为0

步骤4:使能UART中断(用于DMA传输完成、错误等事件)

// 8. 使能接收数据可用中断(当RX FIFO达到触发水平,会触发DMA,同时也可配置产生中断通知CPU“DMA搬运开始”) // 以及线路状态中断(用于溢出、帧错误等) UART0->IER |= (1 << 0) | // 使能接收数据可用中断 (1 << 2); // 使能接收线路状态中断 // 注意:在DMA模式下,THR空中断(IER[1])通常由DMA处理,可以不使能,或者使能用于处理最后不满一次DMA传输的数据。

步骤5:配置DMA控制器(以ARM Cortex-M系列为例)这步高度依赖具体的MCU和DMA控制器。以下是概念性伪代码:

// 配置DMA通道0用于UART0 RX DMA0->CH[0].SRC_ADDR = (uint32_t)&(UART0->RHR); // 源地址:UART数据寄存器 DMA0->CH[0].DST_ADDR = (uint32_t)rx_buffer; // 目的地址:内存缓冲区 DMA0->CH[0].CTRL = DMA_CTRL_SRC_INC_NONE | // 源地址不递增(外设寄存器) DMA_CTRL_DST_INC | // 目的地址递增 DMA_CTRL_SIZE_BYTE | // 传输宽度:字节 DMA_CTRL_MODE_PINGPONG | // 可选:乒乓模式双缓冲 DMA_CTRL_IRQ_EN; // 使能传输完成中断 DMA0->CH[0].COUNT = BUFFER_SIZE; // 传输总字节数 // 将DMA通道0的请求源映射到UART0的RX DMAREQ信号(具体映射查芯片手册) // 配置DMA通道1用于UART0 TX(类似,方向相反) DMA0->CH[1].SRC_ADDR = (uint32_t)tx_buffer; DMA0->CH[1].DST_ADDR = (uint32_t)&(UART0->THR); DMA0->CH[1].CTRL = DMA_CTRL_SRC_INC | DMA_CTRL_DST_INC_NONE | DMA_CTRL_SIZE_BYTE | DMA_CTRL_IRQ_EN; DMA0->CH[1].COUNT = DATA_SIZE_TO_SEND; // 映射通道1到UART0的TX DMAREQ信号 // 使能DMA通道 DMA0->CH[0].CTRL |= DMA_CTRL_ENABLE; DMA0->CH[1].CTRL |= DMA_CTRL_ENABLE;

注意事项:DMA与中断的协同

  1. 事件源:FIFO触发水平产生的是硬件DMA请求,它直接驱动DMA控制器搬运数据。同时,这个事件也可能被配置为产生一个CPU中断(通过IER寄存器)。在典型的DMA应用中,我们通常使能这个中断,但中断服务程序(ISR)里不处理具体数据,而是处理DMA传输完成、半满、错误等“元事件”。
  2. 数据边界:DMA是“盲目的”搬运工。如果UART接收的是不定长数据包,DMA会持续搬运直到你设定的长度。你需要额外的机制(如空闲中断、特定字符中断)来判定一个数据包的结束。常用的方法是使能UART的接收线空闲中断(如果硬件支持),在检测到总线空闲一段时间后,触发中断,在ISR中处理已由DMA搬运到内存的完整数据包。
  3. 缓冲区管理:使用DMA通常需要环形缓冲区双缓冲区(乒乓缓冲)。DMA可以配置为循环模式,自动将数据填入环形缓冲区。CPU则从缓冲区的另一头读取数据。要小心处理缓冲区的读写指针和溢出问题。

5. 相关高级功能与问题排查实录

配置好FIFO触发和DMA,只是搭建了高效传输的通道。在实际项目中,还会遇到各种边界情况和问题。下面结合其他相关寄存器,分享一些实战经验和排查技巧。

5.1 状态寄存器与错误处理

当通信出现问题时,UART_LSR(线路状态寄存器)和UART_IIR(中断标识寄存器)是你的第一站。

  • LSR[0] (DR):接收数据就绪。即使使用DMA,在DMA搬运间隙或处理小数据时,查询此位仍有意义。
  • LSR[1] (OE)溢出错误。这是FIFO使用不当的典型标志。意味着RX FIFO已满,但又有新数据到来,导致数据丢失。解决方法:提高CPU/DMA处理速度,或优化触发水平,确保在FIFO满之前启动搬运。检查DMA是否被正确使能并响应请求。
  • LSR[2] (PE)/[3] (FE)/[4] (BI):奇偶校验、帧错误、中止错误。这些属于线路质量问题,需检查波特率、电平、硬件连接。
  • IIR寄存器:用于快速判断中断源。在DMA模式下,你可能会使能“接收数据可用”中断(IIR[2]=110b)来启动DMA或处理缓冲区,以及“字符超时”中断(IIR[6]=110b)来处理接收到的、但不足以触发FIFO水平线的残留数据。

5.2 SCR与SSR寄存器的其他关键位

  • SCR[4] (RXCTSDSRWAKEUPEN):这是一个“唤醒”中断使能位。当RX、CTS或DSR引脚出现下降沿时,可以产生一个独立的中断(状态在SSR[1])。这在设备需要从低功耗模式被串口数据唤醒时非常有用。注意:这个唤醒中断不映射到IIR,需要单独查询SSR[1]位,并且通过清除SCR[4]来清除中断状态。
  • SSR[2] (DMACTRRST):DMA计数器复位控制。这个位非常关键!当它为1时(默认),如果FIFO被复位(通过FCR[1]或[2]),对应的DMA传输计数器也会被复位。这意味着正在进行中的DMA传输会被强行终止。如果你不希望DMA计数被FIFO复位影响,需要在初始化时将其清零(SSR &= ~(1<<2))。否则,在程序运行中动态复位FIFO可能导致DMA传输数据不完整。

5.3 常见问题排查速查表

现象可能原因排查步骤与解决方案
数据接收不完整/丢失1. RX FIFO溢出(OE错误)
2. DMA未正确启动或配置错误
3. 触发水平设置过高,数据迟迟不搬运
1. 检查LSR[1](OE)是否置位。
2. 检查DMA通道是否使能,源/目的地址、传输宽度、模式是否正确。
3.使用逻辑分析仪或示波器,抓取UART的RX引脚和DMAREQ信号,确认数据到达时DMAREQ是否有效拉高。
4. 降低RX触发水平,测试是否改善。
数据发送卡住1. TX FIFO触发水平设置不当
2. DMA传输未完成或配置错误
3. 流控(CTS)被拉高
1. 检查TX FIFO是否已满(可通过查询状态或TXFIFOFULL位)。
2. 检查DMA传输是否完成,或尝试改用CPU中断填充TX FIFO。
3. 检查CTS引脚电平,确认对方设备是否可以接收。
DMA只能搬运一次数据1. DMA配置为单次模式而非循环/自动重装模式。
2. DMA传输完成后没有重新使能或配置新的传输。
3. SSR[2]位导致DMA计数器被意外复位。
1. 检查DMA控制器的模式配置,改为循环模式或配置自动重装。
2. 在DMA传输完成中断中,重新设置传输计数和使能通道(对于非循环模式)。
3. 检查SSR[2]位,并根据需要清零。
中断和DMA同时响应,导致重复处理IER中使能了“接收数据可用”中断,同时DMA也配置在该事件上。在纯DMA搬运场景,可以考虑禁用“接收数据可用”的CPU中断(IER对应位清零),仅依靠DMA。或者,在中断服务程序(ISR)中,仅进行标志位检查等轻量级操作,不搬运数据。
低功耗模式下无法被串口数据唤醒唤醒功能未正确配置。1. 确认SCR[4]是否置1使能唤醒中断。
2. 确认芯片支持并已进入相应的低功耗模式。
3. 在唤醒后的初始化流程中,检查并清除SSR[1]位,并复位SCR[4]以清除唤醒中断标志。

5.4 调试技巧与思维模型

  1. 分层验证:不要一开始就上DMA。先配置为纯中断模式,并设置一个较低的触发水平(如1-4字节),确保基础通信和中断响应正常。然后再切换到DMA模式。
  2. 善用“探针”:如果芯片支持,用GPIO引脚在关键位置(如中断服务程序入口、DMA启动/完成时)输出脉冲,用示波器测量时间,判断程序执行流是否符合预期。
  3. 理解数据流:在脑海中或纸上画出数据流图:UART RX引脚 -> 移位寄存器 -> RX FIFO -> (触发水平) -> DMAREQ信号 -> DMA控制器 -> 内存缓冲区。沿着这个路径,逐一检查每个环节是否畅通。
  4. 寄存器初始化顺序:有些寄存器之间存在依赖关系。一个稳健的初始化顺序是:先关闭模块(如有),配置基本参数(波特率、数据格式),然后使能FIFO并设置触发水平,接着配置DMA模式,最后使能模块和中断/DMA。务必参考芯片勘误表(Errata),有些芯片的寄存器有特殊的写入顺序要求。

配置UART的FIFO和DMA,就像给一条河流修建水库和自动灌溉系统。TLR寄存器决定了水库的“警戒水位”,DMA模式则是自动灌溉的阀门和管道。理解每个寄存器位背后的设计意图,结合实际的业务数据流(是涓涓细流还是洪水爆发),才能设计出既高效又稳定的通信系统。这个过程少不了反复调试和测试,但一旦调通,其对系统整体性能的提升是立竿见影的。希望这篇深入的解析能成为你手边一份有用的参考。