☰
STM32端口复用与重映射详解:从原理到实战配置指南
2026/10/13 1:33:54 网站建设 项目流程

1. 从一次调试翻车说起:为什么端口复用和重映射值得单独拎出来讲

刚接触STM32那会儿,我踩过一个至今印象深刻的坑。当时用一款STM32F103的板子调串口,代码里明明把USART1的TX配置成了推挽复用输出,时钟也开了,引脚也设了,可串口助手就是收不到任何数据。折腾了大半天,最后翻参考手册才发现——我用的那个引脚,默认功能是普通的GPIO,要让它变成USART1的TX,光配置GPIO模式还不够,还得把复用功能打开,而且这个引脚到底能不能复用成USART1,得看手册里的复用功能映射表。更坑的是,有些引脚默认复用功能不是串口,得先做重映射才能用。

这件事让我意识到,端口复用和重映射是STM32学习路上绕不过去的一道坎。它不像点灯那么简单直观,也不像中断那样有清晰的触发逻辑,它更像是STM32内部一张复杂的“接线图”——你得知道哪个引脚能接哪个外设,默认接的是什么,想换一根线又该怎么改。很多初学者卡在串口、SPI、I2C、CAN这些外设调不通,十有八九问题就出在复用和重映射没搞对。

这篇内容就是把我这些年在这块踩过的坑、总结的方法、以及实际项目里反复验证过的配置思路,完整地梳理一遍。不管你是刚学STM32的新手,还是已经能跑通几个外设但对其中的引脚配置一知半解的朋友,看完之后应该能对“一个引脚为什么能变成串口TX”“为什么有的引脚要重映射才能用”“重映射之后原来的功能还能不能用”这些问题有清晰的认识。我会尽量用生活化的类比把原理讲透,再配上可以直接抄的配置代码和排查清单,让你下次遇到类似问题能自己搞定。

2. 端口复用和重映射到底在解决什么问题

2.1 先搞懂STM32的引脚为什么“不够用”

STM32的芯片引脚数量是有限的,从最小的LQFP48到最大的LQFP176,引脚数虽然不同,但内部集成的外设却非常多——USART、SPI、I2C、TIM、CAN、USB、SDIO、FSMC等等。如果每个外设都独占一组引脚,那芯片封装得做到脸盆那么大才够。所以ST的设计思路是:让多个外设共享同一组引脚,通过配置决定某一时刻这个引脚到底听谁的。

这就好比一栋写字楼里的会议室,总共就那么几间,但公司有销售部、技术部、人事部都要用。解决办法不是给每个部门盖一间专属会议室,而是排班共用——今天上午销售部用,下午技术部用,谁用的时候把牌子挂上就行。STM32的引脚也是这样,一个PA9引脚,默认可能是普通GPIO,也可以配置成USART1_TX,还可以配置成TIM1_CH2,具体听谁的,由寄存器里的配置位决定。

这种“一个引脚多个身份”的机制,就是端口复用。它解决的核心问题是:在有限的引脚资源下,让芯片能支持尽可能多的外设功能。

2.2 复用和重映射的区别,用一句话说清楚

很多人容易把复用和重映射混为一谈,其实两者是不同层次的概念。

端口复用说的是:一个GPIO引脚,除了能做普通输入输出,还能被“借”给某个外设使用。比如PA9可以复用为USART1_TX,PB6可以复用为I2C1_SCL。这是引脚的基本能力,每个引脚出厂时就已经定义好了它支持哪些复用功能,你只需要在代码里选择启用哪一个。

重映射说的是:某些外设的引脚位置不是固定死的,可以通过配置把它们从默认引脚“搬家”到另一组备用引脚上。比如USART1默认用的是PA9和PA10,但如果你这两个引脚已经被其他功能占用了,可以通过重映射把USART1挪到PB6和PB7上。这相当于给外设换了一组物理接口。

打个比方:复用是“这间会议室可以给销售部用,也可以给技术部用”,重映射是“销售部本来在3楼办公,但3楼满了,可以搬到5楼的备用办公室”。复用是选择功能,重映射是选择位置。

2.3 为什么初学者容易在这块翻车

我观察过很多初学者的调试过程,发现几个高频翻车点。

