深入UART FIFO与DMA配置:从芯片手册到高效串口通信实战
2026/7/19 20:11:05 网站建设 项目流程

1. 项目概述:从芯片手册到实战,拆解UART的硬核玩法

搞嵌入式开发的兄弟,对UART(通用异步收发传输器)肯定不陌生。从最基础的printf调试,到设备间的稳定数据通信,UART都是我们最忠实可靠的“老伙计”。但很多时候,我们可能只停留在调用HAL_UART_Transmit()这样的库函数层面,对UART内部那套精密的“流水线”和“调度系统”知之甚少。最近在调一个基于复杂SoC的项目,通信数据量大,对实时性要求又高,CPU被频繁的UART中断搞得焦头烂额。于是,我不得不一头扎进那几百页的芯片参考手册,把UART模块的时钟、FIFO、DMA这套组合拳彻底研究了一遍。

我发现,手册里那些关于UARTi.TLR_REGFCR_REGSYSC_REG的寄存器描述,还有S_DMA_48M_IRQ_72这些硬件请求信号,并不是枯燥的文档,而是一套完整的性能优化工具箱。理解它们,你就能让UART从“能工作”变成“高效工作”。这篇文章,我就结合手册里的硬核原理和实际调试中的踩坑经验,带你深入UART/IrDA/CIR模块的腹地,看看如何通过精细的时钟管理、FIFO配置和DMA操作,把串口通信的潜力榨干。无论你是在做工业控制、智能家居还是任何需要可靠串行通信的设备,这套思路都能直接拿来用。

2. 模块集成与系统级视角:不止是一个串口

在动手配置寄存器之前,我们必须先跳出“UART只是一个外设”的思维定式。在现代SoC中,UART模块是深度集成在系统架构里的一个节点,它的表现与整个系统的时钟、电源、中断和DMA子系统息息相关。

2.1 时钟、复位与电源管理:稳定运行的基石

手册里的一张模块集成图(Figure 17-20)清晰地展示了UART与系统其他部分的连接。其中,PRCM(Power, Reset, and Clock Management)模块扮演着总调度员的角色。每个UART模块都有两路关键时钟:

  • 功能时钟(Functional Clock, FCLK):通常是48MHz,用于驱动UART内部逻辑和生成实际的串行比特流(TX/RX引脚上的波形)。它的启停由PRCM中像CM_FCLKEN1_CORE[13] EN_UART1这样的位控制。
  • 接口时钟(Interface Clock, ICLK):用于CPU通过总线(如L4)访问UART的寄存器。它的使能(CM_ICLKEN*)和自动空闲控制(CM_AUTOIDLE*)同样由PRCM管理。

为什么分两路时钟?这是低功耗设计的关键。当CPU暂时不需要与UART交互时,可以单独关闭接口时钟以省电,而功能时钟可以继续保持,让UART的收发器继续工作。反之,在系统深度睡眠时,也可以关闭功能时钟,仅保留必要的唤醒源。

实操心得:时钟使能顺序上电初始化时,务必先使能接口时钟(ICLKEN),再使能功能时钟(FCLKEN)。关闭时则顺序相反。这个顺序保证了你能先配置寄存器,再启动模块功能,避免出现不可预知的状态。我曾在调试时因为顺序颠倒,导致UART配置无法写入,排查了半天。

电源域与唤醒的局限性手册的“CAUTION”部分提了一个非常重要的点:UART模块本身不在WAKEUP电源域。这意味着,如果UART所在的CORE或PER电源域被完全关闭(深度睡眠),UART是无法直接唤醒系统的,因为它自己都没电了。此时,如果需要通过串口CTS引脚唤醒系统,一个变通方案是将该引脚复用为GPIO,利用GPIO所在的唤醒域来实现。这个设计细节在规划系统低功耗方案时必须考虑进去。

2.2 硬件请求映射:中断与DMA的“门牌号”

