AM261x UART FIFO DMA模式深度解析与实战配置指南
2026/7/26 12:46:23 网站建设 项目流程

1. 项目概述与核心价值

在嵌入式系统开发中,串口通信是连接传感器、执行器、调试终端和其他微控制器的基石。然而,当数据流量增大或系统需要处理多任务时,传统的轮询或中断驱动方式会迅速成为性能瓶颈,大量消耗宝贵的CPU周期。这时,直接内存访问技术就成了解放CPU、提升系统整体效率的关键。它允许数据在外设(如UART)和内存之间“自动”搬运,CPU只需发起和结束传输,中间过程无需干预。

但仅仅启用DMA还不够。想象一下,如果UART每收到一个字节就向DMA控制器请求一次传输,那么DMA请求的频繁中断和微小数据块的搬移开销,反而会抵消DMA带来的优势。这就是FIFODMA触发阈值机制发挥作用的地方。它们共同构成了一个高效的“缓冲-批量”处理流水线:FIFO作为数据的蓄水池,积累到一定水位(阈值)后,才触发DMA进行一次批量搬运。这种设计极大地减少了DMA请求的次数和总线占用,让数据传输变得平滑且高效。

本文将以德州仪器AM261x系列微控制器的UART模块为蓝本,深入剖析其FIFO DMA模式的运作机制。我们不止步于手册的寄存器描述,而是结合我多年的嵌入式通信开发经验,拆解四种DMA模式(0-3)的应用场景、详细解读阈值配置的逻辑陷阱、并手把手演示从模式配置到安全关闭的完整流程。无论你是正在优化现有串口通信性能,还是为新的高带宽应用选型设计,理解这些底层机制都将帮助你构建出更稳定、更高效的嵌入式通信系统。

2. DMA模式深度解析:从模式0到模式3

AM261x的UART模块提供了四种DMA操作模式(Mode 0, 1, 2, 3),其核心区别在于DMA请求信号的分配和使用逻辑。理解这些模式是进行正确配置的第一步。

2.1 模式0与模式1:现代双通道DMA的基石

模式0和模式1是当前最常用、功能最完整的模式,它们为TX(发送)和RX(接收)分别提供了独立的DMA请求信号。

  • DMA模式0:这是最基础的独立双通道模式。当SCR[0]=0FCR[3]=0时启用。在此模式下,TX和RX的DMA请求通过独立的信号线(通常是USARTi_DMA_TXUSARTi_DMA_RX)发出,可以分别映射到DMA控制器的不同通道,实现全双工、完全独立的DMA传输。发送和接收的阈值、传输量均可独立配置,互不干扰。
  • DMA模式1:在模式0的基础上,增加了一层由FCR[3](DMA_MODE)位控制的使能开关。当SCR[0]=0FCR[3]=1时启用。其物理连接与模式0相同,但FCR[3]位提供了一个软件快速开关。你可以通过简单地置位或清零FCR[3]来全局启用或禁用UART的DMA功能,而无需改动SCR寄存器中更复杂的模式配置。这在需要动态切换传输方式(例如从DMA模式切换回中断模式进行调试)时非常方便。

实操心得:在新项目设计中,我强烈建议优先使用模式1。它既保留了模式0的全部功能,又通过FCR[3]位提供了额外的灵活性。你可以将SCR寄存器的配置作为初始化的一部分固化,后续仅通过操作FCR[3]来管理DMA的启停,代码逻辑会更清晰。

2.2 模式2与模式3:理解“传统模式”的遗产

模式2和模式3被手册标记为“传统模式”,它们只使用一个DMA请求信号(USARTi_DMA0)来服务TX或RX中的一个方向。

  • DMA模式2:仅RX使用DMA。在此模式下,USARTi_DMA0信号专用于接收方向的DMA请求。发送方向则必须使用其他方式(如中断或轮询)来处理。这种模式适用于那些数据接收流量大、但发送数据量小或实时性要求不高的场景,例如数据采集器,它大部分时间在接收传感器数据,偶尔才发送配置指令或状态报告。
  • DMA模式3:仅TX使用DMA。与模式2相反,USARTi_DMA0信号专用于发送。接收方向需使用非DMA方式。这适用于需要持续、高速发送数据(如日志流、音频流),但接收多为短命令的应用。

