深入解析UART FIFO中断与DMA机制:从原理到嵌入式通信实战

1. 项目概述与核心价值

在嵌入式开发,尤其是涉及串口、红外或消费电子红外遥控这类串行通信的场景里,数据流的平滑处理一直是个经典难题。想象一下,你正在调试一个通过串口接收传感器数据的设备,如果每收到一个字节,CPU就被打断一次去处理,那么系统大部分时间可能都花在了处理中断的上下文切换上,真正处理业务逻辑的算力所剩无几。更糟的是,如果发送端速度时快时慢,接收端很容易因为来不及处理而导致数据丢失,也就是我们常说的“溢出现象”。

为了解决这个问题,现代微控制器或专用的通信控制器内部,几乎都集成了一个名为FIFO的硬件缓冲区。FIFO,即“先进先出”队列,它的作用就像一个迷你仓库。发送数据时,CPU可以一次性写入多个字节到发送FIFO,然后由硬件自动、按序地发送出去,CPU在此期间可以去处理其他任务;接收数据时,硬件会把从线上收到的字节先存入接收FIFO,等攒到一定数量,再一次性通知CPU来取。这个“一定数量”,就是可编程的触发阈值,它是平衡实时性与系统开销的关键。

今天,我们就以德州仪器一款典型的UART/IrDA/CIR三合一模块为例,深入它的“仓库管理”核心——FIFO管理与中断机制。这个模块的巧妙之处在于,它通过一套高度可配置的寄存器,将FIFO与中断、DMA请求紧密耦合,为开发者提供了中断、轮询、DMA三种工作模式,以适应从低功耗传感器到高速红外通信的不同需求。理解这套机制,不仅能让你在配置串口时知其然更知其所以然,更能让你在面临通信不稳定、数据丢失或系统响应迟缓等问题时,快速定位到是FIFO阈值设置不当,还是中断处理逻辑有误,从而写出更稳健、高效的嵌入式通信代码。

2. FIFO基础架构与核心寄存器解析

在深入工作模式之前,我们必须先搞清楚这个“硬件仓库”是怎么搭建起来的,以及掌管仓库钥匙的几位“管理员”——核心控制寄存器。

2.1 FIFO的物理结构与访问入口

该模块的FIFO设计得非常直观。它包含两个独立的64字节硬件队列:

  • 发送FIFO:用于缓存待发送的数据。CPU通过向THR寄存器写入数据,实际上就是将数据压入了发送FIFO的队尾。发送逻辑电路会自动从FIFO队头取出数据,按照设定的波特率一位一位地发送到TX引脚。
  • 接收FIFO:用于缓存已接收但尚未被CPU读取的数据。当硬件从RX引脚成功接收完一个字节后,会将其放入接收FIFO的队尾。CPU通过读取RHR寄存器,实际上是从接收FIFO的队头取出数据。

这里有一个至关重要的细节:数据在RHR寄存器中不会被覆盖。这意味着,如果发生接收溢出(即FIFO已满,但仍有新数据到来),新的数据会被直接丢弃,而FIFO中已有的数据保持不变。这保证了已接收数据的完整性,避免因溢出导致旧数据被污染。

2.2 核心控制寄存器:FCR, SCR, TLR

仅仅有仓库不够,我们需要一套管理系统来控制何时进货、何时出货。这套系统主要由三个寄存器实现:

  1. FIFO控制寄存器:这是总开关。其最低位FCR[0]是FIFO使能位。该位默认为0,即FIFO功能被禁用。此时,THR和RHR寄存器退化为单字节缓冲区,每次读写都直接与移位寄存器交互,中断或DMA请求的触发阈值固定为1字节。只有将FCR[0]置1,64字节的FIFO和可编程触发阈值功能才会生效。
  2. 补充控制寄存器:这是一个多功能寄存器,其中SCR[0]位与DMA模式选择密切相关。当SCR[0]=0时,DMA模式由FCR[3]单独控制;当SCR[0]=1时,DMA模式则由SCR[2:1]两位共同决定,提供了更丰富的模式选择。
  3. 触发水平寄存器:这是整个机制的“调度中心”。TLR是一个8位寄存器,其高4位TLR[7:4]用于设置接收FIFO的触发阈值,低4位TLR[3:0]用于设置发送FIFO的触发阈值(以“剩余空间”计)。例如,设置TLR[7:4] = 8,意味着当接收FIFO中的数据量达到或超过8字节时,才会产生接收中断或DMA请求;设置TLR[3:0] = 16,意味着当发送FIFO中的剩余空间大于或等于16字节时,才会产生发送中断或DMA请求(或者说,当已用空间超过48字节时,中断/DMA请求会解除)。