这是连接软件驱动和硬件模块的桥梁。手册中的表格(Table 17-14, 17-16)列出了UART向系统发出的“请求信号”。

  • 中断请求(IRQ):比如UART1_IRQ映射到MPU子系统的M_IRQ_72。你在配置中断控制器(如GIC)时,填写的硬件中断号就是这个M_IRQ_72,而不是一个抽象的“UART1中断”。UART3_IRQ还会额外映射到IVA2.2子系统,说明该UART可能服务于音视频协处理器。
  • DMA请求:这是实现高效传输的核心。以UART1为例,UART1_DMA_TXUART1_DMA_RX分别映射到系统DMA控制器的S_DMA_48S_DMA_49通道。当你配置DMA控制器进行UART1的发送/接收时,需要指定这些通道号。

注意事项:DMA模式的选择手册提到,DMA请求的映射默认假设使用DMA Mode 1(即TX和RX有独立的请求通道)。这是最常用、最灵活的模式。但也存在Mode 2和Mode 3这两种“遗产模式”,它们只使用一个DMA请求(TX或RX),另一个请求信号不被模块使用。这通常是为了兼容老旧的DMA控制器设计。在新项目中,除非有特殊兼容性要求,否则一律使用Mode 1。

3. FIFO管理:数据吞吐的“缓冲池”与“触发器”

FIFO(先进先出缓冲区)是UART性能提升的第一个利器。它就像一个蓄水池,缓解了CPU(或DMA)处理速度与串口波特率之间的速度差。

3.1 FIFO基础操作与寄存器

UART的FIFO深度通常是64字节。数据通过写THR_REG(发送保持寄存器)进入发送FIFO,通过读RHR_REG(接收保持寄存器)从接收FIFO取出。FCR_REG(FIFO控制寄存器)的FIFO_EN位用于全局启用/禁用FIFO功能。

关键寄存器:TLR_REG (Trigger Level Register)这是FIFO管理的“指挥官”。它不直接控制FIFO的深浅,而是控制FIFO在何时向系统发出“警报”(中断或DMA请求)。这个警报的阈值就是触发水平(Trigger Level)。

  • TLR_REG[3:0] TX_FIFO_TRIG_DMA:发送FIFO的DMA触发阈值(单位:FIFO剩余空间)。
  • TLR_REG[7:4] RX_FIFO_TRIG_DMA:接收FIFO的DMA触发阈值(单位:FIFO中数据字节数)。

3.2 触发阈值的灵活配置

手册里关于触发水平的设置有点绕,但理解后非常强大。它通过SCR_REGFCR_REG的某些位,提供了三种配置策略:

  1. 传统模式(SCR_REG[6]或[7] = 0,且TLR_REG对应字段为0):触发水平由FCR_REG中的TX_FIFO_TRIGRX_FIFO_TRIG决定,只有几个固定档位(如8, 16, 32, 56字节)。这是最基础的配置。
  2. 增强DMA模式(SCR_REG[6]或[7] = 0,但TLR_REG对应字段非0):触发水平完全由TLR_REG中的4位字段决定,以4字节为粒度,可在4到60字节间灵活设置。这是最常用的DMA模式配置
  3. 字节精确模式(SCR_REG[6]或[7] = 1):触发水平由TLR_REG的4位字段和FCR_REG的2位字段拼接而成,可以实现1到63字节的任意值设置,精度最高。

配置示例:如何设置接收FIFO的DMA触发阈值为16字节?假设我们使用增强DMA模式(最常用):

  • 设置SCR_REG[7] = 0(选择增强DMA/传统模式)。
  • 因为16字节 / 4字节粒度 = 4,所以设置TLR_REG[7:4] = 0x4
  • 同时,确保FCR_REG[7:6] RX_FIFO_TRIG字段的值不被使用(通常保持默认或设为0,因为TLR字段非0时,以TLR为准)。

3.3 FIFO的三种工作模式

3.3.1 中断模式