模式选择的关键逻辑:模式的选择并非在FCRSCR中直接写入一个模式编号,而是通过SCR[0](DMA_MODE_CTL)和FCR[3](DMA_MODE)两个位的组合,以及当SCR[0]=1时由SCR[2:1](DMA_MODE_2)位域共同决定的。这看起来有些复杂,我将其整理成一个决策表:

期望的DMA模式SCR[0](DMA_MODE_CTL)FCR[3](DMA_MODE)SCR[2:1](DMA_MODE_2)说明
禁用DMA1忽略00FCR[3]被忽略,完全由SCR寄存器控制。
模式000忽略经典双通道独立模式。
模式101忽略带使能开关的双通道模式。
模式1(替代)1忽略01通过SCR寄存器配置的模式1,FCR[3]被忽略。
模式2(仅RX)1忽略10传统模式,仅接收使用DMA。
模式3(仅TX)1忽略11传统模式,仅发送使用DMA。

注意事项:这里存在一个关键的“覆盖”规则。当SCR[0]设置为1时,SCR[2:1]的配置将完全覆盖FCR[3]的设置。这意味着,即使你在FCR[3]中使能了DMA,只要SCR[0]=1SCR[2:1]=00,DMA功能依然是被禁用的。在调试DMA不生效的问题时,务必同时检查SCRFCR这两个寄存器,确认它们处于一致的配置状态。

2.3 FIFO禁用时的回退机制

手册中有一句非常关键但容易被忽略的话:“If the FIFOs are disabled (FCR[0]=0), DMA operations occur in single character transfers.”

这意味着,FIFO的使能是高效DMA传输的前提。如果FIFO被禁用(FCR[0]=0),无论你配置了哪种DMA模式,DMA请求都会退化为“每收到/发送一个字符就请求一次”的模式。这相当于把DMA用成了“自动化的单字节中断”,总线仲裁和上下文切换的开销会非常大,完全失去了使用DMA和FIFO的意义。因此,在启用任何DMA功能前,必须确保FCR[0](FIFO使能位)已被置为1

3. 阈值配置的艺术:平衡性能与实时性

DMA传输的效率和实时性,很大程度上由TX/RX FIFO的触发阈值决定。阈值设得太高,可能导致FIFO已满但DMA请求还未触发,造成数据溢出;设得太低,又会频繁产生DMA请求,增加总线负载。AM261x提供了灵活的阈值配置方法,尤其是针对TX DMA。

3.1 RX FIFO阈值:相对简单的触发

对于接收方向,逻辑相对直接。DMA请求在RX FIFO中的数据量达到UART_TLR寄存器中定义的触发水平时产生。当DMA控制器读取了相应数量的字节后,请求取消。例如,如果RX FIFO深度为64字节,阈值设为16,那么每收到16个字节,UART就会产生一个DMA请求,DMA控制器一次搬运16字节。这能有效减少中断次数。

3.2 TX FIFO阈值:两种计算策略与“空间”概念

发送方向的阈值配置更为复杂,因为它引入了“空间”的概念。TX DMA请求的触发条件不是“FIFO中有多少数据”,而是“FIFO中有多少空闲空间”。

AM261x提供了两种阈值编程方式,由UART_MDR3[2](SET_DMA_TX_THRESHOLD)位控制:

方式一:直接阈值法(SET_DMA_TX_THRESHOLD = 1)阈值直接等于UART_TX_DMA_THRESHOLD寄存器的值。当TX FIFO中的空闲空间(即“空间”)大于或等于此阈值时,DMA请求被置起。DMA控制器开始向FIFO写入数据,直到写入了UART_TLR寄存器中定义的“触发空间数”个字节后,请求才取消。如果写入的字节数不足,DMA请求将保持有效。

