STM32引脚重映射原理与实战:解决外设冲突的硬件设计技巧

发布时间:2026/8/2 17:43:40
STM32引脚重映射原理与实战:解决外设冲突的硬件设计技巧 1. 从“引脚不够用”说起为什么需要重映射最近在做一个基于STM32F103的项目需要用到两个串口USART1和USART3和一个SPI接口。画原理图的时候我发现一个尴尬的问题USART1的默认TX/RX引脚PA9/PA10和SPI1的默认SCK/MOSI引脚PA5/PA7冲突了。更麻烦的是PA9和PA10我还想用来接一个LED指示灯和按键因为板子空间有限引脚资源非常紧张。那一瞬间我感觉要么换一个引脚更多的芯片成本增加要么就得重新设计功能牺牲一些特性。就在我纠结的时候突然想起了STM32参考手册里提到的一个功能引脚重映射Remap。这个功能允许我们把某些外设的默认引脚切换到芯片提供的另一组备用引脚上。比如USART1除了默认的PA9/PA10还可以重映射到PB6/PB7上SPI1也可以从PA5/PA6/PA7切换到PB3/PB4/PB5。这简直就像给芯片的引脚做了一次“乾坤大挪移”瞬间解决了我的冲突问题让PA9/PA10得以释放出来做GPIO用。所以重映射到底是什么简单说它就是芯片设计者预留的一扇“后门”。一颗STM32芯片内部集成了几十个甚至上百个外设如USART、I2C、SPI、定时器等但芯片的物理引脚是有限的。不可能每个外设都独占一组永不冲突的引脚。因此芯片设计时就给一些常用的、可能产生冲突的外设设计了多套“引脚方案”。默认使用一套当这套引脚被占用或出于PCB布线考虑不方便使用时我们就可以通过配置特定的寄存器“命令”这个外设换到另一套引脚上去工作。这个过程对程序本身是透明的你操作USART1的寄存器发送数据无论它映射在PA9还是PB6上代码完全一样硬件会自动帮你把信号“搬运”到正确的物理引脚上。理解并掌握重映射是玩转STM32尤其是引脚资源紧张的中低端型号的必备技能。它能让你在有限的硬件资源下实现更灵活的电路设计避免因为引脚冲突而频繁“打板”的窘境。2. 重映射的底层逻辑AFIO与复用功能要理解重映射必须先搞明白STM32引脚的两个核心概念复用功能Alternate Function, AF和复用功能重映射和调试I/O配置寄存器AFIO。首先STM32的GPIO引脚绝非简单的输入输出。每个引脚在芯片内部都通过一个叫做“复用功能选择器”的开关连接到了多个潜在的目的地。这个引脚可以当作普通的数字输入/输出GPIO模式也可以“复用”给某个内置外设使用比如变成USART的TX或者SPI的SCK。当你将某个引脚配置为复用推挽输出模式并指定它的复用功能为USART1_TX时芯片内部就相当于把这个引脚从“GPIO电路”切换到了“USART1发送电路”上。此时你向USART1的数据寄存器写值信号就会从这个引脚输出。这是复用功能的基本操作。那么重映射又是什么可以把它理解为“复用功能选择器”的升级版或者说是第二层开关。以USART1为例默认复用功能PA9 作为 USART1_TX PA10 作为 USART1_RX。这是第一层连接是出厂设置。重映射后的复用功能PB6 作为 USART1_TX PB7 作为 USART1_RX。要实现这个切换就需要一个额外的控制单元来改变“USART1信号线”与“具体哪个物理引脚”的连接关系。这个额外的控制单元就是AFIO模块。AFIO有一组专用的寄存器比如AFIO_MAPR重映射和调试I/O配置寄存器。在这个寄存器里有专门的位来控制各个外设的重映射状态。例如USART1_REMAP这一位当它为0时USART1使用默认引脚PA9, PA10。当它为1时USART1使用重映射引脚PB6, PB7。当你通过程序设置AFIO_MAPR的USART1_REMAP1后芯片内部就进行了一次“线路切换”把USART1的发送和接收信号通路从连接PA9/PA10的开关拨到了连接PB6/PB7的开关上。这里有一个非常重要的顺序问题也是新手最容易踩坑的地方必须先开启AFIO的时钟才能配置AFIO的相关寄存器包括重映射。因为STM32的任何外设在使用前都必须开启其时钟AFIO也不例外。在标准外设库中对应的函数是RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);。在HAL/LL库中重映射配置通常被集成在外设初始化函数里但底层原理不变。注意重映射和调试接口如SWD的SWCLK、SWDIO也由AFIO管理。错误的重映射配置可能会导致下载器无法连接芯片如果遇到无法下载程序的情况需要检查AFIO_MAPR中是否意外禁用了SWJ调试端口。3. 完全重映射与部分重映射以定时器为例重映射并非只有“开”和“关”两种状态。对于一些功能复杂的外设比如高级定时器TIM1/TIM8或者CAN总线STM32提供了更灵活的部分重映射Partial Remap和完全重映射Full Remap选项。我们以STM32F103的高级定时器TIM1为例它的通道1CH1可以输出PWM波。这个通道对应的引脚有多种可能默认复用功能TIM1_CH1 映射到 PA8。部分重映射TIM1_CH1 映射到 PE9。同时TIM1的其他通道如CH2, CH3, CH4和互补输出通道CH1N, CH2N...也会发生一组特定的引脚变化。部分重映射只会改变一部分引脚另一部分可能保持默认或变为另一组备用引脚。完全重映射TIM1_CH1 映射到 PE9。此时TIM1所有的通道包括互补输出、刹车输入等的引脚都会切换到另一套完全不同的引脚组上。完全重映射提供了最彻底的引脚变更方案。在AFIO_MAPR寄存器中对于TIM1有两个控制位TIM1_REMAP[1:0]。00: 无重映射默认。01: 部分重映射。11: 完全重映射。 注意某些型号可能没有10这个状态为什么要设计两种模式主要是为了应对不同的PCB布局和外围电路连接需求。例如我的板子上PA8这个位置被一个重要的传感器占用了但我只需要用TIM1的通道1和通道2输出PWM其他通道不用。这时我就可以选择“部分重映射”只把CH1和CH2挪到PE9和PE11上而其他引脚保持原样这样对整体布局的影响最小。如果我需要用到TIM1的所有功能并且默认引脚组全部冲突那么“完全重映射”就是唯一的选择。在数据手册或参考手册的“引脚定义”章节通常会有一个非常详细的表格列出每个外设在“默认”、“部分重映射”、“完全重映射”下对应的具体引脚。在进行硬件设计前务必仔细查阅这个表格。4. 实战在HAL库中配置USART1重映射理论讲完了我们来看具体怎么操作。现在STM32开发主要用CubeMX图形化配置和HAL库这极大简化了重映射的流程。假设我们要把USART1重映射到PB6和PB7。4.1 使用STM32CubeMX进行图形化配置打开CubeMX选择你的芯片型号例如STM32F103C8T6。在引脚分布图界面找到USART1。默认它应该显示在PA9和PA10上。直接点击PA9或PA10上的“USART1_TX”或“USART1_RX”标签。在弹出的引脚功能列表中你会看到除了“USART1_TX”外还有一个“USART1_TX (Remap)”或类似的选项后面跟着目标引脚比如“PB6”。选择“USART1_TX (Remap) to PB6”。你会发现PA9上的标签消失了而PB6上出现了“USART1_TX”的标签。同理将USART1_RX重映射到PB7。配置USART1参数波特率、字长、停止位、校验位等在左侧的“Connectivity” - “USART1”中配置。生成代码。CubeMX会自动在生成的main.c的MX_USART1_UART_Init()函数中完成所有必要的重映射配置和USART初始化你无需手动操作AFIO寄存器。4.2 手动编写代码基于HAL库如果你想了解底层或者在不使用CubeMX的情况下操作可以这样写// 1. 使能相关时钟必须 __HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB时钟因为我们要用到PB6,PB7 __HAL_RCC_USART1_CLK_ENABLE(); // 使能USART1时钟 __HAL_RCC_AFIO_CLK_ENABLE(); // 使能AFIO时钟重映射必备 // 2. 配置重映射 (关键步骤) // 对于STM32F1系列使用 __HAL_AFIO_REMAP_USART1_ENABLE() 宏 __HAL_AFIO_REMAP_USART1_ENABLE(); // 对于其他系列重映射可能集成在GPIO初始化中但F1系列需要单独调用此宏。 // 3. 初始化GPIO为复用功能 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_6 | GPIO_PIN_7; // PB6, PB7 GPIO_InitStruct.Mode GPIO_MODE_AF_PP; // 复用推挽输出 (TX) / 在RX引脚上实际应为浮空输入此处简化HAL库初始化函数内会分别设置 GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); // 4. 初始化USART1外设 UART_HandleTypeDef huart1; huart1.Instance USART1; huart1.Init.BaudRate 115200; huart1.Init.WordLength UART_WORDLENGTH_8B; huart1.Init.StopBits UART_STOPBITS_1; huart1.Init.Parity UART_PARITY_NONE; huart1.Init.Mode UART_MODE_TX_RX; huart1.Init.HwFlowCtl UART_HWCONTROL_NONE; huart1.Init.OverSampling UART_OVERSAMPLING_16; if (HAL_UART_Init(huart1) ! HAL_OK) { Error_Handler(); }在HAL_UART_Init()函数内部HAL库会调用HAL_UART_MspInit()回调函数我们通常在这个函数里放置GPIO初始化和时钟使能代码。但重映射的宏调用 (__HAL_AFIO_REMAP_USART1_ENABLE())必须在HAL_UART_MspInit()之外、且在GPIO初始化之前执行因为它是改变引脚连接关系的根本操作。4.3 一个常见的坑重映射后原默认引脚的状态成功重映射USART1到PB6/PB7后原来的PA9和PA10会怎样它们自动恢复为普通的GPIO功能你可以像使用其他任何GPIO一样去配置它们输入、输出、模拟输入等。但是这里有一个细节在重映射之前如果你已经初始化了PA9/PA10为USART功能重映射后记得重新初始化PA9/PA10为你想要的新功能比如普通输出驱动LED否则它们可能处于不确定的状态。5. 排查重映射失败的典型问题链重映射配置听起来简单但实际调试时如果串口没数据往往让人一头雾水。下面是一个系统性的排查思路模拟真实的调试过程现象已将USART1重映射至PB6/PB7程序下载后用USB转TTL模块连接PB6/PB7上位机接收不到任何数据发送数据单片机也无反应。第一步确认硬件连接检查接线TX接RXRX接TX。PB6是TX应接USB转TTL模块的RXPB7是RX应接模块的TX。用万用表通断档检查连接是否可靠有没有虚焊。检查共地单片机GND必须和USB转TTL模块的GND连接在一起这是通信的基础。检查电源确保单片机供电正常电压稳定。第二步确认软件配置从底层到上层时钟使能了吗这是最容易被忽略的一步。打开调试器在main()函数开始处设置断点查看RCC-APB2ENR寄存器的值。确认AFIOEN(Bit 0)、IOPBEN(Bit 3)、USART1EN(Bit 14) 这些位都被置1了。如果没有回去检查代码是否漏了__HAL_RCC_AFIO_CLK_ENABLE()等语句。重映射寄存器配置成功了吗查看AFIO-MAPR寄存器。对于USART1重映射USART1_REMAP(Bit 2) 应该为1。可以在程序中通过printf(“AFIO_MAPR: 0x%08X\n”, AFIO-MAPR);来打印前提是其他串口可用或者直接在调试器的“Register”窗口查看。GPIO模式配置正确吗查看GPIOB-CRL寄存器因为PB6/PB7是低8位。PB6TX应配置为复用推挽输出模式Alternate function output Push-pull在寄存器中表现为CNF[6]1, MODE[6]2或3取决于速度。PB7RX应配置为浮空输入或上拉输入Floating input / Input with pull-up即CNF[7]1, MODE[7]0。如果模式不对信号无法正确输入输出。USART外设本身配置正确吗检查USART1-BRR波特率寄存器计算值是否正确例如72MHz系统时钟115200波特率BRR0x1D4C。检查USART1-CR1寄存器UE(USART Enable)、TE(Transmitter Enable)、RE(Receiver Enable) 位是否置1。第三步使用示波器或逻辑分析仪如果以上软件检查都无误就该请出硬件工具了。测TX线PB6程序如果一直循环发送一个固定数据比如0x55二进制01010101用示波器测量PB6引脚应该能看到一个周期性的、符合波特率的方波信号。如果是一条直线高或低说明GPIO根本没输出回到第二步检查GPIO模式。如果有波形但形状奇怪、幅度不对检查硬件连接和上拉电阻。测RX线PB7从USB转TTL模块向单片机发送数据如0xAA测量PB7引脚应该能看到来自模块的方波信号。如果看不到可能是模块问题或接线问题。第四步交叉验证与简化测试换回默认引脚暂时注释掉重映射的代码将串口改回PA9/PA10连接测试。如果此时通信正常那问题100%出在重映射相关的配置上。简化测试程序写一个最简单的程序只做重映射和串口发送不涉及中断、DMA等复杂逻辑持续发送一个字节。排除其他模块的干扰。通过以上四步基本上能定位99%的重映射相关通信故障。核心就是时钟 - 重映射位 - GPIO模式 - 外设使能这个顺序不能乱每一步都要用寄存器视图或调试手段确认。6. 重映射的进阶应用与设计考量掌握了基本操作和排错我们来看看重映射在项目设计中更高级的用法和需要注意的地方。6.1 动态重映射一般不推荐有没有可能在程序运行中动态切换重映射比如上电时用PA9/PA10通信运行一段时间后切换到PB6/PB7从技术上讲修改AFIO_MAPR寄存器是可以的。但是强烈不建议这样做。原因在于当你改变重映射配置时对应外设的引脚连接会瞬间切换。如果此时外设正在工作比如串口正在发送一帧数据会导致信号输出混乱通信必然中断。而且切换前后两组GPIO的初始化状态上拉/下拉、速度等可能不同容易引发意想不到的电气问题。正确的做法是在系统初始化阶段外设开始工作之前就确定好引脚映射并一次性配置完成。6.2 PCB布局与信号完整性重映射给了我们布线灵活性但也要用在刀刃上。在选择重映射引脚时需要考虑信号质量对于高速信号如SPI、SDIO优先选择引脚位置靠近相关外设模块、走线可以更短的备用引脚以减少信号反射和延迟。电源与地模拟外设如ADC、DAC的输入通道重映射时要考虑目标引脚是否远离数字噪声源如高速时钟线、开关电源路径。引脚功能冲突重映射是为了解决冲突但不要“拆东墙补西墙”。确保新的引脚组不会与板上其他已经确定功能的器件如EEPROM的I2C、外置Flash的SPI产生新的冲突。在原理图设计阶段就要用表格列出所有外设的引脚需求包括默认和所有可重映射选项进行全局规划。6.3 与调试接口的冲突这是一个致命的坑。STM32的调试接口SWD/JTAG也占用了一些引脚如PA13/SWDIO, PA14/SWCLK, PA15/JTDI, PB3/JTDO, PB4/NJTRST。AFIO_MAPR寄存器中也有控制调试端口重映射的位如SWJ_CFG。如果你重映射的外设恰好用到了这些引脚例如将SPI1完全重映射PB3变成了SPI1的SCK并且没有正确配置SWJ_CFG位例如禁用了JTAG但保留了SWD那么很可能导致程序下载后调试器再也连不上芯片了因为调试引脚被你的外设占用了。解决方案优先使用SWD接口它只占用PA13和PA14两根线与更多引脚的重映射兼容性好。仔细配置SWJ_CFG位如果重映射使用了PA15、PB3、PB4你需要将SWJ_CFG设置为“禁用JTAG启用SWD”模式。在CubeMX的“System Core” - “SYS” - “Debug”里可以方便地选择。预留调试接口在PCB设计时即使计划使用重映射占用这些引脚也最好把SWD的测试点留出来。万一需要恢复可以通过Bootloader模式或使用复位时按住特定按键进入“无调试”模式来擦除程序再重新下载一个不占用这些引脚的简单程序。7. 超越F1在其他STM32系列中看重映射STM32F1系列的重映射机制相对独立和明显通过AFIO_MAPR寄存器。但在更现代的STM32系列如F4, H7, G0等中重映射的概念被更通用、更强大的GPIO复用功能选择器GPIOx_AFR所取代。在这些系列中每个引脚可以复用的功能更多可能有十几种AF0到AF15。你不再需要去设置一个独立的“重映射使能位”而是直接配置该引脚的“复用功能编号Alternate Function Number”。例如在STM32F407上USART1_TX可以映射到多个引脚PA9: 对应复用功能 AF7 (USART1)PB6: 对应复用功能 AF7 (USART1)此时你想用PB6作为USART1_TX只需要使能GPIOB和USART1时钟。设置PB6的模式为复用功能GPIO_MODE_AF_PP。设置PB6的复用功能选择寄存器为AF7GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_USART1)。整个过程更直观更像是在一个“功能-引脚”映射表中进行选择而不是去“开启”某个重映射开关。这对于从F1过渡到新系列的开发者来说需要转变一下思维。新系列的数据手册中会有一个庞大的“复用功能映射表”是硬件设计的核心参考资料。无论是F1的“重映射开关”还是新系列的“AF选择”其本质都是解决有限引脚与丰富外设之间的矛盾。理解了这个本质无论面对哪个系列的STM32你都能游刃有余地规划引脚让芯片的每一根“触角”都发挥出最大的价值。