当FIFO中数据量达到触发阈值时,硬件自动产生中断。CPU在中断服务程序(ISR)中批量读取或写入数据。

  • 接收中断:当接收FIFO中的数据字节数>=RX_FIFO_TRIG时,产生中断。中断标志要等到CPU读取数据,使FIFO数据量<触发阈值后,才会清除。这避免了频繁中断。
  • 发送中断:当发送FIFO完全空时,产生中断。通知CPU可以继续填充数据。当CPU写入数据,使FIFO剩余空间<=TX_FIFO_TRIG时,中断标志清除。

注意事项:流控制下的阈值协调如果使能了RTS/CTS硬件流控制,需要额外关注TCR_REG中的RX_FIFO_TRIG_HALT(停止阈值)和RX_FIFO_TRIG_START(启动阈值)。手册特别强调:RX_FIFO_TRIG_HALT必须大于RX_FIFO_TRIG。否则,当接收方FIFO快满时(达到HALT阈值,拉高RTS通知对方暂停发送),可能已经超过了产生中断的阈值,导致CPU来不及取走数据,FIFO就已溢出,通信停滞。这是一个经典的死锁陷阱。

3.3.2 轮询模式

通过将FCR_REG[0]设为0禁用FIFO,或禁用UART中断,然后CPU定期查询LSR_REG(线路状态寄存器)的THRE(发送保持寄存器空)和DR(数据就绪)位。这种方式CPU占用率高,只适用于极低数据率或简单场景,现代应用已较少使用。

3.3.3 DMA模式(核心高效模式)

这是解放CPU的关键。DMA控制器在后台负责在UART FIFO和系统内存之间搬运数据,CPU仅在开始和结束时介入。

DMA请求生成逻辑

  • 发送DMA:当发送FIFO时,立即产生DMA请求。DMA控制器响应请求,向FIFO写入一定量数据(数量由TX_FIFO_TRIG_DMA决定,即一次搬运的“块大小”)。写入后,只要FIFO未满,请求就会保持有效,直到写入的数据量达到触发阈值,请求才撤销。这实现了连续、流式的数据填充
  • 接收DMA:当接收FIFO中的数据量>=RX_FIFO_TRIG_DMA时,产生DMA请求。DMA控制器从FIFO中读取对应数量的数据到内存。读取后,FIFO数据量低于阈值,请求撤销,等待下次累积。

关键配置:DMA传输大小与FIFO触发阈值的匹配这是手册用多个图示(Figure 17-25到17-28)反复强调的重点。你必须确保DMA控制器配置的单次传输大小(Transfer Size)与UART模块中设置的FIFO触发阈值完全一致。例如,你设置TX_FIFO_TRIG_DMA = 8(即8个空闲空间),那么DMA控制器的发送配置里,单次传输大小也必须是8字节。如果不匹配:

  • DMA传输大小 > FIFO阈值:DMA一次想写16字节,但FIFO空余空间达到8时就停止请求了,导致DMA传输无法完成。
  • DMA传输大小 < FIFO阈值:DMA一次只写4字节,填不满FIFO的触发阈值(8空间),DMA请求会一直有效,但每次只搬一点,效率低下,且可能造成逻辑混乱。

实操心得:调试DMA传输卡住我曾遇到UART发送DMA启动后,发完第一批数据就卡住的情况。排查后发现,DMA配置的单次传输是16字节,而UART的TX_FIFO_TRIG_DMA默认是56(对应FCR的默认值)。DMA搬了16字节后,FIFO空闲空间为48,并未达到56的触发阈值,因此DMA请求一直处于“已完成但未撤销”的奇怪状态,导致后续传输无法触发。将两者统一设置为16后,问题解决。

4. DMA操作实战:从配置到传输完成

理解了原理,我们来看如何一步步配置并启动一个完整的UART DMA传输流程。这里以**UART1的DMA发送(Mode 1)**为例。