方式二:计算阈值法(SET_DMA_TX_THRESHOLD = 0)阈值 = TX FIFO总大小 -UART_TLR寄存器中定义的“触发空间数”。这是更常用也更直观的方式。DMA请求在TX FIFO中的数据量低于这个计算出的阈值时被置起。例如,TX FIFO大小为64字节,UART_TLR设置的触发空间数为8,那么阈值就是64-8=56字节。当FIFO中数据少于56字节(即空闲空间大于8字节)时,触发DMA请求,DMA控制器写入8字节数据后请求取消。

手册中的图示(Figure 13-73, 13-74)清晰地展示了这个过程。关键在于,UART_TLR寄存器中为TX定义的值,代表的是“希望DMA一次填充多少字节到FIFO中”。这个值必须与DMA控制器的传输量(Burst Size或Transfer Size)精确匹配,否则会导致DMA请求无法正确取消或提前取消,引发传输错误。

核心避坑指南:这里有一个极其重要的保护机制。手册指出:“If the selected threshold level + trigger level exceeds max buffer size, then the original TX DMA threshold method is used to prevent TX overrun, regardless of the UART_MDR3[1] NONDEFAULT_FREQ value.” 意思是,如果你配置的“阈值” + “触发空间数” > FIFO总大小,系统为了防止TX溢出(Overrun),会自动忽略你的配置,回退到使用“FIFO大小”作为阈值的默认方法。例如,FIFO为64字节,你设置阈值为60,触发空间数为8,60+8=68>64,此时配置失效,系统会使用阈值=64。这实际上意味着DMA请求只在FIFO完全空时才触发,失去了阈值调优的意义。在配置时,务必确保“阈值+触发空间数 <= FIFO总大小”

3.3 阈值配置实战建议

  1. 确定传输特性:首先评估你的数据流。是稳定流(如音频)还是突发包(如Modbus帧)?对延迟敏感吗?
  2. 计算合理值:对于稳定流,可以设置较大的触发空间数(如FIFO深度的一半),以减少DMA请求频率。对于突发小包,设置较小的触发空间数(如4或8)可以降低单次传输延迟。
  3. 匹配DMA配置UART_TLR中设置的TX触发空间数,必须与DMA控制器配置的单次传输大小(Transfer Size)完全一致。这是很多驱动BUG的根源。
  4. 启用FIFO:再次强调,配置前确保FCR[0]=1
  5. 选择配置方式:对于大多数应用,使用“计算阈值法”(SET_DMA_TX_THRESHOLD=0)更直观。直接操作UART_TX_DMA_THRESHOLD寄存器的方式,通常在需要非常规阈值时才使用。

4. 完整配置流程与核心代码实现

理解了原理和模式后,我们来看如何将其转化为代码。以下流程基于模式1(双通道DMA)进行配置,这是最通用的场景。

4.1 初始化步骤分解

假设我们要配置UART2,使用DMA模式1,TX/RX FIFO深度均为64,RX阈值设为16,TX触发空间数设为8(即当TX FIFO数据少于56字节时,请求DMA写入8字节)。

步骤1:关闭模块并进入配置模式在对UART的时钟、模式等关键参数进行修改前,必须先将模块置于禁用状态,并进入配置模式以访问所有寄存器。

// 1. 禁用UART功能模块 HWREG(UART2_BASE + UART_MDR1) = 0x7; // MODE_SELECT = 0x7 (Disable) // 2. 进入配置模式A (访问DLL/DLH等寄存器必需) HWREG(UART2_BASE + UART_LCR) |= 0x80; // LCR[7] = 1, 进入配置模式 // 注意:此时LCR[7:0]不能等于0xBF,否则会进入配置模式B

步骤2:配置波特率与帧格式

// 假设系统时钟48MHz, 目标波特率115200, 使用16倍速模式 uint16_t divisor = 48000000 / (16 * 115200); // 计算分频值 = 26 HWREG(UART2_BASE + UART_DLL) = divisor & 0xFF; // 写入分频值低8位 HWREG(UART2_BASE + UART_DLH) = (divisor >> 8) & 0xFF; // 写入分频值高8位 // 配置帧格式:8位数据,无校验,1位停止位 HWREG(UART2_BASE + UART_LCR) = 0x03; // LCR[7]自动清零,退出配置模式A,进入操作模式,并设置8N1

