LPC213x UART0驱动开发:从波特率计算、自动波特到中断FIFO的实战指南

📅 2026/6/21 3:37:41
LPC213x UART0驱动开发:从波特率计算、自动波特到中断FIFO的实战指南
1. LPC213x UART0从手册到实战的深度解析搞嵌入式开发UART通用异步收发传输器绝对是绕不开的基础外设。它简单、可靠是芯片与外界对话最直接的“嘴巴”和“耳朵”。但简单并不意味着可以轻视一个稳定高效的UART驱动往往是整个系统通信稳定的基石。最近在调试一个基于NXP LPC213x系列的老项目再次深入啃了一遍它的UART0模块手册。这个系列的UART0尤其是/01版本功能相当强悍远不止基本的收发那么简单。它内置的16级FIFO、分数波特率生成器以及自动波特Auto-baud功能在当年乃至现在的一些应用中都非常实用。但官方手册UM10120更像是一本字典寄存器位定义很全但如何把它们有机组合起来实现一个健壮的驱动中间有很多坑需要自己踩。今天我就结合手册和实际调试经验把LPC213x UART0的配置、波特率计算尤其是分数分频的玄机、自动波特功能的实现以及那些手册里没明说但至关重要的注意事项系统地梳理一遍。无论你是正在使用这款芯片还是想深入了解UART的增强功能设计这篇文章都能给你提供可直接“抄作业”的代码和避坑指南。2. UART0核心架构与寄存器地图精读开始配置前我们必须像认识新朋友一样先了解UART0的“家庭构成”和“联系方式”寄存器地址。LPC213x的UART0是一个完全独立的串行通信接口与ARM7内核通过APB总线连接。它的增强之处在于并非一个简单的移位寄存器而是一个集成了智能缓冲、灵活时钟管理和自动检测功能的小型协处理器。2.1 核心功能模块拆解根据手册我们可以把UART0的核心划分为几个逻辑模块收发缓冲与FIFO这是数据吞吐的枢纽。包含一个16字节的发送FIFOTX FIFO和一个16字节的接收FIFORX FIFO。FIFO的引入极大地减轻了CPU的中断负担允许一次处理多个字节而不是每个字节都产生中断。分数波特率生成器这是UART0的“心脏”也是其精度和灵活性的来源。它由标准的16位分频器U0DLL/U0DLM和一个分数预分频器U0FDR共同构成能够产生非整数的分频系数从而用固定的外部晶振得到精确的标准波特率。自动波特Auto-baud逻辑这是一个独立的硬件状态机用于自动检测输入数据流的波特率。它通过测量RXD0引脚上特定字符如‘A’或‘a’即“AT”命令开头的位时间宽度自动计算出正确的分频值并写入DLL/DLM寄存器。中断系统一个多层次、可屏蔽的中断控制器用于高效处理接收数据可用、发送保持寄存器空、接收线路错误如帧错误、奇偶校验错误以及自动波特完成等事件。线路控制与状态负责定义数据格式数据位、停止位、奇偶校验并实时反馈收发状态和错误信息。2.2 寄存器地图与DLAB密钥所有对UART0的控制和状态读取都通过访问其内存映射的寄存器完成。地址从0xE000C000开始。这里有一个关键概念DLABDivisor Latch Access Bit位于U0LCR寄存器的第7位。它是访问多路复用寄存器的“开关”。注意这是新手最容易混淆和出错的地方。很多驱动代码初始化顺序不对就是因为没处理好DLAB位的切换时机。当DLAB 1时地址0xE000C000和0xE000C004分别映射到分频锁存器低字节U0DLL和高字节U0DLM。此时你不能通过这两个地址去读写数据。当DLAB 0时地址0xE000C000映射到接收缓冲寄存器U0RBR只读或发送保持寄存器U0THR只写而0xE000C004映射到中断使能寄存器U0IER。标准初始化流程中DLAB位的操作顺序必须是设置U0LCR的DLAB1以允许配置波特率分频器。写入U0DLL和U0DLM设置目标波特率。配置其他线路参数数据位、停止位等并将DLAB位清零恢复正常的数据收发寄存器映射。后续的应用程序只会在DLAB0的情况下访问U0RBR和U0THR来收发数据。搞清楚了这一点我们再看寄存器地图就清晰了。下表是UART0所有寄存器的快速索引建议在编程时放在手边参考寄存器名称描述访问复位值地址 (Hex)DLAB 状态U0RBR接收缓冲寄存器 (只读)RO未定义0xE000C0000U0THR发送保持寄存器 (只写)WO未定义0xE000C0000U0DLL分频锁存器 (低字节)R/W0x010xE000C0001U0DLM分频锁存器 (高字节)R/W0x000xE000C0041U0IER中断使能寄存器R/W0x000xE000C0040U0IIR中断标识寄存器 (只读)RO0x010xE000C008-U0FCRFIFO控制寄存器 (只写)WO0x000xE000C008-U0LCR线路控制寄存器R/W0x000xE000C00C-U0LSR线路状态寄存器 (只读)RO0x600xE000C014-U0SCR便签寄存器R/W0x000xE000C01C-U0ACR自动波特控制寄存器R/W0x000xE000C020-U0FDR分数分频寄存器R/W0x100xE000C028-U0TER发送使能寄存器R/W0x800xE000C030-3. 波特率计算从整数分频到分数分频的跃迁波特率配置是UART初始化的核心。LPC213x的UART0波特率生成公式是理解其灵活性的关键。3.1 基础公式与参数解析手册给出的最终公式为UART0baudrate PCLK / [16 * (256 * U0DL 1) * (1 DIVADDVAL/MULVAL)]看起来有点复杂我们把它拆解一下PCLK外设时钟频率即UART0模块的工作时钟。通常由系统主频分频得到需要在系统初始化时确认。U0DL一个16位的整数分频值由U0DLM高8位和U0DLL低8位组合而成即U0DL (U0DLM 8) | U0DLL。其有效范围为1-655350被当作1处理。DIVADDVAL和MULVAL分数分频器的分子和分母参数位于U0FDR寄存器的低8位。其中DIVADDVAL在bits[3:0]MULVAL在bits[7:4]。它们必须满足0 MULVAL ≤ 15且0 ≤ DIVADDVAL ≤ 15且DIVADDVAL MULVAL这是一个隐含的重要条件否则分频系数小于1无意义。这个公式可以理解为两个分频器的级联首先是一个分数预分频器分频系数为MULVAL / (MULVAL DIVADDVAL)然后是一个标准的16倍波特率整数分频器分频值为(256 * U0DL 1)。3.2 为何需要分数分频器假设你的系统PCLK是12.288MHz这是一个很常见的音频时钟频率。如果你想得到精确的115200波特率我们先用传统的整数分频公式算一下所需分频值 N PCLK / (16 * 目标波特率) 12288000 / (16 * 115200) 6.666...这显然不是一个整数。最接近的整数是7代入计算实际波特率为12288000 / (16 * 7) ≈ 109714 bps误差高达(115200-109714)/115200 ≈ 4.76%。在高速通信中这个误差很可能导致数据帧错误。这时分数分频器就派上用场了。它的作用就是产生一个非常接近1的小数分频系数对PCLK进行“微调”使得最终的N计算出来是一个整数或非常接近整数的值。例如通过设置MULVAL5, DIVADDVAL2分数预分频系数为5/(52) 5/7 ≈ 0.714286。那么调整后的等效PCLK PCLK * (5/7) ≈ 8.777143 MHz。再用这个频率去计算整数分频N 8777143 / (16 * 115200) ≈ 4.7619依然不是整数。但别急我们还有U0DL。我们需要将分数分频和整数分频联合计算通过迭代或计算工具找到一组(U0DL, MULVAL, DIVADDVAL)使得最终计算出的波特率与目标值的误差最小。3.3 实战计算与配置步骤手动计算最优组合非常繁琐。在实际开发中我们通常编写一个函数来自动计算。这里给出一个用C语言实现的简化算法思路和关键代码片段#include stdint.h #include math.h #define PCLK 12000000UL // 假设PCLK为12MHz #define TARGET_BAUD 115200UL typedef struct { uint16_t dlm; uint16_t dll; uint8_t mulval; uint8_t divaddval; float error; // 误差百分比 } baud_calc_t; baud_calc_t calculate_uart0_baud(uint32_t pclk, uint32_t target_baud) { baud_calc_t best {0}; best.error 100.0; // 初始化为一个很大的误差 // 遍历所有可能的MULVAL和DIVADDVAL组合 for (uint8_t m 1; m 15; m) { for (uint8_t d 0; d m; d) { // 注意 d m // 计算分数预分频后的等效频率 // 为避免浮点数溢出先进行64位整数运算 uint64_t temp_freq (uint64_t)pclk * m; uint32_t equiv_freq temp_freq / (m d); // 计算所需的U0DL值 (公式变换: U0DL (PCLK / (16 * Baud * (1 d/m)) - 1) / 256) // 但更直接的方法是计算所需的总分频值 // 理想总分频值 PCLK / (16 * target_baud) float ideal_total_div (float)pclk / (16.0f * target_baud); // 分数分频器贡献的系数 m / (m d) float frac_factor (float)m / (m d); // 因此所需的整数分频部分 ideal_total_div / frac_factor float required_int_div ideal_total_div / frac_factor; // U0DL (required_int_div - 1) / 256 uint32_t u0dl (uint32_t)((required_int_div - 1.0f) / 256.0f 0.5f); // 四舍五入 // 限制U0DL范围 if (u0dl 0) u0dl 1; if (u0dl 0xFFFF) u0dl 0xFFFF; // 根据选择的u0dl反算实际波特率 uint32_t actual_baud (uint32_t)((float)pclk / (16.0f * (256.0f * u0dl 1.0f) * (1.0f (float)d / m))); // 计算误差 float error fabs((float)((int32_t)target_baud - (int32_t)actual_baud)) / target_baud * 100.0f; // 寻找误差最小的组合 if (error best.error) { best.dlm (u0dl 8) 0xFF; best.dll u0dl 0xFF; best.mulval m; best.divaddval d; best.error error; } } } return best; } void uart0_init_baud(uint32_t pclk, uint32_t baud) { baud_calc_t cfg calculate_uart0_baud(pclk, baud); // 1. 设置DLAB1准备配置分频器 U0LCR | (1 7); // DLAB 1 // 2. 写入分频值 U0DLL cfg.dll; U0DLM cfg.dlm; // 3. 如果使用分数分频DIVADDVAL 0配置U0FDR if (cfg.divaddval 0) { // 重要手册强调如果分数分频激活且DLM0则DLL必须大于等于3 if (cfg.dlm 0 cfg.dll 3) { cfg.dll 3; U0DLL 3; // 重新写入 } U0FDR (cfg.mulval 4) | cfg.divaddval; } else { U0FDR 0x10; // 复位值MULVAL1, DIVADDVAL0即分数分频禁用 } // 4. 配置线路参数8N1并清除DLAB U0LCR 0x03; // 8位数据1位停止位无奇偶校验DLAB0 // 5. 使能FIFO建议始终开启 U0FCR 0x01; // FIFO使能触发点为1字节 // 可选根据需要使能中断 // U0IER 0x01; // 使能接收数据可用中断 }实操心得在实际项目中我通常不会在运行时动态计算波特率参数而是在编译前根据已知的PCLK和目标波特率通过脚本或计算器算出最佳参数作为常量写入代码。这样节省了MCU宝贵的启动时间和代码空间。例如对于PCLK12MHz目标115200一组常用的较优参数是U0DLM0, U0DLL4, MULVAL7, DIVADDVAL12。代入公式计算误差极小可以满足高速通信要求。4. 自动波特Auto-baud功能详解与实战自动波特是LPC213x UART0一个非常酷的功能尤其适用于需要与未知波特率设备通信的场合比如烧录器、调试工具或者自适应通信模块。4.1 自动波特的工作原理其核心思想是硬件自动测量RXD0引脚上一个已知字符的位时间。它支持两种模式通过U0ACR[1]的Mode位选择模式0测量起始位的下降沿到第一个数据位LSB的下降沿之间的时间。这要求发送方发送的字符LSB位为0。ASCII字符‘A’(0x41)和‘a’(0x61)的LSB都是1所以模式0实际上测量的是起始位下降沿到第二个数据位下降沿的时间因为0x41的二进制是0100 0001bit1是0。模式1测量起始位的下降沿到其上升沿即起始位本身的宽度的时间。这种方法更直接但要求起始位干净、无毛刺。自动波特过程由U0ACR寄存器的Start位bit 0触发。一旦置位硬件会复位内部计数器并等待RXD0引脚上的下降沿起始位开始。随后计数器开始对可能经过分数预分频的PCLK进行计数。在模式0下计数器在下一个下降沿LSB位或第二个数据位的下降沿停止在模式1下计数器在起始位的上升沿停止。最终计数器的值会被硬件自动换算并更新到U0DLM和U0DLL寄存器中。4.2 自动波特的配置与使用流程使用自动波特功能必须遵循严格的步骤否则极易失败。步骤一前期准备确保PCLK时钟稳定。配置U0LCR设置与预期字符格式一致的数据位、停止位、奇偶校验。通常我们使用8位数据、1位停止位、无校验0x03因为“AT”命令通常是这种格式。强烈建议将分数分频器禁用即设置U0FDR 0x10DIVADDVAL0, MULVAL1。手册提到使能分数分频时也可用但会引入额外变量增加不确定性。关键在启动自动波特之前先给U0DLM和U0DLL写入一个初始值。这个值不要求精确但必须是一个有效的非零值例如0x0001以确保波特率发生器有一个初始状态。如果它们是0可能导致无法检测。步骤二启动与等待配置U0ACR寄存器。选择模式通常模式0更稳定决定是否使能超时自动重启AutoRestart。如果需要中断则配置U0IER中的ABEOIntEn和ABTOIntEn位。置位U0ACR的Start位启动自动波特过程。此时需要外部设备在RXD0引脚上发送一个特定的引导字符。最经典的就是大写字母‘A’ (0x41) 或小写字母‘a’ (0x61)。因为它们的二进制位序列中包含了从起始位到第一个0位对于模式0或完整的起始位对于模式1的清晰时间信息。等待自动波特完成。可以通过两种方式查询法循环读取U0ACR的Start位该位会在自动波特完成后由硬件自动清零。中断法使能ABEO中断在中断服务程序中处理。步骤三结果验证与后续配置自动波特完成后U0DLM和U0DLL寄存器中已经包含了计算出的分频值。重要读取U0DLM和U0DLL的值并验证其合理性。例如对于12MHz的PCLK和115200波特率计算出的U0DL值应该在4左右。如果读出的值是0或极大说明自动波特可能失败例如未收到信号、信号质量差、模式选择错误。如果使能了分数分频器此时可以根据计算出的U0DL值再结合目标波特率反推出是否需要以及如何配置U0FDR来进一步精确化波特率。但通常自动波特后得到的整数分频值已经足够准确。清除可能产生的自动波特中断标志通过写U0ACR中的ABEOIntClr或ABTOIntClr位。此时UART0的波特率已经与对方设备匹配。你可以正常配置U0IER、U0FCR等寄存器开始数据通信。/** * brief 使用自动波特功能检测并设置UART0波特率 (模式0) * param pclk 外设时钟频率用于验证结果 * return 检测到的波特率如果失败返回0 */ uint32_t uart0_autobaud_detect(uint32_t pclk) { // 1. 前期准备 U0LCR 0x83; // 8N1, 并设置DLAB1为手动写入初始DLL/DLM做准备可选但建议 U0DLL 0x01; // 写入一个安全的初始分频值 U0DLM 0x00; U0FDR 0x10; // 禁用分数分频MULVAL1, DIVADDVAL0 U0LCR 0x03; // 保持8N1但DLAB0恢复正常寄存器映射 U0FCR 0x01; // 使能FIFO U0IER 0x00; // 暂时禁用所有中断使用查询方式 // 2. 配置并启动自动波特 U0ACR 0x00; // 先清零确保Start0, Mode0, AutoRestart0 U0ACR | (1 0); // 设置Start位启动自动波特 (模式0) // 3. 等待完成或超时 uint32_t timeout 1000000; // 根据你的PCLK调整超时计数 while ((U0ACR 0x01) ! 0) { // 等待Start位被硬件清零 timeout--; if (timeout 0) { // 超时自动波特失败可能没收到字符 U0ACR 0x00; // 强制停止 return 0; } } // 4. 检查是否发生超时中断如果使能了中断这里可以检查U0IIR // 查询方式下我们主要验证结果寄存器 // 5. 读取计算出的分频值 U0LCR | (1 7); // DLAB1读取DLL/DLM uint16_t dl_value ((uint16_t)U0DLM 8) | U0DLL; U0LCR ~(1 7); // DLAB0 if (dl_value 0 || dl_value 0x0001) { // 分频值无效或仍是初始值自动波特可能未成功 return 0; } // 6. 根据公式计算检测到的波特率 // 简化计算Baud PCLK / (16 * dl_value) 因为分数分频已禁用 // 注意实际dl_value是公式中的 (256*U0DL 1)但自动波特设置的是U0DLM/DLL // 根据手册自动波特设置的是U0DLM/DLL因此直接代入公式 // Baud PCLK / (16 * (256 * U0DL 1))其中U0DL (U0DLM8 | U0DLL) uint32_t detected_baud pclk / (16UL * (256UL * dl_value 1)); // 7. 可选根据检测到的波特率精细调整分数分频器以获得更精确的标准波特率 // ... 这里可以调用之前的calculate_uart0_baud函数 ... return detected_baud; }避坑指南自动波特失败最常见的原因有三个。第一在启动自动波特前RXD0引脚上已经有噪声或非预期的电平跳变导致硬件提前开始了错误的测量。解决方法是在启动前确保线路空闲高电平一段时间。第二发送的引导字符不是‘A’或‘a’。务必确认对方发送的是0x41或0x61。第三U0DLM/DLL的初始值异常。务必在启动前写入一个非零的合理值。我曾遇到过因为初始值为0导致硬件无法正常工作的案例。5. 中断与FIFO的协同工作构建高效驱动UART通信中轮询方式效率低下尤其是在高波特率或主频较低的MCU上。利用中断和FIFO是提升效率的关键。5.1 中断系统深度解析UART0的中断源通过U0IER使能通过U0IIR来识别。U0IIR是一个只读寄存器其低4位编码了当前最高优先级的中断类型。中断优先级与处理逻辑最高优先级 - 接收线路状态中断 (RLS)当发生溢出错误(OE)、奇偶错误(PE)、帧错误(FE)或线路中断(BI)时触发。清除方法读取U0LSR寄存器。第二优先级 - 接收数据可用中断 (RDA)当接收FIFO中的数据量达到U0FCR[7:6]所设置的触发点时触发。例如设置为014字节则当FIFO中数据4字节时产生中断。清除方法读取U0RBR直到FIFO数据量低于触发点。第二优先级 - 字符超时中断 (CTI)当FIFO中至少有1个字符但在3.5到4.5个字符时间内没有新字符输入也没有被读取时触发。这是处理非整块数据、防止数据“卡”在FIFO中的关键。例如对方发送了13字节数据触发点是8字节。你会先收到一个RDA中断8字节然后如果不再读取约3.5个字符时间后会收到CTI中断提醒你还有5字节没取走。清除方法读取U0RBR。第三优先级 - 发送保持寄存器空中断 (THRE)当发送FIFO完全为空时触发。清除方法写入数据到U0THR或读取U0IIR如果该中断是当前最高优先级。中断服务程序ISR的标准写法void __irq UART0_IRQHandler(void) { uint32_t iir_value; // 必须循环处理因为一次可能产生多个中断 while (((iir_value U0IIR) 0x01) 0) { switch (iir_value 0x0E) { // 取中断标识位 case 0x06: // 0110: 接收线路错误 (最高优先级) handle_line_error(); // 通过读取U0LSR清除中断 volatile uint32_t lsr U0LSR; // 读取即可清除 break; case 0x04: // 0100: 接收数据可用 (RDA) handle_rx_data_available(); // 中断会在FIFO数据低于触发点后自动清除 break; case 0x0C: // 1100: 字符超时 (CTI) handle_char_timeout(); // 通过读取U0RBR清除中断 while (U0LSR 0x01) { // 当DR1时表示有数据 rx_buffer[rx_index] U0RBR; } break; case 0x02: // 0010: 发送保持寄存器空 (THRE) handle_tx_thre(); // 通过写入U0THR或读取U0IIR清除 if (tx_fifo_has_data()) { U0THR get_tx_data(); } else { // 如果发送完成可以禁用THRE中断以避免空中断 // U0IER ~(1 1); } break; default: // 可能是保留值或自动波特中断如果支持 if (iir_value (1 8)) { // ABEO中断 handle_autobaud_end(); U0ACR | (1 8); // 写1清除ABEO中断标志 } if (iir_value (1 9)) { // ABTO中断 handle_autobaud_timeout(); U0ACR | (1 9); // 写1清除ABTO中断标志 } break; } } // 中断向量寄存器清零取决于具体ARM7芯片如VICVectAddr VICVectAddr 0x00; }5.2 FIFO配置与流控考虑U0FCR寄存器控制着FIFO。上电后必须将U0FCR[0]设置为1来使能FIFO否则UART0可能无法正常工作。触发点选择 (U0FCR[7:6])这需要权衡。设置较高的触发点如14字节可以减少中断频率提升批量传输效率但会增加数据接收的延迟。设置较低的触发点如1字节延迟最低但中断频繁适合对实时性要求极高、数据量小的场景。对于大多数应用4字节或8字节是一个平衡的选择。FIFO复位 (U0FCR[2:1])这两个位是“自清零”的。写1可以分别清空发送和接收FIFO。在初始化或通信异常复位后建议执行一次FIFO清零操作。硬件流控虽然手册提到UART0支持硬件流控机制但LPC213x的UART0本身没有专用的RTS/CTS引脚。需要利用GPIO和软件模拟或者使用额外的UART模块如果支持。在高速或大数据量传输时如果没有流控强烈建议使用FIFO并设置合理的触发点同时确保你的接收程序能及时取走数据防止FIFO溢出。6. 常见问题排查与调试技巧实录即使理解了所有寄存器实际调试中还是会遇到各种问题。下面是我在多年项目中总结的一些典型问题及其解决方法。6.1 通信完全无反应或乱码检查时钟源确认PCLK频率是否与你计算波特率时使用的频率一致。使用示波器测量PCLK或系统主时钟。确认波特率参数使用前面提供的计算函数或工具反复核对U0DLM、U0DLL、U0FDR的值。一个常见的错误是忽略了U0DL (U0DLM8 | U0DLL)这个组合而错误地只设置了U0DLL。验证DLAB位操作顺序这是最经典的错误。确保在初始化时先设DLAB1写分频器再设DLAB0进行正常通信。可以在写完后读取U0LCR确认。检查引脚复用LPC213x的许多引脚是复用的。确保你使用的RXD0和TXD0引脚已经正确配置为UART功能而不是GPIO或其他外设功能。参考芯片的引脚连接模块PINSEL寄存器。电平匹配确认通信双方的电平标准一致通常是TTL 3.3V。如果不一致需要电平转换电路。6.2 能发送但不能接收或反之检查中断配置如果使用中断确认U0IER中相应的接收或发送中断已使能例如接收使能位是U0IER[0]。同时确认ARM7核心的中断控制器VIC已正确配置UART0中断向量和使能位已设置。检查FIFO使能确认U0FCR[0]已设置为1。未使能FIFO是导致许多奇怪问题的根源。线路状态检查在中断服务程序或主循环中读取U0LSR寄存器。关注OE溢出错误、FE帧错误、PE奇偶错误位。如果这些位被置位说明物理层通信有问题可能是波特率不匹配、线路干扰或硬件连接问题。发送部分特定问题如果发送第一个字符后卡住检查THRE中断处理。确保在THRE中断中如果发送缓冲区已空要么填充新数据要么禁用THRE中断U0IER ~(11)否则会持续进入空发送中断。6.3 自动波特功能失败引导字符确保对方发送的是单个字符‘A’(0x41)或‘a’(0x61)并且是在你启动自动波特置位Start位之后发送的。字符格式数据位、停止位、校验位必须与U0LCR中的设置完全一致。信号质量用示波器观察RXD0引脚上的波形。起始位是否干净下降沿是否陡峭是否有明显的毛刺或振铃较差的信号质量会导致测量计数器值不准。超时与重启如果通信环境嘈杂可以尝试使能U0ACR的AutoRestart位。这样在测量超时计数器溢出后硬件会自动等待下一个下降沿重新开始测量而无需软件干预。结果验证自动波特完成后一定要读取U0DLM/DLL的值并手动计算一下得到的波特率是否在一个合理的范围内例如对于12MHz PCLK115200波特率对应的U0DL值大约在4左右。如果读出的值是0或65535基本可以断定失败。6.4 中断无法进入或频繁进入中断向量与优先级确认在VIC中正确分配了UART0的中断通道并设置了正确的优先级。确保在启动UART0前已经配置好VIC并打开了全局中断通过CPSR的I位。中断标志清除这是导致中断“锁死”或“只进一次”的常见原因。务必根据中断类型按照手册要求清除中断源RLS中断读U0LSR。RDA中断读U0RBR直到FIFO低于触发点。CTI中断读U0RBR。THRE中断写U0THR或读U0IIR当它是最高优先级中断时。自动波特中断写U0ACR的对应清除位。中断嵌套与处理时间确保你的中断服务程序执行时间足够短。如果在一个低优先级中断中停留时间过长可能导致高优先级中断无法及时响应甚至丢失数据。对于UART接收尤其是在高波特率下应在中断中快速将数据从U0RBR复制到软件缓冲区然后尽快退出中断。最后分享一个调试小技巧充分利用U0SCR便签寄存器和U0TER发送使能寄存器。U0SCR可以作为一个临时变量用于在中断和主程序之间传递简单的状态标志。U0TER的TXEN位bit7在上电后默认为1使能发送千万不要去动它。我曾见过有代码误操作此寄存器导致无法发送数据排查了很久。