第一个是只配置了GPIO模式,忘了开复用功能。在STM32F1系列里,配置复用功能需要把GPIO模式设成“复用推挽输出”或“复用开漏输出”,而不是普通的推挽输出。在STM32F4、F7、H7等系列里,还需要额外调用GPIO_PinAFConfig()之类的函数来指定具体的复用功能编号。很多人照着点灯的代码改,把模式改成推挽输出就以为完事了,结果外设根本没接管引脚。

第二个是不知道重映射需要先开AFIO时钟。在STM32F1系列中,重映射相关的配置寄存器属于AFIO(Alternate Function I/O)模块,使用重映射之前必须先使能AFIO时钟。这个坑非常隐蔽,因为AFIO时钟不开,重映射配置写进去也不生效,但代码编译下载都没问题,就是功能不对。

第三个是重映射之后忘了原来的引脚状态。重映射是把外设功能搬到新引脚,但原引脚并不会自动变回普通GPIO,它可能还保留着之前的复用配置。如果原引脚上接了其他电路,可能会产生冲突。

第四个是没查手册就乱配。STM32不同型号、不同封装的复用功能映射表都不一样,同一个引脚在F103上能复用成USART1,在F407上可能就不是这个功能了。不查手册凭感觉配,大概率要返工。

3. 复用功能配置的底层逻辑与实操方法

3.1 从寄存器层面理解复用配置

要真正搞懂复用,得稍微看一下寄存器。以STM32F1系列为例,每个GPIO端口有兩個32位的配置寄存器:GPIOx_CRL负责低8位引脚(0-7),GPIOx_CRH负责高8位引脚(8-15)。每个引脚占4个bit,其中2个bit是MODE位,2个bit是CNF位。

MODE位决定输出速度或输入模式,CNF位决定具体的工作方式。当CNF配置为“复用推挽输出”或“复用开漏输出”时,这个引脚的控制权就从GPIO模块交给了对应的外设模块。这时候你写GPIO_SetBits()是不会改变引脚电平的,因为引脚已经不听GPIO的了,它听外设的。

在STM32F4系列里,机制更复杂一些。每个引脚除了模式寄存器GPIOx_MODER、输出类型寄存器GPIOx_OTYPER、速度寄存器GPIOx_OSPEEDR、上下拉寄存器GPIOx_PUPDR之外,还有两个复用功能选择寄存器GPIOx_AFRL和GPIOx_AFRH。每个引脚用4个bit来选择复用功能编号AF0到AF15。比如PA9要复用成USART1_TX,查手册知道USART1的AF编号是AF7,那就把PA9对应的AFR位设成7。

这种设计的好处是灵活性更高,一个引脚可以支持多达16种复用功能,但代价是配置步骤更多,容易漏掉某一步。

3.2 标准库和HAL库的配置差异

现在大家用的开发方式主要有两种:标准外设库和HAL库。两者在复用配置上的写法差别挺大。

标准库的典型配置长这样:

GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);

HAL库的写法则是:

GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART1_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; // 指定复用功能编号 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

关键区别就在最后那个Alternate字段。标准库在F1系列上不需要指定AF编号,因为F1的复用功能是固定的,配置成复用模式后硬件自动连接。但F4及以后的系列,必须通过AFR寄存器指定AF编号,HAL库用Alternate字段来设置。

我个人的建议是:如果你用的是F1系列,标准库和HAL库都能用,标准库更轻量;如果是F4及以上系列,直接用HAL库或者LL库,因为手动配置AFR寄存器比较繁琐,HAL库封装得比较完善。

3.3 配置复用功能的完整检查清单

不管用什么库,配置一个复用功能引脚时,我习惯按下面这个清单逐项确认:

  1. 使能GPIO时钟:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)或__HAL_RCC_GPIOA_CLK_ENABLE()。这一步漏了,后面全白搭。
  2. 使能外设时钟:比如用USART1就要开USART1的时钟。外设时钟和GPIO时钟是分开的,别搞混。
  3. 查手册确认引脚支持该复用功能:不是每个引脚都能复用成任何外设,必须查数据手册的“Alternate function mapping”表格。
  4. 配置GPIO模式为复用模式:输出用GPIO_Mode_AF_PP或GPIO_Mode_AF_OD,输入用GPIO_Mode_IN_FLOATING或带上下拉的输入模式。
  5. F4及以上系列指定AF编号:通过GPIO_InitStruct.Alternate或直接写AFR寄存器。
  6. 如果用到重映射,先开AFIO时钟并配置重映射寄存器:这一步在F1系列尤其重要。
  7. 初始化外设本身:GPIO配置好只是把线接上了,外设的波特率、数据位、停止位等参数还得单独配。