步骤3:使能FIFO并设置触发阈值

// 使能FIFO,并设置TX/RX触发阈值 // FCR[0]=1: 使能FIFO // FCR[1:2]: 保留位 // FCR[3]: DMA模式使能位,稍后设置 // FCR[7:4]: 保留位 // TLR[3:0]: RX FIFO触发等级 (设为16字节) // TLR[7:4]: TX FIFO触发空间数 (设为8字节) // 注意:TLR寄存器在某些模式下可能与SPR共享地址,需确保在正确的子模式下访问。 // 通常需要先设置EFR[4]和MCR[6]来进入TCR_TLR子模式。 // 进入TCR_TLR子模式以访问TLR寄存器 HWREG(UART2_BASE + UART_EFR) |= (1 << 4); // EFR[4] = 1 HWREG(UART2_BASE + UART_MCR) |= (1 << 6); // MCR[6] = 1 // 现在可以安全写入TLR uint8_t tlr_value = (8 << 4) | 16; // TX触发空间=8, RX触发等级=16 HWREG(UART2_BASE + UART_TLR) = tlr_value; // 使能FIFO (FCR[0]=1) HWREG(UART2_BASE + UART_FCR) = 0x01;

步骤4:配置DMA模式我们选择通过SCR寄存器配置DMA模式1(SCR[0]=1, SCR[2:1]=01),这样FCR[3]位将被忽略,配置更清晰。

// 配置SCR寄存器,启用DMA模式1 // 先读取当前值,避免修改其他位 uint32_t scr_val = HWREG(UART2_BASE + UART_SCR); scr_val &= ~0x07; // 清除[2:0]位 scr_val |= (1 << 0); // SCR[0] = 1, DMA_MODE_CTL使能 scr_val |= (1 << 1); // SCR[2:1] = 01b, 选择DMA模式1 // SCR[2]保持为0 HWREG(UART2_BASE + UART_SCR) = scr_val; // 由于SCR[0]=1,此时FCR[3]位被忽略,但为了代码清晰,可以将其清零 uint32_t fcr_val = HWREG(UART2_BASE + UART_FCR); fcr_val &= ~(1 << 3); // FCR[3] = 0 HWREG(UART2_BASE + UART_FCR) = fcr_val;

步骤5:配置TX DMA阈值方式我们选择“计算阈值法”,即SET_DMA_TX_THRESHOLD = 0

// 配置MDR3寄存器 uint32_t mdr3_val = HWREG(UART2_BASE + UART_MDR3); mdr3_val &= ~(1 << 2); // 清除MDR3[2], 即SET_DMA_TX_THRESHOLD = 0 HWREG(UART2_BASE + UART_MDR3) = mdr3_val; // 此时TX DMA阈值 = FIFO_SIZE - TLR[7:4] = 64 - 8 = 56

步骤6:重新使能UART功能模块

// 重新使能UART为16x模式 HWREG(UART2_BASE + UART_MDR1) = 0x0; // MODE_SELECT = 0x0 (UART 16x mode)

至此,UART模块本身的DMA相关配置已完成。接下来需要在系统级的DMA控制器(如EDMA)中配置对应的通道,将外设请求与内存缓冲区关联起来,这部分代码依赖于具体的DMA控制器驱动,但核心是正确映射USARTi_DMA_TXUSARTi_DMA_RX请求信号。

4.2 安全关闭TX DMA的官方序列详解

