深入解析UART FIFO、中断与DMA:从寄存器视角优化嵌入式串口通信

发布时间:2026/7/21 10:38:18
深入解析UART FIFO、中断与DMA:从寄存器视角优化嵌入式串口通信 1. 项目概述与核心价值在嵌入式开发的日常里UART通用异步收发传输器就像一位沉默寡言但极其可靠的老伙计。无论是调试信息输出、传感器数据读取还是两个微控制器之间的“悄悄话”都离不开它。但很多开发者对UART的认知可能还停留在“配置波特率、数据位、停止位然后读写数据寄存器”的层面。实际上现代微控制器中的UART模块远比这复杂和强大其内置的FIFO先进先出缓冲区、灵活的中断系统以及与DMA直接内存访问控制器的深度集成才是实现高效、稳定串行通信的“内功心法”。这次我们就以德州仪器TI的Cortex-M系列微控制器中的UART模块为蓝本进行一次深度“解剖”。你手头那份技术手册的片段正是我们探索的起点。我们将超越简单的API调用深入到寄存器位、时序逻辑和硬件状态机的层面彻底搞明白FIFO是如何工作的中断触发条件如何精确配置DMA又是如何与UART无缝协作把CPU从繁琐的字节搬运工作中解放出来的理解这些不仅能让你在调试通信异常时游刃有余更能让你在设计系统时做出最合理的资源分配与性能权衡避免因配置不当导致的丢包、延迟或CPU资源被无谓占用的问题。无论你是正在学习嵌入式的新手还是希望优化现有通信代码的老手这篇从寄存器视角出发的解析都将提供扎实的实践指导。2. UART核心工作机制再探超越起止位在深入FIFO和中断之前我们有必要快速回顾并深化对UART基础工作原理的理解。很多人知道UART靠起始位、数据位、校验位和停止位来框定一帧数据但这背后的硬件状态机是如何运转的2.1 发送与接收的状态机发送过程并非简单地把数据扔进寄存器就结束了。当你写入数据到发送保持寄存器或FIFO后硬件会自动在数据前加上一个低电平的起始位在数据后加上停止位可能还有校验位然后按照设定的波特率一位一位地将这个完整的帧通过TX引脚“推”出去。在此期间UART的BUSY标志位会置起直到整个帧包括停止位完全离开移位寄存器才会清零。这意味着即使你快速写入了多个字节它们也是在内部排队依次发送的。接收端则始终在监听RX引脚。当检测到从高电平空闲状态到低电平的下跳变时便认为是一个起始位的开始。随后接收逻辑会在每个比特位的中间时刻通常是在波特率周期的中点对RX引脚进行采样以确定该比特是0还是1。一帧接收完成后硬件会检查停止位是否正确通常应为高电平并进行可选的奇偶校验。只有所有这些检查都通过或配置为忽略某些错误接收到的数据字节才会被存入接收保持寄存器或FIFO中并可能触发中断。2.2 波特率生成的奥秘整数与小数分频波特率的准确性是通信的基石。手册中给出的公式BRD UARTCLK / (16 * Baud)是经典公式其中UARTCLK是UART模块的输入时钟频率。BRDBaud-Rate Divisor这个除数通常不是整数例如在20MHz时钟下配置115200波特率BRD 20,000,000 / (16 * 115,200) ≈ 10.8507。现代UART模块通过两个寄存器来精确实现这个分频比UARTIBRD (整数部分寄存器)写入整数部分这里是10。UARTFBRD (小数部分寄存器)写入integer(小数部分 * 64 0.5)。计算过程0.8507 * 64 54.4448加0.5后取整得54。这个值会被一个小数分频器使用通过控制时钟脉冲的细微延迟在长时间尺度上精确匹配目标波特率。关键操作顺序必须先写UARTIBRD和UARTFBRD最后再写UARTLCRH寄存器。这是因为对波特率除数寄存器的写操作其生效的“门控”信号是由写UARTLCRH这个动作触发的。如果顺序颠倒波特率配置将不会立即生效可能导致通信乱码。2.3 LIN同步场的自动检测与波特率自适应你提供的资料片段里提到了一个高级功能LINLocal Interconnect Network同步场处理。LIN是汽车电子中常用的低成本串行网络协议。它的帧头包含一个特殊的“同步间隔”Sync Break后跟一个“同步场”Synch Field。UART的LIN模式硬件支持自动检测这个同步间隔。更强大的是它能通过测量同步场中特定边沿如第一个和第五个下降沿的时间间隔自动计算出主节点实际使用的波特率。计算公式为实际波特率 (T2 - T1) / 8。其中T1和T2是硬件捕获的定时器值。从机UART可以根据这个计算结果动态调整自己的UARTIBRD和UARTFBRD寄存器实现与主节点的波特率自动同步这对于存在时钟偏差的系统非常有用。这个过程由LME1RIS和LME5RIS等中断标志位来驱动。3. FIFO操作详解从1字节缓存到16级深度缓冲复位后UART的发送和接收路径都只是一个1字节深的“保持寄存器”。这在低速或极简应用中没问题但在高速或实时性要求高的场景下频繁的中断会压垮CPU。FIFO的引入就是为了解决这个问题。3.1 FIFO的启用与工作模式通过设置UARTLCRH寄存器中的FENFIFO Enable位为1可以启用发送和接收FIFO。通常每个FIFO的深度是16字节具体需查芯片手册。启用后发送当你向UARTDR寄存器写入数据时数据实际上被压入发送FIFO队列尾部。发送器硬件从FIFO头部取出数据装入移位寄存器进行串行化发送。只要FIFO非空发送就会持续进行。接收接收硬件将解串后的数据字节连同4个错误状态位共12位一起推入接收FIFO队列尾部。你从UARTDR寄存器读取时实际是从接收FIFO头部取出数据。3.2 FIFO状态监控与中断触发阈值如何知道FIFO是空是满这需要通过UARTFRFlag Register寄存器来监控TXFE发送FIFO空。为1时表示发送FIFO或保持寄存器已空最后一帧数据也已离开移位寄存器。TXFF发送FIFO满。为1时表示不能再写入数据。RXFE接收FIFO空。为1时表示没有数据可读。RXFF接收FIFO满。为1时表示接收FIFO已满如果此时再收到新数据就会发生溢出错误Overrun Error新数据会丢失。仅仅监控状态位效率太低我们通常用中断。UARTIFLSInterrupt FIFO Level Select寄存器就是用来配置中断触发阈值的。它可以独立配置发送和接收FIFO在达到什么“水位线”时触发中断。接收中断可配置为FIFO达到1/8, 1/4, 1/2, 3/4, 7/8满时触发。例如设为1/4对于16级FIFO就是4个字节则当接收到第4个字节时就会产生接收中断。这样你可以一次读取多个字节大幅减少中断次数。默认是1/28字节。发送中断可配置为FIFO达到1/8, 1/4, 1/2, 3/4, 7/8空时触发。例如设为1/2空剩下8个字节空位则当发送FIFO从满状态被取走数据空位达到8个时触发发送中断提示你可以继续填充数据了。这避免了在FIFO还满的时候CPU盲目查询或过早进入中断。3.3 溢出错误与FIFO行为这里有一个关键细节当接收FIFO已满RXFF1时如果又收到一个完整的数据帧会发生溢出错误OE。此时FIFO内的数据保持不变且有效但新接收到的这个字节会被丢弃它甚至不会覆盖FIFO尾的数据。OE标志位会在UARTRSR寄存器或UARTDR读取到的状态位中置起。清除溢出错误标志的方法是向UARTECR寄存器执行任何写操作通常写0。但更重要的是你必须尽快从接收FIFO中读取数据腾出空间否则后续数据会持续丢失。4. 中断系统全解析从事件到服务例程UART的中断系统是一个典型的事件驱动架构。它把各种可能的情况抽象成独立的事件软件可以自由选择关心哪些。4.1 中断事件源UART可以产生中断的事件包括接收事件当接收FIFO中的数据量达到UARTIFLS.RXIFLSEL设定的阈值时触发。发送事件当发送FIFO中的空余空间达到UARTIFLS.TXIFLSEL设定的阈值时触发。注意还有一个UARTCTL.EOTEnd of Transmission位会影响发送中断行为。若EOT0中断由FIFO阈值触发若EOT1则只在所有数据包括停止位都已离开串行移位寄存器即物理发送完全结束时才触发中断。这在需要精确控制帧间时序如驱动某些需要特定延时才能响应的设备时非常有用。接收超时当接收FIFO非空但在后续32个比特周期内没有收到任何新数据时触发。这非常实用比如你收到一个不完整的数据包或者对方发送较慢超时中断可以提醒你“目前FIFO里有些数据已经‘晾’了很久了该来处理一下了”从而避免数据在FIFO中无限期等待。错误事件包括帧错误FE停止位不是1、奇偶校验错误PE、溢出错误OE和线路中断BERX线被长时间拉低。4.2 中断的使能、状态与清除所有上述事件首先会反映在原始中断状态寄存器UARTRIS中。无论你是否关心事件发生了对应的RIS位就会置1。中断使能寄存器UARTIM你可以通过设置UARTIM中的相应位如RXIM,TXIM,RTIM,OEIM等来选择让哪些事件能够产生控制器级中断即触发CPU跳转到中断服务程序。屏蔽中断状态寄存器UARTMIS这个寄存器是UARTRIS UARTIM的结果。它直接告诉你哪些已使能的中断事件当前正处于活跃状态。在中断服务程序ISR中你应该首先读取UARTMIS来判断具体是哪个或哪几个事件触发了本次中断。中断清除寄存器UARTICR这是一个“写1清除”的寄存器。在ISR中处理完某个中断事件后必须向UARTICR中对应的位写1才能清除该中断状态同时会清除UARTRIS和UARTMIS中的对应位。这是最常见的错误来源之一忘了清中断导致中断持续触发系统卡死。重要提示所有中断事件在逻辑上是“或”的关系最终只产生一个中断信号给NVIC嵌套向量中断控制器。因此你的UART中断服务程序必须能够通过查询UARTMIS寄存器来服务多个同时发生的事件。一个健壮的ISR模板通常是void UART0_Handler(void) { uint32_t mis UART0-MIS; // 读取屏蔽中断状态 if (mis UART_MIS_RXMIS) { // 接收中断 // 循环读取UARTDR直到RXFE置位 while (!(UART0-FR UART_FR_RXFE)) { uint16_t data UART0-DR; // 读取数据及错误位 // 处理数据... } UART0-ICR UART_ICR_RXIC; // 清除接收中断 } if (mis UART_MIS_TXMIS) { // 发送中断 // 检查TXFF是否为空如果未满则填充更多待发送数据 // 如果所有数据发送完毕可以关闭发送中断UART0-IM ~UART_IM_TXIM UART0-ICR UART_ICR_TXIC; // 清除发送中断 } if (mis UART_MIS_RTMIS) { // 接收超时中断 // 处理FIFO中滞留的数据 UART0-ICR UART_ICR_RTIC; } // ... 处理其他错误中断 }5. DMA配置与高效数据传输当需要传输大量数据如固件升级、图像传输、高速日志记录时即使有FIFO频繁的中断仍然会给CPU带来负担。DMA直接内存访问正是为此而生它允许外设和内存之间直接交换数据无需CPU介入。5.1 UART的DMA接口机制UART模块为DMA控制器提供了独立的发送和接收请求通道。接收DMA当接收FIFO中有任何数据时UART会向DMA控制器发出一个“单次传输请求”当接收FIFO中的数据量达到或超过UARTIFLS中设置的触发阈值时会发出一个“突发传输请求”。DMA控制器根据自身配置决定以单次还是突发模式从UARTDR寄存器读取数据到指定内存。发送DMA当发送FIFO中至少有一个空位时UART会发出“单次传输请求”当发送FIFO中的空余位置数量达到或超过触发阈值时发出“突发传输请求”。DMA控制器据此将内存中的数据写入UARTDR。5.2 DMA的启用与错误处理通过设置UARTDMACTL寄存器中的RXDMAE和TXDMAE位来分别启用接收和发送DMA通道。一个重要的安全机制是DMAERR在UARTDMACR寄存器中虽然你提供的片段未详述此寄存器但概念通用。如果使能了该功能当UART发生接收错误如帧错误、奇偶错误时DMA接收请求会被自动禁用。这防止了错误数据被DMA源源不断地搬移到内存中。此时需要软件介入先读取UARTDR或UARTRSR来清除UART的错误中断状态然后才能重新启用DMA。5.3 DMA中断与UART中断的协同当DMA传输完成例如传输了预设的数据量时DMA控制器本身会产生一个完成中断。在许多微控制器架构中如提供的资料中提到的Cortex-M3与μDMA这个DMA完成中断是通过UART的中断向量入口上报的。这意味着如果你的UART也开启了中断例如用于错误处理并且也开启了DMA那么你的UART中断服务程序ISR必须额外处理DMA传输完成事件。你需要在ISR中检查是否是DMA完成触发了中断并进行相应的处理如设置标志位通知主程序、启动下一次传输等。6. 实战配置从寄存器映射到代码实现理论最终要落地为代码。我们以手册中的示例为目标在20MHz UART时钟下配置115200波特率、8位数据位、1位停止位、无校验、禁用FIFO和中断。6.1 寄存器配置步骤分解禁用UART任何关键配置修改前应先清除UARTCTL寄存器中的UARTEN位确保UART处于禁用状态。配置GPIO复用这不是UART寄存器本身但至关重要。需要使能对应GPIO端口的时钟并将特定引脚的功能复用到UART的TX和RX上通过AFSEL和PCTL寄存器。计算并设置波特率除数BRD 20,000,000 / (16 * 115,200) 10.8507UARTIBRD 10(整数部分)UARTFBRD integer(0.8507 * 64 0.5) 54(小数部分)配置线路控制参数写入UARTLCRH寄存器。数据位8位WLEN 0x3无校验PEN 01位停止位STP2 0禁用FIFOFEN 0其他位如SPS,EPS,BRK保持0。计算值0x0000.0060(即WLEN3FEN0其他为0)。关键对UARTLCRH的操作会锁存并应用之前写入的UARTIBRD和UARTFBRD值。可选配置DMA如果需要配置μDMA通道并设置UARTDMACTL寄存器。重新使能UART设置UARTCTL寄存器中的UARTEN位同时通常也会设置TXE和RXE位以使能发送和接收功能。6.2 关键寄存器速查与编程要点下表汇总了最核心的UART寄存器及其关键位寄存器名称偏移地址关键位/字段功能描述编程注意事项UARTDR0x000DATA[7:0]数据读写端口。写则发送读则接收。读取时高4位包含错误状态OE, BE, PE, FE。UARTFR0x018TXFE, RXFE, TXFF, RXFF, BUSY标志寄存器反映FIFO空/满、忙状态。查询式编程时主要依赖此寄存器。BUSY位在发送移位寄存器工作时置位。UARTIBRD0x024DIVINT[15:0]波特率除数整数部分。必须先于UARTLCRH写入。UARTFBRD0x028DIVFRAC[5:0]波特率除数小数部分/64。必须先于UARTLCRH写入。UARTLCRH0x02CWLEN[6:5], FEN[4], STP2[3], PEN[1], etc.线路控制字长、FIFO使能、停止位、校验等。写此寄存器会使波特率除数生效。修改前最好禁用UART。UARTCR0x030UARTEN[0], TXE[8], RXE[9], etc.UART总使能、发送/接收使能、回环等。修改控制位前建议先禁用UART (UARTEN0)。UARTIFLS0x034RXIFLSEL[2:0], TXIFLSEL[5:3]设置接收/发送FIFO的中断触发水位线。在启用FIFO(FEN1)和中断前配置好。UARTIM0x038RXIM, TXIM, RTIM, etc.中断使能屏蔽。想用哪种中断就置位对应的IM位。UARTRIS0x03C各类RIS位原始中断状态。只读反映所有事件无论是否使能中断。UARTMIS0x040各类MIS位屏蔽后的中断状态。中断服务程序中应首先读取此寄存器判断中断源。UARTICR0x044各类IC位中断清除寄存器。写1清除对应的中断状态。必须在ISR中处理。7. 调试技巧与常见问题排查即使理解了所有原理实际调试中仍会踩坑。以下是一些血泪教训总结出的排查清单。7.1 通信完全失败无数据收发时钟与电源首先确认UART模块和对应GPIO端口的时钟是否使能例如通过RCGC1和RCGC2寄存器。这是最容易被忽略的一步。引脚复用确认TX和RX引脚是否正确配置为外设功能设置AFSEL位并且通过PCTL寄存器映射到了正确的UART模块。波特率计算UARTIBRD和UARTFBRD的值是否正确是否遵循了先写波特率除数最后写UARTLCRH的顺序用示波器或逻辑分析仪测量TX引脚输出的波形计算实际波特率是否与预期相符。基本使能UARTCTL寄存器中的UARTEN、TXE、RXE位是否都已置1硬件连接TX是否接对方的RXRX是否接对方的TX共地是否良好电平是否匹配如3.3V对3.3V7.2 能发送但不能接收或反之单向使能检查UARTCTL中的TXE和RXE位是否只使能了一个方向中断与查询如果使用中断是否正确使能了接收中断UARTIM.RXIMNVIC中的UART中断是否已启用如果使用查询程序是否在持续检查UARTFR.RXFE位FIFO配置如果启用了FIFO接收中断的水位线UARTIFLS.RXIFLSEL是否设置得过高导致一直达不到触发条件7.3 数据错乱或丢失波特率偏差这是最常见的原因。确保通信双方使用相同的波特率且时钟源准确。计算波特率除数时注意UART输入时钟频率UARTCLK可能不等于系统主频可能经过分频。帧格式不匹配检查双方的数据位、停止位、校验位设置是否完全一致。UARTLCRH寄存器的配置是关键。FIFO溢出检查UARTRSR或UARTDR读出的错误位是否有溢出错误OE。如果有说明你的程序读取FIFO的速度跟不上数据接收的速度。考虑提高接收中断优先级、降低波特率、增大FIFO触发中断的水位线一次处理更多数据或者启用DMA。中断服务程序效率中断服务程序是否执行时间过长是否因为关闭全局中断导致丢失后续数据ISR应尽可能短平快只做必要的状态检查和数据搬运标志位设置等耗时操作应放到主循环中。7.4 中断无法触发或持续触发中断使能链确保三层使能都已打开外设级UARTIM、控制器级NVIC中的中断使能、系统级全局中断使能如Cortex-M的PRIMASK位。中断标志未清除这是导致中断持续触发中断风暴的最常见原因在中断服务程序中处理完事件后必须向UARTICR寄存器的对应位写1以清除中断标志。检查你的ISR是否漏掉了对某些中断源特别是错误中断的清除操作。中断优先级如果系统中有其他更高优先级的中断长时间执行可能会阻塞UART中断。7.5 DMA传输异常DMA未正确初始化除了使能UART端的RXDMAE/TXDMAEDMA控制器本身的通道配置源地址、目标地址、传输数据量、传输模式是否正确DMA与中断冲突如果同时使能了UART接收中断和接收DMA当数据到达时两者可能竞争。通常建议在DMA传输期间禁用对应的UART中断。DMA传输完成处理DMA传输完成中断是否得到了处理是否正确地重新配置或停止了DMA通道对于循环传输模式要特别注意缓冲区管理和传输计数的重置。理解UART的FIFO、中断和DMA本质上是在理解现代微控制器如何通过硬件机制来优化软件与物理世界交互的效率。从查询到中断再到DMA是一个将CPU从低级重复劳动中逐步解放出来的过程。配置这些功能时多问几个“为什么”为什么中断触发阈值要这么设DMA突发传输和单次传输请求在什么场景下分别有利错误标志位在哪个阶段被设置和清除把这些细节琢磨透下次再遇到诡异的通信问题你就能像侦探一样根据有限的线索寄存器状态、错误标志迅速定位到问题的根源而不是盲目地四处修改代码。这份从寄存器层面建立起来的直觉和掌控感正是资深嵌入式工程师与新手之间的一道分水岭。