注意:第3步查手册是最容易被忽略的。我见过有人把PA13配置成USART1_TX,结果怎么都不工作,因为PA13默认是SWDIO调试接口,虽然手册上可能标了某个复用功能,但实际使用时会和调试器冲突。选引脚时一定要避开调试接口和已经占用的功能。

4. 重映射的机制、配置步骤与实战案例

4.1 重映射到底改了什么东西

重映射的本质是修改芯片内部的外设引脚连接关系。STM32内部有一组“选择器”,默认情况下外设信号连接到默认引脚,当重映射使能后,选择器切换到备用引脚上。这个切换是在芯片内部完成的,外部电路不需要改动,但你的PCB设计得提前预留好备用引脚的位置。

以STM32F103的USART1为例,默认引脚是PA9(TX)和PA10(RX)。如果使能USART1重映射,TX和RX就分别变成PB6和PB7。这时候PA9和PA10就不再是USART1的功能了,你可以把它们当普通GPIO用,或者复用成其他外设。

重映射的配置在F1系列里是通过AFIO模块的AFIO_MAPR寄存器完成的。比如USART1重映射对应的是AFIO_MAPR的bit2,写1使能重映射,写0取消重映射。标准库提供了封装函数:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 必须先开AFIO时钟 GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

HAL库在F1系列上的写法类似:

__HAL_RCC_AFIO_CLK_ENABLE(); __HAL_AFIO_REMAP_USART1_ENABLE();

注意,F4及以上系列的重映射机制和F1完全不同。F4系列没有独立的AFIO模块,重映射是通过AFR寄存器选择不同的AF编号来实现的。比如USART1在F4上可以映射到PA9/PA10(AF7),也可以映射到PB6/PB7(AF7),具体用哪组引脚,取决于你初始化了哪组GPIO。换句话说,F4系列的重映射更灵活,不需要专门的“重映射开关”,你配置哪个引脚就用哪个引脚。

4.2 重映射配置的五个关键步骤

不管哪个系列,重映射的配置我总结为五个步骤:

第一步:确认目标引脚支持重映射功能。查数据手册的“Pinouts and pin description”章节,里面会标注每个引脚支持的所有功能,包括重映射后的功能。比如PB6在F103上标注了USART1_TX(重映射),说明它支持。

第二步:使能AFIO时钟(F1系列必须)。这是最容易被漏掉的一步。AFIO时钟不开,重映射寄存器写不进去,但代码不会报错,只是功能不生效。

第三步:配置重映射寄存器。用标准库的GPIO_PinRemapConfig()或HAL库的__HAL_AFIO_REMAP_xxx_ENABLE()宏。注意有些重映射是“部分重映射”,有些是“完全重映射”,比如USART3有部分重映射和完全重映射两种模式,对应不同的引脚组合,配置时要看清楚。

第四步:配置新引脚的GPIO模式。重映射只是把外设信号引到新引脚,新引脚本身还得配置成复用模式,否则信号出不来。

第五步:确认原引脚不再被使用。如果原引脚上接了其他电路,重映射后原引脚可能变成浮空或保留之前的状态,需要根据实际情况处理。

4.3 一个完整的重映射实战:把USART1搬到PB6/PB7

下面以STM32F103标准库为例,展示一个完整的USART1重映射配置过程。假设我们要用PB6作为TX,PB7作为RX,波特率115200。

void USART1_Remap_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 1. 使能时钟:GPIOB、USART1、AFIO RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_USART1 | RCC_APB2Periph_AFIO, ENABLE); // 2. 使能USART1重映射 GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE); // 3. 配置PB6为复用推挽输出(TX) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // 4. 配置PB7为浮空输入(RX) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, &GPIO_InitStructure); // 5. 配置USART1参数 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); // 6. 使能USART1 USART_Cmd(USART1, ENABLE); }

这段代码里,第2步的重映射使能和第3、4步的新引脚配置缺一不可。我实测过,如果只做重映射不配置PB6/PB7的GPIO模式,串口完全没反应;如果只配置GPIO不做重映射,信号还是从PA9/PA10出来,PB6/PB7上什么都没有。