手册13.1.4.4.6.4.1节详细描述了一个“关闭TX DMA”的序列。这个序列非常重要,尤其是在传输中途需要动态禁用DMA,或进入低功耗模式前。其核心目标是避免在FIFO中尚有未传输数据时关闭DMA导致的数据丢失或损坏。这里以我们配置的模式1(通过SCR设置)为例,解析其步骤:

  1. 准备工作:确保所有待发送数据已从TX FIFO发出(可通过查询UART_LSRTEMT位确认),并且所有RX FIFO中的数据已被读取(如果不再需要RX DMA)。对于UART模式,可以考虑使用RTS/CTS硬件流控来阻止对方继续发送数据。

  2. 执行关闭序列: a.清除FIFO与计数器:将UART_FCR[2:1](注意,手册中标注为DMA_MODE位域,此处可能为笔误或特定含义,结合上下文,应是指FCR中用于清除FIFO的位)设置为11b。这步会清空TX和RX FIFO,并重置其内部计数器。

    fcr_val = HWREG(UART2_BASE + UART_FCR); fcr_val |= (0x03 << 1); // 假设[2:1]位是FIFO清除位 HWREG(UART2_BASE + UART_FCR) = fcr_val; // 该位会自动清零

    b.切换至仅RX DMA模式:将SCR[2:1]修改为10b,即DMA模式2(仅RX DMA)。这一步实质上是解除了TX方向与DMA控制器的关联。

    scr_val = HWREG(UART2_BASE + UART_SCR); scr_val &= ~(0x03 << 1); // 清除[2:1] scr_val |= (0x02 << 1); // 设置为10b, 模式2 (仅RX) HWREG(UART2_BASE + UART_SCR) = scr_val;

    c.再次清除FIFO与DMA请求:重复步骤a,再次清除FIFO,并确保任何挂起的DMA请求被复位。 d.完全禁用DMA或保持RX DMA:根据需求,将SCR[2:1]设置为00b(完全禁用DMA),或者保持10b(如果RX DMA仍需运行)。

致命细节警告:手册在步骤a和b之间特别加了一条Note:“It is strongly suggested to do steps ‘a’ and ‘b’ in two separate write in order to avoid malfunction of the device.”必须将清除FIFO和修改DMA模式这两步操作分成两次独立的寄存器写操作,中间至少间隔几个NOP指令或确保写操作完成。如果合并成一次赋值,可能会导致芯片内部状态机紊乱,引发不可预知的行为。这是来自芯片设计者的重要警告,务必遵守。

5. 传输机制图解与实战问题排查

5.1 DMA传输时序全景

手册中的图13-77和13-78清晰地展示了DMA传输和接收的流程。对于发送,流程是:DMA将数据从内存搬至TX FIFO -> FIFO达到触发阈值后停止请求 -> UART内核将数据从FIFO移位发出 -> FIFO数据量低于阈值,再次触发DMA请求。这是一个“填充-发送-再填充”的循环。

对于接收,流程是:UART将数据存入RX FIFO -> FIFO达到触发阈值 -> 触发DMA请求 -> DMA将数据从FIFO搬至内存 -> 搬移完成后请求取消。这是一个“积累-搬运-再积累”的循环。

关键点在于“DMA元素同步”:每次DMA请求,传输的是“一个元素”,在UART上下文中通常就是1个字节。但DMA控制器可以配置为一次请求传输一个“突发”(Burst)的数据块,其大小必须与UART中设置的触发空间数(TX)或触发等级(RX)匹配。

5.2 常见问题与排查技巧实录

在实际项目中,UART DMA配置出错的表现可能千奇百怪。以下是我总结的常见问题速查表:

