
1. UART串口通信嵌入式开发的“高速公路”基石在嵌入式开发的世界里如果说主控芯片是大脑那么UART串口通信就是连接大脑与外部世界的“高速公路”。无论是调试信息打印、传感器数据读取还是与蓝牙、Wi-Fi模块通信UART都扮演着不可或缺的角色。我接触过不少项目从简单的温湿度数据采集到复杂的多节点物联网网关UART的稳定性和灵活性往往是项目成败的关键一环。对于德州仪器TI的CC26x0和CC13x0这类主打低功耗与无线连接的微控制器来说深入理解其内置UART模块的每一个细节更是榨干芯片性能、实现稳定长续航的必修课。很多人觉得串口通信无非是“配置波特率读写数据”但实际踩过坑的工程师都知道差之毫厘谬以千里。一个没处理好的FIFO溢出可能导致关键数据丢失波特率计算稍有偏差通信就会时好时坏而流控制配置不当则在高速或大数据量传输时直接“卡死”。本文我将结合TI官方技术手册和多年实战经验为你彻底拆解UART的核心原理并聚焦于CC26x0/CC13x0的寄存器级配置。我会从最基础的通信帧格式讲起深入到波特率生成、FIFO机制、中断与DMA配合最后手把手带你完成一个从零开始的配置实例。无论你是刚接触嵌入式的新手还是想深入了解特定芯片实现的老鸟相信都能从中找到“干货”。2. UART核心原理与通信帧深度解析2.1 异步串行通信的本质无需时钟线的握手UART通信的核心在于“异步”。这与SPI、I2C等需要时钟信号同步的通信方式截然不同。异步意味着通信双方没有共享的时钟线来指示每一位数据的开始和结束。那么如何保证发送方和接收方步调一致呢答案就是波特率Baud Rate和通信帧格式的预先约定。你可以把UART通信想象成两个相隔很远的人通过闪光灯发送莫尔斯电码。他们必须事先约定好一次“短亮”代表一个基本时间单位波特率的倒数一个“字符”由固定的“点”和“划”的组合构成帧格式。接收方在检测到起始信号比如一次长暗后就按照约定的时间间隔去解读后续的闪光组合成一个完整的字符。UART的工作方式与此高度相似。2.2 通信帧结构每一位的使命一个完整的UART字符帧Character Frame是数据交换的基本单元其结构是理解所有配置和故障排查的基础。根据输入材料中的图19-2一个帧由以下几部分组成我结合自己的调试经验为你解读起始位Start Bit这是一个逻辑‘0’低电平位。它的作用就像跑步比赛时的发令枪告诉接收方“注意一个字符的数据马上要来了”接收端持续监测RX线当检测到从空闲高电平跳变到低电平时便启动内部定时器准备在约定的时间点采样后续数据。这里有个关键细节为了防止毛刺误触发CC26x0的UART会在波特率时钟的第8个周期再次采样RX线如果此时仍是低电平才确认为有效的起始位。这个设计大大增强了抗干扰能力。数据位Data Bits紧接起始位之后是要传输的实际数据长度可以是5、6、7或8位。数据以LSBLeast Significant Bit最低有效位优先的方式发送。例如你要发送字节0x55二进制01010101在线上看到的顺序将是1-0-1-0-1-0-1-0从LSB到MSB。在配置寄存器时务必清楚这一点否则解析出的数据将是错乱的。奇偶校验位Parity Bit可选用于简单的错误检测。发送方根据数据位中‘1’的个数计算并附加一个校验位使得整个数据位校验位中‘1’的个数满足奇偶性要求奇校验或偶校验。接收方重新计算并进行比对。注意校验位只能检测奇数个位错误如1位、3位翻转对于偶数个位错误无能为力。在可靠性要求高的场合通常需要更上层的校验协议如CRC。停止位Stop Bits这是一个或两个逻辑‘1’高电平位。它标志着一个字符帧的结束并确保线路恢复到空闲高电平状态为下一个起始位的检测做好准备。停止位的长度也给了接收方一定的容错空间。大多数情况下1个停止位是标准配置。一个完整的传输过程发送端从TX FIFO或保持寄存器取出一个字节按照配置的帧格式数据位长度、校验、停止位将其转换为一个位流从TXD引脚依次送出。接收端在RXD引脚检测到起始位后在每位数据的中间点通常是波特率时钟的第16个周期进行采样将位流重新组装成字节并检查校验和停止位是否正确最后将数据和状态信息错误标志一起存入RX FIFO。3. CC26x0/CC13x0 UART模块架构与关键特性CC26x0/CC13x0的UART模块并非简单的“串行转换器”它是一个高度集成、功能丰富的通信外设其设计充分考虑了低功耗和高性能应用的需求。3.1 模块整体框图与数据流根据输入材料中的图19-1我们可以将UART模块的核心数据流梳理如下发送路径CPU或DMA将数据写入数据寄存器UART_DR数据进入32x8位的TX FIFO。发送逻辑从FIFO中取出数据经由波特率发生器控制的移位寄存器转换为串行比特流最终从UARTTxD引脚输出。接收路径串行数据从UARTRxD引脚输入由接收逻辑在波特率控制下采样转换为并行数据并附带4位错误状态溢出、帧错误、奇偶校验错误、线路中断一起存入32x12位的RX FIFO。CPU或DMA通过读取UART_DR寄存器一次性获得8位数据和4位状态。控制与状态控制寄存器UART_CTL负责全局使能、收发使能、回环模式等。线控制高字节寄存器UART_LCRH配置帧格式。标志寄存器UART_FR实时反映FIFO空/满、忙线状态。中断与DMA逻辑则负责在FIFO达到特定水位、发生错误或传输完成时产生中断或DMA请求解放CPU。3.2 区别于传统UART的核心优势CC26x0的UART模块有几个设计亮点在实际项目中能带来显著好处独立的深度FIFO32字节的TX FIFO和32条目每个条目12位的RX FIFO是降低CPU中断频率的利器。你可以将FIFO触发中断的水位设置为1/8、1/4、1/2、3/4或7/8。例如设置RX FIFO触发点为1/2即16字节那么只有当接收到16个字节后才会产生一次接收中断CPU可以一次性处理这16个字节而不是每收到1个字节就被打断一次极大地提升了系统效率。22位高精度波特率发生器这是实现精准波特率的关键。它由一个16位整数部分UART_IBRD和一个6位小数部分UART_FBRD组成。通过小数分频模块能够用固定的系统时钟如24MHz产生出非常接近标准波特率如115200的实际频率减少累积误差保证长距离或高速通信的稳定性。灵活的硬件流控制通过UART_CTL寄存器的CTSEN和RTSEN位可以独立启用CTSClear To Send和RTSRequest To Send流控制。当启用硬件流控时发送方只有在检测到CTS信号有效低电平时才会发送数据而接收方的RTS信号会在其RX FIFO快满时变无效高电平通知对方暂停发送。这能完美防止FIFO溢出导致的数据丢失在无线模组如通过UART连接蓝牙芯片通信中尤为重要。与µDMA的无缝集成这是实现超低功耗的关键特性。UART模块可以产生独立的TX和DMA请求单次请求和突发请求。结合CC26x0强大的µDMA控制器可以实现数据在内存和UART FIFO之间的自动搬运。在需要持续高速收发数据的场景如固件升级、大数据量传感器读取配置好DMA后CPU可以进入睡眠模式仅由DMA和UART协同工作功耗可以降到极低水平。4. 寄存器配置详解与实战步骤理解了原理我们进入实战环节如何通过配置寄存器让UART模块按照我们的意愿工作。我会以配置一个115200波特率、8N18数据位、无校验、1停止位、无FIFO、无中断的基础通信为例但过程中会穿插讲解其他关键寄存器的用法。4.1 波特率计算精度决定成败波特率配置是第一步也是容易出错的一步。计算公式在技术手册中给出BRD PERDMACLK / (ClkDiv × Baud Rate)其中ClkDiv固定为16。假设系统时钟PERDMACLK为24MHz目标波特率为115200。计算理论分频值BRD 24,000,000 / (16 * 115200) 13.02083333...整数部分BRDI 13小数部分BRDF 0.02083333...计算小数寄存器值 公式UART_FBRD.DIVFRAC integer(BRDF × 64 0.5)代入integer(0.02083333... × 64 0.5) integer(1.33333... 0.5) integer(1.83333...) 1所以UART_FBRD寄存器应写入1。为什么加0.5这是一个四舍五入的技巧。因为寄存器只能存储整数通过0.5后取整可以实现最接近的舍入使得生成的实际波特率误差最小。计算出的实际波特率为24,000,000 / (16 * (13 1/64)) ≈ 115207误差极小完全满足通信要求。注意UART_IBRD、UART_FBRD和UART_LCRH这三个寄存器共同构成一个内部30位的影子寄存器。这个影子寄存器只有在写入UART_LCRH时才会被更新到实际生效的波特率发生器中这是一个至关重要的顺序要求。因此正确的写入顺序必须是先写IBRD和/或FBRD最后写LCRH。4.2 关键寄存器功能与配置指南以下表格整理了最核心的几个寄存器及其关键位域方便你快速查阅寄存器名称 (偏移地址)核心功能关键位域与配置说明数据寄存器 (DR) (0x00)数据读写读写该寄存器即访问TX/RX FIFO。读操作返回12位数据低8位为数据高4位Bit11-8为接收该字节时的错误状态OE, BE, PE, FE。写操作将8位数据写入TX FIFO。线控制高字节寄存器 (LCRH) (0x2C)帧格式控制WLEN[1:0]: 数据位长度 (005位, ..., 118位)。STP2: 停止位 (01位, 12位)。EPS, PEN: 奇偶校验使能与类型选择。FEN: FIFO使能位。必须置1才能启用32级FIFO否则为1字节深度保持寄存器。控制寄存器 (CTL) (0x30)模块全局控制UARTEN: UART模块总使能。任何配置变更前必须先清除此位TXE, RXE: 单独使能发送器和接收器。LBE: 回环测试使能。芯片内部将TXD连接到RXD用于自检。CTSEN, RTSEN: 硬件流控制CTS/RTS使能位。中断FIFO水位选择寄存器 (IFLS) (0x34)中断触发点RXSEL[2:0], TXSEL[2:0]: 分别设置RX和TX FIFO的中断触发水位。例如010代表1/2满16字节触发。合理设置能平衡响应速度和中断频率。中断屏蔽置位/清除寄存器 (IMSC) (0x38)中断源使能对应位置1则使能该中断源。包括接收中断(RXIM)、发送中断(TXIM)、接收超时中断(RTIM)、 modem状态中断等。使能后中断事件会反映到UART_MIS寄存器。原始中断状态寄存器 (RIS) (0x3C)原始中断状态无论UART_IMSC是否屏蔽所有中断事件都会在此寄存器置位。用于查询所有潜在的中断源。屏蔽后中断状态寄存器 (MIS) (0x40)有效中断状态仅显示被UART_IMSC使能的中断源的状态。中断服务程序(ISR)中应读取此寄存器来判断具体中断来源。中断清除寄存器 (ICR) (0x44)中断标志清除向对应位写1可清除UART_RIS和UART_MIS中的相应中断标志。注意有些中断标志如RX在读取数据后会自动清除而有些如接收超时必须手动写ICR清除。4.3 完整初始化流程与代码示例基于寄存器直接操作下面我们按照技术手册19.7节的指导完成一个完整的初始化序列。这里假设使用CC26x0系统时钟已配置为24MHz。// 假设 UART0 基地址为 0x4000C000 #define UART0_BASE 0x4000C000 #define UART0_CTL (*(volatile uint32_t *)(UART0_BASE 0x030)) #define UART0_IBRD (*(volatile uint32_t *)(UART0_BASE 0x024)) #define UART0_FBRD (*(volatile uint32_t *)(UART0_BASE 0x028)) #define UART0_LCRH (*(volatile uint32_t *)(UART0_BASE 0x02C)) #define UART0_DR (*(volatile uint32_t *)(UART0_BASE 0x000)) void UART0_Init(void) { // 步骤 1: 禁用UART。在修改关键配置如波特率、帧格式前必须禁用模块。 UART0_CTL ~(0x00000001); // 清除 UARTEN 位 // 步骤 2 3: 配置波特率除数寄存器 (BRD 24M / (16 * 115200) 13.0208) UART0_IBRD 13; // 整数部分 13 UART0_FBRD 1; // 小数部分 integer(0.0208 * 64 0.5) 1 // 步骤 4: 配置线路控制寄存器 (LCRH) // 位[6:5] (WLEN): 11 8位数据 // 位[4] (FEN): 0 禁用FIFO (本例中使用查询模式先禁用) // 位[3] (STP2): 0 1位停止位 // 位[2] (EPS): 0 偶校验 (本例无校验此位无关) // 位[1] (PEN): 0 禁用奇偶校验 // 写入LCRH的同时会锁存IBRD和FBRD的值到影子寄存器使其生效。 UART0_LCRH (0x3 5); // 等价于 0x60 即8位数据无校验1停止位无FIFO // 步骤 5: 重新使能UART UART0_CTL | 0x00000001; // 置位 UARTEN 位 // 可选同时使能发送器和接收器 (复位后默认已使能通常无需再设置) // UART0_CTL | (0x00000100 | 0x00000200); // 置位 TXE 和 RXE } // 简单的查询式发送一个字符 void UART0_SendChar(char c) { // 等待发送FIFO或保持寄存器非满。当FIFO禁用时检查TXFF位。 // UART_FR (0x18) 的 Bit5 为 TXFF (Transmit FIFO Full) while ((*(volatile uint32_t *)(UART0_BASE 0x018)) (0x1 5)) { ; // 忙等待 } UART0_DR c; // 写入数据寄存器启动发送 } // 简单的查询式接收一个字符 (非阻塞若无数据返回-1) int UART0_ReceiveChar(void) { // 检查接收FIFO或保持寄存器是否为空。UART_FR 的 Bit4 为 RXFE (Receive FIFO Empty) if ((*(volatile uint32_t *)(UART0_BASE 0x018)) (0x1 4)) { return -1; // 无数据 } // 读取数据寄存器低8位为数据 return (int)(UART0_DR 0xFF); }代码解析与要点顺序是铁律UARTEN位清零 - 写IBRD/FBRD- 写LCRH- 置位UARTEN。这个顺序不能乱否则可能导致波特率配置不生效或通信异常。FIFO的使能本例中LCRH的FEN位为0即禁用FIFO。此时UART使用单字节的保持寄存器UART_FR寄存器中的TXFF和RXFE位反映的是保持寄存器的状态。在简单应用中这样没问题。但在实际产品中强烈建议启用FIFO并结合中断或DMA以提升效率。查询式发送的陷阱while循环等待的是“非满”而不是“空闲”。UART_FR的BUSY位只有在移位寄存器也发送完最后一个停止位后才清零。如果你等待BUSY位会浪费大量时间。等待TXFF或TXFE才是正确做法它表示“可以写入下一个数据了”。5. 高级功能应用与性能优化策略基础通信搭建起来后要构建稳定可靠的系统必须用好UART的高级功能。5.1 FIFO与中断的协同设计启用FIFO和中断是解放CPU、实现高效并发的关键。配置流程如下计算并设置FIFO触发水位通过UART_IFLS寄存器设置。例如设置RXSEL0101/2满即16字节触发TXSEL0011/8满即4字节空触发。这样接收端攒够16字节才通知CPU处理发送端在FIFO剩余空间大于28字节时就会请求CPU填充始终保持流水线畅通。使能FIFO在UART_LCRH寄存器中置位FEN位。配置并使能中断在UART_IMSC寄存器中使能所需中断源如接收中断(RXIM)、发送中断(TXIM)。然后在NVIC嵌套向量中断控制器中使能UART中断。编写中断服务程序(ISR)void UART0_IRQHandler(void) { uint32_t mis_status UART0_MIS; // 读取屏蔽后中断状态寄存器 if (mis_status 0x0010) { // 接收中断 // 循环读取UART0_DR直到RX FIFO为空 (UART_FR.RXFE 1) while (!(UART0_FR (14))) { uint32_t data_with_status UART0_DR; uint8_t received_char data_with_status 0xFF; uint8_t error_flags (data_with_status 8) 0x0F; // 处理 received_char并检查 error_flags process_received_data(received_char, error_flags); } } if (mis_status 0x0020) { // 发送中断 // 检查TX FIFO是否低于触发水位如果是填充更多待发送数据 if (!(UART0_FR (15))) { // 如果TX FIFO未满 fill_tx_fifo_from_buffer(); } // 如果所有数据发送完毕可以禁用发送中断避免空循环 } // 清除中断标志 (对于某些中断读数据或写ICR) UART0_ICR mis_status; // 写1清除对应的中断标志 }关键点在接收中断中一定要循环读取直到FIFO为空否则会频繁进入中断失去使用FIFO的意义。发送中断中在填充数据后如果发送缓冲区已空应及时禁用发送中断(UART0_IMSC ~0x0020)待有新的数据需要发送时再重新使能。5.2 利用µDMA实现零CPU开销的数据搬运对于CC26x0结合µDMA使用UART是终极性能解决方案。其核心思想是CPU只负责设置好源地址内存、目标地址UART_DR、传输数据量然后启动DMA即可去处理其他任务或进入低功耗模式。发送DMA配置思路配置UART的DMA控制寄存器(UART_DMACTL)使能发送DMA (TXDMAE1)。配置µDMA通道例如通道1给UART TX。设置传输模式为“基本模式”源地址为你的数据缓冲区地址递增目标地址为UART0_DR固定传输大小为字节数据量为N。当TX FIFO有空间时UART会向DMA控制器发出请求DMA自动将数据从内存搬移到UART0_DR。传输完成后DMA产生完成中断CPU在中断中处理后续事宜如通知应用层发送完成。接收DMA配置思路使能接收DMA (RXDMAE1)。配置µDMA通道例如通道2给UART RX。设置目标地址为接收缓冲区地址递增源地址为UART0_DR固定。当RX FIFO中有数据达到触发水位时UART发出突发请求DMA一次性读取多个字节到内存。可以设置DMA在传输指定数量后中断或者利用UART的接收超时中断RTIM来通知CPU处理已接收的不完整数据包。优势CPU干预极少特别适合高速、持续的数据流传输并且在整个传输过程中CPU可以处于睡眠状态功耗极低。5.3 硬件流控制RTS/CTS配置实战在波特率较高如921600以上或通信对方处理速度不确定时必须启用硬件流控以防止数据丢失。硬件连接将本机的UART0_RTS引脚连接到对方的CTS引脚将本机的UART0_CTS引脚连接到对方的RTS引脚。注意RTS/CTS是低电平有效。软件配置// 在UART初始化序列中在使能UART之前 UART0_CTL | (1 15) | (1 14); // 同时使能 CTSEN 和 RTSEN工作原理发送方本机在发送每个字符前会检查UART0_CTS引脚输入。如果为低电平对方告知“Clear to Send”则正常发送如果为高电平则暂停发送UART_FR寄存器的BUSY位会保持有效直到CTS变低。接收方本机当本机的RX FIFO剩余空间小于预设的“空间水位”时UART0_RTS引脚会自动输出高电平通知对方“Not Ready to Receive”当空间被读出剩余空间大于水位时RTS变回低电平“Ready”。水位设置这个“空间水位”通常与FIFO大小和触发中断的水位相关。启用硬件流控后可以有效避免因接收方处理不及时导致的FIFO溢出错误OE。6. 调试技巧、常见问题与故障排查即使配置完全正确在实际硬件调试中仍会遇到各种问题。以下是我总结的一些常见“坑点”和排查方法。6.1 通信全无检查物理层与基本配置电平与共地这是最常见的问题。确保通信双方的电平兼容CC26x0是3.3V CMOS电平。务必连接GND线形成共同的参考地否则电平识别会完全错误。引脚复用配置CC26x0的UART引脚是通过IOCIO控制器模块映射到具体GPIO上的。你必须先调用IOCPortConfigureSet()或类似函数将对应的GPIO引脚配置为UART功能UART0_RX,UART0_TX等。忘记这一步信号根本不会从芯片引脚出来。时钟与电源域UART模块位于PERIPH电源域。确保在访问UART寄存器前已经通过PRCM模块使能了该外设的时钟运行模式、睡眠模式等。TI的驱动库函数PRCMPeripheralRunEnable(PRCM_UART0)和PRCMLoadSet()就是干这个的。波特率再确认用示波器或逻辑分析仪测量TXD引脚。发送一个字节0x55二进制01010101在8N1格式下波形应该是“低-高-低-高-低-高-低-高-高”。测量一个位的时间从起始位低电平开始到第一个数据位结束其倒数应等于你配置的波特率。如果偏差太大检查系统时钟PERDMACLK是否正确。6.2 能发送但不能接收或数据错乱帧格式不匹配这是数据错乱的头号元凶。务必确认通信双方的数据位、停止位、奇偶校验设置完全一致。一个常见的疏忽是PC端串口助手默认可能是“8N1”而你的设备配置成了“8E1”偶校验那么每个字节的校验位都会被对方当成数据位解析导致数据完全错误。FIFO与中断的坑接收数据丢失如果启用了FIFO和接收中断但中断服务程序没有读完FIFO中的所有数据就退出了那么残留的数据可能会在FIFO中累积最终导致溢出OE。务必在RX中断中循环读取直到RXFE位为1。发送数据卡住在查询式发送中如果只检查BUSY位可能会在发送长数据流时效率极低。正确的做法是检查TXFF位FIFO未满或TXFE位FIFO空。奇偶校验错误PE如果使能了校验但持续出现PE错误除了检查双方配置还要注意线路干扰。长导线、不规范的接线容易引入噪声导致数据位在传输中翻转从而校验失败。6.3 使用DMA时的特殊问题DMA传输不启动首先检查UART的DMA使能位(TXDMAE/RXDMAE)是否置位。其次确认µDMA通道的配置是否正确特别是源/目标地址、传输模式Basic, Ping-Pong, Auto和数据大小。一个有用的调试方法是先禁用DMA用查询或中断方式确认UART本身工作是正常的。DMA传输完成中断不触发检查DMA通道的控制字中是否使能了传输完成中断。同时确保在NVIC中使能了DMA中断。数据错位或重复这通常是因为DMA的传输数据量xferSize设置错误或者源/目标地址的递增模式配置不对。例如从内存到UART_DR源地址应递增目标地址固定反之亦然。6.4 低功耗应用下的注意事项CC26x0常用于电池供电设备UART的使用会影响功耗。未通信时关闭UART如果长时间不需要通信在进入低功耗模式前除了关闭UART模块(UARTEN0)最好也通过PRCM关闭其时钟源以节省功耗。利用DMA和中断降低活跃时间配置DMA进行大数据量传输并设置DMA完成中断。在DMA工作期间CPU可以进入IDLE或更深的睡眠模式。这是实现低功耗的关键策略。唤醒源配置如果设备需要通过UART接收数据来唤醒则需要配置UART RX引脚为唤醒源并确保在睡眠模式下UART和对应的IO模块仍有适当的时钟。最后分享一个我调试CC26xx UART时的小技巧在程序初始化后先让MCU通过UART循环发送一个固定的字符串如UART Ready\n。这样一旦上电你就可以在串口助手上看到这个信息它能立刻告诉你电源、时钟、引脚复用、基本UART配置这几关都过了。剩下的就是数据收发逻辑的问题了。这种“灯塔”信号在硬件调试初期非常管用。