4.4 重映射的常见组合与注意事项

STM32F1系列支持重映射的外设不少,常用的有USART1、USART2、USART3、TIM1、TIM2、TIM3、SPI1、I2C1、CAN等。每个外设的重映射选项和对应引脚都不一样,我整理了一个常用外设的对照表:

外设默认引脚重映射引脚备注
USART1PA9/PA10PB6/PB7完全重映射
USART2PA2/PA3PD5/PD6需要PD端口
USART3PB10/PB11PC10/PC11或PD8/PD9部分/完全重映射
TIM2PA0-PA3PA15/PB3/PB10/PB11部分重映射
TIM3PA6-PA9PB4/PB5/PB0/PB1部分重映射
SPI1PA5-PA7PB3-PB5需要禁用JTAG
I2C1PB6/PB7PB8/PB9完全重映射
CANPA11/PA12PB8/PB9需要AFIO配置

注意:SPI1重映射到PB3-PB5时,PB3和PB4默认是JTAG的JTDO和NJTRST引脚。要使能SPI1重映射,必须先禁用JTAG功能(保留SWD),否则PB3/PB4会被JTAG占用。标准库用GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)来禁用JTAG。这个坑我在实际项目中遇到过,当时调SPI1重映射怎么都不行,后来才发现是JTAG没关。

5. 调试复用与重映射问题的排查思路

5.1 外设不工作时,按这个顺序查

当你配置了复用或重映射,但外设就是不工作时,我建议按下面的顺序排查,基本能覆盖90%的问题:

第一查时钟。确认GPIO时钟、外设时钟、AFIO时钟(F1系列)都开了。用调试器读一下RCC相关寄存器,看看对应位是不是1。我习惯在初始化函数开头把所有相关时钟都使能一遍,避免遗漏。

第二查引脚配置。确认GPIO模式设对了。输出用复用推挽或复用开漏,输入用浮空或上拉。F4系列还要确认AF编号选对了。可以用调试器读GPIOx_MODER和GPIOx_AFRL/AFRH寄存器,看看值是否符合预期。

第三查重映射配置。如果用了重映射,确认AFIO时钟开了,重映射寄存器写对了。F1系列的AFIO_MAPR寄存器可以直接读出来看。

第四查硬件连接。确认新引脚上确实接了线,没有虚焊,没有和其他信号短路。用万用表量一下引脚电压,或者用示波器看有没有波形输出。

第五查外设本身配置。波特率、数据位、停止位、使能位这些参数是否正确。有时候GPIO和重映射都没问题,但外设的使能位忘了置1,照样不工作。

5.2 几个我踩过的典型坑

坑一:重映射后原引脚还在输出。有一次我把USART1重映射到PB6/PB7,但PA9上还接着一个LED。重映射后PA9不再是USART1_TX,但它之前被配置成了复用推挽输出,引脚状态不确定,导致LED微微发亮。后来我把PA9重新配置成普通推挽输出并置低,问题解决。所以重映射后,原引脚最好显式配置成确定的状态。

坑二:F4系列忘了设Alternate字段。从F1转到F4开发时,我习惯性地只配了GPIO_MODE_AF_PP,忘了设GPIO_InitStruct.Alternate。结果引脚模式是对的,但AF编号默认是0(AF0),对应的是系统功能,不是USART。后来加上GPIO_AF7_USART1才正常。F4系列的AF编号一定要查手册,不同外设的AF编号不同。

坑三:重映射和调试接口冲突。前面提到的SPI1重映射到PB3/PB4和JTAG冲突就是典型例子。类似的还有TIM2重映射到PA15,PA15默认是JTDI,也需要禁用JTAG。凡是重映射到PA13-PA15、PB3、PB4这几个引脚的,都要先处理JTAG/SWD冲突。

坑四:部分重映射和完全重映射搞混。USART3有部分重映射和完全重映射两种模式,部分重映射只改TX和RX,完全重映射还会改CTS和RTS。如果只需要TX/RX,用部分重映射就行;如果需要硬件流控,就得用完全重映射。配置时看清楚宏定义,GPIO_PartialRemap_USART3和GPIO_FullRemap_USART3是两个不同的宏。

5.3 用调试器直接看寄存器

