1. 项目概述:从寄存器视角看透TMS320C54x的UART与中断
在嵌入式系统,尤其是像TMS320C54x这类经典的定点DSP平台上,串口通信(UART)是连接外部世界最基础、最可靠的桥梁之一。无论是调试信息输出、与上位机通信,还是连接传感器、执行器,UART都扮演着不可或缺的角色。然而,很多开发者在使用时,往往停留在调用库函数或简单配置波特率的层面,一旦遇到数据丢失、通信不稳定或中断响应异常等问题,就感到束手无策。究其根本,是对UART内部那些“沉默的指挥官”——控制与状态寄存器——理解不够深入。
今天,我们就抛开抽象的API,直接深入到TMS320C54x DSP的UART模块寄存器层面,把线控制寄存器(LCR)、线状态寄存器(LSR)、**中断识别寄存器(IIR)和中断使能寄存器(IER)**这几个核心寄存器掰开揉碎了讲清楚。你会发现,理解了它们每一位(Bit)的含义和工作机制,就相当于掌握了UART通信的“底层密码”。无论是配置8N1格式,还是处理奇偶校验错误,或是构建一个高效的中断驱动接收引擎,都将变得有章可循。这篇文章适合所有正在或即将使用C54x系列DSP进行嵌入式开发的工程师,无论你是刚接触硬件寄存器编程的新手,还是想巩固底层原理的资深开发者,相信都能从中获得实用的干货。
2. UART核心寄存器功能深度解析
要驾驭C54x的UART,必须首先理解其寄存器模型。它并非一个简单的“发送/接收”黑盒,而是一组精密协作的逻辑单元,每个寄存器都有其明确的职责。我们将核心寄存器分为三类:配置类、状态类和中断管理类。配置类寄存器(如LCR)决定了通信的“规则”;状态类寄存器(如LSR)反映了通信的“健康状况”;中断管理类寄存器(如IIR、IER)则负责事件的“通知机制”。只有三者协同,才能构建稳定可靠的通信链路。
2.1 线控制寄存器(LCR):通信协议的制定者
线控制寄存器(Line Control Register, LCR)是UART的“宪法”,它定义了每一帧数据的具体格式。对LCR的写入操作,直接决定了数据在物理线路上的表现形式。它是一个8位可读可写寄存器,每一位都至关重要。
2.1.1 数据位宽与停止位配置(Bit 1-0, Bit 2)
LCR的最低两位(Bit 1和Bit 0)共同决定了每个字符的数据位长度。这是一个非常基础但容易出错的设置点。其编码规则如下:
- Bit[1:0] = 00: 字长为5位。现在已较少使用,多见于一些古老的电信协议。
- Bit[1:0] = 01: 字长为6位。
- Bit[1:0] = 10: 字长为7位。常用于传输ASCII字符,因为标准ASCII码是7位的。
- Bit[1:0] = 11: 字长为8位。这是目前最常用的配置,可以传输一个完整的字节(0-255),适用于绝大多数二进制数据通信。
Bit 2用于配置停止位的数量。这里有一个关键细节需要注意:停止位的数量并非完全独立,它会受到数据位宽的影响。当Bit 2为0时,固定产生1个停止位。当Bit 2为1时,停止位数量取决于数据位宽:
- 若字长为5位,则产生1.5个停止位。
- 若字长为6、7或8位,则产生2个停止位。
注意:接收器(Receiver)在采样时,只检测第一个停止位。无论你设置为1个、1.5个还是2个停止位,接收逻辑都只期望在第一个停止位的位置采样到高电平(Mark状态)。设置更多的停止位主要是为了给接收端更充裕的时间来处理数据,特别是在较低波特率下,是一种增加通信可靠性的传统手段。但在现代应用中,通常1个停止位(Bit 2=0)配合8位数据(Bit[1:0]=11)的“8N1”格式就足够了。
2.1.2 奇偶校验配置(Bit 5-3)
奇偶校验是用于检测数据传输过程中单比特错误的一种简单方法。LCR的Bit 3、4、5共同控制这一功能。
- Bit 3 (PEN): 奇偶校验使能位。置1时,在发送数据的最后一个数据位和第一个停止位之间插入一个校验位;在接收时,则对收到的数据进行校验检查。清零则禁用校验。
- Bit 4 (EPS): 偶校验选择位。当PEN=1时,此位决定校验类型。EPS=1选择偶校验(数据位和校验位中‘1’的总数为偶数);EPS=0选择奇校验(‘1’的总数为奇数)。
- Bit 5 (STICK): 粘附校验位。这是一个比较特殊的功能。当PEN=1且STICK=1时,校验位会被强制固定为一个已知值,而不再根据数据计算。具体来说:
- 若
PEN=1, STICK=1, EPS=1,则校验位固定为0。 - 若
PEN=1, STICK=1, EPS=0,则校验位固定为1。 粘附校验通常用于与某些需要固定校验位格式的老旧设备兼容,或者在某些诊断模式下使用。在常规通信中,应保持STICK=0。
- 若
2.1.3 特殊功能位(Bit 6, Bit 7)
- Bit 6 (BC): 间断控制位。将此位置1会强制串行输出线(SOUT)持续保持低电平(Spacing状态),即发送一个“Break”信号。Break信号是一种持续时间长于一个完整字符传输时间的低电平,常用于协议中表示帧开始、线路复位或吸引对方注意。切记,此位只控制输出引脚的状态,不影响发送器内部逻辑。在正常通信时,必须将其清零。
- Bit 7 (DLAB): 除数锁存访问位。这是访问波特率发生器分频器锁存器的“钥匙”。UART的波特率由输入时钟(XIN)通过一个16位的分频器产生。这个分频值(Divisor)存储在两个8位的“除数锁存器”(DLL和DLH)中。由于DLL/DLH与接收缓冲寄存器(RBR)、发送保持寄存器(THR)和中断使能寄存器(IER)共享相同的I/O地址,因此需要通过DLAB位来区分访问目标。
- DLAB = 1: 此时访问的是波特率发生器的除数锁存器(DLL和DLH)。
- DLAB = 0: 此时访问的是RBR(读)、THR(写)或IER(读/写)。
实操心得:配置UART波特率的标准流程是:1) 将LCR的DLAB位置1;2) 写入高8位分频值(DLH)和低8位分频值(DLL);3) 将DLAB位清零,以配置其他参数(如数据位、停止位)并正常读写数据。忘记操作DLAB位是导致波特率配置失败的常见原因。
2.2 线状态寄存器(LSR):通信健康的晴雨表
线状态寄存器(Line Status Register, LSR)是一个至关重要的只读寄存器(TI文档强烈建议不要对其写入)。它像仪表盘一样,实时显示接收和发送通道的状态,特别是各种错误条件。在中断驱动或查询式程序中,定期检查LSR是确保通信可靠性的关键。
2.2.1 接收状态与错误指示(Bit 0-4)
- Bit 0 (DR): 数据就绪位。这是最常用的状态位。当接收缓冲寄存器(RBR)或接收FIFO中有数据可读时,该位被置1。在TL16C450模式(非FIFO模式)下,每收到一个完整字符,DR置1。在FIFO模式下,当FIFO中的数据量达到预设的触发水平时,DR也会置1。读取RBR或清空FIFO会使该位清零。
- Bit 1 (OE): 溢出错误位。当CPU尚未读取RBR中的上一个字符,而下一个字符已经接收完毕并准备覆盖RBR时,此位置1。在FIFO模式下,只有当FIFO完全满,且移位寄存器中又有一个新字符接收完成时,才会发生溢出错误。OE位在每次读取LSR时被清零。
- Bit 2 (PE): 奇偶校验错误位。当接收到的字符的奇偶性与LCR中设定的奇偶校验类型(奇校验或偶校验)不匹配时,此位置1。在FIFO模式下,该错误信息与FIFO中对应的字符关联,只有当这个出错的字符被移到FIFO顶部(即将被读取)时,PE状态才会反映在LSR中。读取LSR会清零此位。
- Bit 3 (FE): 帧错误位。当接收到的字符没有在预期的停止位位置检测到有效的停止位(高电平)时,此位置1。这通常意味着发送和接收双方的波特率不匹配,或者线路受到严重干扰。发生帧错误后,UART会尝试重新同步,它会假设下一个低电平是起始位并进行采样。FE位同样在读取LSR时清零,在FIFO模式下也与特定字符关联。
- Bit 4 (BI): 间断中断指示位。当接收数据输入线(SIN)保持低电平的时间超过一个完整字符的传输时间(起始位+数据位+校验位+停止位)时,此位置1。间断条件被视为一种错误。在FIFO模式下,发生间断时,只会有一个全0的字符被加载到FIFO中。只有当SIN回到高电平并保持至少两个接收时钟(RCLK)周期,且下一个有效的起始位被检测到后,接收才会继续。
2.2.2 发送状态指示(Bit 5-6)
- Bit 5 (THRE): 发送保持寄存器空位。当发送保持寄存器(THR)为空,表示UART已准备好接受CPU发送的新字符时,此位置1。在TL16C450模式下,当THR的内容被转移到发送移位寄存器(TSR)时,THRE置1。当CPU写入THR时,THRE清零。在FIFO模式下,THRE在发送FIFO为空时置1,在至少有一个字节被写入发送FIFO时清零。如果中断使能寄存器(IER)的Bit 1被置位,那么当THRE=1时,会产生一个发送中断,提示CPU可以发送下一个数据了。
- Bit 6 (TEMT): 发送器空位。当THR和TSR都为空时,此位置1。只要THR或TSR中存有数据字符,此位就为0。在FIFO模式下,当发送FIFO和发送移位寄存器都为空时,TEMT置1。THRE和TEMT的区别在于:THRE只表示“蓄水池”(THR/FIFO)空了,可以灌新水;而TEMT表示“蓄水池”和“输水管”(TSR)都空了,整个发送通道完全空闲。
2.2.3 FIFO错误汇总位(Bit 7)
- Bit 7 (FIFO Error): 此位仅在FIFO模式下有效(在TL16C450模式下恒为0)。当接收FIFO中至少存在一个奇偶校验错误、帧错误或间断错误时,此位置1。当CPU读取LSR,且FIFO中不再有后续错误时,此位清零。这是一个快速检查FIFO中是否存在任何错误的便捷方法,无需遍历FIFO中的每个字符。
注意事项:LSR的Bit 1到Bit 4(OE, PE, FE, BI)这四种错误条件中的任何一种发生,都会触发一个接收线路状态中断(如果该中断被使能)。因此,在中断服务程序中,除了读取数据,必须检查LSR以判断是否发生错误并进行相应处理(如清空错误FIFO、重发数据或记录错误日志),否则错误状态可能无法及时清除,导致通信卡死。
2.3 中断管理寄存器:高效事件响应的引擎
在实时性要求高的系统中,让CPU不断轮询LSR的DR位或THRE位是一种资源浪费。C54x的UART提供了完善的中断机制,可以让UART在特定事件发生时主动通知CPU。
2.3.1 中断识别寄存器(IIR):中断源的裁判官
中断识别寄存器(Interrupt Identification Register, IIR)是一个只读寄存器。当有中断发生时,CPU通过读取IIR可以快速识别出最高优先级的、待处理的中断源是什么,而无需逐个检查所有可能的状态位。IIR的低4位提供了中断信息。
- Bit 0 (IP): 中断待处理标志。这是最重要的位。当IP=0时,表示有中断正在等待处理(Pending)。当IP=1时,表示没有待处理的中断。在查询中断时,应首先检查此位。
- Bit 2-1 (ID1, ID0): 中断识别码。这两位指示了当前最高优先级的待处理中断的类型,具体编码见下表(与输入资料中的Table 3-17对应):
| IIR Bit 3 | IIR Bit 2 | IIR Bit 1 | IIR Bit 0 | 优先级 | 中断类型 | 中断源 | 复位方法 |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 1 | 无 | 无中断 | 无 | - |
| 0 | 1 | 1 | 0 | 1 (最高) | 接收线路状态 | 溢出、奇偶、帧或间断错误 | 读LSR |
| 0 | 1 | 0 | 0 | 2 | 接收数据就绪 | RBR有数据或FIFO达到触发水平 | 读RBR |
| 1 | 1 | 0 | 0 | 2 | 字符超时 | FIFO中有数据,但超时未读 | 读RBR |
| 0 | 0 | 1 | 0 | 3 | 发送保持寄存器空 | THR为空 | 读IIR或写THR |
- Bit 3 (TOUT): 超时中断指示(仅FIFO模式有效)。在TL16C450模式下,此位恒为0。在FIFO模式下,当Bit 2也为1时,表示发生的是字符超时中断。这种中断发生在:接收FIFO中至少有一个字符,但在过去4个字符的传输时间内,既没有新字符被存入FIFO,也没有任何字符被CPU读出。这有助于CPU及时处理FIFO中残留的不完整数据包。
2.3.2 中断使能寄存器(IER):中断的开关
中断使能寄存器(Interrupt Enable Register, IER)决定了哪些UART事件可以触发中断。它是一个可读可写的寄存器。需要注意的是,在访问IER之前,必须确保LCR的DLAB位为0,否则访问的将是波特率分频器的高字节(DLH)。
IER的低4位分别对应四种中断源:
- Bit 0 (ERBFI): 接收数据可用中断使能。置1使能,当RBR有数据(非FIFO模式)或接收FIFO达到触发水平(FIFO模式)时产生中断。
- Bit 1 (ETBEI): 发送保持寄存器空中断使能。置1使能,当THR为空(非FIFO模式)或发送FIFO为空(FIFO模式)时产生中断。
- Bit 2 (ELSI): 接收线路状态中断使能。置1使能,当发生溢出、奇偶、帧或间断错误时产生中断。
- Bit 3 (EDSSI): Modem状态变化中断使能。在C54x的UART中,此位通常保留或用于特定功能,需参考具体器件手册。
实操心得:中断的优先级是固定的(线路状态 > 接收就绪/超时 > 发送就绪)。在中断服务程序(ISR)中,读取IIR获取中断源后,必须执行相应的“复位”操作来清除中断。例如,对于“接收数据就绪”中断,必须读取RBR;对于“发送保持寄存器空”中断,需要写入THR或读取IIR本身(具体看芯片手册);对于“接收线路状态”中断,必须读取LSR。如果不清除,中断会一直保持 pending 状态。
3. 从理论到实践:UART初始化与中断服务程序实现
理解了寄存器功能后,我们将其组合起来,完成一个完整的、中断驱动的UART通信模块的软件实现。这里以TMS320C54x汇编语言为例进行说明,其思路同样适用于C语言编程。
3.1 UART模块初始化流程
初始化是确保UART正常工作的第一步,必须严格按照顺序进行,特别是涉及DLAB位的操作。
步骤1:配置波特率这是首要步骤,因为波特率分频器的访问受DLAB控制。
; 假设目标波特率为115200,输入时钟XIN频率为3.6864MHz ; 计算公式:Divisor = XIN_Freq / (Desired_Baud * 16) ; Divisor = 3,686,400 / (115200 * 16) = 3,686,400 / 1,843,200 = 2 ; 则 DLL = 0x02, DLH = 0x00 PORTR LCR_ADDR, A ; 读取当前LCR值 OR #80h, A ; 设置DLAB=1 (Bit7) PORTW A, LCR_ADDR ; 写入LCR,开启除数锁存器访问 STM #00h, DLH_ADDR ; 写入除数高字节 (DLH = 0x00) STM #02h, DLL_ADDR ; 写入除数低字节 (DLL = 0x02)步骤2:配置通信格式与模式设置数据位、停止位、奇偶校验,并关闭DLAB。
; 配置为8位数据,1位停止位,无奇偶校验,禁用间断,关闭DLAB ; LCR = 0000 0011b = 0x03 (Bit7=DLAB=0, Bit6=BC=0, Bit5=STICK=0, ; Bit4=EPS=0, Bit3=PEN=0, Bit2=STB=0, ; Bit[1:0]=WLS=11) STM #03h, LCR_ADDR步骤3:配置FIFO(如果使用)如果需要使用FIFO缓冲功能来减少中断频率,需要配置FIFO控制寄存器(FCR)。注意,写入FCR会同时清空发送和接收FIFO。
; 使能FIFO,设置接收FIFO触发级别为14字节(例如),清空FIFO ; 假设FCR的Bit0=1使能FIFO,Bit[7:6]=00表示1字节触发(这里仅为示例,实际触发级别选择需看具体需求) ; 更常见的配置可能是设置一个适中的触发水平,如8字节。 STM #0C7h, FCR_ADDR ; 示例值:使能FIFO,设置接收触发级别,清空FIFO ; 具体值需根据芯片手册的FCR位定义确定步骤4:配置中断使能所需的中断源,并确保CPU全局中断和相应的中断屏蔽位已打开。
; 使能接收数据就绪中断和接收线路状态错误中断 ; IER = 0000 0101b = 0x05 (Bit0=ERBFI=1, Bit1=ETBEI=0, Bit2=ELSI=1) STM #05h, IER_ADDR ; 在CPU层面,需要设置中断屏蔽寄存器(IMR),使能UART中断对应的位(例如INT14) ; 并开启全局中断控制位(INTM = 0) SSBX INTM ; 首先关闭全局中断 ... ; 配置IMR,使能UART中断位 RSBX INTM ; 开启全局中断3.2 中断服务程序(ISR)编写要点
中断服务程序需要高效、准确地处理中断,并清除中断源。
UART_ISR: PSHM ST0 ; 保存上下文 PSHM ST1 PSHM AL ... ; 保存其他必要寄存器 PORTR IIR_ADDR, A ; 读取IIR,识别中断源 AND #0Fh, A ; 屏蔽高4位,只看中断识别码和IP位 BC NO_INT, ANEQ ; 如果A不为0(IP=0),则有中断,否则跳转 ; 根据IIR的低4位判断中断类型 ; 这里是一个简单的判断逻辑示例 SUB #02h, A ; 检查是否是接收线路状态中断 (0110b) BC HANDLE_LSR_ERR, AEQ ; 如果是,跳转处理错误 PORTR IIR_ADDR, A ; 重新读取IIR(因为上一步修改了A) AND #0Fh, A SUB #04h, A ; 检查是否是接收数据就绪中断 (0100b) 或超时中断(1100b) BC HANDLE_RX_DATA, AEQ ; Bit2=1且Bit1=0,是接收相关中断 PORTR IIR_ADDR, A AND #0Fh, A SUB #0Ch, A ; 检查是否是字符超时中断 (1100b) BC HANDLE_RX_TIMEOUT, AEQ ; 可以继续检查发送空中断 (0010b) 等... HANDLE_LSR_ERR: PORTR LSR_ADDR, B ; 读取LSR,查看具体错误类型 ... ; 根据LSR的Bit1-4判断是溢出、奇偶、帧还是间断错误 ; 并进行相应处理,如记录日志、复位接收器等 B ISR_END HANDLE_RX_DATA: HANDLE_RX_TIMEOUT: ; 数据就绪和超时处理类似,都是读取数据 PORTR RBR_ADDR, B ; 读取接收到的数据到B寄存器 STL B, *AR1+ ; 将数据存储到缓冲区,AR1为缓冲区指针 ... ; 更新缓冲区索引,检查是否满等 B ISR_END ISR_END: ... ; 恢复上下文 POPM AL POPM ST1 POPM ST0 RETE ; 中断返回 NO_INT: ... ; 可能是伪中断,直接恢复上下文返回 POPM AL POPM ST1 POPM ST0 RETE关键点:在
HANDLE_LSR_ERR分支中,读取LSR这个动作本身就会清除OE、PE、FE、BI这些错误标志位(对于FIFO模式,则是清除与顶部字符关联的错误状态)。这是清除线路状态中断的必要操作。对于接收数据中断,读取RBR是清除中断的条件。对于发送空中断,写入THR或读取IIR可以清除中断(具体需查阅数据手册)。
4. 常见问题排查与调试技巧实录
即使严格按照手册编程,在实际项目中仍会遇到各种问题。以下是我在多年开发中总结的一些常见陷阱和调试技巧。
问题1:通信双方波特率一致,但收不到数据或收到乱码。
- 排查思路:
- 检查时钟源:确认DSP的UART模块输入时钟(XIN)频率是否与软件计算分频值时所假设的一致。使用示波器测量XIN引脚频率。
- 验证分频值计算与写入:双重检查波特率计算公式
Divisor = XIN_Freq / (Desired_Baud * 16)。确保计算结果为整数或误差极小(通常要求误差<2%)。使用1.8432MHz或3.6864MHz等标准晶振可以产生许多标准的无误差波特率。 - 确认DLAB位操作顺序:这是最易出错的地方。务必先置位DLAB(LCR Bit7=1),然后写入DLL和DLH,最后再清除DLAB以配置其他参数。可以在写入前后读取LCR值进行验证。
- 检查硬件连接:确认TX、RX线是否交叉连接(本机的TX接对端的RX)。用示波器观察TX引脚是否有正确的、符合波特率的串行波形输出。起始位为低电平,停止位为高电平。
问题2:能发送数据,但接收中断无法触发。
- 排查思路:
- 检查IER配置:确认IER的Bit 0(ERBFI)是否已置1。切记,在写IER前,LCR的DLAB位必须为0,否则你写的是DLH。
- 检查CPU中断配置:
- 确认DSP的**中断屏蔽寄存器(IMR)**中,UART对应的中断位(例如INT14)是否已使能。
- 确认**中断标志寄存器(IFR)**中是否有UART中断标志被挂起。有时需要手动清除旧的标志。
- 确认全局中断开关INTM位是否已打开(
RSBX INTM)。
- 检查IIR状态:在中断服务程序或主循环中读取IIR,看Bit 0(IP)是否为0,以及中断识别码是什么。这能直接告诉你UART内部是否有中断待处理,以及是什么类型。
- 验证数据是否真正到达:使用另一个串口工具(如电脑串口助手)向DSP发送数据,并用示波器检查DSP的RX引脚是否有信号。确保信号电平符合要求(通常是TTL或RS232电平)。
问题3:通信过程中偶尔出现数据丢失或错误。
- 排查思路:
- 检查LSR中的错误位:在接收中断或查询循环中,务必在读取数据后检查LSR,特别是OE(溢出错误)和FE(帧错误)位。溢出错误意味着CPU处理速度跟不上数据接收速度,需要考虑使用FIFO或提高中断优先级。帧错误通常意味着波特率有微小偏差或噪声干扰。
- 评估中断响应时间:如果使用中断,确保中断服务程序的执行时间足够短。如果ISR太复杂,可能导致在处理当前数据时,下一个数据已经到来并造成溢出。可以考虑在ISR中只做最必要的操作(如将数据存入缓冲区),将复杂处理放到主循环中。
- 启用并合理配置FIFO:对于高速或大数据量通信,强烈建议启用UART的FIFO功能。设置合适的接收FIFO触发水平(例如8字节或14字节),可以在积累一定数据后再产生中断,大幅减少中断次数,给CPU更充裕的处理时间。
- 注意“字符超时”中断:在FIFO模式下,如果接收到的数据包长度不定,且最后一个包的数据量小于FIFO触发水平,这些数据可能会一直留在FIFO中,等待达到触发水平才产生中断,导致数据无法及时被读取。使能“字符超时中断”(IIR Bit 3相关)可以解决此问题,它在数据进入FIFO后一段时间内未被取走时产生中断,确保数据能被及时处理。
问题4:发送数据不完整或卡死。
- 排查思路:
- 检查THRE/TEMT状态:在查询式发送中,发送每个字节前必须检查LSR的Bit 5(THRE)是否为1(表示THR空)。在中断式发送中,确保在THRE中断触发后,有新的数据写入THR。
- 避免写THR过快:即使THRE=1,连续向THR写入两个字节,第二个字节也可能丢失。必须等待THRE再次置1后才能写入下一个字节。在中断服务中,这是自然保证的。在查询式中,必须循环等待。
- 发送Break信号后复位:如果使用了LCR的Bit 6(BC)发送Break信号,结束后一定要将该位清零,否则TX线会一直保持低电平,无法进行正常通信。
调试技巧:
- 寄存器打印法:在程序关键点(如初始化后、中断发生时、错误发生时),将LCR、LSR、IIR、IER等关键寄存器的值通过UART本身或其他方式(如LED、其他串口)打印出来。这是最直接的诊断方法。
- 示波器/逻辑分析仪:这是硬件调试的终极武器。可以直观地看到TX/RX线上的波形、波特率、数据帧格式(起始位、数据位、停止位),以及中断信号线的变化。可以迅速定位是软件配置问题还是硬件信号问题。
- 软件流控:如果通信不稳定,可以考虑在软件层面实现XON/XOFF流控。当接收方缓冲区快满时,发送一个XOFF字符(如0x13)通知发送方暂停;当缓冲区有空闲时,再发送一个XON字符(如0x11)通知发送方继续。这需要通信双方协议支持。