问题现象可能原因排查步骤与解决方案
DMA传输完全无法启动1. DMA模式配置错误。
2. FIFO未使能。
3. DMA控制器通道未正确映射或使能。
1. 检查SCR[0]SCR[2:1]FCR[3],确保模式配置一致且非零(非禁用状态)。
2. 确认FCR[0]为1。
3. 使用示波器或逻辑分析仪检查USARTi_DMA_TX/RX信号线是否有脉冲。检查DMA控制器的请求源(Source)是否选择正确,通道是否使能。
数据发送不完整,最后几个字节丢失1. TX DMA关闭序列不正确,导致FIFO中残留数据未发出。
2. DMA传输大小与UART触发空间数不匹配。
1. 严格按照手册的“关闭TX DMA序列”操作,并在关闭后查询LSR寄存器的TEMT位,确认发送移位寄存器已空。
2. 核对DMA配置的传输大小(Transfer Size)是否等于UART_TLR中设置的TX触发空间数。
数据接收出现乱码或断帧1. RX FIFO溢出。
2. DMA搬运速度慢于UART接收速度。
3. 波特率误差过大。
1. 检查UART_LSROE(溢出错误)位。如果置位,说明数据丢失。尝试增大RX FIFO阈值,或提高DMA优先级/带宽。
2. 优化DMA传输,使用更高的总线时钟,或减少内存访问冲突。
3. 校准时钟源,使用手册提供的公式精确计算分频值。
DMA请求过于频繁,CPU性能下降TX/RX FIFO触发阈值设置过小。适当增大UART_TLR中的触发值。但需权衡:增大阈值会减少请求次数,但会增加单次传输延迟。对于实时性要求高的场景,需找到平衡点。
仅能传输一次,无法循环触发DMA控制器配置为单次(One-shot)模式,而非自动重装(Ping-Pong或Continuous)模式。检查DMA通道的配置,确保在每次传输完成后,传输参数(如源/目标地址、数据量)能自动重载,以准备下一次触发。
在IrDA模式下,传输结束判断过早手册Note指出,在IrDA模式下,UART_THR空(TX FIFO空)并不代表传输真正结束,CRC和停止位仍在发送。在IrDA模式中,不要仅凭TX FIFO空来判断DMA传输结束。需要等待特定的IrDA传输完成中断,或增加一个固定的延时(几毫秒)。

调试心得

  1. 善用寄存器回读:在初始化序列的每一步之后,都回读并打印关键寄存器(如SCRFCRMDR3TLR)的值,确保写入成功且位域符合预期。硬件寄存器访问可能因总线问题失败。
  2. 先中断,后DMA:在复杂驱动开发初期,不要一开始就启用DMA。先使用中断模式让UART收发数据正常工作,确保波特率、帧格式等基础配置无误。然后再在此基础上叠加DMA配置,这样能将问题域隔离。
  3. 逻辑分析仪是关键:遇到棘手的时序问题,没有什么比逻辑分析仪更直观。同时抓取UART的TX/RX引脚、DMA请求信号线、以及相关的GPIO(用于标记软件事件),可以清晰地看到数据流、DMA请求触发时机、以及软件响应的延迟,很多问题一目了然。

6. 不同工作模式下的寄存器映射差异

AM261x的UART模块是一个多功能模块,除了标准的UART模式,还支持IrDA、CIR等模式。手册13.1.4.4.7节详细列出了不同模式下可访问的寄存器差异。这对于编写健壮的驱动非常重要。

核心要点

  • 访问模式:通过UART_LCR[7]位切换操作模式(LCR[7]=0)和配置模式(LCR[7]=1)。在配置模式下,才能访问DLL/DLH等波特率设置寄存器。
  • 子模式:在配置模式下,又通过UART_EFR[4]UART_MCR[6]的组合,分为MSR_SPR、TCR_TLR、XOFF等子模式,用于访问不同的寄存器集合(如TLRSPR共享地址)。
  • 功能模式UART_MDR1[2:0]选择UART、IrDA或CIR等功能。不同功能下,同一寄存器地址的位域含义可能不同(例如UART_IER_UART在不同模式下中断使能位不同),且可用的寄存器集合也不同(详见手册Table 13-86, 13-87, 13-88)。

驱动设计建议:在驱动层抽象出统一的初始化、发送、接收接口。在接口内部,根据UART_MDR1中设置的模式,调用不同的底层配置函数。特别是访问TLREFR等寄存器前,务必检查当前所处的访问模式和子模式,必要时进行临时切换,操作完成后再恢复。可以编写类似UART_enterConfigModeA()UART_exitConfigMode()这样的辅助函数来封装这些细节,避免寄存器误写。

最后,关于波特率计算,手册提供了48MHz时钟下的常用表格。但在实际项目中,时钟源可能不同。务必使用公式除数 = 操作频率 / (波特率倍数 × 目标波特率)进行精确计算,并评估误差(通常要求<2%)。高波特率(如921600以上)对时钟精度要求更高,误差过大会直接导致通信失败。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询