4. UART 初始化编程:从时钟到使能,一步都不能少
UART 初始化,说白了就是给这个外设“上电”、“接好线”、“定好规矩”,最后让它开始工作。这四步缺一不可,顺序也不能乱。我见过不少新手,上来就配波特率,结果发现数据根本发不出去——回头一看,时钟都没开。
好,咱们一步步来。这一章,我会带着你手写寄存器,把每个配置位都讲清楚。
4.1 第一步:时钟使能——让 UART 先“通电”
任何外设要工作,第一步就是打开它的时钟。在 STM32 里,这由 RCC(复位与时钟控制)模块负责。你想想看,外设的时钟都不给,它怎么干活?
我个人习惯,在初始化任何外设之前,先看一眼参考手册的 RCC 章节,找到对应外设的时钟使能位。
以 STM32F103 的 USART1 为例,它挂在 APB2 总线上。使能代码很简单:
// 使能 USART1 时钟(APB2 总线)
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
如果是 USART2 或 USART3,它们挂在 APB1 总线上:
// 使能 USART2 时钟(APB1 总线)
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
4.2 第二步:GPIO 复用配置——把引脚“借”给 UART
时钟打开了,但 UART 的 TX 和 RX 引脚还没接好。GPIO 引脚默认是普通 I/O 口,你得告诉它:“嘿,你现在归 UART 管了。” 这就是复用功能配置。
这里有两个关键点:
- 模式: 设置为复用推挽输出(AF_PP)或复用开漏输出(AF_OD)。TX 通常用复用推挽输出。
- 速度: 一般设置为 50 MHz 或更高,保证高速通信时信号不畸变。
我记得有一次,一个同事调试 UART 死活收不到数据,折腾了半天。最后发现是 RX 引脚没配置成浮空输入,而是配成了推挽输出——直接把对方的信号给“掐死”了。
以 USART1 的 TX(PA9)和 RX(PA10)为例:
// 1. 开启 GPIOA 时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 2. 配置 PA9 (TX) 为复用推挽输出,50 MHz
GPIOA->CRH &= ~(0xF << 4); // 先清空 CNF9 和 MODE9 位
GPIOA->CRH |= (0xB << 4); // 0xB = 1011: CNF=10(复用推挽), MODE=11(50MHz)
// 3. 配置 PA10 (RX) 为浮空输入
GPIOA->CRH &= ~(0xF << 8); // 先清空 CNF10 和 MODE10 位
GPIOA->CRH |= (0x4 << 8); // 0x4 = 0100: CNF=01(浮空输入), MODE=00(输入模式)
4.3 第三步:波特率发生器配置——BRR 寄存器的计算
波特率,就是每秒传输的位数。UART 通信双方必须用同一个波特率,否则就是“鸡同鸭讲”。
波特率发生器本质上是一个分频器。它从外设时钟(PCLK)出发,通过一个分频系数,得到目标波特率。这个分频系数就写在 USART_BRR 寄存器里。
计算公式如下:
举个例子,假设 PCLK = 72 MHz,目标波特率 = 115200:
BRR = 72000000 / (16 * 115200) = 72000000 / 1843200 ≈ 39.0625
BRR 寄存器是 16 位的,高 4 位放小数部分(DIV_Fraction),低 12 位放整数部分(DIV_Mantissa)。39.0625 怎么拆?
- 整数部分: 39 (0x27)
- 小数部分: 0.0625 * 16 = 1 (0x1)
所以 BRR 的值 = (39 << 4) | 1 = 0x271。代码实现:
// 假设 PCLK = 72 MHz,目标波特率 = 115200
uint32_t pclk = 72000000;
uint32_t baud = 115200;
uint32_t brr_value = (pclk + (baud * 8)) / (baud * 16); // 四舍五入优化
USART1->BRR = brr_value;
4.4 第四步:数据格式设置——字长、停止位、校验位
波特率定好了,接下来要约定“数据包”的格式。这由 USART_CR1 和 USART_CR2 寄存器控制。
| 参数 | 寄存器位 | 说明 |
|---|---|---|
| 字长 | CR1 的 M 位 | 0 = 8 位数据,1 = 9 位数据 |
| 停止位 | CR2 的 STOP[1:0] 位 | 00 = 1 位,01 = 0.5 位,10 = 2 位,11 = 1.5 位 |
| 校验位 | CR1 的 PCE 和 PS 位 | PCE=1 使能校验,PS=0 偶校验,PS=1 奇校验 |
最常用的配置是:8 位数据、1 位停止位、无校验。也就是所谓的 8N1。
// 配置 USART1 为 8N1 模式
// 字长:8 位(M=0)
USART1->CR1 &= ~USART_CR1_M;
// 停止位:1 位(STOP=00)
USART1->CR2 &= ~USART_CR2_STOP;
// 校验位:无校验(PCE=0)
USART1->CR1 &= ~USART_CR1_PCE;
如果你需要 9 位数据 + 偶校验 + 1 位停止位:
// 字长:9 位(M=1)
USART1->CR1 |= USART_CR1_M;
// 停止位:1 位
USART1->CR2 &= ~USART_CR2_STOP;
// 使能校验,偶校验
USART1->CR1 |= USART_CR1_PCE; // 使能校验
USART1->CR1 &= ~USART_CR1_PS; // 偶校验(PS=0)
4.5 第五步:使能 UART 模块——最后的“点火”
所有配置都写好了,最后一步就是使能 UART。这由 CR1 寄存器的 UE(USART Enable)位控制。
// 使能 USART1
USART1->CR1 |= USART_CR1_UE;
使能之后,UART 就开始工作了。但注意,这时候发送器和接收器还没打开。你还需要分别使能它们:
// 使能发送器(TE 位)
USART1->CR1 |= USART_CR1_TE;
// 使能接收器(RE 位)
USART1->CR1 |= USART_CR1_RE;
我个人习惯,在使能 UE 之前,先把 TE 和 RE 都配好,然后一次性使能 UE。这样能避免一些奇怪的时序问题。
- 使能 UART 时钟(RCC)
- 配置 GPIO 复用功能
- 计算并设置 BRR 寄存器(波特率)
- 配置数据格式(CR1、CR2)
- 使能发送器和接收器(TE、RE)
- 使能 UART 模块(UE)
4.6 本章知识体系总览
下面这张图,把 UART 初始化的整个流程串起来了。你可以把它当作一个检查清单,每次写初始化代码时对照着看。
嗯,到这里,UART 初始化的五个步骤就全讲完了。从时钟到 GPIO,从波特率到数据格式,最后使能模块——每一步都有对应的寄存器操作。你写代码的时候,按这个顺序来,基本不会出错。