注意:TLR的配置与FCR中的FCR[7:6](接收触发)和FCR[5:4](发送触发)字段是联动的。具体使用哪一组配置,取决于模块的工作模式。通常,在增强功能模式下,TLR提供更精细的控制。

2.3 状态监控:LSR与SSR寄存器

作为程序员,我们还需要随时知道仓库的“库存”情况。这通过状态寄存器实现:

  • 线路状态寄存器:这是一个经典的状态寄存器,在轮询模式下尤其重要。其LSR[0]位指示接收数据就绪,LSR[5]位指示发送保持寄存器空(即THR可写入)。当FIFO禁用时,查询这些位是判断能否进行读写操作的唯一方法。
  • 子状态寄存器:其SSR[0]位直接指示了一个关键状态:发送FIFO满。当该位为1时,意味着发送FIFO已完全填满,此时再向THR写入数据会导致写入失败或数据丢失。在编写发送函数时,检查此位或通过中断/DMA机制来避免写满,是保证数据不丢失的关键。

3. FIFO的三种工作模式深度剖析

理解了仓库的基本构造和管理员职责后,我们来看三种不同的“运营模式”。模式的选择主要通过配置FCR[0](FIFO使能)和IER(中断使能寄存器)来实现。

3.1 中断模式:事件驱动的效率之选

中断模式是平衡实时性与CPU效率的常用手段。在此模式下,FCR[0]=1,并且通过IER寄存器使能了相应的接收或发送中断。

接收中断的工作流程如下

  1. 硬件持续接收数据并填充接收FIFO。
  2. 当FIFO中的数据量达到或超过TLR[7:4]设定的阈值时,硬件自动拉低中断请求线,通知CPU。
  3. CPU跳转到中断服务程序,开始从RHR寄存器读取数据。关键点在于,中断请求信号会一直保持有效(低电平),直到CPU读取了足够多的数据,使得FIFO中的数据量低于设定的阈值
  4. 一旦数据量低于阈值,中断请求信号被释放(变为高电平)。即使FIFO中仍有数据,只要不超过阈值,就不会产生新的中断。这避免了CPU被持续打断。

发送中断的工作流程则相反

  1. 当CPU将数据写入发送FIFO并启动发送后,FIFO数据被逐渐发送出去。
  2. 当发送FIFO完全变空时,硬件立即产生发送中断,通知CPU“仓库已空,可以补货了”。
  3. CPU在中断服务程序中向THR写入新的数据。中断请求信号会在CPU向FIFO写入数据,使得FIFO中的数据量(或剩余空间)跨越TLR[3:0]设定的阈值后被解除。
  4. 例如,设定发送阈值为8字节空间。当FIFO空时中断产生,CPU开始写入。当写入的数据量使得FIFO剩余空间从64变为56(即已用8字节),恰好跨越阈值,中断信号解除。之后,即使CPU继续写入直到FIFO满,也不会产生中断,直到这些数据被发送完,FIFO再次变空。

实操心得:中断阈值的设置是一门艺术。接收阈值设得太小(如1-2字节),中断会过于频繁,系统开销大;设得太大(如60字节),数据响应延迟高,且在高速数据流下容易因处理不及时导致溢出。一个常见的起始策略是,对于交互式、要求低延迟的应用(如AT指令���析),接收阈值可设为4-8字节;对于大数据量、吞吐优先的应用(如文件传输),可以设为16-32字节。发送阈值通常设置为FIFO深度的一半左右,以保证连续发送时DMA或中断能及时补充数据。

3.2 轮询模式:简单直接的掌控感