4.1 系统级准备

  1. 使能时钟与引脚:通过PRCM模块使能UART1的接口时钟(UART1_ICLK)和功能时钟(UART1_FCLK)。配置对应IO引脚为UART功能模式。
  2. 配置DMA控制器:找到映射关系(Table 17-16),UART1_DMA_TX对应S_DMA_48
    • 在DMA控制器中,配置通道48的源地址为你的发送数据缓冲区(内存),目标地址为UART1的THR_REG寄存器地址。
    • 设置传输方向为内存到外设。
    • 关键一步:设置单次传输大小(Burst Size)或传输宽度,使其与即将在UART中设置的TX_FIFO_TRIG_DMA值匹配(例如16字节)。
    • 配置传输完成中断,以便在全部数据发完后得到通知。

4.2 UART模块配置

  1. 基本参数:配置LCR_REG设置数据位、停止位、校验位。配置DLL_REGDLH_REG设置波特率。
  2. 启用FIFO和DMA模式
    • FCR_REG:设置FIFO_EN = 1,启用FIFO。根据你的需求设置TX_FIFO_TRIGRX_FIFO_TRIG(如果使用传统模式)。对于DMA,我们主要用TLR_REG
    • SCR_REG:设置DMA_MODE_CTL = 0,选择通过FCR_REG[3]控制DMA模式。
    • FCR_REG:设置DMA_MODE = 1,启用DMA Mode 1(独立TX/RX请求)。
  3. 设置DMA触发阈值
    • SCR_REG[6] = 0,选择增强DMA模式(使用TLR寄存器)。
    • 计算触发值。假设我们希望当发送FIFO有16字节空闲空间时,就请求DMA填充。FIFO深度64,触发阈值是“剩余空间”,所以TX_FIFO_TRIG_DMA = 16 / 4 = 4
    • TLR_REG[3:0] = 0x4
  4. 启用发送DMA:写IER_REG,通常有一个专门位来启用发送DMA请求(可能叫ETBEI或类似名称)。注意:在DMA模式下,一般要禁用发送空中断(THREI),否则会和DMA请求冲突。

4.3 启动传输与完成处理

  1. 启动DMA:使能DMA控制器的通道48。
  2. 启动UART发送:向THR_REG写入第一个数据(或直接由DMA写入),或者通过设置某个控制位启动发送器。由于FIFO初始为空,UART会立即产生S_DMA_48请求。
  3. DMA自动搬运:DMA控制器响应请求,从内存搬运16字节数据到UART的发送FIFO。填满16字节后,DMA请求暂时撤销。UART硬件开始从FIFO中取出数据,按照波特率串行发出。
  4. 循环与结束:当FIFO中的数据被发出,空闲空间再次达到16字节时,DMA请求再次产生,DMA控制器搬运下一个16字节。如此循环,直到DMA控制器完成全部数据的传输,产生传输完成中断。
  5. 检查发送完成:在DMA传输完成中断中,你还需要查询UART的LSR_REG寄存器,确认TEMT(发送移位寄存器空)和THRE(发送保持寄存器空)位都为1,才能确保最后一个字节也已从FIFO移出并完全发送到线路上。

5. 常见问题排查与调试技巧实录

即使配置正确,在实际调试中也会遇到各种问题。下面是我总结的一些典型故障和排查手段。

5.1 数据收发异常排查表

