1. 项目概述与UART核心价值
在嵌入式开发的世界里,设备间的“对话”是项目成败的关键。无论是让主控芯片读取传感器数据,还是将调试信息打印到电脑,都需要一个可靠、高效的通信桥梁。在众多通信协议中,UART(通用异步收发器)以其简单、成熟、无需时钟线的特点,成为了工程师们最信赖的“老伙计”。它不追求极致的速度,但胜在稳定可靠,几乎每一款微控制器(MCU)都将其作为标准配置。
这次,我们把目光聚焦在德州仪器(TI)的CC26x0和CC13x0系列无线微控制器上。这两款芯片在低功耗物联网领域大名鼎鼎,其内置的UART模块远不止一个简单的串口那么简单。它集成了深度可调的FIFO缓冲区、灵活的硬件流控制、与DMA控制器无缝协作的能力,以及精细的中断管理机制。理解并熟练配置这些寄存器,意味着你能从“能用”串口,跃升到“精通”串口,能榨干硬件性能,设计出更稳定、更高效、更省电的通信链路。对于从事蓝牙、Zigbee、Sub-1GHz等无线应用开发的工程师来说,这是绕不开的基本功。本文就将带你深入CC26x0/CC13x0的UART模块内部,从原理到寄存器,手把手拆解配置要点和避坑指南。
2. UART通信基础与CC26x0模块架构解析
2.1 异步串行通信的本质
在开始摆弄寄存器之前,我们必须先统一“语言”。UART通信是异步的,这意味着通信双方没有共享的时钟信号来同步每一位数据。那么,接收方如何知道从哪里开始读、读多快呢?答案就在于预先约定好的帧格式和波特率。
一个标准的UART数据帧就像一列火车:起始位(Start Bit)是车头,永远是一个逻辑低电平(0),它告诉接收方:“注意,数据要来了!”。紧接着是5到8个数据位(Data Bits),承载着实际信息,通常从最低有效位(LSB)开始发送。数据位之后是可选的校验位(Parity Bit),用于最简单的错误检测(如奇校验、偶校验)。最后是停止位(Stop Bit),一个或多个逻辑高电平(1),标志着本帧数据的结束,并为下一帧的起始位留出准备时间。
波特率(Baud Rate)定义了这列“火车”的运行速度,即每秒传输的符号数。对于UART,一个符号就是一位,所以115200的波特率意味着每秒传输115200位。发送和接收双方必须严格使用相同的波特率、数据位、停止位和校验位设置,否则通信就会乱码,就像两个人用不同的语言和语速对话,根本无法理解彼此。
2.2 CC26x0/CC13x0 UART模块亮点
CC26x0/CC13x0的UART模块可以看作一个高度集成的“通信管家”,它基于经典的16C550架构思想,但寄存器并不兼容,拥有许多现代化增强特性:
- 高性能波特率发生器:支持高达3 Mbps的通信速率,足以应对大多数高速数据采集或固件升级场景。
- 深度缓冲的FIFO:独立的32x8位发送FIFO和32x12位接收FIFO。接收FIFO多出的4位用于存储每字节数据的错误状态(帧错误、奇偶校验错误、溢出错误、Break错误)。FIFO能显著减少CPU处理中断的频率,提升系统效率。
- 可编程FIFO触发水位:中断并非来一个字节就触发一次。你可以设置FIFO填充到1/8、1/4、1/2、3/4、7/8时再产生中断,让CPU批量处理数据,进一步优化性能。
- 灵活的帧格式:数据位(5-8位)、停止位(1或2位)、校验位(奇、偶、固定、无)均可自由配置。
- 硬件流控制(RTS/CTS):通过RTS(请求发送)和CTS(清除发送)信号线,实现收发双方的速度匹配,防止缓冲区溢出导致数据丢失,这是实现可靠通信的关键。
- 强大的中断系统:可独立屏蔽发送、接收、接收超时、调制解调器状态、各类错误等多种中断源,并提供原始和掩码后两种中断状态查询。
- μDMA(微直接内存访问)支持:为发送和接收提供了独立的DMA通道,可以将CPU从繁重的数据搬运工作中彻底解放出来,实现高效的数据吞吐。
注意:在配置任何UART功能寄存器之前,必须先通过清除
UART_CTL寄存器的UARTEN位来禁用UART模块。否则,在UART使能状态下修改关键配置(如波特率、帧格式)可能导致不可预测的行为或通信失败。这是一个非常容易踩坑的地方。
3. 核心寄存器详解与配置实战
手册里寄存器列表看起来令人望而生畏,但实际常用、需要重点关注的也就那么十来个。我们按功能分组,并结合一个具体的配置实例来讲解。
3.1 波特率配置:精度与同步的艺术
波特率配置是通信的基石。CC26x0的波特率发生器由一个22位的分频器驱动,包括16位整数部分(UART_IBRD)和6位小数部分(UART_FBRD)。这种设计允许它生成非常精确的标准波特率。
计算公式如下:BRD = UART_CLK / (16 * 期望的波特率)其中,UART_CLK是供给UART模块的时钟频率(例如PERDMACLK,通常为48MHz或24MHz)。BRD的结果是一个浮点数,其整数部分写入IBRD,小数部分乘以64再加0.5(用于四舍五入)后取整,写入FBRD。
实战配置(115200波特率,时钟24MHz):
- 计算BRD:
24,000,000 / (16 * 115200) = 13.0208333 - 整数部分
IBRD = 13(0xD) - 小数部分计算:
FBRD = integer(0.0208333 * 64 + 0.5) = integer(1.83333 + 0.5) = integer(2.33333) = 2
因此,我们需要向UART_IBRD写入13,向UART_FBRD写入2。
关键陷阱:IBRD、FBRD和UART_LCRH(线路控制寄存器)共同构成一个内部30位锁存器。这个锁存器只有在写入UART_LCRH时才会被更新!这意味着,更改波特率必须遵循正确的顺序:
- 顺序A:写
IBRD-> 写FBRD-> 写LCRH - 顺序B:写
FBRD-> 写IBRD-> 写LCRH - 顺序C:写
IBRD-> 写LCRH(仅改整数部分) - 顺序D:写
FBRD-> 写LCRH(仅改小数部分)
最常见的错误是只写了IBRD和FBRD,却忘了随后“触发”一下LCRH的写操作(即使LCRH的值没变也需要写一次),导致新波特率不生效。
3.2 帧格式与FIFO控制:UART_LCRH寄存器
UART_LCRH寄存器是UART的“面貌塑造师”,它决定了数据帧长什么样,以及FIFO如何工作。
WLEN(Bits 6:5):数据位长度。00=5位,01=6位,10=7位,11=8位。绝大多数应用使用8位(0x60)。FEN(Bit 4):FIFO使能位。必须置1才能启用32字节的深度缓冲,这是发挥该UART性能优势的关键。复位后默认为0(禁用),此时FIFO退化为1字节的保持寄存器。STP2(Bit 3):停止位数量。0=1个停止位,1=2个停止位。EPS,PEN,SPS(Bits 2:1:0):共同控制校验位。PEN使能校验,EPS选择奇偶(0=奇校验,1=偶校验),SPS选择粘性校验(通常为0)。如果要配置为8位数据、无校验、1停止位、使能FIFO,则UART_LCRH应写入0x70(二进制0111 0000)。
3.3 核心控制与状态:UART_CTL与UART_FR寄存器
UART_CTL是总开关。
UARTEN(Bit 0):UART模块总使能。在完成所有初始化(波特率、帧格式、GPIO映射等)后,最后置1。TXE,RXE(Bits 8, 9):分别使能发送器和接收器。通常两者都使能。LBE(Bit 7):回环模式使能。置1后,TX引脚输出 internally loopback 到RX引脚输入,用于自测试,无需外部连线。CTSEN,RTSEN(Bits 15, 14):硬件流控制使能。具体配置见下文硬件流控制部分。
UART_FR是状态指示器,主要用于查询(轮询)模式。
TXFF,TXFE:发送FIFO满、发送FIFO空。RXFF,RXFE:接收FIFO满、接收FIFO空。BUSY(Bit 3):忙碌标志位。这是一个非常重要的位。当发送FIFO非空或移位寄存器正在发送时,该位为1。它只有在发送FIFO完全清空且最后一个字符(包括停止位)已完全移出后,才会变为0。因此,在准备关闭UART或进入低功耗模式前,不能仅仅检查TXFE,还必须等待BUSY位变为0,以确保所有数据都已物理发送完毕。
3.4 数据收发:UART_DR寄存器
UART_DR寄存器是数据进出的门户。读操作返回一个12位的值:低8位是数据,高4位(Bits 11:8)分别是OE(溢出错误)、BE(Break错误)、PE(奇偶校验错误)、FE(帧错误)状态位。写操作则向发送FIFO写入8位数据。
操作心得:在启用FIFO的情况下,你可以连续向UART_DR写入多达32个字节,而无需等待。同样,读取时也可以一次读取多个字节,并同时检查其错误状态。这比传统的单字节查询效率高得多。
3.5 中断系统深度配置
CC26x0的中断系统设计得非常精细,目的是让CPU只在必要时被唤醒。
1. 中断水位设置 (UART_IFLS): 这个寄存器决定了FIFO填充到什么程度时触发中断。例如,设置接收中断水位为1/4,那么当接收FIFO中累积了8个字节(32 * 1/4)时,才会产生接收中断。这对于处理数据包非常有用,你可以设置水位略高于预期数据包长度,让一个中断处理一整个包。
2. 中断掩码与状态 (UART_IMSC,UART_RIS,UART_MIS,UART_ICR):
UART_IMSC:中断掩码设置/清除寄存器。你想让哪种事件触发中断,就把对应的位置1。UART_RIS:原始中断状态寄存器。无论掩码如何,只要事件发生,对应位就置1。UART_MIS:被掩码后的中断状态寄存器。只有UART_IMSC中使能了的事件,其状态才会出现在这里。在中断服务程序(ISR)中,通常读取这个寄存器来判断中断源。UART_ICR:中断清除寄存器。向某个位写1,可以清除UART_RIS和UART_MIS中对应的中断状态位。注意,有些中断(如接收中断)在读取FIFO数据使其低于触发水位后,也会自动清除。
中断处理流程示例:
void UART0_IRQHandler(void) { uint32_t mis = HWREG(UART0_BASE + UART_O_MIS); // 读取被掩码的中断状态 if (mis & UART_INT_RX) { // 接收中断 // 1. 循环读取UART_DR,直到UART_FR.RXFE为1(FIFO空) // 2. 处理读取到的数据 // 3. 接收中断会自动清除(当FIFO水位低于阈值) } if (mis & UART_INT_TX) { // 发送中断 // 1. 检查是否还有数据要发送 // 2. 如果有,写入UART_DR直到FIFO满或数据写完 // 3. 如果发完了,可以在UART_IMSC中屏蔽发送中断,避免空触发 // 4. 发送中断在向FIFO写入数据使其超过水位后会自动清除 } if (mis & UART_INT_RT) { // 接收超时中断 // 当FIFO非空但超过32个位时间没有新数据时触发 // 这是处理“数据包尾”或“短帧”的绝佳时机,立即读取FIFO中剩余的数据 HWREG(UART0_BASE + UART_O_ICR) = UART_INT_RT; // 需要手动写ICR清除 } if (mis & UART_INT_OE) { // 溢出错误 // 严重错误,数据已丢失。需要读取UART_RSR或UART_DR获取错误状态并复位接收逻辑 HWREG(UART0_BASE + UART_O_ECR) = 0xFF; // 写ECR清除所有错误标志 } // ... 处理其他错误中断 }4. 高级功能实现:硬件流控制与DMA传输
4.1 硬件流控制(RTS/CTS)实战
硬件流控制是保证高速或大数据量通信不丢数据的“保险丝”。它通过UART0_RTS(输出)和UART0_CTS(输入)两根信号线实现。
- RTS (Request To Send):本设备告诉对方:“我的接收缓冲区快满了,请你暂停发送”。当接收FIFO空间达到预设水位(例如快满时),
RTS信号会变为无效(高电平),通知对方停止发送。 - CTS (Clear To Send):本设备接收对方指令:“对方允许我发送数据”。只有当
CTS信号有效(低电平)时,本设备的发送器才会工作。如果CTS变高,发送会暂停在当前字符发送完后,直到CTS再次变低。
在CC26x0上配置硬件流控制:
- GPIO配置:将两个GPIO引脚分别配置为
UART0_RTS和UART0_CTS功能。 - 控制寄存器配置:在
UART_CTL寄存器中,同时设置RTSEN和CTSEN位为1,使能完整的RTS/CTS流控制。 - 理解模式:手册中提到,UART可作为DTE或DCE,信号定义会反转。在大多数嵌入式设备作为终端设备的场景下,我们通常将其配置为DTE模式,即
UART0_RTS是我们的输出(请求发送),UART0_CTS是我们的输入(清除发送)。连接时,本设备的RTS接对端设备的CTS,本设备的CTS接对端设备的RTS,交叉连接。
重要提示:务必在使能UART (
UARTEN=1)之前,通过IOC模块正确配置GPIO的复用功能。如果先使能UART再配置GPIO,UART模块可能会在引脚状态不稳定时检测到错误的起始位或信号跳变,导致通信紊乱或无法启动。
4.2 DMA传输配置:解放CPU的利器
对于需要连续、高速传输数据的应用(如通过串口上传大量传感器数据或下载固件),使用DMA是必不可少的优化手段。
配置步骤:
- 使能UART的DMA功能:设置
UART_DMACTL寄存器的TXDMAE位(使能发送DMA)和/或RXDMAE位(使能接收DMA)。 - 配置μDMA控制器:
- 为UART发送和接收分配独立的DMA通道(例如Channel 1用于发送,Channel 2用于接收)。
- 设置通道的源地址(对于发送,是内存数据地址;对于接收,是
UART_DR寄存器地址)和目标地址(对于发送,是UART_DR寄存器地址;对于接收,是内存缓冲区地址)。 - 配置传输模式:通常使用“基本模式”(Basic),也可以使用Ping-Pong模式实现双缓冲。
- 设置传输数据量(仲裁大小)。
- 理解UART的DMA请求:UART会向DMA控制器发出两种请求:单次请求(Single Request)和突发请求(Burst Request)。
- 发送:当发送FIFO有空间时,发出请求。单次请求对应有1个空位,突发请求对应空位达到FIFO触发水位。
- 接收:当接收FIFO有数据时,发出请求。单次请求对应有1个数据,突发请求对应数据量达到FIFO触发水位。
- 在DMA控制器的通道配置中,你需要选择响应哪种请求(通常选择突发请求以提高效率)。
- 连接与清除:如图19-3所示,UART的DMA请求清除信号需要连接到DMA控制器的“通道活跃”(DMA active)信号。这确保了在DMA传输期间,UART不会重复产生请求。TI的驱动库通常会帮你处理好这些底层连接。
DMA配置示例片段(概念性代码):
// 假设使用TI DriverLib // 1. 配置UART DMA使能 HWREG(UART0_BASE + UART_O_DMACTL) |= (UART_DMACTL_TXDMAE | UART_DMACTL_RXDMAE); // 2. 配置μDMA发送通道(例如通道1) uDMAChannelControlSet(UDMA_CHANNEL_UART0_TX | UDMA_PRI_SELECT, UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_8); uDMAChannelTransferSet(UDMA_CHANNEL_UART0_TX | UDMA_PRI_SELECT, UDMA_MODE_BASIC, txBuffer, (void *)(UART0_BASE + UART_O_DR), bufferSize); // 3. 配置μDMA接收通道(例如通道2) uDMAChannelControlSet(UDMA_CHANNEL_UART0_RX | UDMA_PRI_SELECT, UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 | UDMA_ARB_8); uDMAChannelTransferSet(UDMA_CHANNEL_UART0_RX | UDMA_PRI_SELECT, UDMA_MODE_BASIC, (void *)(UART0_BASE + UART_O_DR), rxBuffer, bufferSize); // 4. 使能DMA通道 uDMAChannelEnable(UDMA_CHANNEL_UART0_TX); uDMAChannelEnable(UDMA_CHANNEL_UART0_RX);配置完成后,当你需要发送数据时,只需将数据放入txBuffer,然后启动DMA传输,CPU就可以去处理其他任务了。数据会自动从内存搬运到UART的发送FIFO并发出。接收亦然。
5. 完整初始化流程、常见问题与调试技巧
5.1 一个完整的UART初始化流程
结合以上所有知识点,一个稳健的UART0初始化流程如下:
- 使能外设时钟:通过PRCM模块使能UART0所在的外设时钟域(运行、睡眠模式)。使用函数
PRCMPeripheralRunEnable(PRCM_PERIPH_UART0)。 - 加载时钟设置:调用
PRCMLoadSet()使能时钟生效。 - 配置GPIO引脚复用:通过IOC模块,将目标GPIO引脚配置为
UART0_RXD、UART0_TXD功能。如果需要硬件流控制,还要配置UART0_RTS和UART0_CTS。 - 禁用UART:写
UART_CTL寄存器,清除UARTEN位。 - 配置波特率:计算并写入
UART_IBRD和UART_FBRD寄存器。 - 配置帧格式和FIFO:写入
UART_LCRH寄存器,设置数据位、停止位、校验位,并务必置位FEN以启用FIFO。这一步也锁存了步骤5的波特率设置。 - (可选)配置中断:设置
UART_IFLS选择中断触发水位,设置UART_IMSC使能所需的中断源,在NVIC中使能UART0中断。 - (可选)配置硬件流控制:如果需要,在
UART_CTL中设置RTSEN和CTSEN。 - (可选)配置DMA:如果需要,设置
UART_DMACTL,并配置μDMA通道。 - 最后使能UART:置位
UART_CTL寄存器的UARTEN位。如果需要,同时置位TXE和RXE。
5.2 常见问题排查速查表
| 现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 完全无通信,TX引脚无波形 | 1. UART模块时钟未使能。 2. GPIO引脚复用功能未正确配置。 3. UARTEN位未使能。4. TXE位被禁用。 | 1. 检查PRCM配置代码。 2. 用万用表或示波器检查引脚电压,或用IO口功能测试程序验证。 3. 确认 UART_CTL寄存器值。4. 确认 UART_CTL寄存器值。 |
| 能发送但不能接收(或反之) | 1.RXE或TXE位未使能。2. 线路连接错误(RX/TX接反)。 3. 对方设备故障或配置错误。 | 1. 检查UART_CTL寄存器。2. 交换RX和TX线缆测试。 3. 用USB转串口工具等已知良好的设备交叉验证。 |
| 通信乱码 | 1.波特率不匹配(最常见)。 2. 数据位、停止位、校验位配置不匹配。 3. 系统时钟源(如HF晶振)精度不够,导致波特率偏差过大。 | 1. 双方设备使用示波器测量位宽度,计算实际波特率。 2. 仔细核对双方 UART_LCRH配置。3. 检查芯片时钟树配置,确保UART时钟源稳定准确。 |
| 高波特率时丢数据 | 1. 未使用FIFO,CPU处理中断不及时。 2. 未使用硬件流控制,缓冲区溢出。 3. 中断优先级太低,被其他中断阻塞。 4. DMA配置错误或未使用。 | 1. 确认UART_LCRH.FEN=1。2. 检查并连接RTS/CTS线,确认 UART_CTL中已使能。3. 提高UART中断优先级。 4. 检查DMA通道配置和传输大小。 |
| 发送完成后系统挂起或复位 | 在发送FIFO未完全清空、BUSY位仍为1时,关闭了UART模块时钟或进入了深度睡眠。 | 在关闭UART或进入低功耗前,循环检查UART_FR寄存器,直到TXFE和BUSY位同时为0。 |
| 中断不触发或频繁触发 | 1. 中断未在NVIC中使能。 2. UART_IMSC中未屏蔽对应中断源。3. UART_IFLS触发水位设置不合理(如接收水位为1/8,则每4字节就中断一次)。4. 中断服务程序(ISR)中未清除中断标志。 | 1. 检查NVIC配置。 2. 检查 UART_IMSC寄存器值。3. 根据数据包大小调整 UART_IFLS。4. 对于需要手动清除的中断(如接收超时 RT、错误中断),确保在ISR中写UART_ICR。 |
| DMA传输卡住或数据不全 | 1. DMA通道未使能或配置错误(源/目标地址、传输大小)。 2. UART的DMA使能位( TXDMAE/RXDMAE)未设置。3. DMA传输完成中断未处理或未重新配置下一次传输。 | 1. 单步调试,检查DMA通道控制寄存器配置。 2. 检查 UART_DMACTL寄存器。3. 实现DMA传输完成中断,并在其中重新装填缓冲区或处理数据。 |
5.3 调试心得与高级技巧
- 活用回环模式:在硬件连接前,先将
UART_CTL.LBE置1,进入内部回环模式。然后自发自收数据。这是验证UART底层驱动代码(波特率计算、数据读写)是否正确的最快方法,无需连接任何外部设备。 - 善用状态寄存器:当通信异常时,第一时间读取
UART_FR(标志寄存器)和UART_RSR/UART_DR高4位(错误状态)。FE(帧错误)往往意味着波特率严重失配或线路干扰;OE(溢出错误)意味着你的程序读取速度跟不上接收速度,需要优化代码或启用流控制/DMA。 - 接收超时中断的妙用:
RT(接收超时)中断在处理不定长数据包时非常有用。设置一个合理的超时时间(例如,对应4-8个字符的时间),当一帧数据发送完毕,线路空闲超过这个时间,就会触发此中断。在ISR中读取FIFO里剩余的数据,即可完整获取一帧。这比依赖特定结束符更可靠。 - 低功耗设计考虑:在CC26x0这种低功耗设备上,UART通信期间外设域是活动的。通信间隙,如果没有数据,应让CPU进入睡眠。此时,可以将UART中断唤醒CPU。更高级的做法是,配合DMA,设置DMA传输完成中断,让CPU只在数据块准备好时才被唤醒一次,最大化睡眠时间。
- 计算波特率误差:波特率误差是通信距离和可靠性的隐形杀手。使用公式
误差(%) = (|实际波特率 - 目标波特率| / 目标波特率) * 100%计算你的配置产生的误差。对于常用的115200波特率,误差最好控制在2%以内,长距离或高速通信则要求更严。CC26x0的分数分频器能让误差变得非常小。