复用功能(AF)概念:GPIO 的“第二人格”
GPIO 复用功能,说白了就是让一个引脚干两样活。默认情况下,引脚是普通的输入输出,用来点灯、读按键。但当你需要用 USART 发数据、用 SPI 读传感器、用 I2C 挂设备时,这个引脚就得切换到“第二人格”——也就是复用功能模式。
我刚开始学 STM32 时,总觉得复用功能很玄乎。后来调试一块板子,串口死活不工作,查了半天才发现是引脚模式配成了推挽输出,而不是复用推挽输出。嗯,从那以后我再也没犯过这个错。
什么是 GPIO 复用功能?
每个 GPIO 引脚在芯片内部都连接着多个外设的输入输出信号。比如 PA9,既可以做普通 GPIO,也可以连接到 USART1_TX,还可以做 TIM1_CH2。这些外设信号和 GPIO 之间通过一个多路选择器来切换,而这个选择器的控制权就在 AF 寄存器 手里。
核心要点:复用功能模式下,引脚的电平控制权从 GPIO 输出数据寄存器(ODR)移交给了外设模块。CPU 不再直接写引脚电平,而是由 USART、SPI、定时器等外设自动驱动。
AF 寄存器配置原理
STM32 的每个 GPIO 端口(如 GPIOA)有两组 AF 寄存器:AFRL(低 8 位引脚)和 AFRH(高 8 位引脚)。每个引脚占用 4 位,用来选择 0~15 共 16 种复用功能。
| 寄存器 | 覆盖引脚 | 每引脚位数 | 可选功能数 |
|---|---|---|---|
| GPIOx_AFRL | Pin 0 ~ Pin 7 | 4 bit | AF0 ~ AF15 |
| GPIOx_AFRH | Pin 8 ~ Pin 15 | 4 bit | AF0 ~ AF15 |
配置代码其实很简单,以 PA9 复用为 USART1_TX(AF7)为例:
// 使能 GPIOA 时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
// 配置 PA9 为复用功能模式
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1; // 选择 AF7
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
个人习惯:我每次配完 AF 寄存器后,都会读一下 AFRL/AFRH 寄存器确认写入值。曾经遇到过一次,代码里写的是 AF7,但实际读回来是 AF0——原因是时钟没使能,寄存器写不进去。
AF0 ~ AF15 映射表解读
不同 STM32 系列,AF 映射表略有差异。以 STM32F4 系列为例,常见的映射关系如下:
| AF 编号 | 典型外设 | 说明 |
|---|---|---|
| AF0 | MCO、SWD/JTAG、RTC | 系统功能,一般不动 |
| AF1 | TIM1、TIM2 | 定时器输入捕获/输出比较 |
| AF2 | TIM3~TIM5 | 通用定时器 |
| AF3 | TIM8~TIM11 | 高级/通用定时器 |
| AF4 | I2C1~I2C3 | I2C 总线 |
| AF5 | SPI1、SPI2 | SPI 总线 |
| AF6 | SPI3 | SPI3 |
| AF7 | USART1~USART3 | 串口,最常用 |
| AF8 | UART4~UART5 | 串口 |
| AF9 | CAN1、CAN2、TIM12~TIM14 | CAN 总线 |
| AF10 | OTG_FS、OTG_HS | USB |
| AF11 | ETH | 以太网 |
| AF12 | FSMC、SDIO | 外部存储器接口 |
| AF13 | DCMI | 数字摄像头接口 |
| AF14 | 保留 | — |
| AF15 | EVENTOUT | 事件输出 |
避坑指南:我曾经在 F103 上直接把 F4 的 AF 表套用,结果 SPI 怎么都配不通。不同系列 AF 编号可能不同,比如 F1 系列没有 AF 寄存器,用的是重映射(REMAP)方式。一定要以你所用芯片的参考手册为准。
复用功能配置的完整流程
配置一个引脚为复用功能,我建议按这个顺序来:
- 查手册:确定你要用的外设信号映射到哪个引脚,以及对应的 AF 编号。
- 使能时钟:GPIO 时钟和外设时钟都要使能。
- 配置 GPIO 模式:设置为复用功能模式(AF_PP 或 AF_OD)。
- 设置 AF 寄存器:写入正确的 AF 编号。
- 配置外设:初始化外设本身(如 USART、SPI)。
- 验证:用逻辑分析仪或示波器看引脚波形。
你想想看,如果顺序搞反了——先配外设再配 GPIO,有些外设在初始化时会检测引脚状态,可能导致误触发。我习惯先配 GPIO 再配外设,这样更稳妥。
AF 映射的 SVG 结构图
下面这张图展示了 GPIO 引脚内部从外设信号到引脚输出的完整路径:
从图上可以看得很清楚:多个外设信号都连到了同一个引脚,但最终只有一路能通过 AF 多路选择器到达引脚。这个选择器就是 AFRL/AFRH 寄存器控制的 4 位开关。
常见配置错误与排查
我在项目里遇到过几次 AF 配置问题,总结下来就这几类:
- AF 编号写错:比如 USART1 在 F4 上是 AF7,在 F7 上可能是 AF7 也可能是 AF8,一定要看 datasheet 的引脚复用表。
- 模式配错:复用推挽输出(AF_PP)和复用开漏输出(AF_OD)搞混。I2C 必须用开漏,USART 用推挽。
- 时钟遗漏:只开了 GPIO 时钟,没开外设时钟。这时候 AF 寄存器写不进去,引脚还是普通 GPIO。
- 重映射冲突:有些引脚同时被多个外设占用,比如 PA9 既是 USART1_TX 也是 TIM1_CH2,如果两个外设都使能了,信号会打架。
调试技巧:我习惯在代码里加一段自检——配置完 AF 后,读取 AFRL 或 AFRH 寄存器,打印出来和预期值对比。如果值不对,先查时钟,再查代码逻辑。这招帮我省了不少时间。
复用功能配置其实不难,但细节多。只要把 AF 映射表记牢,配置流程走对,基本不会出大问题。记住:查手册、看时钟、配模式、写 AF、验结果,五步走稳,复用功能就稳了。