UART FIFO与DMA配置实战:从原理到AM62L优化指南

📅 2026/7/25 14:26:53 👁️ 阅读次数 📝 编程学习
UART FIFO与DMA配置实战:从原理到AM62L优化指南

1. UART FIFO与DMA:从基础概念到实战优化

在嵌入式开发里,UART(通用异步收发传输器)就像系统的“嘴巴”和“耳朵”,负责与外部世界对话。但如果你处理过高速、大数据量的串口通信,比如从传感器持续采集数据或向显示屏发送长帧指令,肯定遇到过CPU被频繁中断“打懵”,或者数据因为处理不及时而丢失的窘境。这背后的核心矛盾在于:UART硬件收发一个字节的速度(微秒级)和CPU处理一个中断、搬运数据的速度(可能涉及上下文切换,微秒到毫秒级)不在一个量级上。

为了解决这个问题,现代微控制器(比如TI的AM62L系列)的UART模块普遍集成了两大“神器”:FIFO(先进先出缓冲区)和DMA(直接内存访问)。FIFO相当于在UART和CPU之间加了一个“蓄水池”,可以暂存多个数据,避免每个字节都去打扰CPU。而DMA则像是一个“专职搬运工”,能在“蓄水池”达到一定水位时,不经过CPU,直接与内存进行大批量数据搬运。今天,我就结合AM62L的技术手册和实际调试经验,把这套机制掰开揉碎了讲清楚,重点聊聊如何配置FIFO的中断与轮询模式,以及如何玩转DMA来最大化释放CPU性能。

简单来说,FIFO管理解决了“何时通知CPU”的问题,而DMA解决了“谁来搬运数据”的问题。两者的组合使用,是构建高效、可靠串口通信系统的基石。无论是做工业PLC、智能家居网关还是车载设备,吃透这套机制,能让你在调试通信稳定性、优化系统功耗时,心里更有底。

2. FIFO工作机制深度解析:不止是个缓冲区

很多人把FIFO简单理解为一个数据缓存区,这没错,但它的管理逻辑才是精髓。AM62L的UART模块通常包含一个64字节的发送(TX)FIFO和一个64字节的接收(RX)FIFO。关键不在于它有多大,而在于我们如何通过寄存器精准控制它的行为。

2.1 FIFO的启停与清零:精细化的控制起点

控制FIFO的核心寄存器是UART_FCR(FIFO控制寄存器)。其中,FIFO_EN位是总开关。上电复位后,FIFO默认是禁用的,此时UART工作在传统的单字节模式,每收/发一个字节都可能产生中断,对CPU负载极高。

注意:在启用FIFO(FIFO_EN=1)后,之前残留在UART_THR(发送保持寄存器)或UART_RHR(接收保持寄存器)的单字节数据并不会自动转入FIFO。安全的做法是,在初始化流程中,先启用FIFO,紧接着使用RX_FIFO_CLEARTX_FIFO_CLEAR位进行复位。手册中提到,这两个清零位会在4 x PCLK + 5 x UARTi_FCLK个时钟周期后由硬件自动清零,软件需要等待这个操作完成(通常用短暂延时或检查位状态)后才能进行后续的数据读写。

2.2 触发阈值(Trigger Level):性能调优的关键阀门

这是FIFO管理的核心参数,直接决定了中断或DMA请求的触发频率。AM62L的触发阈值配置非常灵活,主要通过UART_FCRUART_TLR(触发等级寄存器)来设置。

对于发送(TX)FIFO: 阈值指的是FIFO中的空余空间(Space)。例如,设置TX触发阈值为8,意味着当TX FIFO中已填满的数据被发送出去,使得空余空间大于或等于8字节时,就会触发TX中断或DMA请求,通知系统“可以再送8个字节数据过来了”。

对于接收(RX)FIFO: 阈值指的是FIFO中已存数据量(Data Level)。例如,设置RX触发阈值为16,意味着当RX FIFO中累积收到16个字节时,就会触发RX中断或DMA请求,通知系统“快来取走至少16个字节的数据”。