轮询模式是最基础、最直接的方式。在此模式下,FCR[0]=0(FIFO禁用)或即使FCR[0]=1但通过IER禁用了所有中断。此时,CPU需要主动、周期性地查询LSR寄存器的状态位来判断能否进行读写操作。

  • 发送:在写入THR之前,程序必须循环检查LSR[5](THRE,发送保持寄存器空)或SSR[0](TXFIFOFULL,发送FIFO满)位,确保有空闲空间。
  • 接收:在读取RHR之前,程序必须循环检查LSR[0](DR,数据就绪)位,确认已有数据到达。

轮询模式的优缺点非常明显

  • 优点:实现简单,无需复杂的中断向量表配置和上下文保护,对简单的小型系统或初始化阶段很友好。
  • 缺点:CPU利用率低。在等待数据的过程中,CPU处于忙等待状态,无法执行其他任务。在高波特率或主频较低的系统里,轮询可能无法及时响应,导致数据丢失。

3.3 DMA模式:解放CPU的吞吐量利器

当数据吞吐量成为瓶颈时,DMA模式是终极解决方案。它允许数据在外设(UART FIFO)和内存之间直接搬运,无需CPU介入。该模块支持多达4种DMA模式,通过SCR[0]FCR[3]SCR[2:1]进行选择。

DMA模式的核心信号是DMA请求,其触发条件与中断模式类似,但目的不同:

  • 接收DMA请求:当接收FIFO中的数据量达到TLR[7:4]设定的阈值时产生。DMA控制器响应此请求,发起一次批量传输,从接收FIFO中读取阈值指定的字节数到内存。请求会在DMA完成指定数量的传输后解除。
  • 发送DMA请求:当发送FIFO变空时产生。DMA控制器响应此请求,从内存搬运TLR[3:0]指定的字节数(发送FIFO的剩余空间阈值)到发送FIFO。请求会在写入指定数量的数据后解除。

这里存在一个至关重要的配置匹配问题:模块内设定的DMA触发阈值(TLR),必须与系统级DMA控制器的传输量配置相匹配。例如,你将接收FIFO阈值设为32字节,那么DMA控制器的单次传输量也应配置为32字节。如果不匹配,就会导致问题:

  • DMA传输量 > FIFO阈值:DMA试图读取的数据量超过FIFO中实际可读的数据量,可能导致DMA传输错误或读到无效数据。
  • DMA传输量 < FIFO阈值:FIFO中数据已达到阈值并触发了DMA请求,但DMA只搬走了一小部分,导致FIFO水平很快又低于阈值,无法再次触发DMA,剩余数据只能等待超时中断或轮询来读取,失去了DMA的批量效率。

模块的文档中给出了一个生动的图示例子:发送FIFO空间阈值设为56字节,DMA控制器配置为每次传输56字节。当FIFO空时,DMA请求激活,DMA一次性写入56字节填满大部分FIFO,请求解除。硬件开始发送数据,当这56字节快发完,FIFO再次变空时,新的DMA请求产生,如此循环,形成一个高效的流水线。

4. 中断机制详解与多模式差异

中断是驱动FIFO高效工作的“警报器”。该模块的中断系统设计精巧,针对UART、IrDA和CIR三种不同的物理层协议,提供了差异化的中断源和优先级。

4.1 中断的通用框架:IER与IIR

所有中断的使能/禁用都由中断使能寄存器统一控制。你想让哪个事件触发中断,就去置位IER中对应的位。当有中断事件发生时,中断标识寄存器IIR[0]位会被硬件清零,表示有中断待处理。同时,IIR[5:1]位会编码出具体的中断类型和优先级,CPU读取IIR即可判断该处理哪个事件,并执行相应的清除操作(通常是读/写某个特定寄存器)。

4.2 UART模式中断:经典七类

