深入UART FIFO与DMA配置:从芯片手册到高效串口通信实战 📅 2026/7/21 4:29:23 1. 项目概述从芯片手册到实战拆解UART的硬核玩法搞嵌入式开发的兄弟对UART通用异步收发传输器肯定不陌生。从最基础的printf调试到设备间的稳定数据通信UART都是我们最忠实可靠的“老伙计”。但很多时候我们可能只停留在调用HAL_UART_Transmit()这样的库函数层面对UART内部那套精密的“流水线”和“调度系统”知之甚少。最近在调一个基于复杂SoC的项目通信数据量大对实时性要求又高CPU被频繁的UART中断搞得焦头烂额。于是我不得不一头扎进那几百页的芯片参考手册把UART模块的时钟、FIFO、DMA这套组合拳彻底研究了一遍。我发现手册里那些关于UARTi.TLR_REG、FCR_REG、SYSC_REG的寄存器描述还有S_DMA_48、M_IRQ_72这些硬件请求信号并不是枯燥的文档而是一套完整的性能优化工具箱。理解它们你就能让UART从“能工作”变成“高效工作”。这篇文章我就结合手册里的硬核原理和实际调试中的踩坑经验带你深入UART/IrDA/CIR模块的腹地看看如何通过精细的时钟管理、FIFO配置和DMA操作把串口通信的潜力榨干。无论你是在做工业控制、智能家居还是任何需要可靠串行通信的设备这套思路都能直接拿来用。2. 模块集成与系统级视角不止是一个串口在动手配置寄存器之前我们必须先跳出“UART只是一个外设”的思维定式。在现代SoC中UART模块是深度集成在系统架构里的一个节点它的表现与整个系统的时钟、电源、中断和DMA子系统息息相关。2.1 时钟、复位与电源管理稳定运行的基石手册里的一张模块集成图Figure 17-20清晰地展示了UART与系统其他部分的连接。其中PRCMPower, 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_TX和UART1_DMA_RX分别映射到系统DMA控制器的S_DMA_48和S_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_REGFIFO控制寄存器的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_REG和FCR_REG的某些位提供了三种配置策略传统模式(SCR_REG[6]或[7] 0且TLR_REG对应字段为0)触发水平由FCR_REG中的TX_FIFO_TRIG和RX_FIFO_TRIG决定只有几个固定档位如8, 16, 32, 56字节。这是最基础的配置。增强DMA模式(SCR_REG[6]或[7] 0但TLR_REG对应字段非0)触发水平完全由TLR_REG中的4位字段决定以4字节为粒度可在4到60字节间灵活设置。这是最常用的DMA模式配置。字节精确模式(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 系统级准备使能时钟与引脚通过PRCM模块使能UART1的接口时钟(UART1_ICLK)和功能时钟(UART1_FCLK)。配置对应IO引脚为UART功能模式。配置DMA控制器找到映射关系Table 17-16UART1_DMA_TX对应S_DMA_48。在DMA控制器中配置通道48的源地址为你的发送数据缓冲区内存目标地址为UART1的THR_REG寄存器地址。设置传输方向为内存到外设。关键一步设置单次传输大小Burst Size或传输宽度使其与即将在UART中设置的TX_FIFO_TRIG_DMA值匹配例如16字节。配置传输完成中断以便在全部数据发完后得到通知。4.2 UART模块配置基本参数配置LCR_REG设置数据位、停止位、校验位。配置DLL_REG和DLH_REG设置波特率。启用FIFO和DMA模式写FCR_REG设置FIFO_EN 1启用FIFO。根据你的需求设置TX_FIFO_TRIG和RX_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请求。设置DMA触发阈值写SCR_REG[6] 0选择增强DMA模式使用TLR寄存器。计算触发值。假设我们希望当发送FIFO有16字节空闲空间时就请求DMA填充。FIFO深度64触发阈值是“剩余空间”所以TX_FIFO_TRIG_DMA 16 / 4 4。写TLR_REG[3:0] 0x4。启用发送DMA写IER_REG通常有一个专门位来启用发送DMA请求可能叫ETBEI或类似名称。注意在DMA模式下一般要禁用发送空中断THREI否则会和DMA请求冲突。4.3 启动传输与完成处理启动DMA使能DMA控制器的通道48。启动UART发送向THR_REG写入第一个数据或直接由DMA写入或者通过设置某个控制位启动发送器。由于FIFO初始为空UART会立即产生S_DMA_48请求。DMA自动搬运DMA控制器响应请求从内存搬运16字节数据到UART的发送FIFO。填满16字节后DMA请求暂时撤销。UART硬件开始从FIFO中取出数据按照波特率串行发出。循环与结束当FIFO中的数据被发出空闲空间再次达到16字节时DMA请求再次产生DMA控制器搬运下一个16字节。如此循环直到DMA控制器完成全部数据的传输产生传输完成中断。检查发送完成在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. IrDASIR使用UART的异步时序但MIR/FIR需要更高的时钟和特定编码检查时钟配置和使能。5.2 调试技巧与心得善用寄存器回读在初始化序列的每一步之后特别是配置复杂的FCR、SCR、TLR寄存器后立刻回读该寄存器确认写入的值是否正确。硬件可能存在写保护或位依赖回读是验证配置生效的最直接方法。分阶段测试不要试图一次性配置好所有高级功能波特率、FIFO、DMA、中断。建议流程a) 先不用FIFO和中断用轮询方式测试基本收发。b) 启用FIFO和中断测试小批量数据。c) 最后再引入DMA并从小数据量开始测试。逻辑分析仪是神器面对时序问题、DMA请求/应答信号、中断信号逻辑分析仪比串口打印更直观。可以同时抓取UART的TX/RX引脚、DMA请求线、中断线观察它们之间的时序关系能快速定位是配置问题、硬件问题还是软件同步问题。理解“空间”与“数据”的差异在配置发送FIFO触发阈值时手册和寄存器描述常使用“space”空间这个词。TX_FIFO_TRIG_DMA8意味着“当FIFO中空闲空间大于等于8个字节时触发请求”。而接收阈值RX_FIFO_TRIG_DMA8意味着“当FIFO中已存数据大于等于8个字节时触发请求”。这个概念混淆是配置错误的常见源头。低功耗下的陷阱在进入低功耗模式前如果UART通信未完成务必妥善处理。最好关闭UART并等待当前传输结束或确保DMA传输已完成并禁用DMA请求。否则系统休眠后时钟关闭未完成的DMA传输会挂住总线导致唤醒失败或系统异常。通过这次对UART模块从系统集成到FIFO/DMA细节的梳理我最大的体会是嵌入式开发尤其是驱动层绝不能停留在API调用。手册里每一段描述、每一个注意CAUTION和注释NOTE都是前人踩过的坑。把时钟管理、FIFO阈值、DMA同步这几个环节吃透你就能设计出稳定、高效且低功耗的串口通信方案让这个经典的接口在现代复杂系统中继续发挥强大的作用。