配置方式有三种,由UART_SCR(辅助控制寄存器)的相关位决定:

  1. 基础模式(SCR[6]SCR[7]= 0,且TLR对应字段为0):使用UART_FCR中的TX_FIFO_TRIGRX_FIFO_TRIG位域。通常提供几个固定档位,如8、16、32、56字节。这是最常用的快速配置方式。
  2. DMA优化模式(SCR[6]SCR[7]= 0,且TLR对应字段非0):使用UART_TLR中的TX_FIFO_TRIG_DMARX_FIFO_TRIG_DMA位域。提供以4字节为粒度的灵活设置(如4, 8, 12, …, 60)。这在配合DMA控制器时非常有用,可以对齐DMA传输的块大小。
  3. 精细模式(SCR[6]SCR[7]= 1):将TLR中的DMA位域和FCR中的基础位域拼接起来,实现以1字节为粒度的设置(1到63)。这提供了最高的灵活性。

实操心得:阈值的设置是一场吞吐量实时性的权衡。阈值设得高(如RX=56),一次中断/搬运的数据量大,CPU干预频率低,吞吐量高,但数据在FIFO中停留时间变长,实时性变差。阈值设得低(如RX=1),实时性好,但中断风暴会让CPU疲于奔命。我的经验是,对于连续流数据(如日志输出),设高阈值;对于需要快速响应的命令帧(如Modbus RTU),设低阈值(如4或8),并在软件中实现超时断帧机制。

2.3 流控(Flow Control)下的阈值协调

当使用硬件流控(RTS/CTS)或软件流控(XON/XOFF)时,会涉及另一个重要寄存器UART_TCR(传输控制寄存器)。它包含了RX_FIFO_TRIG_STARTRX_FIFO_TRIG_HALT两个阈值。

  • RX_FIFO_TRIG_START:当RX FIFO数据量低于此阈值时,重新断言RTS信号(拉低),通知对端“我可以继续接收数据了”。
  • RX_FIFO_TRIG_HALT:当RX FIFO数据量达到此阈值时,取消断言RTS信号(拉高),通知对端“暂停发送”。

这里有一个黄金法则:在中断模式下,必须保证RX_FIFO_TRIG_HALT >= RX_FIFO_TRIG(你设置的接收中断阈值)。否则,可能会出现一种尴尬局面:FIFO数据达到了中断阈值,CPU被通知来处理,但同时RTS信号已经拉高让对端暂停发送了。如果CPU处理速度稍慢,对端暂停后,FIFO数据被取走一部分,低于HALT阈值,RTS又会拉低,对端重新开始发送……这可能导致流控振荡和不稳定的数据传输。在DMA模式下,由于每个字节到达都可能产生DMA请求,这个限制不存在。

3. FIFO中断模式 vs. 轮询模式:场景化选择策略

FIFO可以与两种CPU交互模式配合:中断模式和轮询模式。这不仅仅是“开中断”和“关中断”的区别,而是两种完全不同的软件架构。

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

这是最常用、也是推荐的方式。通过设置UART_FCR[0] FIFO_EN = 1并启用UART_IER_UART(中断使能寄存器)中相应的中断位(如IER[0]用于RHR中断,IER[1]用于THR中断)来工作。

接收流程

  1. 外部数据持续进入RX FIFO。
  2. 当FIFO中数据量达到预设的RX触发阈值时,硬件自动拉高中断线。
  3. CPU跳转到中断服务程序(ISR)。
  4. ISR读取UART_IIR_UART(中断标识寄存器)判断中断源,然后从UART_RHR寄存器中连续读取数据,直到FIFO数据量低于触发阈值。一次中断可以处理多个字节。
  5. 中断清除。对于RHR中断,读取UART_RHR直至条件消失即可清除。