在标准的UART模式下,中断被分为6个优先级,共7种类型,形成了一个清晰的处理链条:

  1. 最高优先级 - 接收线路状态错误:包括溢出错、帧错误、奇偶校验错和线路间断。这些错误通常意味着物理连接或数据格式有严重问题,需要立即处理。清除方法是:对于FE, PE, BI错误,读取RHR寄存器;对于OE错误,需要读取LSR寄存器。
  2. 第二优先级 - 接收超时与数据就绪:当接收FIFO中有数据,但在一段时间内(例如4个字符时间)没有新数据到达也没有被读走,会产生超时中断,防止数据“滞留”。同时,当接收FIFO中的数据量超过触发阈值(FIFO使能时)或有数据就绪(FIFO禁用时),会产生RHR中断。清除方法是读取RHR直到中断条件消失。
  3. 第三优先级 - 发送保持寄存器空中断:即发送FIFO空或低于阈值,通知CPU可以发送新数据了。清除方法是向THR写入数据直到中断条件消失。
  4. 更低优先级:包括调制解调器状态变化中断、XOFF/特殊字符中断、CTS/RTS/DSR引脚状态变化中断等,用于流量控制和握手协议。

4.3 IrDA与CIR模式中断:面向帧的优化

IrDA和CIR模式基于UART,但针对其帧传输特性做了优化,中断逻辑有所不同。

  • IrDA模式:增加了状态FIFO中断接收帧结束中断。状态FIFO是一个独立的8条目队列,专门记录每个已接收IrDA帧的长度和错误状态(CRC错误、帧过长等)。这允许CPU在DMA批量接收数据后,通过查询状态FIFO来高效地定位帧边界和筛选错误帧,无需解析每一个字节。TX状态中断则明确指示了发送结束时是否发生了“下溢”错误(即FIFO在帧结束前已空)。
  • CIR模式:作为消费电子红外遥控协议,它简化了中断,主要关注接收停止中断(根据设定的引导头长度触发)和基本的收发数据中断。其IIR[5]位专门用于指示“红外数据的最后一位已发送至IR TX引脚”,这对于精确控制红外载波脉冲的结束时刻至关重要。

4.4 一个关键实践:中断的清除

务必查阅数据手册中每个中断类型对应的“清除方法”。这是中断编程中最容易出错的地方之一。例如:

  • 有些中断通过读取特定数据寄存器来清除(如RHR中断读RHR)。
  • 有些通过读取状态寄存器来清除(如线路状态错误读LSR)。
  • 有些则通过读取IIR本身来清除(如某些特殊字符中断)。
  • 对于FIFO溢出/下溢这类严重错误,清除步骤更复杂:需要先复位对应的FIFO,然后读取RESUME寄存器来清除内部错误标志,之后模块才能恢复正常通信。

错误的中断清除操作会导致中断持续触发,系统陷入死循环。

5. 高级主题与实战避坑指南

掌握了基本模式后,我们再看几个高级主题和实际开发中必然遇到的“坑”。

5.1 波特率生成器的配置禁忌

模块的波特率由DLL和DLH这两个分频器寄存器控制。计算公式为:分频值 = 系统时钟频率 / (波特率 × 波特率倍数)。其中,波特率倍数在UART 16x模式下为16,在IrDA SIR模式下也为16,在MIR模式下为41或42。

严重警告:在修改DLL和DLH寄存器的值之前,必须先将模式选择寄存器MDR1[2:0]设置为7h(即禁用模式)。如果模块正在���作(任何模式)时修改波特率分频器,会导致不可预测的行为,通常表现为通信彻底紊乱。正确的配置顺序是:停止模块 -> 设置MDR1为禁用 -> 配置DLL/DLH -> 设置MDR1为所需模式 -> 使能模块。

5.2 IrDA/CIR特有的状态FIFO与帧管理

IrDA模式下的状态FIFO是提升效率的神器。它解耦了数据流和帧管理信息。DMA可以持续地将原始数据搬运到内存的环形缓冲区,而CPU只需在状态FIFO达到触发阈值时被中断一次,然后批量读取多个帧的状态信息,再根据帧长度去内存缓冲区中切割出完整的帧数据。这极大地减少了中断频率。

帧的结束有两种方式:

  • 帧长度法:CPU预先将帧长度写入TXFLH/L寄存器。硬件发送完指定长度的数据后,自动添加结束标志。适用于帧长度固定的场景。
  • 设置EOT位法:CPU在写入帧的最后一个字节到TX FIFO之前,先将ACREG[0](EOT位)置1。硬件会给这个字节打上“结束标记”,并在发送它时自动添加结束标志。适用于变长帧。

