1. 项目概述:为什么UART中断是嵌入式通信的“神经末梢”
在嵌入式系统开发中,串口通信(UART)就像设备与外界对话的“嘴巴”和“耳朵”。但如果你让CPU一直盯着串口的“嘴巴”有没有话要说,或者“耳朵”有没有听到新消息,那CPU就什么别的活也干不了了,这就是轮询(Polling)的弊端。中断机制的出现,彻底改变了这种低效的互动方式。它让UART这个外设在“有话要说”或“听到消息”时,能主动“拍一下”CPU的肩膀,告诉CPU:“嘿,我这儿有情况,你快来处理一下!”处理完后,CPU又可以继续忙自己的事情。这种“事件驱动”的模式,是构建高效、实时响应系统的基石。
UART中断机制的核心价值,在于它精准地平衡了实时性与资源占用。对于数据收发这种不可预测的异步事件,中断能确保在数据到达的瞬间或发送缓冲区空出的第一时间得到处理,避免了数据丢失或发送延迟。同时,它解放了CPU,让系统可以并行处理其他任务,或在空闲时进入低功耗模式,这对于电池供电的设备至关重要。
要实现这套精妙的“通知系统”,硬件上需要一套完整的逻辑电路来捕获事件、管理优先级并发出信号,而软件上则需要开发者通过配置几个关键的寄存器来“定制”这套系统的行为规则。本次,我们就深入UART内部,从事件触发到寄存器配置,完整拆解这套中断机制,让你不仅能看懂手册,更能写出稳定、高效的驱动代码。
2. UART中断机制的整体架构与工作流程
要理解UART中断,不能孤立地看一两个寄存器,必须把它看作一个由事件源、仲裁器、状态记录和CPU接口组成的完整系统。这个系统的工作流程,可以类比为一个高效的公司前台(UART)与忙碌的经理(CPU)之间的协作。
2.1 中断事件的产生与类型
UART在运行过程中,会持续监控内部多个关键节点的状态。当特定条件满足时,便会产生一个中断事件。根据输入资料,UART主要定义了三类优先级的中断源:
优先级1(最高):接收线路状态中断(RLSINT)。这是“紧急事件”,通常意味着通信链路出现了严重问题。其触发源包括:
- 溢出错(OE):数据来得太快,CPU或DMA还没取走旧数据,新数据就把缓冲区冲掉了。
- 奇偶校验错(PE):接收到的数据奇偶校验位与预期不符,表明数据传输可能受到干扰。
- 帧错误(FE):停止位没有被正确检测到,通常意味着波特率不匹配或信号畸变。
- 中止条件(BI):对方持续发送逻辑0的时间超过一个完整字符的传输时间,通常用于表示通信中断。
优先级2:接收数据中断。这是“常规工作通知”,告诉CPU有“邮件”到了。它又细分为两种情况:
- 接收数据就绪(RDAINT):在非FIFO模式下,只要接收缓冲寄存器(RBR)里有一个有效字符,就产生中断。在FIFO模式下,则要等到接收FIFO中的数据量达到预设的触发水平(如1、4、8、14字节)。
- 接收超时中断(RTOINT):仅在FIFO模式下有效。这是一个非常巧妙的设计,用于处理“零星数据”。当接收FIFO中有数据,但数据量一直未达到触发水平,且在超过4个字符传输时间内没有新数据到来,也没有数据被取走时,就会产生此中断。这确保了即使最后几个字节不足以填满FIFO触发线,也能被及时处理,避免数据在FIFO中“睡大觉”。
优先级3(最低):发送保持寄存器空中断(THREINT)。这是“资源就绪通知”,告诉CPU:“发送缓冲区空了,可以给我下一批要发送的数据了。”在非FIFO模式下,当发送保持寄存器(THR)为空时触发;在FIFO模式下,当整个发送FIFO为空时触发。
注意:RDAINT和RTOINT在中断使能寄存器(IER)中共享同一个使能位(ERBI)。这意味着你无法单独只使能超时中断而不使能数据就绪中断,它们是绑定在一起的。在设计协议时需要考虑这一点。
2.2 中断的仲裁、使能与上报路径
UART内部可能有多个中断事件同时发生或等待处理,但CPU通常只提供一个中断引脚给UART。因此,UART内部需要一个仲裁器(Arbiter)来解决“谁先向CPU报告”的问题。
其工作逻辑如下:
- 事件发生:上述任一中断事件被检测到。
- 使能检查:硬件会首先检查该事件在中断使能寄存器(IER)中对应的位是否被置1(使能)。如果未被使能,则该事件被直接忽略,不会产生任何后续动作。这是中断系统的“总开关”。
- 记录与仲裁:如果事件已使能,它会被记录到中断标识寄存器(IIR)中。此时,仲裁器会根据预设的优先级(RLSINT > RDAINT/RTOINT > THREINT)进行裁决。即使有多个中断同时等待,UART也只会向CPU发出一个中断请求信号。IIR中会记录当前优先级最高的那个待处理中断的类型和状态。
- CPU响应:CPU收到中断请求后,会跳转到中断服务程序(ISR)。ISR要做的第一件事,就是读取IIR寄存器,通过其中的中断标识码(INTID)和中断挂起位(IPEND)来判断具体是哪个中断源触发了本次请求,并据此进行相应的处理(如读取数据、填充发送缓冲区、检查错误等)。
- 中断清除:处理完一个中断后,必须通过特定的操作来清除该中断的挂起状态,否则CPU会认为中断一直存在。清除方式因中断类型而异(例如,读RBR可以清除RDAINT,写THR可以清除THREINT)。一个关键动作是,在ISR中,即使你认为已经处理完了,也必须再次读取IIR[0](IPEND位),检查是否还有其它较低优先级的中断在等待处理。因为UART每次只上报一个最高优先级中断,只有当前中断被清除后,下一个最高优先级的中断才会被记录并上报。
整个路径可以概括为:特定事件发生 -> IER使能检查 -> IIR记录与仲裁 -> 向CPU发送单一中断请求 -> CPU读取IIR识别中断源 -> 执行服务 -> 特定操作清除中断 -> 再次检查IIR[0]。
3. 核心寄存器详解与配置实战
理解了流程,我们就要通过配置寄存器来驾驭这套机制。UART中断相关的寄存器主要围绕“使能”、“标识”、“状态”和“控制”展开。
3.1 中断使能寄存器(IER):设定你的关注列表
IER是你的“中断订阅列表”。你只会在IER中使能你关心的事件,对于不关心的事件,即使发生,UART也不会打扰CPU。
IER是一个可读可写的寄存器,其关键位如下(基于典型16550兼容UART,位宽可能因芯片而异,但概念通用):
| 位 | 字段名 | 描述 |
|---|---|---|
| 0 | ERBI | 接收器数据就绪中断使能。置1时,使能RDAINT(接收数据就绪)和RTOINT(接收超时)中断。 |
| 1 | ETBEI | 发送保持寄存器空中断使能。置1时,使能THREINT(发送缓冲区空)中断。 |
| 2 | ELSI | 接收线路状态中断使能。置1时,使能RLSINT(溢出错、校验错、帧错误、中止)中断。 |
| 3 | - | 现代UART中通常保留或用于其他功能(如Modem状态中断)。 |
配置示例与心得: 假设我们只需要在数据收到和发送缓冲区空时被通知,但暂时不处理线路错误(错误处理可能由更高层协议负责,或初期调试时先关闭以简化问题)。
// 使能接收数据就绪中断和发送缓冲区空中断 UART->IER = (1 << 0) | (1 << 1); // 设置ERBI和ETBEI位 // 等价于: UART->IER = 0x03;实操心得:在系统初始化阶段,建议最后才配置IER。先完成波特率、数据格式(LCR)、FIFO(FCR)等设置,再打开中断使能。这可以避免在配置过程中因状态不稳而产生不必要的中断。此外,在进入低功耗模式前,通常需要清除IER以禁用所有UART中断,防止它唤醒CPU。
3.2 中断标识寄存器(IIR):中断服务程序中的“导航仪”
IIR是一个只读寄存器,当CPU进入中断服务程序后,第一个动作就是读取它,以确定中断来源。它与FIFO控制寄存器(FCR)共享同一地址,读操作访问IIR,写操作访问FCR。
其关键字段如下:
| 位 | 字段名 | 描述 |
|---|---|---|
| 0 | IPEND | 中断挂起位。这是最重要的位之一。0表示有中断正在挂起(等待处理),1表示无中断挂起。在ISR中,必须循环处理直到此位变为1。 |
| 3:1 | INTID | 中断标识码。指示当前挂起的最高优先级中断的类型。其值与中断类型的对应关系是固定的(见下文表格)。 |
| 7:6 | FIFOEN | FIFO状态指示。可用于判断UART当前是否工作在FIFO模式。 |
INTID值与中断源对应关系:
| INTID值 | 优先级 | 中断类型 | 中断源 | 清除中断的操作 |
|---|---|---|---|---|
| 0x06 | 2 | 接收超时 (RTOINT) | FIFO模式下的接收超时 | 从接收FIFO读取一个字符;或新字符到达;或复位接收器。 |
| 0x04 | 2 | 接收数据就绪 (RDAINT) | 非FIFO模式:RBR有数据。 FIFO模式:达到触发水平。 | 非FIFO:读RBR。 FIFO:使FIFO数据量低于触发水平。 |
| 0x02 | 3 | 发送保持寄存器空 (THREINT) | 非FIFO模式:THR为空。 FIFO模式:发送FIFO为空。 | 写数据到THR(或发送FIFO)。 |
| 0x03 | 1 | 接收线路状态 (RLSINT) | 发生溢出错、校验错、帧错误或中止。 | 读LSR寄存器(对于OE错误)。对于PE、FE、BI错误,需要读完所有错误数据。 |
中断服务程序(ISR)模板示例:
void UART_IRQHandler(void) { uint8_t iir_value; // 必须循环处理,因为一次可能只报告一个中断,但实际有多个 while (((iir_value = UART->IIR) & 0x01) == 0) { // 检查IPEND位是否为0(有中断挂起) switch (iir_value & 0x0E) { // 取INTID字段(位3:1) case 0x06: // 接收超时中断 (0110) handle_rx_timeout(); // 清除方式:读取接收FIFO直到为空或低于触发水平 while (UART->LSR & 0x01) { // 检查DR位 process_received_byte(UART->RBR); } break; case 0x04: // 接收数据就绪中断 (0100) handle_rx_data_ready(); // 非FIFO模式:直接读一次RBR即可。 // FIFO模式:建议读取所有可用的数据,直到DR位为0。 while (UART->LSR & 0x01) { process_received_byte(UART->RBR); } break; case 0x02: // 发送缓冲区空中断 (0010) handle_tx_buffer_empty(); // 填充发送缓冲区。例如,从发送环形缓冲区取数据写入THR。 if (tx_buffer_count > 0) { UART->THR = get_char_from_tx_buffer(); tx_buffer_count--; } else { // 如果所有数据都发送完毕,可以考虑禁用THRE中断,避免空转 // UART->IER &= ~(1 << 1); } break; case 0x03: // 接收线路状态错误 (0011) handle_line_status_error(); // 必须读取LSR以清除OE错误标志,并获取具体错误类型 uint8_t lsr_status = UART->LSR; if (lsr_status & 0x02) { // OE 溢出错 // 通常意味着处理速度跟不上接收速度,需要优化或流控 } if (lsr_status & 0x04) { // PE 校验错 // 数据可能损坏,根据协议决定丢弃或重传 } if (lsr_status & 0x08) { // FE 帧错误 // 检查波特率、线缆连接 } if (lsr_status & 0x10) { // BI 中止 // 对方可能断线 } // 注意:对于PE/FE/BI,还需要读取RBR来清除错误字符本身 if (lsr_status & 0x01) { // 如果错误字符还在缓冲区 uint8_t dummy = UART->RBR; // 读取并丢弃错误数据 } break; default: // 可能是其他未定义的中断,或需要处理的中断已清除 break; } } }关键技巧:
while循环检查IPEND位是必须的。因为UART一次只上报一个中断。假设同时发生了RLSINT(高优先级)和THREINT(低优先级),ISR第一次进入时,IIR只会显示RLSINT。处理完RLSINT并清除后,IIR中的THREINT才会“浮出水面”,此时IPEND位仍为0,循环会继续,从而处理THREINT。这确保了单次ISR调用能处理所有挂起的中断。
3.3 线路状态寄存器(LSR):诊断通信状态的“仪表盘”
LSR是一个只读的状态寄存器,它实时反映了UART收发通道的健康状况。它不仅为RLSINT中断提供具体的错误原因,也是轮询模式下判断状态的主要依据。
其核心位如下:
| 位 | 字段名 | 描述 |
|---|---|---|
| 0 | DR | 数据就绪。为1时表示接收缓冲器(RBR或FIFO)中有数据可读。这是轮询接收时最常检查的位。 |
| 1 | OE | 溢出错。为1表示数据丢失。非FIFO模式下,新数据覆盖了未读的旧数据;FIFO模式下,FIFO已满后新数据仍到达。 |
| 2 | PE | 奇偶校验错。为1表示接收数据的奇偶校验失败。 |
| 3 | FE | 帧错误。为1表示未检测到有效的停止位。 |
| 4 | BI | 中止中断。为1表示RX线路上检测到长时间的低电平(Break信号)。 |
| 5 | THRE | 发送保持寄存器空。为1表示THR(或发送FIFO)为空,可以写入新数据。这是轮询发送时检查的位。 |
| 6 | TEMT | 发送器空。为1表示THR和TSR都为空,即所有数据已完全发出。 |
| 7 | RCVR FIFO Error | 接收FIFO错误(如果支持)。指示FIFO中是否存在至少一个错误(PE/FE/BI)。 |
在ISR中处理LSR的要点:当IIR指示为RLSINT时,必须读取LSR来获取具体的错误类型,并且这个读取操作本身会清除OE错误标志。对于PE、FE、BI,标志位在对应的错误数据被从RBR或FIFO中读出后才会清除。
3.4 FIFO控制寄存器(FCR)与中断的关系
FCR是一个只写寄存器,与IIR共享地址。它控制着UART的FIFO模式,直接影响着RDAINT和RTOINT的行为。
- FIFOEN(位0):置1使能收发FIFO。必须在设置其他FCR位之前先设置此位。
- RXCLR/TXCLR(位1/2):写1分别用于清除接收和发送FIFO。这在初始化或需要丢弃缓冲区数据时非常有用。
- RXFIFOTL(位7:6):设置接收FIFO的触发水平。这决定了在FIFO模式下,接收了多少字节才会触发RDAINT。选项通常是1、4、8、14字节。设置较高的触发水平可以减少中断频率,提升批量传输效率;设置较低的触发水平则可以降低数据接收延迟。
配置示例:
// 启用FIFO,设置接收FIFO触发水平为8字节,并清除FIFO UART->FCR = (1 << 0) | (1 << 1) | (1 << 2) | (0x02 << 6); // FIFOEN=1, RXCLR=1, TXCLR=1, RXFIFOTL=2 (代表8字节) // 注意:先使能FIFOEN,再设置其他位。清除位是“写1清零”,操作后硬件会自动将其复位为0。4. 从零开始:一个UART中断驱动的收发实例
让我们结合一个具体的场景,将上述所有知识点串联起来:配置一个UART,使用FIFO和中断,实现数据的异步接收和发送。
4.1 硬件初始化与寄存器配置步骤
确定基地址与关闭中断:首先获取UART模块的基地址。为安全起见,先关闭所有中断。
#define UART_BASE 0x4000C000 #define UART_IER (*(volatile uint8_t *)(UART_BASE + 0x01)) UART_IER = 0x00; // 禁用所有中断设置波特率(访问DLL/DLH):这需要操作线路控制寄存器(LCR)的DLAB位。
#define UART_LCR (*(volatile uint8_t *)(UART_BASE + 0x03)) #define UART_DLL (*(volatile uint8_t *)(UART_BASE + 0x00)) // 当DLAB=1时 #define UART_DLH (*(volatile uint8_t *)(UART_BASE + 0x01)) // 当DLAB=1时 // 假设系统时钟为50MHz,目标波特率为115200 uint16_t divisor = 50000000 / (16 * 115200); // 计算分频值 UART_LCR |= (1 << 7); // 设置DLAB=1,允许访问DLL/DLH UART_DLL = divisor & 0xFF; // 写入低8位 UART_DLH = (divisor >> 8) & 0xFF; // 写入高8位 UART_LCR &= ~(1 << 7); // 清除DLAB=0,恢复访问RBR/THR/IER设置数据格式(LCR):配置数据位、停止位、奇偶校验。
// 8位数据,1位停止位,无奇偶校验 UART_LCR = 0x03; // 位[1:0]=11 (8 bits), 位[2]=0 (1 stop bit), 位[3]=0 (Parity Disable)配置FIFO(FCR):
#define UART_FCR (*(volatile uint8_t *)(UART_BASE + 0x02)) // 与IIR同地址,写操作 UART_FCR = (1 << 0) | (1 << 1) | (1 << 2) | (0x01 << 6); // 使能FIFO,清除FIFO,触发水平设为4字节最后,使能所需中断(IER):
UART_IER = (1 << 0) | (1 << 1) | (1 << 2); // 使能ERBI(RDAINT/RTOINT), ETBEI(THREINT), ELSI(RLSINT)配置NVIC(如果使用ARM Cortex-M等):在微控制器层面使能对应的UART中断向量。
NVIC_EnableIRQ(UART0_IRQn);
4.2 中断服务程序(ISR)的完整实现逻辑
假设我们有一个环形缓冲区用于接收和发送数据。
// 全局缓冲区 #define RX_BUF_SIZE 256 #define TX_BUF_SIZE 256 volatile uint8_t rx_buffer[RX_BUF_SIZE]; volatile uint16_t rx_head = 0, rx_tail = 0; volatile uint8_t tx_buffer[TX_BUF_SIZE]; volatile uint16_t tx_head = 0, tx_tail = 0; volatile bool tx_busy = false; // 标记发送是否在进行中 void UART0_IRQHandler(void) { volatile uint8_t iir = UART0->IIR; // 读取IIR, volatile防止编译器优化 // 循环处理所有挂起的中断 while ((iir & 0x01) == 0) { switch (iir & 0x0E) { case 0x04: // 接收数据就绪 case 0x06: // 接收超时 (处理方式与数据就绪类似,都意味着有数据可读) // 读取所有可用数据到环形缓冲区 while (UART0->LSR & 0x01) { // 检查LSR的DR位 uint8_t data = UART0->RBR; // 读RBR会清除中断/状态 uint16_t next_head = (rx_head + 1) % RX_BUF_SIZE; if (next_head != rx_tail) { // 缓冲区未满 rx_buffer[rx_head] = data; rx_head = next_head; } else { // 缓冲区溢出!可以设置一个溢出标志,或丢弃数据。 // 更好的做法是使用流控或增大缓冲区。 } } // 可以在这里设置一个信号量或标志,通知主程序有数据到达 break; case 0x02: // 发送保持寄存器空 if (tx_head != tx_tail) { // 发送缓冲区还有数据 UART0->THR = tx_buffer[tx_tail]; // 写THR会清除中断 tx_tail = (tx_tail + 1) % TX_BUF_SIZE; } else { // 发送缓冲区已空,禁用THRE中断,避免无意义的中断 UART0->IER &= ~(1 << 1); tx_busy = false; } break; case 0x03: // 接收线路状态错误 { uint8_t lsr = UART0->LSR; // 读LSR清除OE标志并获取错误类型 // 记录或处理错误(例如,通过日志输出错误类型) // 必须读取并丢弃错误数据,否则错误标志可能无法清除 if (lsr & 0x01) { // 如果错误数据还在缓冲区 volatile uint8_t dummy = UART0->RBR; } } break; default: // 可能是其他未处理的中断源,或中断已被清除 break; } iir = UART0->IIR; // 再次读取IIR,检查是否还有中断挂起 } }4.3 主程序中的发送函数
发送函数需要管理发送环形缓冲区,并在需要时启动发送中断。
bool uart_send_bytes(const uint8_t *data, uint16_t length) { // 1. 检查缓冲区剩余空间 uint16_t bytes_to_copy = 0; uint16_t space_available = 0; // 关中断保护缓冲区操作 __disable_irq(); space_available = (tx_head >= tx_tail) ? (TX_BUF_SIZE - (tx_head - tx_tail) - 1) : (tx_tail - tx_head - 1); if (length > space_available) { __enable_irq(); return false; // 空间不足 } // 2. 拷贝数据到发送环形缓冲区 for (uint16_t i = 0; i < length; i++) { tx_buffer[tx_head] = data[i]; tx_head = (tx_head + 1) % TX_BUF_SIZE; } // 3. 如果发送器空闲,则手动触发第一次发送并打开中断 if (!tx_busy) { tx_busy = true; // 直接写第一个字节到THR,启动发送过程 UART0->THR = tx_buffer[tx_tail]; tx_tail = (tx_tail + 1) % TX_BUF_SIZE; // 使能THRE中断,以便后续字节自动发送 UART0->IER |= (1 << 1); } __enable_irq(); return true; }5. 常见问题、调试技巧与避坑指南
在实际项目中,UART中断驱动开发会遇到各种问题。以下是一些典型场景和解决方案。
5.1 中断不触发或丢失
- 问题现象:数据已到达,但CPU没有进入中断服务程序。
- 排查步骤:
- 检查IER配置:确认所需的中断(如ERBI)已使能。常见错误是初始化顺序不对,在配置LCR(特别是修改DLAB)或FCR后,IER被意外覆盖。
- 检查NVIC配置:在Cortex-M等内核中,除了外设自身的中断使能,还需在嵌套向量中断控制器(NVIC)中使能对应的中断通道。
- 检查总中断开关:确认全局中断是否已开启(例如,ARM中的
CPSIE I指令或__enable_irq()函数)。 - 使用示波器或逻辑分析仪:测量UART的RX/TX引脚和中断输出引脚,确认物理信号和中断请求信号是否确实产生。
- 轮询LSR/IIR:在中断服务程序中添加调试输出,或者暂时改用轮询模式读取LSR和IIR,看状态位是否正常变化。这能帮助你区分是中断信号路径问题,还是UART本身状态问题。
5.2 中断服务程序卡死或重复进入
- 问题现象:系统进入中断后不再返回,或频繁进入中断。
- 排查步骤:
- 确认中断清除操作:这是最常见的原因。每个中断类型都有其特定的清除方式:
- THREINT:向THR写入数据。
- RDAINT:在非FIFO模式下,读取RBR;在FIFO模式下,使FIFO数据量低于触发水平(通常就是读空FIFO)。
- RTOINT:读取接收FIFO中的一个字符,或有新字符到达。
- RLSINT:必须读取LSR寄存器。对于OE错误,读LSR即可清除;对于PE/FE/BI,还需要读取并丢弃RBR中的错误数据字符。
- 检查IIR读取方式:确保在ISR中是以
while循环检查IPEND位的方式处理所有挂起中断,而不是只读一次IIR。否则低优先级中断可能被“饿死”。 - 检查硬件连接:错误的接线导致持续的帧错误或噪声,可能引发连续的RLSINT。
- 确认中断清除操作:这是最常见的原因。每个中断类型都有其特定的清除方式:
5.3 FIFO模式下数据接收不完整或延迟大
- 问题现象:设置了较高的FIFO触发水平(如14字节),但收到少量数据(如3字节)时中断迟迟不触发。
- 原因与解决:
- 原因:RDAINT只在FIFO数据量达到触发水平时产生。如果数据流中断,最后一批不足触发水平的数据会一直留在FIFO里。
- 解决方案:务必同时使能并处理RTOINT(接收超时中断)。RTOINT会在数据停止到达超过4个字符时间后触发,确保这些“残留”数据能被及时取出。这就是为什么在FIFO模式下,RDAINT和RTOINT通常需要一起使用。
5.4 发送中断(THREINT)的“空转”问题
- 问题现象:发送完所有数据后,THRE中断仍然不断产生,浪费CPU资源。
- 解决方案:在发送环形缓冲区为空时,在ISR中主动禁用THRE中断(清除IER中的ETBEI位)。当主程序有新的数据需要发送时,先填充缓冲区,然后手动写入第一个字节到THR,再重新使能THRE中断。参考上文
uart_send_bytes函数的实现。
5.5 寄存器地址映射与访问宽度陷阱
- 问题:有些32位MCU的UART寄存器可能是32位或16位对齐的,而示例代码常以8位(
uint8_t)访问。不匹配的访问可能导致错误。 - 检查清单:
- 仔细查阅芯片数据手册的内存映射表,确认UART寄存器区的起始地址和每个寄存器的偏移量。
- 确认寄存器的访问宽度(8/16/32位)。有些寄存器可能要求32位访问,用8位访问会失败。
- 使用
volatile关键字定义寄存器指针,防止编译器优化掉必要的读写操作。 - 对于共享地址的寄存器(如RBR/THR/DLL),严格遵循DLAB位的切换流程。一个良好的实践是:使用独立的、经过严格定义的地址去访问DLL和DLH(如果芯片提供),这样就可以始终保持DLAB=0,简化RBR/THR/IER的访问。
5.6 调试心得:从轮询开始
对于UART驱动开发,一个非常有效的调试策略是先实现轮询(Polling)版本,再迁移到中断版本。
- 轮询验证基础通信:先不使用中断,在主循环中不断读取LSR的DR位来接收数据,检查THRE位来发送数据。这能确保最基本的硬件连接、波特率配置、数据格式是正确的。
- 逐步引入中断:先只使能接收中断(ERBI),在简单的接收ISR中点亮一个LED或通过另一个串口打印信息,确认中断能正常触发和进入。
- 添加发送中断:再使能发送中断(ETBEI),实现简单的回环测试(收到什么就发回什么)。
- 最后处理错误中断:加入RLSINT使能和错误处理,使驱动更加健壮。 这种渐进式的方法,能将复杂问题分解,更容易定位故障点。