发送流程

  1. 初始时,TX FIFO为空,硬件立即产生THR中断。
  2. CPU在ISR中向UART_THR写入数据,直到填满FIFO或达到软件设定的单次写入上限。
  3. 当写入的数据量使得TX FIFO的空闲空间低于触发阈值时,中断信号被撤销。
  4. 随着数据被串行发出,TX FIFO空间逐渐增加。当空间再次大于或等于触发阈值时,再次产生THR中断,请求CPU填充数据。

注意事项:发送中断的触发条件是“空间可用”,而不是“FIFO空”。这是一个关键细节。如果你设置TX触发阈值为8,那么当FIFO从满状态发送掉一个字节,剩下63字节时(空间=1),并不会触发中断。只有当发送到剩下56字节时(空间=8),才会触发中断。这避免了过于频繁的中断。

3.2 FIFO轮询模式:简单场景的轻量方案

通过设置UART_FCR[0] FIFO_EN = 0(禁用FIFO)或保持FIFO启用但禁用所有UART中断(UART_IER_UART = 0),然后定期查询UART_LSR_UART(线路状态寄存器)来工作。

你需要轮询的关键位包括:

  • LSR[0]:接收数据就绪(DR)。为1表示UART_RHR(或FIFO)中有数据可读。
  • LSR[5]:发送保持寄存器空(THRE)。为1表示UART_THR(或FIFO)有空位可以写入新数据。
  • LSR[1-4]:溢出错误(OE)、奇偶错误(PE)、帧错误(FE)、间隔中断(BI)。用于错误检测。

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

  • 优点:实现简单,无需管理中断向量和上下文切换,代码逻辑直观。
  • 缺点:CPU必须不断“询问”UART状态,占用大量CPU周期,效率低下。实时性取决于轮询周期,可能引入不可预测的延迟。

适用场景:仅适用于数据量极少、对实时性要求极低、或是在一个超级循环(Super Loop)架构的简单任务中。在实际产品级代码中,我几乎从不建议在主循环中轮询UART,除非是用于调试输出。

3.3 模式选择与实战建议

  • 绝大多数应用,请使用中断模式。它合理利用硬件特性,在数据到来时主动通知CPU,是效率与实时性的平衡点。
  • 在RTOS环境中,通常会在UART中断服务程序(ISR)中仅做最少的操作(如读取数据到临时缓冲区),然后通过消息队列、信号量或任务通知等方式,唤醒一个高优先级的数据处理任务。这样能保持ISR短小精悍,避免在ISR内进行复杂处理阻塞其他中断。
  • 轮询模式仅作为调试辅助。例如,在系统初始化阶段,用轮询方式发送几个调试字符;或者在所有中断尚未配置好的早期启动代码中临时使用。

4. DMA模式:解放CPU的终极武器

当数据量进一步增大,比如要通过UART传输图像帧、音频采样块或大量传感器数据时,即使有FIFO缓冲,频繁的中断和内存拷贝仍会成为CPU的沉重负担。此时,DMA(直接内存访问)是必选项。

4.1 AM62L UART的DMA模式概览

AM62L支持多种DMA模式(模式0-3),主要通过UART_SCR[0] DMA_MODE_CTLUART_FCR[3] DMA_MODE以及UART_SCR[2-1] DMA_MODE_2位域来配置。

  • 模式0:无DMA操作。DMA相关信号不活动。
  • 模式1(推荐):独立的TX和RX DMA通道。这是最强大、最常用的模式,发送和接收可以独立、并发地由DMA控制器处理。
  • 模式2:传统模式,仅使用RX DMA请求。TX部分仍需CPU中断或轮询处理。
  • 模式3:传统模式,仅使用TX DMA请求。RX部分仍需CPU中断或轮询处理。

配置逻辑如下(这是手册的精华,也是容易出错的地方):

  1. 如果DMA_MODE_CTL = 0,则DMA模式由DMA_MODE位决定:0=模式0,1=模式1。
  2. 如果DMA_MODE_CTL = 1,则DMA模式完全由DMA_MODE_2位域决定:00=模式0,01=模式1,10=模式2,11=模式3。此时DMA_MODE位被忽略。