很多时候光看代码看不出问题,直接读寄存器最直观。以F1系列为例,几个关键寄存器:

  • RCC_APB2ENR:看GPIO、USART、AFIO时钟是否使能
  • GPIOA_CRL/CRH:看引脚模式配置
  • AFIO_MAPR:看重映射配置
  • USART1_BRR:看波特率配置

用Keil或IAR的调试器,在Watch窗口里直接输入寄存器地址或外设结构体,就能看到实时值。我习惯在初始化完成后打个断点,逐个检查这些寄存器,比盲猜快得多。

6. 复用与重映射的进阶应用与经验总结

6.1 在PCB设计阶段就规划好引脚分配

复用和重映射的问题,最好在画PCB之前就解决。我的习惯是:拿到芯片型号后,先列出项目需要用到的所有外设,然后查数据手册的复用功能表,把每个外设的默认引脚和备用引脚都列出来,再结合PCB布局、走线难度、引脚间距等因素做分配。

比如一个项目要用USART1、SPI1、I2C1、TIM2,默认引脚可能有冲突,这时候就要考虑哪些外设可以重映射,重映射后引脚是否方便布线。提前规划好,能避免后期飞线或者改板。

6.2 用宏定义管理引脚配置

在实际项目里,我习惯把引脚配置写成宏定义,集中放在一个头文件里。这样换芯片型号或者改引脚分配时,只需要改宏定义,不用满代码找。

// board_config.h #define USART1_TX_PORT GPIOB #define USART1_TX_PIN GPIO_Pin_6 #define USART1_RX_PORT GPIOB #define USART1_RX_PIN GPIO_Pin_7 #define USART1_REMAP ENABLE

然后在初始化函数里用这些宏。这样代码可读性好,移植也方便。

6.3 复用功能冲突时的优先级判断

有时候一个引脚被多个外设需要,这时候得判断优先级。我的原则是:调试接口优先,其次是有硬件流控或时钟要求的外设,最后是普通GPIO模拟的功能。比如PA13/PA14是SWD调试接口,除非万不得已,不要把它们复用成其他功能,否则调试器连不上,后续开发很麻烦。

如果确实需要复用调试引脚,可以先把调试接口切到SWD模式(禁用JTAG),释放出PA15、PB3、PB4,但PA13和PA14最好保留给SWD。

6.4 重映射对中断和DMA的影响

重映射只改变外设的物理引脚位置,不影响外设的中断向量和DMA通道。比如USART1重映射后,中断服务函数还是USART1_IRQHandler,DMA请求还是走USART1的通道。这一点可以放心,不需要因为重映射而改中断或DMA配置。

但有一点要注意:重映射后,原引脚如果之前配置了外部中断,需要重新检查。比如PA9之前配了EXTI9中断,重映射后PA9不再是USART1_TX,但EXTI9中断可能还在,如果PA9上有信号变化,还是会触发中断。不需要的话记得关掉。

6.5 我个人的几条经验

第一,新项目尽量选默认引脚,少用重映射。重映射虽然灵活,但增加了配置复杂度和出错概率。除非默认引脚被占用或者布线实在不方便,否则优先用默认引脚。

第二,重映射配置和GPIO配置写在一起,别分开。我见过有人在main函数里配GPIO,在另一个文件里配重映射,结果顺序搞反了,重映射还没使能就配了GPIO,功能不正常。把相关配置放在同一个初始化函数里,按顺序执行,最稳妥。

第三,每次换芯片型号,重新查一遍复用表。不同型号的STM32,即使引脚编号相同,复用功能也可能不同。F103的PA9是USART1_TX,F407的PA9也是USART1_TX,但AF编号可能不一样。别凭记忆配,查手册最靠谱。

第四,用示波器验证输出。配置完复用或重映射后,如果条件允许,用示波器直接量新引脚的波形。有波形说明配置对了,没波形再查代码。这比在代码里反复改效率高得多。

第五,保留一份自己的引脚分配表。每个项目我都维护一个Excel表格,记录每个引脚的功能、复用配置、重映射状态、外部连接。调试时对着表查,一目了然。这个习惯帮我省了很多时间。

复用和重映射这块内容,刚学的时候觉得繁琐,但用多了会发现它其实是STM32灵活性的体现。理解了底层机制,配置起来就是按部就班的事。希望这篇内容能帮你少走一些弯路,下次遇到串口不出数据、SPI没波形的时候,能第一时间想到查复用和重映射配置。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询