4. GPIO复用功能与重映射:AFR寄存器配置、复用功能选择与引脚冲突解决
各位做嵌入式开发的朋友,今天我们来聊聊GPIO复用功能与重映射。说实话,这个知识点在项目里踩坑率极高。我见过不少工程师,明明代码逻辑没问题,板子就是跑不起来,最后发现是引脚功能冲突了。
嗯,咱们直接进入正题。
4.1 AFR寄存器配置——复用功能的“开关”
每个GPIO引脚,除了做普通的输入输出,还能干别的活。比如PA9,你可以让它输出高低电平,也可以让它变成USART1的TX发送数据。这个切换,靠的就是AFR寄存器。
AFR全称是Alternate Function Register,也就是复用功能寄存器。在STM32中,每个端口有16个引脚,对应两个AFR寄存器:AFRL(低8位)和AFRH(高8位)。
每个引脚占用4位,用来选择15种复用功能中的一种。说白了,就是给引脚分配一个“第二职业”。
核心要点:
- AFRL控制Pin0~Pin7,AFRH控制Pin8~Pin15
- 每个引脚4位,取值0~15,对应AF0~AF15
- 复位后默认是AF0,也就是系统功能
配置代码其实很简单,我习惯用HAL库或者直接操作寄存器。直接操作寄存器的方式更直观:
// 将PA9配置为AF7(USART1_TX)
GPIOA->AFR[1] |= (7 << 4); // AFRH,Pin9对应第4位开始
这里要注意,AFR[1]对应的是AFRH,AFR[0]对应AFRL。我刚开始学的时候,经常搞混下标,后来干脆记成:下标0管低8位,下标1管高8位。
4.2 复用功能选择——USART、SPI、I2C、TIM怎么配
不同的外设,对应的AF编号不一样。这个得查数据手册,别靠猜。
我整理了一张常用外设的AF映射表,以STM32F103为例:
| 外设功能 | AF编号 | 常用引脚 |
|---|---|---|
| USART1_TX | AF7 | PA9, PB6 |
| USART1_RX | AF7 | PA10, PB7 |
| SPI1_SCK | AF5 | PA5, PB3 |
| SPI1_MOSI | AF5 | PA7, PB5 |
| I2C1_SCL | AF4 | PB6, PB8 |
| I2C1_SDA | AF4 | PB7, PB9 |
| TIM2_CH1 | AF1 | PA0, PA15 |
配置的时候,先查表找到AF编号,然后设置对应的寄存器。举个例子,配置PA9为USART1_TX:
// 1. 开启GPIOA时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 2. 配置PA9为复用推挽输出
GPIOA->CRH &= ~(0xF << 4); // 清除配置
GPIOA->CRH |= (0xB << 4); // 复用推挽输出,50MHz
// 3. 选择AF7功能
GPIOA->AFR[1] |= (7 << 4);
我个人习惯,配置完一定要检查一下寄存器值。用调试器读一下AFR,确认写进去了。有一次我忘了开启时钟,折腾了半天才发现。
4.3 重映射功能配置——引脚不够用时的救命稻草
什么叫重映射?说白了,就是同一个外设功能,可以换到别的引脚上。比如USART1,默认在PA9/PA10,但你可以把它重映射到PB6/PB7。
为什么要重映射?因为引脚冲突了。你想想看,一个项目里要用USART、SPI、I2C、好几个定时器,引脚就那么几个,不打架才怪。
重映射的配置,主要靠AFIO寄存器。以STM32F103为例,重映射控制寄存器是AFIO->MAPR。
我的经验:重映射之前,一定要先开启AFIO时钟。很多人忘了这一步,结果配置无效。我曾经因为这个,浪费了整整一个下午。
代码示例:将USART1重映射到PB6/PB7
// 1. 开启AFIO时钟
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
// 2. 配置重映射
AFIO->MAPR |= AFIO_MAPR_USART1_REMAP;
// 3. 配置PB6/PB7为复用功能
GPIOB->CRL &= ~(0xFF << 24); // 清除PB6/PB7配置
GPIOB->CRL |= (0xBB << 24); // 复用推挽输出
// 4. 选择AF7
GPIOB->AFR[0] |= (7 << 24); // PB6
GPIOB->AFR[0] |= (7 << 28); // PB7
这里要注意,不是所有功能都能重映射。有些功能只有一组引脚,没得选。所以选型的时候,就得提前规划好引脚分配。
4.4 调试接口(SWD/JTAG)的GPIO复用——小心把调试口搞没了
这个坑,我估计很多人都踩过。SWD接口的SWDIO和SWCLK,默认在PA13和PA14上。JTAG接口占用更多引脚:PA15、PB3、PB4。
如果你把这些引脚配置成普通GPIO,调试器就连不上了。嗯,后果就是——程序烧不进去,板子变砖。
警告:千万不要轻易把SWD引脚配置成普通GPIO!除非你确定不再需要调试功能,或者有备用调试接口。
如果你确实需要释放这些引脚,可以这样做:
// 禁用JTAG,保留SWD
AFIO->MAPR |= AFIO_MAPR_SWJ_CFG_1; // 0x02,仅SWD
// 完全禁用调试接口(慎用!)
// AFIO->MAPR |= AFIO_MAPR_SWJ_CFG_2; // 0x04,全禁用
配置之后,PA15、PB3、PB4就可以当普通GPIO用了。但PA13和PA14还是SWD功能,调试器还能连。
我曾经犯过一个错误:为了省引脚,把SWD全禁用了。结果程序跑飞了,想调试都调不了,只能重新烧bootloader。从那以后,我再也不敢全禁用了。
4.5 实际项目中的引脚冲突解决——一个真实案例
去年我做了一个项目,主控是STM32F103C8T6,要用到:
- USART1(调试打印)
- USART2(与传感器通信)
- SPI1(驱动LCD)
- I2C1(读取温度传感器)
- TIM2_CH1(PWM输出)
引脚一分配,发现冲突了。USART1默认在PA9/PA10,SPI1的SCK在PA5,MOSI在PA7,这些都不冲突。但I2C1的SCL在PB6,而USART1重映射后也在PB6。
怎么办?我的解决方案是:
- USART1保持默认引脚PA9/PA10,不动
- I2C1重映射到PB8/PB9(I2C1支持重映射)
- TIM2_CH1用PA0,不冲突
最终引脚分配表:
| 功能 | 引脚 | 备注 |
|---|---|---|
| USART1_TX | PA9 | 默认 |
| USART1_RX | PA10 | 默认 |
| USART2_TX | PA2 | 默认 |
| USART2_RX | PA3 | 默认 |
| SPI1_SCK | PA5 | 默认 |
| SPI1_MOSI | PA7 | 默认 |
| I2C1_SCL | PB8 | 重映射 |
| I2C1_SDA | PB9 | 重映射 |
| TIM2_CH1 | PA0 | 默认 |
这个案例告诉我们,做项目之前,先画一张引脚分配表。把所有要用到的功能列出来,看看有没有冲突。有冲突就考虑重映射,或者换功能。
避坑指南:
- 先查数据手册的引脚定义表,确认每个功能有哪些可选引脚
- 优先使用默认引脚,减少配置复杂度
- 重映射时,记得开启AFIO时钟
- 调试接口引脚,非必要不改动
- 画PCB之前,用Excel或表格工具做引脚分配
好了,关于GPIO复用功能与重映射,就讲到这里。说白了,就是学会怎么给引脚分配“第二职业”,以及当引脚不够用时怎么“换岗”。这些技能,在实际项目中几乎天天用。希望各位能少踩坑,一次点亮。