重要提示:为了配置的清晰和可维护性,我强烈建议统一使用DMA_MODE_CTL = 1,然后通过DMA_MODE_2位域来选择模式。这样可以避免寄存器位之间的隐含覆盖关系导致的意外行为。

4.2 DMA传输的工作流程与阈值设定

DMA模式下的FIFO触发阈值概念与中断模式类似,但目的不同:它决定的是何时向DMA控制器发起传输请求

接收DMA流程

  1. RX FIFO持续接收数据。
  2. 当FIFO中数据量达到RX_FIFO_TRIG_DMA(或拼接后的阈值)时,UART模块向DMA控制器发出请求。
  3. DMA控制器启动一次传输,从UART的UART_RHR“外设地址”读取阈值指定数量的字节,直接搬运到内存中指定的缓冲区。
  4. 传输完成后,DMA请求撤销。FIFO数据被取走,水位下降。
  5. 当后续数据再次填满FIFO达到阈值时,重复步骤2-4。

发送DMA流程

  1. 初始时,TX FIFO为空,UART模块立即向DMA控制器发出请求。
  2. DMA控制器启动一次传输,从内存缓冲区中读取阈值指定数量的字节,写入UART的UART_THR“外设地址”。
  3. 传输完成后,DMA请求撤销。FIFO被填充,数据开始串行发出。
  4. 随着数据发出,FIFO空间逐渐增加。当空闲空间再次达到TX触发阈值时,UART再次发出DMA请求,DMA控制器搬运下一批数据。

这里有一个至关重要的匹配原则:你为UART设置的DMA触发阈值(例如56字节),必须与DMA控制器的传输宽度(Transfer Width)突发大小(Burst Size)相匹配。例如,如果你设置UART TX阈值为56字节,那么配置DMA控制器时,单次传输大小也应设为56字节。如果DMA单次只搬32字节,那么搬完后FIFO仍有空间,但未达到阈值,DMA请求会保持有效,直到你通过软件写入剩余数据或重新配置,这可能导致DMA挂起或数据流中断。

4.3 高级话题:TX DMA阈值的独立设定

在默认情况下,TX DMA的触发阈值等于TX FIFO的总大小减去你在UART_TLR中设置的TX触发阈值。AM62L提供了一个高级功能,允许你直接设定一个独立的TX DMA阈值,通过UART_MDR3[2] SET_DMA_TX_THRESHOLD位和UART_TX_DMA_THRESHOLD寄存器实现。

  • SET_DMA_TX_THRESHOLD = 0(默认):使用上述的默认计算方式。
  • SET_DMA_TX_THRESHOLD = 1:TX DMA阈值直接采用UART_TX_DMA_THRESHOLD寄存器的值。但手册警告,如果TX_DMA_THRESHOLD + TX触发阈值 > 64,则会回退到默认方式,以防止FIFO溢出。

这个功能有什么用?想象一个场景:你的TX FIFO是64字节,触发阈值设为8(即空闲空间>=8时请求DMA)。默认的DMA阈值就是64-8=56字节。这意味着DMA每次会搬56字节来填满FIFO。但如果你希望DMA以更小的块(比如16字节)进行传输,以获得更平滑的数据流和更低的单次延迟,就可以启用此功能,并设置TX_DMA_THRESHOLD = 16。此时,当TX FIFO空闲空间>=8时触发请求,但DMA只搬16字节就停止,而不是搬56字节。

4.4 安全关闭DMA的序列

