3. STM32基础外设回顾:GPIO、UART、I2C、SPI、定时器的基础配置与调试方法
做嵌入式AI边缘计算,说白了就是把AI模型塞进MCU里跑。但不管上层算法多花哨,最终都得跟物理世界打交道——点个灯、收个传感器数据、跟外设聊聊天。这些全靠基础外设。
我见过不少新手,一上来就追AI框架,结果连GPIO都翻车。LED死活不亮,查半天发现是时钟没开。嗯,这章咱们就把STM32的五个看门外设捋一遍。不追求大而全,只讲你真正用得上的配置和调试技巧。
3.1 GPIO —— 最基础也最容易翻车的外设
GPIO,通用输入输出。说白了就是让芯片引脚变成你的手——可以往外推电平(输出),也可以感知外部电平(输入)。
配置GPIO其实就三步:开时钟、配模式、设电平。但我见过太多人在第一步就跪了。STM32的GPIO挂在AHB总线上,你不开时钟,写再多寄存器也没用。
3.1.1 输出模式配置
// 以STM32F4为例,配置PA5为推挽输出
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // 开时钟
GPIOA->MODER |= GPIO_MODER_MODER5_0; // 设为输出模式
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_5; // 推挽输出
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR5; // 高速
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR5; // 无上下拉
// 输出高电平
GPIOA->BSRR = GPIO_BSRR_BS_5;
这里有个坑——输出速度。我做过一个项目,用PA5输出1MHz的方波驱动LED,结果波形严重失真。查了半天才发现OSPEEDR配的是低速。你想想看,GPIO的输出驱动能力跟速度等级直接相关,配低了高频就上不去。
3.1.2 输入模式配置
// 配置PC13为输入,内部上拉
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;
GPIOC->MODER &= ~GPIO_MODER_MODER13_0; // 输入模式
GPIOC->PUPDR |= GPIO_PUPDR_PUPDR13_0; // 上拉
// 读取电平
uint8_t level = (GPIOC->IDR & GPIO_IDR_ID13) ? 1 : 0;
3.2 UART —— 调试的命脉
做嵌入式开发,UART就是你的眼睛。没有串口打印,你连程序跑到哪了都不知道。我个人习惯,任何新板子到手,第一件事就是调通UART。
3.2.1 基础配置
// USART2配置:PA2-TX, PA3-RX, 115200-8N1
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 配置TX为复用功能
GPIOA->MODER |= GPIO_MODER_MODER2_1; // AF模式
GPIOA->AFR[0] |= 7 << 8; // AF7 = USART2
// 配置RX为复用功能
GPIOA->MODER |= GPIO_MODER_MODER3_1;
GPIOA->AFR[0] |= 7 << 12;
// 设置波特率
USART2->BRR = 84000000 / 115200; // 假设APB1=42MHz, 过采样16
USART2->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
波特率计算是个容易出错的地方。公式很简单:BRR = 时钟频率 / (16 × 目标波特率)。但时钟源是哪个?APB1还是APB2?查清楚再算。
3.2.2 调试技巧
- 打印函数: 重定向printf到UART,用半主机模式或直接写ITM。我推荐用ITM,不占用UART硬件资源。
- 环形缓冲区: 中断接收时一定要用环形缓冲区。否则丢数据是必然的。
- 波特率验证: 用逻辑分析仪抓TX引脚的波形,量一下位宽。1/位宽 = 实际波特率。
3.3 I2C —— 双线制下的优雅与烦恼
I2C只有两根线:SCL和SDA。但正因为简单,反而容易出问题。我调试I2C设备时,最常用的工具就是逻辑分析仪。看波形比看代码快多了。
3.3.1 主机模式配置
// I2C1配置:PB6-SCL, PB7-SDA
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
GPIOB->MODER |= GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1; // AF
GPIOB->AFR[0] |= 4 << 24 | 4 << 28; // AF4 = I2C1
GPIOB->OTYPER |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7; // 开漏
GPIOB->PUPDR |= GPIO_PUPDR_PUPDR6_0 | GPIO_PUPDR_PUPDR7_0; // 上拉
// 配置速率:100kHz标准模式
I2C1->CR2 = 42; // APB1时钟42MHz
I2C1->CCR = 210; // 42MHz / (2*210) = 100kHz
I2C1->TRISE = 43; // 1000ns / (1/42MHz) + 1
3.3.2 常见故障排查
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| SCL无时钟 | 时钟未使能/GPIO配置错 | 检查RCC和MODER |
| SDA一直低 | 从机拉死总线/上拉电阻缺失 | 测SDA电平,加外部上拉 |
| ACK丢失 | 地址错误/从机未就绪 | 用逻辑分析仪看地址字节 |
| 数据错位 | 速率不匹配/噪声干扰 | 降低速率,加滤波电容 |
3.4 SPI —— 高速但挑剔的通信
SPI比I2C快得多,但线也多——至少4根。而且SPI的模式(CPOL/CPHA)配置错了,数据就全乱。我调试SPI时,一定会先看示波器,确认时钟极性和相位对不对。
3.4.1 全双工主机配置
// SPI1配置:PA5-SCK, PA6-MISO, PA7-MOSI, PA4-NSS
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
GPIOA->MODER |= GPIO_MODER_MODER5_1 | GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1;
GPIOA->AFR[0] |= 5 << 20 | 5 << 24 | 5 << 28; // AF5 = SPI1
// 软件NSS
GPIOA->MODER |= GPIO_MODER_MODER4_0; // 推挽输出
GPIOA->BSRR = GPIO_BSRR_BS_4; // 拉高
// SPI配置:模式0, 8位数据, 1MHz
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_SSI | SPI_CR1_SSM | (3 << 3);
// 波特率 = 42MHz / 16 = 2.625MHz
SPI1->CR1 |= SPI_CR1_SPE;
3.4.2 收发数据
// 发送一个字节
SPI1->DR = data;
while(!(SPI1->SR & SPI_SR_TXE));
while(!(SPI1->SR & SPI_SR_RXNE));
uint8_t recv = SPI1->DR;
3.5 定时器 —— 嵌入式世界的节拍器
定时器是STM32里最强大的外设之一。从简单的延时到复杂的PWM、输入捕获、编码器接口,全靠它。我个人觉得,用好定时器,你的嵌入式水平就上了一个台阶。
3.5.1 基础定时中断
// TIM3配置:1ms中断
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
TIM3->PSC = 41999; // 42MHz / 42000 = 1kHz
TIM3->ARR = 999; // 1kHz / 1000 = 1Hz
TIM3->DIER |= TIM_DIER_UIE;
TIM3->CR1 |= TIM_CR1_CEN;
NVIC_EnableIRQ(TIM3_IRQn);
这里有个细节:预分频器PSC和自动重装载ARR的关系。PSC决定计数时钟频率,ARR决定计数周期。两者配合,就能得到任意想要的定时时间。
3.5.2 PWM输出
// TIM3通道1 PWM输出:PA6
TIM3->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; // PWM模式1
TIM3->CCER |= TIM_CCER_CC1E; // 使能输出
TIM3->CCR1 = 500; // 50%占空比
3.6 调试方法总结
说了这么多,其实调试外设就三板斧:
- 看时钟: 外设时钟开了吗?GPIO时钟开了吗?这是90%问题的根源。
- 看配置: 模式对不对?复用功能选对了吗?上下拉、速度、类型都配了吗?
- 看波形: 逻辑分析仪或示波器接上去,看实际信号跟预期是否一致。
嗯,这章内容不少。但别急,这些外设后面每个都会反复用到。尤其是做AI边缘计算时,传感器数据采集(I2C/SPI)、推理结果输出(UART/GPIO)、任务调度(定时器),一个都跑不掉。先把基础打牢,后面才能飞得起来。