现象可能原因排查步骤
发送数据丢失或错位波特率不匹配1. 用示波器或逻辑分析仪测量TX引脚实际波特率。
2. 核对双方设备的DLL/DLH计算值,注意系统时钟频率是否准确。
FIFO溢出1. 检查接收方是否及时取走数据(中断/DMA是否正常)。
2. 在接收中断服务程序(ISR)中,读取LSR_REG检查OE(溢出错误)位。
DMA配置不匹配1.重点核对:UART的TX/RX_FIFO_TRIG_DMA值与DMA控制器的单次传输大小是否严格相等。
2. 检查DMA源/目标地址自增模式是否正确。
DMA传输启动后只进行一次就停止DMA请求未正确连接/使能1. 确认DMA控制器的对应通道(如48)已使能,且触发源选择为对应的UART DMA请求。
2. 确认UART的IER_REG中已启用DMA请求位。
FIFO触发阈值与DMA传输大小不匹配如前所述,这是最常见原因。务必校验TLR_REG设置与DMA Burst Size。
传输完成中断误处理检查DMA传输完成中断服务程序,是否错误地禁用了DMA通道或UART。
系统无法从UART CTS唤醒UART所在电源域关闭1. 检查系统低功耗状态,确认UART所在的CORE或PER电源域是否保持供电。
2. 如该域已掉电,需按手册建议,将uart_cts引脚复用为GPIO,利用GPIO的唤醒功能。
唤醒���能未启用检查WER_REG(唤醒使能寄存器)中对应的唤醒事件位是否已置位。
中断无法产生中断未在各级使能1. UART级:IER_REG中使能特定中断(如接收中断ERBFI)。
2. 系统级:确认PRCM已给UART提供时钟(ICLKEN)。
3. 中断控制器级:使能对应的硬件中断号(如M_IRQ_72),并设置优先级和CPU亲和性。
中断标志未清除在中断服务程序(ISR)中,必须读取IIR_REG或相关状态寄存器来清除中断源标志。
IrDA/CIR模式不工作模式选择错误或时钟问题1. 确认MDR1_REG[2:0] MODE_SELECT字段已正确设置为IrDA或CIR模式(仅UART3支持)。
2. IrDA(SIR)使用UART的异步时序,但MIR/FIR需要更高的时钟和特定编码,检查时钟配置和使能。

5.2 调试技巧与心得

  1. 善用寄存器回读:在初始化序列的每一步之后,特别是配置复杂的FCRSCRTLR寄存器后,立刻回读该寄存器,确认写入的值是否正确。硬件可能存在写保护或位依赖,回读是验证配置生效的最直接方法。
  2. 分阶段测试:不要试图一次性配置好所有高级功能(波特率、FIFO、DMA、中断)。建议流程:a) 先不用FIFO和中断,用轮询方式测试基本收发。b) 启用FIFO和中断,测试小批量数据。c) 最后再引入DMA,并从小数据量开始测试。
  3. 逻辑分析仪是神器:面对时序问题、DMA请求/应答信号、中断信号,逻辑分析仪比串口打印更直观。可以同时抓取UART的TX/RX引脚、DMA请求线、中断线,观察它们之间的时序关系,能快速定位是配置问题、硬件问题还是软件同步问题。
  4. 理解“空间”与“数据”的差异:在配置发送FIFO触发阈值时,手册和寄存器描述常使用“space”(空间)这个词。TX_FIFO_TRIG_DMA=8意味着“当FIFO中空闲空间大于等于8个字节时触发请求”。而接收阈值RX_FIFO_TRIG_DMA=8意味着“当FIFO中已存数据大于等于8个字节时触发请求”。这个概念混淆是配置错误的常见源头。
  5. 低功耗下的陷阱:在进入低功耗模式前,如果UART通信未完成,务必妥善处理。最好关闭UART并等待当前传输结束,或确保DMA传输已完成并禁用DMA请求。否则,系统休眠后时钟关闭,未完成的DMA传输会挂住总线,导致唤醒失败或系统异常。

通过这次对UART模块从系统集成到FIFO/DMA细节的梳理,我最大的体会是:嵌入式开发,尤其是驱动层,绝不能停留在API调用。手册里每一段描述、每一个注意(CAUTION)和注释(NOTE),都是前人踩过的坑。把时钟管理、FIFO阈值、DMA同步这几个环节吃透,你就能设计出稳定、高效且低功耗的串口通信方案,让这个经典的接口在现代复杂系统中继续发挥强大的作用。

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

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

立即咨询