手册中特别强调,当需要禁用DMA(例如所有数据传输完成,准备进入低功耗模式)时,必须遵循严格的软件序列,否则可能导致数据丢失或DMA状态机卡死。这里以禁用TX DMA(从模式1切换到模式2或0)为例,概括其核心思想:

  1. 确保数据已处理:在尝试切换模式前,必须确认所有RX数据已被读取,所有TX数据已发送完毕。对于RX,可以使用流控或直接禁用接收器。
  2. 执行FIFO清零:写入UART_FCR[2:1] = 0b11,同时清零TX和RX FIFO及其计数器。这是一个关键步骤,用于重置内部状态。
  3. 切换DMA模式:将DMA_MODE_2位域从01(模式1)改为10(模式2,仅RX)或00(模式0,无DMA)。
  4. 再次确认:根据手册,在某些序列后可能需要再次执行FIFO清零操作。

踩坑记录:我曾遇到过在高速数据传输中直接修改DMA模式寄存器导致系统锁死的情况。根本原因是DMA控制器可能还在进行未完成的传输,而UART模块的状态突然改变。最安全的做法是:在计划停止DMA传输前,先通过软件停止DMA控制器的通道,等待其完成当前传输并进入空闲状态,然后再执行上述UART模块的模式切换和FIFO清零操作。这需要仔细查阅你所用的具体DMA控制器文档。

5. 功耗管理与实战中的平衡艺术

UART作为常开的外设,其功耗管理在电池供电的物联网设备中至关重要。AM62L的UART提供了模块级和系统级的睡眠机制。

5.1 模块睡眠模式(Sleep Mode)

在UART模式下,通过设置UART_IER_UART[4] SLEEP_MODE = 1(且UART_EFR[4] ENHANCED_EN = 1)来使能。进入睡眠模式需要满足严格条件:

  1. RX线处于空闲状态(无数据)。
  2. TX FIFO和移位寄存器为空。
  3. RX FIFO为空。
  4. 只有THR中断可能处于挂起状态(这意味着所有数据已发送完)。

进入睡眠后,模块内部时钟和波特率时钟会停止,功耗大幅降低。唤醒事件包括:RX线状态变化、数据写入TX FIFO、或Modem输入引脚状态变化。

重要警告:手册明确指出,在睡眠模式下,绝对不能对波特率发生器寄存器UART_DLLUART_DLH进行写操作。在修改波特率前,务必先通过清除SLEEP_MODE位来禁用睡眠模式。

5.2 系统级空闲与唤醒

UART还支持通过UART_SYSC[2] ENAWAKEUP位和UART_WER(唤醒使能寄存器)参与系统级的电源管理。当系统准备进入低功耗状态时,可以通过Idle Req/Ack握手协议,干净地关闭UART时钟,并将其从中断生成模式切换到唤醒生成模式。当UART_WER中使能的唤醒事件(如RX引脚边沿)发生时,UART可以产生唤醒信号,将整个系统从低功耗状态拉回。

实战中的功耗优化策略

  1. 动态调整波特率:在需要高速传输时使用高波特率,快速完成传输后进入睡眠。在低速监测场景使用低波特率。
  2. 利用FIFO和DMA减少CPU活跃时间:配置合理的FIFO阈值和DMA块大小,让CPU在一次唤醒中处理更多数据,然后迅速回到休眠状态。DMA搬运数据时,CPU甚至可以进入更深度的睡眠。
  3. 及时进入模块睡眠:在通信间歇期,如果确认短时间内不会有数据,主动检查并满足睡眠条件,让UART模块进入睡眠。
  4. 关闭不必要的功能:如果不需要硬件流控、红外编码(IrDA)等高级功能,确保相关模块和时钟被禁用。

6. 配置流程与常见问题排查

