LPC2101 UART1自动流控制:寄存器级配置与实战避坑指南
1. 项目概述与核心价值
在嵌入式开发,尤其是基于ARM7这类经典内核的项目里,串口通信几乎是工程师的“必修课”。它不仅是调试信息的输出窗口,更是设备与外界通信最直接、最可靠的桥梁。然而,很多开发者对串口的理解往往停留在“配置波特率、发送接收数据”的层面,一旦遇到数据量大、速率高的场景,就容易出现数据丢失、系统卡顿等问题。其根源,常常在于对串口内部机制,特别是流控制(Flow Control)的理解不够深入。
NXP(原飞利浦半导体)的LPC2101/02/03系列微控制器,作为一代经典的ARM7TDMI-S内核产品,其内置的UART1模块功能相当完善,尤其是支持基于FIFO的硬件自动流控制。这不仅仅是芯片手册里的一个功能描述,更是提升通信稳定性和解放CPU资源的“利器”。本文将彻底拆解LPC2101/02/03的UART1寄存器,并聚焦于自动流控制(Auto-RTS/Auto-CTS)这一核心高级功能。我会结合自己多年在工业控制和通信设备开发中的实际踩坑经验,告诉你每个寄存器位背后的设计逻辑、配置时的“潜规则”,以及如何通过自动流控制构建一个高效、可靠的串口通信链路。无论你是正在使用这款老将芯片,还是希望深入理解UART流控制的本质,这篇文章都将提供寄存器级别的实战指南。
2. UART1核心寄存器深度解析
要驾驭UART1,尤其是其自动流控制功能,必须首先透彻理解几个关键控制寄存器。手册上的表格是冰冷的,我们需要的是有温度、有场景的解读。
2.1 UART1 FIFO控制寄存器 (U1FCR - 0xE0010008)
这个寄存器是UART1高效工作的“总开关”。很多初学者配置串口后无法收发数据,第一步就应该检查它。
| 位 | 符号 | 值 | 描述 | 复位值 | 实战解读与注意事项 |
|---|---|---|---|---|---|
| 0 | FIFO使能 | 0 | UART1 FIFO被禁用。禁止在应用中使用此设置。 | 0 | 【致命陷阱】这是最容易出错的地方!复位后FIFO是关闭的。如果你不将此位置1,UART将工作在无FIFO缓冲的模式下,此时对U1RBR/U1THR的每一次读写都直接操作移位寄存器,极易因CPU响应不及时导致数据覆盖或丢失。任何情况下,启用UART1后第一件事就是置位此位。 |
| 1 | 高电平有效,同时使能UART1接收和发送FIFO,并允许访问U1FCR[7:1]。此位必须设置为1以保证UART1正常工作。此位的任何变化都会自动清空UART1 FIFO。 | 【核心操作】写入1。注意“任何变化”这四个字,意味着你从0写1会清空FIFO,从1写0再写回1也会清空。在通信中途不要随意改动此位。 | |||
| 1 | RX FIFO复位 | 0 | 对UART1 FIFO无影响。 | 0 | 通常用于软件流控制或异常恢复。当你想丢弃接收FIFO中所有未读的、可能错误的数据时,向此位写1。 |
| 1 | 向U1FCR[1]写入逻辑1将清除UART1接收FIFO中的所有字节并复位指针逻辑。此位是自清除的。 | 【重要特性】“自清除”意味着你只需要写1,硬件会自动将其恢复为0,无需软件写0。读取此位将总是返回0。 | |||
| 2 | TX FIFO复位 | 0 | 对UART1 FIFO无影响。 | 0 | 用法同RX FIFO复位。当发送流程出现异常,需要清空发送队列重新开始时使用。 |
| 1 | 向U1FCR[2]写入逻辑1将清除UART1发送FIFO中的所有字节并复位指针逻辑。此位是自清除的。 | 同样具有自清除特性。 | |||
| 7:6 | RX触发等级 | 00 | 这两个位决定了在激活中断前,必须有多少个字符被写入接收FIFO。 | 0 | 【性能关键】这是配置接收中断策略的核心,直接关联到后文讲的自动RTS。 |
| 01 | 触发等级 0 (1 个字符或 0x01)。 | 等级0 (1字符):每收到1个字节就产生接收中断。优点:响应延迟极低。缺点:中断频率极高,在高速或大数据量下会严重消耗CPU资源,通常不推荐。 | |||
| 10 | 触发等级 1 (4 个字符或 0x04)。 | 等级1 (4字符):平衡性较好的选择。适合大多数中等速率通信,能在及时响应和中断负载间取得平衡。 | |||
| 11 | 触发等级 2 (8 个字符或 0x08)。 | 等级2 (8字符)和等级3 (14字符):适用于高波特率、大数据块传输的场景。目的是积累足够多的数据再通知CPU处理,大幅降低中断频率。但副作用是增加了接收端的数据处理延迟。这个延迟需要根据你的通信协议和系统实时性要求来权衡。 |
注意:U1FCR[5:3]是保留位。一个良好的编程习惯是:永远不要向芯片手册中标记为“Reserved”的位写入1。虽然LPC2101这些位可能未连接,但写入1在某些芯片上可能导致不可预知的行为。读取它们是无定义的,意味着你读到的可能是0,可能是1,也可能是随机值,不要依赖其值做任何逻辑判断。
2.2 UART1线路控制寄存器 (U1LCR - 0xE001000C)
这个寄存器定义了通信的“语言规则”,双方必须一致才能听懂彼此。
| 位 | 符号 | 值 | 描述 | 复位值 | 实战解读与注意事项 |
|---|---|---|---|---|---|
| 1:0 | 字长选择 | 00 | 5位字符长度 | 0 | 选择数据位长度。99%的现代通信都使用8位(值11),对应一个字节。7位(值10)常用于旧的ASCII设备,6位和5位现在极少使用。 |
| 01 | 6位字符长度 | ||||
| 10 | 7位字符长度 | ||||
| 11 | 8位字符长度 | ||||
| 2 | 停止位选择 | 0 | 1个停止位。 | 0 | 【标准配置】通常选择1个停止位(值0)。只有在某些非常古老的或特定的设备要求下,才使用2个停止位(值1)。注意描述中的特例:如果字长选择是5位(00),那么设置2个停止位实际会产生1.5个停止位周期,这是历史遗留的电传打字机标准,现在基本遇不到。 |
| 1 | 2个停止位(如果U1LCR[1:0]=00,则为1.5个)。 | ||||
| 3 | 奇偶校验使能 | 0 | 禁用奇偶校验生成和检查。 | 0 | 根据通信协议要求选择。如果通信双方不校验,则禁用(0)以节省带宽。如果需要校验,则使能(1)。 |
| 1 | 使能奇偶校验生成和检查。 | ||||
| 5:4 | 奇偶校验选择 | 00 | 奇校验。发送字符和附加校验位中‘1’的总数为奇数。 | 0 | 奇校验 (00)和偶校验 (01)是常用的两种校验方式,用于检测单个位错误。强制“1” (10)和强制“0” (11)校验位(Stick Parity)则较少使用,通常用于与某些要求校验位固定状态的旧设备兼容。 |
| 01 | 偶校验。发送字符和附加校验位中‘1’的总数为偶数。 | ||||
| 10 | 强制“1”校验位。 | ||||
| 11 | 强制“0”校验位。 | ||||
| 6 | 间隔控制 | 0 | 禁用间隔传输。 | 0 | 【高级功能】置1后,TXD引脚将被强制拉低(逻辑0),持续至少一帧的时间,用于向对方发送一个“间隔(Break)”信号。这是一个特殊的通信信号,常用于某些工业协议(如Modbus)中表示一帧的开始或结束,或用于唤醒处于休眠状态的设备。发送完成后务必记得清零此位,否则TXD会一直为低,导致正常通信无法进行。 |
| 1 | 使能间隔传输。当U1LCR[6]为高电平时,输出引脚UART1 TXD被强制为逻辑0。 | ||||
| 7 | 除数锁存访问位 (DLAB) | 0 | 禁止访问除数锁存器。 | 0 | 【关键开关】这是访问波特率发生器除数锁存器(U1DLL和U1DLM)的“钥匙”。要设置波特率时,必须先将此位置1,然后才能正确写入U1DLL/U1DLM。设置完成后,必须将此位清零,才能正常访问数据寄存器(U1RBR/U1THR)和其他寄存器。一个常见的错误是设置了波特率后忘记清零DLAB,导致后续发送接收数据全部错乱。 |
2.3 UART1调制解调器控制寄存器 (U1MCR - 0xE0010010)
这是实现自动流控制的“司令部”。在嵌入式系统中,我们通常不连接真实的调制解调器(Modem),而是利用其RTS和CTS引脚实现两个微控制器之间的硬件流控制。
| 位 | 符号 | 值 | 描述 | 复位值 | 实战解读与注意事项 |
|---|---|---|---|---|---|
| 0 | DTR控制 | - | 调制解调器输出引脚DTR的信号源。当调制解调器环回模式激活时,此位读为0。 | 0 | 在典型的嵌入式点对点流控制中,DTR(数据终端就绪)和DSR(数据设备就绪)这对握手信号较少使用。我们更关注RTS和CTS。 |
| 1 | RTS控制 | - | 调制解调器输出引脚RTS的信号源。当调制解调器环回模式激活时,此位读为0。 | 0 | 手动流控制模式:当自动RTS禁用时(U1MCR[6]=0),软件通过读写此位可以直接控制RTS引脚的电平输出。例如,接收方缓冲区快满时,软件将此位置0(拉高RTS)通知对方暂停发送。 |
| 4 | 环回模式选择 | 0 | 禁用调制解调器环回模式。 | 0 | 【强大的自测试工具】将此位置1后,UART进入内部环回状态:TXD输出被固定为高电平(Marking State),发送器的数据直接内部连接到接收器。同时,四个Modem输入引脚(CTS, DSR, RI, DCD)被断开,其内部状态由U1MCR的低四位驱动。这允许你在不连接外部线路的情况下,测试UART的收发功能和中断逻辑是否正常。例如,你可以通过写U1MCR[3:0]来模拟CTS等信号的变化,并检查U1MSR寄存器和中断是否正确响应。调试阶段非常有用。 |
| 1 | 使能调制解调器环回模式。 | ||||
| 6 | RTS使能 | 0 | 自动-RTS流控制位。禁用自动-RTS流控制。 | 0 | 自动流控制的核心配置位之一。 |
| 1 | 使能自动-RTS流控制。 | 【工作原理】当此位置1,RTS引脚的控制权将从软件(U1MCR[1])移交给硬件。硬件会根据接收FIFO的填充水平和U1FCR[7:6]设置的触发等级,自动控制RTS引脚的电平,从而反向控制对方设备的发送。 | |||
| 7 | CTS使能 | 0 | 自动-CTS流控制位。禁用自动-CTS流控制。 | 0 | 自动流控制的核心配置位之一。 |
| 1 | 使能自动-CTS流控制。 | 【工作原理】当此位置1,UART的发送器硬件在发送每一个字节前,都会检查CTS输入引脚的电平。只有CTS为低电平(有效)时,发送才会进行;如果CTS为高电平,发送器会暂停,直到CTS再次变低。这允许接收方通过拉高CTS来暂停发送方的数据流。 |
注意:U1MCR[3:2]和[5:3]是保留位,同样遵循“不写1”的原则。
3. 自动流控制 (Auto-Flow Control) 实战精讲
理解了寄存器,我们进入最核心的实战部分:自动流控制。它分为自动RTS(接收方控制)和自动CTS(发送方响应),通常配合使用以实现全双工硬件流控制。
3.1 自动RTS (Auto-RTS) 工作机制与配置
自动RTS的本质是:接收方根据自己缓冲区(接收FIFO)的“忙闲”程度,自动通过RTS引脚告诉发送方“暂停”或“继续”。
1. 硬件连接:发送方的TXD引脚连接接收方的RXD引脚。 发送方的RTS引脚连接接收方的CTS引脚。 接收方的RTS引脚连接发送方的CTS引脚。 这样就形成了一个交叉的硬件流控制环。
2. 工作流程详解:假设我们设置接收方的U1FCR[7:6] =10(触发等级2,即8字节触发)。
- 初始状态:接收方FIFO为空,自动RTS使能。硬件会自动将RTS引脚置为有效(低电平),相当于对发送方说:“我准备好了,你可以发数据了。”
- 数据接收中:发送方开始发送数据,接收方FIFO逐渐填充。
- 触发暂停:当接收方FIFO中的数据量达到或超过设定的触发等级(8字节)时,硬件会自动将RTS引脚置为无效(高电平)。这个信号通过连线传递到发送方的CTS引脚。
- 发送方响应:如果发送方也使能了自动CTS(见3.2节),它在发送完当前字节后,检测到CTS变为高电平(无效),就会暂停后续发送,将数据暂存在自己的发送FIFO中。
- 恢复接收:接收方CPU开始从FIFO中读取数据。当FIFO中的数据量被读到低于上一个触发等级(注意,不是清零!对于等级2,上一个等级是等级1,即4字节)时,硬件会自动将RTS引脚重新置为有效(低电平)。
- 发送方继续:发送方检测到CTS变低(有效),自动恢复数据发送。
3. 关键细节与避坑指南:
- “上一个触发等级”的玄机:这是防止频繁切换RTS信号、产生“抖动”的关键设计。如果阈值都是8字节,那么FIFO在8字节附近波动时,RTS会频繁跳变。设置一个释放阈值(如4字节)创造了滞后区间,使流控制更稳定。
- “一个字节的滞后”问题:手册明确指出,当接收FIFO达到触发水平时,RTS被取消置位。但发送方可能已经开始了下一个字节的发送,因此接收方在达到触发水平后,还可能多收到一个字节。在设计FIFO大小时,必须为此留出余量。LPC2101的接收FIFO深度是16字节,触发等级最高14字节,就是考虑了这个问题。
- 配置步骤:
- 使能FIFO:
U1FCR = (1 << 0);// 必须首先使能FIFO - 设置接收触发等级:
U1FCR |= (2 << 6);// 例如,设置为等级2 (8字节) - 使能自动RTS:
U1MCR |= (1 << 6);// 置位RTSen位 - (可选)配置中断:如果你希望当FIFO数据达到触发水平时产生中断通知CPU来取数据,还需要使能接收线状态中断
U1IER |= (1 << 0);。
- 使能FIFO:
3.2 自动CTS (Auto-CTS) 工作机制与配置
自动CTS是自动RTS的“响应端”。它的逻辑更直接:发送方在发送每个字符前,检查CTS引脚电平,只有对方允许(CTS为低)才发送。
1. 工作流程详解:
- 发送条件:发送方使能自动CTS后,其发送移位寄存器在开始发送一个字符的起始位之前,会采样CTS引脚的状态。
- 正常发送:如果CTS为低(有效),则正常发送该字符。
- 暂停发送:如果CTS为高(无效),则发送器会暂停。它不会将THR或TX FIFO中的数据移入移位寄存器,TXD引脚将保持“标记”状态(高电平)。整个发送流程被挂起。
- 恢复发送:一旦CTS引脚被对方拉低(接收方RTS变低),发送器会在下一个波特率周期检测到这一变化,并立即开始发送下一个字符的起始位。
2. 关键细节与避坑指南:
- 严格的时序要求:手册强调,为了成功停止发送下一个字符,CTS必须在当前正在发送的字符的最后一个停止位的中点之前被释放(变为高电平)。这是因为UART硬件需要时间在字符边界处理流控制信号。在实际编程中,这意味着接收方需要在FIFO达到触发水平后尽快拉高RTS,不能拖延。
- 中断的减少:自动CTS的一个巨大优势是减少系统中断。如果没有自动CTS,发送方需要依赖软件检测CTS引脚变化(通常通过GPIO中断),然后在中断服务程序里控制发送。使能自动CTS后,这个流程完全由硬件完成,CPU无需干预,大大降低了负载。
- 配置步骤:
- 使能FIFO:同样,这是前提。
- 使能自动CTS:
U1MCR |= (1 << 7);// 置位CTSen位 - 连接硬件:确保发送方的CTS引脚正确连接到接收方的RTS引脚。
3.3 自动流控制综合配置示例
假设我们设计一个LPC2101与另一个设备(可以是另一个MCU或PC的串口)进行115200bps、8N1格式、带硬件流控制的通信。
发送方配置代码思路:
void UART1_Init_Sender(void) { // 1. 设置波特率 (假设PCLK=12MHz, 115200bps) // 计算除数: Divisor = PCLK / (16 * 波特率) = 12,000,000 / (16 * 115200) ≈ 6.51 // 实际取整为6,会有误差。更精确需使用分数波特率发生器,此处简化。 uint16_t div = 6; U1LCR |= (1 << 7); // DLAB=1, 允许访问DLL/DLM U1DLL = div & 0xFF; U1DLM = (div >> 8) & 0xFF; U1LCR &= ~(1 << 7); // DLAB=0, 恢复访问数据寄存器 // 2. 设置线路格式:8位数据,1位停止位,无校验 U1LCR = (3 << 0) | (0 << 2) | (0 << 3); // 8位,1停止位,无校验 // 3. 使能并配置FIFO U1FCR = (1 << 0); // 使能FIFO // 4. 使能自动CTS,让发送受对方RTS控制 U1MCR = (1 << 7); // 使能自动CTS (CTSen=1) // 注意:此时U1MCR[1] (RTS控制位)在自动CTS模式下无效,由硬件管理。 // 5. (可选)使能发送空中断,当THR空时可以填充新数据 U1IER |= (1 << 1); // 使能THRE中断 }接收方配置代码思路:
void UART1_Init_Receiver(void) { // 1. 设置波特率 (必须与发送方一致) U1LCR |= (1 << 7); U1DLL = 6 & 0xFF; U1DLM = (6 >> 8) & 0xFF; U1LCR &= ~(1 << 7); // 2. 设置线路格式 (必须与发送方一致) U1LCR = (3 << 0) | (0 << 2) | (0 << 3); // 3. 使能并配置FIFO,设置触发等级为8字节 U1FCR = (1 << 0) | (2 << 6); // 使能FIFO,并设置RX触发等级为2(8字节) // 4. 使能自动RTS,让本机RTS根据FIFO状态自动控制对方CTS U1MCR = (1 << 6); // 使能自动RTS (RTSen=1) // 5. 使能接收数据可用中断(RDA)和线状态中断(RLS) U1IER |= (1 << 0) | (1 << 2); // RDA中断:当接收FIFO达到触发水平时产生 // RLS中断:当发生溢出、奇偶校验、帧错误或间隔中断时产生 }4. 相关寄存器与中断协同工作
自动流控制的高效运行,离不开中断机制的配合。UART1的中断识别寄存器U1IIR和中断使能寄存器U1IER是软件响应的枢纽。
4.1 中断协同策略
对于接收方,我们通常使能U1IER[0](RDA中断)和U1IER[2](RLS中断)。
- RDA中断:当接收FIFO中的数据量达到U1FCR设置的触发水平时触发。这是通知CPU来批量取走数据的主要信号。由于有了自动RTS,我们不必担心在CPU处理期间数据会涌入导致溢出,因为FIFO快满时硬件会自动暂停对方发送。
- RLS中断:当发生接收错误(溢出OE、校验错PE、帧错误FE)或接收到间隔信号(BI)时触发。用于处理通信异常。
对于发送方,如果采用中断驱动发送,则使能U1IER[1](THRE中断)。
- THRE中断:当发送保持寄存器(或TX FIFO)为空时触发。在中断服务程序中,CPU可以检查是否还有待发送数据,并写入U1THR。在自动CTS使能的情况下,即使THRE中断发生,硬件也会在CTS无效时自动暂停发送,因此无需在发送中断中处理流控制逻辑,代码更简洁。
4.2 调制解调器状态寄存器 (U1MSR) 与自动流控制
在自动流控制使能后,U1MSR寄存器仍然有重要作用:
U1MSR[0](Delta CTS):记录CTS引脚状态是否发生变化。即使自动CTS使能,这个状态位依然会被更新。你可以通过轮询此位来监控CTS线的状态变化(例如,用于诊断或超时判断),而不会产生Modem状态中断(除非你特意使能了U1IER[3]和U1IER[7])。- 在自动RTS模式下,
U1MCR[1](RTS控制位)会变成只读,其值反映的是硬件自动控制的RTS引脚实际电平。你可以通过读取U1MCR[1]来了解当前RTS的输出状态。
5. 常见问题、调试技巧与实战心得
问题1:使能了自动流控制,但通信仍然卡死或丢失数据。
- 检查硬件连接:这是最常见的问题。务必确认TX接RX,RX接TX,RTS接CTS,CTS接RTS是交叉连接。同一条线上的电平标准要一致(通常是3.3V TTL)。
- 检查初始电平:在通信开始前,用万用表或示波器测量RTS和CTS引脚。在自动RTS使能且FIFO为空时,接收方的RTS(即发送方的CTS)应该是低电平(有效)。如果不是,检查U1MCR配置是否正确,以及引脚复用功能是否已正确设置为UART。
- 确认FIFO已使能:没有使能U1FCR[0],后续的所有FIFO和流控制相关功能都无法工作。
问题2:自动流控制使能后,发送方一直不发送数据。
- 检查CTS引脚状态:发送方因CTS为高而阻塞。检查接收方的RTS输出是否为低。如果不为低,检查接收方配置:
- U1FCR[0]是否置1?
- U1MCR[6](RTSen)是否置1?
- 接收方MCU的RTS引脚功能是否已正确映射(通过PINSEL寄存器)?
- 检查环路:尝试将发送方的CTS引脚直接接地(强制低电平)。如果此时能发送数据,问题就在接收方或连接线上。
问题3:通信速率很高时,偶尔还是会丢失数据。
- 调整FIFO触发等级:提高接收方的触发等级(如从4字节调到8字节或14字节),让CPU一次中断处理更多数据,减少中断上下文切换的开销。
- 优化中断服务程序(ISR):确保UART中断的ISR尽可能短小精悍。只做最必要的操作:读取数据到缓冲区,或从缓冲区填充发送寄存器。复杂的处理(如协议解析)应放到主循环或低优先级任务中。
- 检查波特率误差:计算实际的波特率除数与理论值的误差。误差过大会导致采样点偏移,在高速时容易出错。使用芯片的分数波特率发生器(如果支持)可以获得更精确的波特率。
问题4:如何调试自动流控制是否真的在工作?
- 使用环回模式:将U1MCR[4]置1,进入环回模式。然后,自己发送一串数据。由于TXD被内部连接到RXD,且Modem信号内部环回,你可以通过软件控制U1MCR[1](模拟RTS)和读取U1MSR[4](观察CTS状态),来验证自动CTS的逻辑响应是否正确,而无需连接外部硬件。
- 示波器/逻辑分析仪观察:这是最直观的方法。同时捕捉TXD、RXD、RTS、CTS四根线。你可以清晰地看到:当接收方FIFO数据增多,RTS如何变高;随后发送方在发送完当前字节后,TXD如何暂停;当接收方读取数据后,RTS变低,TXD又如何恢复发送。这是理解流控制时序最有效的方式。
个人实战心得:
在早期的项目中,我曾尝试用软件模拟流控制:在接收中断里判断缓冲区剩余空间,然后操作GPIO去拉高/拉低RTS引脚。结果在高波特率下,中断响应延迟和软件执行时间导致流控制信号严重滞后,经常发生溢出。切换到LPC2101的硬件自动流控制后,系统瞬间变得稳定可靠,CPU占用率也大幅下降。硬件能做的事,绝不交给软件,这是嵌入式开发里提升可靠性和效率的一条黄金法则。自动流控制正是这一法则的完美体现。它看似只是芯片手册里的几段描述和几个寄存器位,但一旦正确运用,就能为你解决串口通信中最令人头疼的流量管理问题。
