1. UART通信中的接收溢出:原理、影响与实战恢复
在嵌入式开发中,UART(通用异步收发传输器)是我们与外部世界对话最基础的“嘴巴”和“耳朵”。它简单、可靠,几乎存在于每一个微控制器上。但正是这种简单,让一些隐藏的“坑”变得尤为致命,接收溢出(Overrun)就是其中最典型的一个。很多工程师都遇到过数据莫名其妙丢失的问题,最后排查下来,往往就是溢出在作祟。今天,我就结合TI AM263P这款芯片的UART模块,把接收溢出这件事掰开揉碎了讲清楚,并延伸到IrDA和CIR这两种特殊的红外模式,分享一些从数据手册里读不出来的实战经验。
接收溢出,说白了就是“耳朵”听到的数据太快,“大脑”(CPU)来不及处理。UART接收端有一个RX FIFO(先进先出缓冲区),它像一个临时的快递柜。当数据从RX引脚源源不断地进来,硬件状态机就会把数据包(帧)放进这个快递柜。问题出在,如果快递柜(RX FIFO)已经满了,但下一个数据包又到了门口,状态机试图往里塞,这时就会触发接收溢出错误。
一旦发生溢出,AM263P的UART模块会做三件事,这三件事环环相扣,理解透了才能正确恢复:
- 触发中断:它会立即向主机CPU发送一个中断,并且将中断识别寄存器(
UART_IIR_UART[5:1])中的中断类型字段(IT_TYPE)设置为0x3,这对应“接收线路状态错误”。这是通知CPU出问题的第一道警报。 - 丢弃数据:发生溢出的那个数据帧,其剩余部分会被硬件直接丢弃。这意味着你不仅可能丢失当前帧的尾部,如果溢出发生在帧中间,整帧数据都可能不完整。这是数据丢失的直接原因。
- 锁死接收:最关键也最容易被忽略的一步——溢出会设置一个内部标志位,这个标志位会禁用后续的一切接收操作。也就是说,溢出发生后,你的UART接收功能实际上已经“瘫痪”了,即使RX引脚再有数据进来,硬件也不会处理。很多新手以为清空了FIFO就万事大吉,结果通信再也无法恢复,根源就在这里。
因此,从溢出中恢复,不是一个动作,而是一个标准的“急救流程”。你必须严格按顺序执行以下两步,缺一不可:
- 复位RX FIFO:通过相应的寄存器位(通常是
UART_FCR[1]或UART_FCR[2],具体需查阅手册)将接收FIFO清空。这相当于把已经塞满且混乱的快递柜清空,为接收新数据腾出空间。 - 清除内部锁存标志:读取
UART_RESUME寄存器。这个操作本身没有数据交换的意义,其唯一作用就是清除那个导致接收功能被禁用的内部标志位。只有执行了该读操作,接收状态机才会被重新激活。
注意:这个恢复流程是阻塞性的。在CPU执行这两步操作期间,所有正在传输和后续到达的数据都会丢失。因此,在通信协议设计时,必须考虑错误恢复机制,例如在应用层加入帧序号、超时重传或校验重发等逻辑。
1.1 超时与断线检测:UART的“心跳”与“急停”
除了溢出,UART还有两个重要的状态监控机制:超时(Time-Out)和断线(Break)条件。它们像是通信链路的“心跳监测仪”和“紧急制动按钮”。
超时计数器(Time-Out Counter)用于检测接收线路的空闲状态。它的工作原理是监测RX线保持高电平(即空闲状态)的时间。这个时间阈值可以配置,默认是“4 × 编程字长 + 12个比特”的时间,你也可以通过超时寄存器手动设置一个特定的波特率时钟数。当空闲时间超过这个阈值,就会触发超时中断。AM263P提供了两种超时模式,其区别是微妙的:
- 默认模式(
UART_EFR2[6] TIMEOUT_BEHAVE = 0):超时计数器仅在RX FIFO中有数据时才开始计数。一旦检测到RX线上有新的活动(开始位),或者CPU读取了接收保持寄存器(UART_RHR),计数器就会清零。这种模式很智能,它只关心“有数据来了但没取完”的情况,避免因线路正常空闲而产生误中断。 - 可选模式(
UART_EFR2[6] TIMEOUT_BEHAVE = 1):超时计数器即使FIFO为空也会持续计数。如果RX线长期保持空闲,它会周期性地产生超时中断。这种模式适用于需要严格监控链路是否完全死寂的场景,比如某些需要心跳包确认的工业协议。在这个模式下,计数器在每次超时后会自动复位,读取UART_IIR_UART会清除中断,但不会复位计数器本身。
断线条件(Break Condition)则是一种主动的、强制的线路状态。通过将UART_LCR[6]的BREAK_EN位置1,你可以强制TX线输出持续的低电平。这不同于发送一个值为0x00的数据字节,它是一个不受字符边界限制的、长时间的低电平信号,通常被对端设备解释为“线路故障”、“复位”或“帧开始”的特殊标志。发送一个完整的、对齐字符边界的断线信号需要一点技巧:
- 复位TX FIFO(如果使能):确保没有残留的待发送数据干扰。
- 等待发送移位寄存器清空:检查
UART_LSR_UART[6]的TX_SR_E位,确保其为1。这表示最后一个比特已经移出,发送逻辑完全空闲。 - 插入保护时间:根据你配置的停止位长度,等待足够的时间,确保上一个字符的停止位已经完全发送完毕。
- 置位BREAK_EN:此时再设置
BREAK_EN为1,TX线将拉低,直到你将该位清零。
重要提示:超时和断线检测功能仅适用于标准的UART(调制解调器)操作模式。当你将UART配置为IrDA或CIR红外模式时,这两个功能是不生效的。红外通信有自己的一套帧同步和错误检测机制。
2. 红外通信的两种面孔:IrDA与CIR模式深度解析
当UART遇上红外光,就衍生出了IrDA(红外数据协会)和CIR(消费红外)两种模式。它们虽然都基于红外介质,但设计目标和实现方式截然不同,就像电话线和电视遥控器的区别。
IrDA模式是为点对点高速数据通信而生的,比如老式手机间的“红外传图”、笔记本间的文件互传。它追求的是可靠、有效率的数据传输。IrDA内部有一个可编程的波特率发生器,将系统时钟(如48MHz)分频到目标速率。其核心在于不同的子模式:
- SIR(标准红外):速率从2.4kbps到115.2kbps,采用经典的3/16编码(即一个比特周期内,用占空比为3/16的脉冲表示“0”,无脉冲表示“1”)。这是最兼容、最常用的模式。
- MIR(中速红外):速率达到0.576Mbps和1.152Mbps,采用1/4周期脉冲编码,速率更高。
- FIR(快速红外):速率高达4Mbps,采用4PPM(脉冲位置调制)编码,是IrDA的巅峰速度。
CIR模式则专为消费电子遥控器设计,比如你的电视、空调遥控器。它传输的不是连续的字节流,而是编码了“电源”、“音量+”等命令的特定脉冲序列。CIR的核心是载波调制,通常使用36kHz、38kHz或40kHz的载波频率对数据脉冲进行调制,以提高抗干扰能力和传输距离。接收端则需要解调这个载波,还原出基带脉冲序列。
2.1 IrDA模式下的数据格式化与高级功能
在IrDA模式下,数据不再是简单的串行流,而是被组织成具有明确边界和状态的“帧”。这带来了一系列UART模式所没有的高级特性。
地址检查(IR Address Checking)是IrDA在多设备环境中的“寻址”机制。你可以通过UART_XON1_ADDR1和UART_XON2_ADDR2��存器设置两个地址字。使能地址检查后(通过UART_EFR[0]和UART_EFR[1]位),UART模块只会将目标地址与这两个地址匹配的帧写入RX FIFO,其他帧则被静默丢弃。这有效避免了在同一个红外空间内,设备收到非目标数据包的干扰。
帧结束(Frame Closing)有两种方式,体现了硬件自动化和软件精细控制的权衡:
- 帧长度法(
UART_MDR1[7] FRAME_END_MODE = 0):CPU预先将帧长度写入UART_TXFLH和UART_TXFLL寄存器。发送时,硬件自动计数,发完指定长度的数据后,自动附加结束标志。这种方式简单,但要求预先知道精确的帧长。 - 置位EOT法(
UART_MDR1[7] FRAME_END_MODE = 1):CPU在将帧的最后一个字节写入TX FIFO之前,先将UART_ACREG[0]的EOT位置1。当最后一个字节被写入时,硬件会为该字节在FIFO中打上一个“标签”。发送状态机读到这个带标签的字节时,就知道该在此后添加结束标志。这种方式更灵活,适合动态生成或长度可变的帧。
存储控制传输(Store and Controlled Transmission, SCT)模式解决了小帧发送的“下溢”问题。在普通模式下,数据一写入TX FIFO,只要发送器空闲就立即开始发送。如果要发送的帧很短,可能在你写完整个帧之前,发送器就已经把开头的数据发出去了,导致TX FIFO变空,在帧结束前就触发“发送下溢”(TX Underrun)。SCT模式(UART_MDR1[5] SCT = 1)下,写入数据不会立即触发发送。只有当CPU将UART_ACREG[2]的SCTX_EN位置1后,整个预装载好的帧才会被一次性送出,完美避免了小帧发送时的下溢错误。
状态FIFO(Status FIFO)是IrDA模式的一个强大工具。它独立于数据FIFO,专门记录每个接收帧的元数据,包括帧长度和错误状态位(如帧错误、CRC错误、中止错误)。CPU可以通过读取UART_SFREGH/L获取帧长,通过UART_SFLSR获取错误状态。这个深度为8的FIFO,使得CPU可以批量处理多个帧的状态,结合DMA搬运数据,能极大提高效率——CPU只需在状态FIFO达到触发水位时被中断一次,而不是每收到一帧就中断一次。
2.2 CIR模式的特殊配置与硬件局限
CIR模式的配置思维需要从“字节传输”切换到“脉冲波形生成”。它的核心是载波频率生成和脉冲占空比控制。
载波频率由UART_CFPS寄存器控制,计算公式为:分频值 = (F_CLK / 12) / 目标调制频率。其中F_CLK是系统时钟(如48MHz),12是固定的波特率倍数。例如,要得到36kHz的载波,计算过程是:(48MHz / 12) / 36kHz = 4000 / 36 ≈ 111.11,取整后UART_CFPS应设置为111(0x6F)。手册中示例给出的0x7(十进制7)对应36.04kHz,是一个近似值,实际开发中应根据计算和示波器测量进行微调。
脉冲的占空比(高电平在一个周期内的比例)通过UART_MDR2[5:4]的CIR_PULSE_MODE位选择,可选1/4、1/3、5/12或1/2。不同的遥控器协议可能要求不同的占空比,这是匹配接收端解调电路的关键参数。
CIR的传输和接收有其独特的挑战:
- 传输时的实时性:发送一系列数据包(如遥控器连续按键码)时,包与包之间必须有精确的延迟。这通常不能靠FIFO自动完成,需要CPU干预。有两种方法:一是在TX FIFO中填充特定数量的“0”比特来充当延时;二是使用外部系统定时器,结合
SCT模式和TX_STATUS_IT中断,来精确控制每一帧的发射时机。 - 接收时的硬件局限:这是AM263P手册中明确指出的一个硬件限制。某些市售的IrDA收发器,在接收时会产生脉冲宽度收缩的现象。例如,发送端发出的是7μs的调制脉冲(在28μs周期内),但接收端经过某些收发器后,可能只剩下2μs的脉冲。而UART CIR接收端的解调滤波电路是基于发射端的编码机制设计的,过窄的脉冲(如2μs)可能会被过滤掉,导致RX FIFO收不到任何数据。这个限制仅影响CIR接收,不影响CIR发送。如果遇到此问题,可以尝试设置
UART_MDR3[0]的DISABLE_CIR_RX_DEMOD位来旁路内部解调,但这就需要CPU软件来实现更复杂的脉冲解码逻辑。
3. 从寄存器到代码:AM263P UART/IrDA/CIR配置实战指南
理解了原理,最终要落到代码上。TI的参考手册提供了详尽的编程模型,但直接照搬寄存器序列容易让人迷失。我将以最常见的场景为例,梳理出清晰的配置逻辑和避坑要点。
3.1 UART基础模式配置流程
配置一个带FIFO和中断的UART,遵循“初始化 -> 模式选择 -> FIFO/DMA -> 协议与中断”的流程。
第一步:全局初始化与软件复位任何外设配置前,确保其处于已知状态。向UART_SYSC[1]的SOFTRESET位写1,然后轮询UART_SYSS[0]的RESETDONE位,直到其为1。这步操作能清除所有寄存器到复位状态,避免残留配置干扰。
第二步:穿越“寄存器访问模式”的迷宫AM263P的UART寄存器访问有模式(A/B)和子模式(如TCR_TLR, XOFF)的概念,这是为了在有限的地址空间内容纳更多功能寄存器。这是一个常见的“坑点”。
- 要配置波特率除数(
UART_DLH/DLL),必须进入模式B:将UART_LCR[7:0]写为0xBF。 - 要配置FIFO触发水平(
UART_TCR/TLR),需要先进入模式B,然后使能增强功能(UART_EFR[4] ENHANCED_EN=1),再切回模式A(UART_LCR[7] DIV_EN=1且UART_LCR[7:0] != 0xBF),最后设置UART_MCR[6] TCR_TLR=1来进入TCR_TLR子模式。 - 完成特殊寄存器配置后,务必通过设置
UART_LCR[7] DIV_EN=0来切回操作模式,UART才能正常工作。
核心技巧:建议将寄存器模式切换封装成独立的函数,如
uart_enter_mode_b()、uart_enter_tcr_tlr_submode()。在每次配置特殊寄存器前调用,并在配置完成后切回操作模式。这能极大降低因模式切换错误导致配置失效的风险。
第三步:配置FIFO与DMA触发在正确的子模式下(如TCR_TLR),设置UART_FCR[7:6](RX触发)和UART_FCR[5:4](TX触发),或更精细地使用UART_TLR寄存器。DMA模式则通过UART_FCR[3]或UART_SCR[2:1]选择。关键在于,触发水位决定了DMA请求或中断在FIFO有多少数据时产生,需要根据你的数据包大小和CPU处理能力做权衡。
第四步:设置波特率、协议与中断
- 波特率:切到模式B,设置
UART_MDR1[2:0] MODE_SELECT=0x7禁用UART模式(关键!),然后才能安全写入UART_DLH/DLL。计算公式为:除数 = 输入时钟频率 / (16 × 期望波特率)。例如,48MHz时钟下,115200波特率的除数为 48,000,000 / (16 * 115200) = 26.0417 ≈ 26。 - 协议:在
UART_LCR寄存器中配置数据位长度、停止位、奇偶校验等。 - 中断:在模式B下,使能
UART_EFR[4] ENHANCED_EN=1以访问完整的中断使能寄存器UART_IER_UART,然后使能所需的中断源,如接收数据可用(RHR_IT)、发送保持寄存器空(THR_IT)或线路状态错误(LINE_STS_IT)。
第五步:最终模式选择最后,在UART_MDR1[2:0]中选择最终的工作模式:0x0为UART,0x1为IrDA SIR,0x4为MIR,0x5为FIR,0x6为CIR。如果需要RS-485方向控制,则使能UART_MDR3[4] DIR_EN。
3.2 IrDA SIR/MIR/FIR模式配置实例���析
手册中的编程模型是很好的起点,但需要理解每一步的意图。
以SIR模式发送(115.2kbps, 7位数据,无校验)为例:
MODE_SELECT=0x7:禁用当前模式,安全配置时钟。UART_LCR=0x80:进入除数锁存访问模式(即模式B的一种���现)。- 写入
DLL/DLH=0x1A/0x00:设置波特率除数26。 MODE_SELECT=0x1:切换到SIR模式。UART_LCR=0x00:退出除数锁存模式,并设置数据格式为8位、1停止位、无校验(注意,对于7位数据,可能需要结合UART_LCR[1:0]另行设置,此处手册示例可能有简化)。- 使能
THR_IT中断:准备以中断方式发送。 - 设置
UART_TXFLL=0x06:告知硬件本帧长度为6字节(帧长度法)。 - 设置
UART_ACREG[7] PULSE_TYPE=1:选择1.6μs脉冲宽度(另一种是3/16编码)。
关键点:IrDA模式下,波特率设置必须在切换到IrDA模式之前完成。因为MODE_SELECT切换后,时钟分频路径可能改变,此时再改除数可能无效或导致错误时钟。
FIR模式配置的额外注意:FIR模式(4Mbps)下,波特率发生器被旁路,使用的是固定的6分频(48MHz / 6 = 8MHz,再经4PPM编码为4Mbps)。因此,配置FIR模式时,DLL/DLH的设置通常被忽略或设为0,重点在于正确设置MODE_SELECT=0x5以及使能FIFO。
3.3 CIR模式配置核心:载波计算与发射控制
CIR配置的核心是载波。假设我们需要发射38kHz的PWM载波(许多遥控器的标准频率):
- 计算
UART_CFPS:分频值 = (48MHz / 12) / 38kHz = 4,000,000 / 38,000 ≈ 105.26。取整为105(0x69)。注意:手册提到复位值105对应38.1kHz,这与我们的计算吻合。细微误差通常可被接收端容忍。 - 设置
UART_MDR2[5:4]选择占空比,例如01对应1/3占空比。 - 模式选择
UART_MDR1[2:0]=0x6,启用CIR模式。 - 数据准备:CIR TX FIFO中的数据不再是字节,而是直接控制脉冲(1)和空格(0)的比特流。你需要根据目标红外协议(如NEC、RC5),用软件生成对应的比特流序列(包括引导码、地址、命令、反码等),并将其按位写入FIFO。
- 发射时序控制:这是难点。以NEC协议为例,其“重复码”要求一个108ms的间隔。你无法单纯靠FIFO实现。可靠的做法是:使用
SCT模式。先将要发送的帧数据写入TX FIFO,但不启动发送。启动一个硬件定时器,设定108ms超时。在定时器中断服务程序中,置位SCTX_EN,触发该帧发送。帧发送完成后,会产生TX_STATUS_IT中断,在此中断中准备下一帧数据并再次装入FIFO,等待下一个定时器触发。这样就实现了精确的帧间延时控制。
4. 调试陷阱与实战问题排查实录
在实际项目中,配置寄存器只是第一步,真正的挑战来自调试阶段各种诡异的问题。下面是我总结的几个典型场景和排查思路。
问题一:UART能发送,不能接收,或接收全是乱码。
- 排查思路:
- 检查波特率:这是头号嫌疑犯。确保发送和接收双方波特率、数据位、停止位、校验位完全一致。用示波器测量一个字节的时长,反算波特率是否准确。
- 检查引脚映射与电平:确认RX/TX引脚是否交叉连接(本机TX接对端RX)。确认双方电平标准匹配(如3.3V对3.3V,若对端是5V TTL,可能需要电平转换)。
- 检查FIFO与中断:确认RX FIFO已使能(
UART_FCR[0]=1)。确认接收中断(RHR_IT)或DMA已正确使能。在中断服务程序或DMA回调中,是否及时读取了UART_RHR寄存器?读取不及时会导致溢出。 - 检查寄存器访问模式:你是否在配置
UART_DLH/DLL或UART_EFR等寄存器后,忘记将UART_LCR[7]的DIV_EN位清零切回操作模式?这会导致配置未生效。
问题二:IrDA通信距离极短或不稳定。
- 排查思路:
- 检查收发器与对准:IrDA对角度非常敏感,确保收发器窗口清洁且正对。SIR模式典型距离在1米内,且有角度限制。
- 检查脉冲类型:确认
UART_ACREG[7] PULSE_TYPE设置与收发器期望的编码一致。是3/16编码还是1.6μs固定脉冲? - 检查电源噪声:红外发射管(IRED)在导通瞬间电流较大,可能引起电源波动,干扰敏感的接收电路。确保发射管电源有足够的去耦电容(如100nF陶瓷电容紧贴器件)。
- 用示波器看波形:这是最直接的方法。测量TX引脚输出的红外驱动波形,看脉冲宽度、幅度、形状是否符合IrDA标准。接收端在看解调后的数字信号是否清晰。
问题三:CIR模式无法控制电视/空调等设备。
- 排查思路:
- 确认协议与载波:你的目标设备使用什么协议(NEC、Samsung、RC5)?载波频率是38kHz、36kHz还是40kHz?占空比是多少?必须完全匹配。网上可以找到常见协议的详细时序图。
- 验证发射波形:用示波器观察连接到红外发射二极管的引脚波形。你看到的应该是被载波调制过的脉冲串(一串38kHz的方波)。如果只是一个高低电平变化的数字信号,说明载波调制未开启,检查
UART_CFPS和CIR_PULSE_MODE配置。 - 检查发射功率:普通红外发射二极管(IRED)的驱动电流通常在20-100mA。检查你的驱动电路(通常是三极管或MOSFET)能否提供足够的电流。电流不足会导致传输距离大幅缩短。
- 尝试旁路解调:如果作为接收端无法解码,尝试设置
UART_MDR3[0] DISABLE_CIR_RX_DEMOD=1,然后用GPIO中断捕获上升沿/下降沿,在软件中实现协议解码,以排除硬件解调电路的兼容性问题。
问题四:通信过程中偶发性数据丢失。
- 排查思路:
- 首要怀疑溢出:在中断服务程序中,检查
UART_IIR_UART寄存器,看IT_TYPE是否为0x3(线路状态错误),然后进一步读取UART_LSR_UART寄存器,检查溢出错误标志位是否置位。 - 检查中断服务程序效率:如果中断服务程序执行时间过长,或者被更高优先级中断频繁打断,可能导致FIFO已满而来不及读取。优化中断服务程序,只做最必要的操作(如读取数据到缓冲区),将处理逻辑放到主循环中。
- 调整FIFO触发水位:如果使用中断,可以尝试降低RX FIFO的触发水位(例如从8字节改为1字节),让中断更早触发,虽然增加了中断频率,但降低了溢出的风险。如果使用DMA,确保DMA缓冲区足够大,且传输完成中断能及时处理。
- 检查流控制:如果通信速率很高或数据量突发性强,考虑启用硬件(RTS/CTS)或软件(XON/XOFF)流控制,让接收方能在处理不过来时通知发送方暂停。
- 首要怀疑溢出:在中断服务程序中,检查
问题五:从UART模式切换到IrDA/CIR模式后,通信完全失败。
- 排查思路:
- 确认模式切换顺序:牢记“先配时钟,再切模式”的铁律。在
MODE_SELECT设置为0x7(禁用)的状态下,配置好所有时钟相关参数(DLH/DLL,CFPS等),最后再切换到目标模式(0x1,0x4,0x5,0x6)。 - 检查引脚复用:有些MCU的UART引脚和IrDA/CIR功能可能是不同的引脚,或者需要额外的配置来启用红外驱动/接收电路。仔细查阅数据手册的引脚复用章节和红外控制寄存器。
- 检查外部电路:IrDA和CIR都需要外部红外发射器和接收器。确认这些外部器件已正确焊接、供电,并且方向正确(发射/接收不要接反)。
- 确认模式切换顺序:牢记“先配时钟,再切模式”的铁律。在
我个人在多个使用AM263P或类似UART模块的项目中,最深的一点体会是:数据手册是你的地图,但示波器才是你的眼睛。再复杂的寄存器描述,最终都要转化为线上真实的电信号。遇到通信问题,不要只盯着代码和寄存器看,一定要用示波器去抓TX、RX线上的波形,对照协议标准逐位分析。很多时候,一个错误的波特率、一个畸变的脉冲、一个被忽略的溢出标志,在波形面前都无所遁形。把配置流程模块化、模式切换函数化,能避免很多低级错误。对于红外通信,耐心和细致的波形调试比任何高级算法都管用。