5.3 睡眠模式下的注意事项

模块支持睡眠模式以降低功耗。在UART模式下,通过设置IER[4](需EFR[4]=1)进入睡眠。进入条件苛刻:RX线空闲、TX FIFO和移位寄存器空、RX FIFO空、且除THR中断外无其他中断挂起。特别注意:在睡眠模式下,绝对不能去修改DLL和DLH寄存器。因此,如果需要改变波特率,必须先退出睡眠模式。

5.4 常见问题排查速查表

现象可能原因排查步骤与解决方案
数据接收不完整,随机丢失1. 接收FIFO溢出。
2. 中断服务程序处理太慢,或中断被高优先级任务阻塞。
3. 波特率误差过大。
1. 检查LSR寄存器OE位是否置位。若是,需按手册复位RX FIFO并读RESUME。
2. 优化ISR代码,减少处理时间;或提高接收FIFO触发阈值,降低中断频率。
3. 核对系统时钟和DLL/DLH计算值,确保波特率误差在可接受范围(通常<2%)。
发送数据卡住,无法发出1. 发送FIFO已满仍持续写入。
2. 线路断开或对方设备未就绪(如CTS流控)。
3. 发送下溢错误(IrDA/CIR模式)。
1. 在发送前检查SSR[0](TXFIFOFULL)位,或使用中断/DMA机制避免写满。
2. 检查硬件连接;如果启用了硬件流控,确认CTS信号有效。
3. 检查IIR寄存器,查看是否有TX状态中断(IrDA/CIR),并处理下溢错误(复位TX FIFO,读RESUME)。
中断频繁触发,系统卡顿1. FIFO触发阈值设置过小。
2. 中断清除方式错误,导致中断重复触发。
3. 通信线缆噪声导致频繁的帧错误或间断中断。
1. 适当增大TLR中的触发阈值。
2. 严格对照数据手册表格,确认每种中断的正确清除方式。
3. 检查LSR中的FE/BI位,优化硬件设计(如增加滤波、使用屏蔽线)。
DMA传输数据错位或丢失DMA控制器传输量配置与UART模块的TLR阈值不匹配。核对并确保DMA控制器的单次传输字节数等于UART模块TLR中设置的触发字节数。
配置寄存器后模块无反应1. 关键配置步骤顺序错误(如未先禁用模块就改波特率)。
2. 寄存器访问地址或模式错误(LCR[7]等位影响寄存器映射)。
1. 严格按照手册顺序初始化:先禁用(MDR1=7h),再配波特率、FIFO、模式,最后使能。
2. 使用调试器或读取回配置寄存器,确认写入值是否正确。注意LCR[7]和LCR[3:0]的值会影响某些寄存器的映射地址。

5.5 配置流程建议

根据多年经验,一个稳健的初始化流程如下:

  1. 禁用模块:将MDR1[2:0]设置为7h
  2. 配置波特率:根据所需模式(UART 16x/13x, IrDA SIR/MIR)计算分频值,写入DLL和DLH寄存器。
  3. 配置线路控制:设置LCR寄存器,确定数据位、停止位、奇偶校验位。
  4. 配置FIFO与触发:写FCR寄存器使能FIFO;根据应用需求(延迟 vs 吞吐)设置TLR寄存器中的收发阈值。
  5. 选择工作模式:配置SCR寄存器选择DMA模式(如需要);配置IER寄存器使能所需中断。
  6. 选择物理层模式并使能:将MDR1[2:0]设置为目标模式(如UART模式),模块开始工作。
  7. (若用DMA)配置DMA控制器:将DMA的单次传输量设置为与TLR阈值一致。

最后,分享一个调试小技巧:在项目初期,可以先将FIFO深度设小(虽然硬件是64字节,但通过TLR可以模拟浅FIFO行为),并启用中断。这会让数据阻塞或溢出问题更早、更明显地暴露出来,便于在开发阶段就发现并解决潜在的流控或处理逻辑缺陷,等稳定后再调整阈值至最优值。