6.1 一个典型的UART带FIFO与DMA的初始化流程

  1. 时钟与引脚配置:使能UART模块的时钟,配置对应引脚为UART功能。
  2. 软件复位:置位UART_SYSC[1] SOFTRESET,等待复位完成(通常该位会自动清零)。
  3. 基本参数设置:配置UART_LCR(线路控制寄存器)设置数据位、停止位、奇偶校验。配置UART_DLLUART_DLH设置波特率。注意:修改波特率前需确保睡眠模式已禁用。
  4. FIFO配置
    • UART_FCR:先设置FIFO_EN=1启用FIFO。
    • 设置RX_FIFO_TRIGTX_FIFO_TRIG选择基础阈值,或通过UART_SCRUART_TLR配置更精细的阈值。
    • 置位RX_FIFO_CLEARTX_FIFO_CLEAR位,等待硬件清零(短暂延时)。
  5. DMA配置
    • 设置UART_SCR[0] DMA_MODE_CTL = 1
    • 根据需求设置UART_SCR[2:1] DMA_MODE_2(例如01为模式1)。
    • 如果需要独立TX DMA阈值,配置UART_MDR3UART_TX_DMA_THRESHOLD
  6. 中断/DMA使能
    • 若用中断模式:配置UART_IER_UART,使能RHR、THR等所需中断。在系统层面配置NVIC(中断控制器)。
    • 若用DMA模式:配置DMA控制器,设置源/目标地址、传输宽度、突发大小,并链接到UART的DMA请求信号。使能DMA通道。
  7. 使能传输:通过UART_ECR寄存器独立使能接收器(RX)和发送器(TX)。

6.2 常见问题与排查技巧

问题现象可能原因排查步骤与解决方案
数据丢失(接收不完整)1. RX FIFO溢出。
2. 中断/DMA响应太慢。
3. 流控配置错误。
1. 检查UART_LSR_UART[1] OE(溢出错误)位。如果置位,说明数据来得太快。
2.提高RX FIFO触发阈值,降低中断频率。
3. 检查并启用硬件流控(RTS/CTS),确保RX_FIFO_TRIG_HALT设置合理。
4. 优化ISR或DMA回调函数,减少处理延迟。
发送速度慢,有卡顿1. TX FIFO阈值设置过高。
2. DMA传输块大小与阈值不匹配。
3. 系统中断被长时间关闭。
1.降低TX FIFO触发阈值(如从56改为8),让DMA/中断更早请求数据。
2. 核对DMA控制器配置的传输大小是否等于UART的TX DMA阈值。
3. 检查是否有高优先级任务或中断长时间占用CPU。
无法进入睡眠模式睡眠条件不满足。1. 检查UART_SSR或相关状态位,确认TX/RX FIFO是否真正为空,RX线是否空闲。
2. 确认是否只有THR中断可能挂起(即发送已完成)。
3. 在尝试睡眠前,手动清除所有可能的中断标志。
DMA传输意外停止1. DMA控制器传输完成中断后未重新配置。
2. UART的DMA模式被意外更改。
3. 内存缓冲区边界处理错误。
1. 在DMA传输完成中断中,检查是半传输完成还是全传输完成,并正确重新配置源/目标地址和计数。
2. 在关键代码段避免操作UART_SCR的DMA模式位。
3. 使用“双缓冲”(Ping-Pong Buffer)机制,当DMA在操作缓冲区A时,CPU处理缓冲区B,反之亦然,实现无缝连续传输。
奇偶校验或帧错误频发1. 波特率不匹配。
2. 线路干扰。
3. 时钟精度不够。
1. 双方计算并核对波特率寄存器的值。
2. 检查硬件连接,在长距离或噪声环境使用差分信号(如RS485)并加终端电阻。
3. 使用更高精度的外部晶振。

调试UART的FIFO和DMA问题时,逻辑分析仪带协议解码功能的示波器是无价之宝。你可以直接抓取UART的TX、RX引脚信号,以及DMA请求线、中断线的波形,直观地看到数据流、触发时机和硬件信号之间的时序关系,很多软件中难以捉摸的问题在波形图前会一目了然。

最后,再分享一个小心得:在项目初期,不要急于把所有优化(大FIFO、高阈值、复杂DMA)都加上。先用最简单的中断模式(甚至轮询)把通信链路调通,确保数据能正确收发。然后,逐步引入FIFO,观察中断频率的变化。最后,再上DMA,并仔细调整阈值和块大小。这种渐进式的调试方法,能帮你清晰地定位每一层引入的问题,最终构建出一个既高效又稳定的串口通信子系统。