ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GD32引脚重映射详解:部分映射、完全映射与AFIO配置

GD32引脚重映射详解:部分映射、完全映射与AFIO配置 做嵌入式开发的人迟早会和引脚重映射打交道。我最早接触GD32重映射是在一块自行设计的控制板上因为走线失误USART0默认的PA9/PA10被一颗大电容旁边的过孔占得死死的飞线很难看不得不查手册找替代引脚。翻遍数据手册的复用功能表最终用一行重映射配置把串口挪到了PB6/PB7板子救回来了但也从那时起我才真正搞懂GD32中部分映射、完全映射以及AFIO寄存器这三个概念之间的关系。这篇文章就围绕GD32重映射的完整配置链展开从原理到寄存器操作再到我实测中遇到的坑一次讲清楚。1. 重映射究竟解决了什么问题引脚冲突、布线与封装限制1.1 重映射不是软件随便换脚而是硬件预留的选择分支很多刚接触GD32的人会把重映射和普通的GPIO复用搞混。GPIO复用是让某个引脚从普通的数字IO口变成串口、定时器、I2C等功能引脚比如把PA9配置为复用推挽模式后这个引脚就承担USART0的TX功能。而重映射是另一层逻辑同一个外设功能在芯片内部存在多组可选的引脚位置通过配置AFIO寄存器中的重映射位选择其中一组作为最终的功能引脚。体感上像是软件换引脚但底层是芯片设计时的硬连线。芯片出厂时USART0的TX信号在芯片内部其实同时连到了PA9和PB6两个引脚对应的输出电路上但默认情况下只有PA9这一组被激活PB6那边被断开。重映射寄存器做的事情不是重新拉一根内部走线而是切换开关——打开PB6的通道同时关闭PA9的通道。明白这一点就能理解为什么重映射要受引脚封装限制如果芯片物理上没有PB6这个引脚那么这个重映射选项即使写在寄存器里也毫无意义。1.2 引脚冲突才是重映射最大的存在价值我做过的几个项目中真正促使我去查重映射表的场景几乎都是引脚冲突。GD32F103C8T6这类48脚小封装芯片引脚资源非常紧张PA9/PA10经常会分配给USB或者别的功能而串口调试又必须保留。这时候把USART0重映射到PB6/PB7等于白捡了一组串口引脚。另一个常见场景是PCB布局优化。MCU放在板子一角串口要连到板子另一侧的连接器如果默认引脚正好分布在芯片两侧走线要绕一圈甚至需要打过孔。通过重映射把串口引脚换到靠近连接器的位置走线长度大幅缩短对高速信号的信号完整性也有帮助。对于带CAN、以太网这类差分信号的板子把敏感的时钟或通信引脚挪远一点还能改善EMC表现。1.3 不是所有外设都有重映射功能这点容易想当然。GPIO的复用功能表确实每个引脚都有但重映射选项只存在于部分外设和部分引脚组之间。在GD32F103中USART0、USART1、定时器0、定时器1、CAN0这些常见外设都有重映射位但I2C、SPI的重映射选项就少很多有些型号根本没有。判断一个外设是否有重映射选项最直接的方法是看对应芯片参考手册中AFIO寄存器章节的重映射位列表而不是去复用功能表里找。GD32各系列的重映射能力差异很大。GD32F10x和GD32F30x是Cortex-M3内核沿用AFIO寄存器的方式控制重映射到了GD32E230这类Cortex-M23内核产品AFIO的概念就被取消改为每个引脚直接配置AF编号类似STM32F4系列的做法。如果你在GD32E230上找gpio_pin_remap_config函数找不到不用慌不是库装错了是架构变了需要用gpio_af_set函数配置复用功能编号。2. 部分映射与完全映射的本质差别USART映射表拆解2.1 部分映射一个重映射位切换一组引脚部分映射英文叫Partial Remap通常只用一个重映射位控制把外设的主要引脚换到另一组位置但并非所有辅助引脚都跟着变。以GD32F103的USART0为例默认映射和部分映射的对应关系如下映射模式USART0_REMAP位TXRX默认映射0PA9PA10部分映射1PB6PB7这种情况下重映射位只有两个取值映射表也只有两行配置逻辑最简单。部分映射的本质是给一组引脚做了一个整体搬移不影响外设功能本身数据位、停止位、波特率这些配置和引脚完全无关。2.2 完全映射两个重映射位的组合编码完全映射英文叫Full Remap通常出现在外设引脚比较多的情况。以GD32F103的USART1为例对应STM32的USART3它除了TX和RX还有CK时钟脚、CTS/RTS硬件流控脚一共5个引脚。这5个信号全部挪到另一组位置就是完全映射如果只挪TX/RX/CK就是部分映射。具体映射关系如下映射模式USART1_REMAPUSART1_FULL_REMAPTXRXCKCTSRTS默认映射00PB10PB11PB12PB13PB14部分映射10PC10PC11PC12PB13PB14完全映射01PD8PD9PD10PD11PD12注意完全映射那一行USART1_REMAP位是0USART1_FULL_REMAP位是1。也就是说这两个位不是简单的都置1实现更彻底的映射而是选择不同映射模式的开关。当需要完全映射时要先确保部分映射位被清除否则映射模式会冲突外设行为无法预测。我见过有人这样写先配置了部分映射后面想升级成完全映射直接在原代码上追加了一行gpio_pin_remap_config(GPIO_USART1_FULL_REMAP, ENABLE)结果两个位同时为1串口彻底不出数据了。这就是没有理解这两个位的组合关系导致的。2.3 为什么会有部分和完全之分封装尺寸与通道数量的权衡这个问题我一开始也想不通既然能把全部引脚都重映射为什么还要搞一个只映射一部分的模式当你把GD32F103C8T6和GD32F103VET6放在一起对比就明白了。C8T6是48脚封装没有PC10/PC11、PD8/PD9这些引脚。如果芯片设计只提供完全映射一种模式那么小封装芯片上的USART1就永远只能使用默认的PB10/PB11。这时候部分映射的价值就出来了——C8T6虽然没有PD8/PD9但有PC10/PC11通过部分映射小封装也能获得第二组串口引脚位置。完全映射则主要服务于大封装芯片。VET6是100脚封装引脚资源丰富PC10/PC11、PD8/PD9都在不仅能提供第二组位置还能顺便解决像流控信号和普通引脚冲突的问题。所以设计部分映射和完全映射两种模式本质上是芯片厂商在封装尺寸和功能灵活性之间做的平衡设计。小封装用户用部分映射解决基本问题大封装用户用完全映射获得更彻底的引脚释放。3. 配置重映射的三个前置条件AFIO时钟、GPIO模式与库函数3.1 最容易漏掉的AFIO时钟重映射写寄存器等于白写GD32F10x系列中重映射寄存器AFIO_PCF0位于AFIO外设模块中而AFIO外设是挂在APB2总线上的。如果AFIO外设的时钟没有使能那么你对AFIO_PCF0寄存器的写入操作实际不会生效但MCU也不会报错程序继续跑功能就是不出来。这在前面的时钟使能那部分就能看出问题/* 正确的时钟使能顺序 */ rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_AFIO); // 很多人漏掉这一行 rcu_periph_clock_enable(RCU_USART0);注意GPIO本身在APB2总线上外设USART0在APB2还是APB1取决于具体型号AFIO也在APB2上。虽然它们共用APB2时钟使能寄存器但每个外设的时钟位是独立的。开GPIO时钟并不会顺带打开AFIO时钟必须显式使能RCU_AFIO。调试这个问题的典型现象是代码下载进去后通过调试器查看GPIOB的配置寄存器引脚模式已经是复用推挽但用示波器量PB6引脚没有任何波形。这时候去看AFIO_PCF0寄存器的实际值USART0_REMAP位还是0就说明写入被吞掉了基本可以断定是AFIO时钟没开。3.2 复用推挽模式是前提但不同信号模式选择有讲究重映射引脚的功能模式与普通复用引脚完全一样。对于串口TX这样的输出信号GPIO模式必须配置为复用推挽GPIO_MODE_AF_PP。如果把引脚配置成通用推挽输出然后去初始化USART信号不会自动连接到USART的外设输出电路上引脚只会保持一个固定电平。对于RX输入引脚GPIO模式要配置为浮空输入或上拉输入具体看外部电路。如果外部没有接上拉或下拉电阻建议配置为上拉输入避免悬空时电平不确定导致误触发。GD32的gpio_init函数一次调用可以统一配置多个引脚但模式混用时需要分开初始化gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6); // TX复用推挽 gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_7); // RX上拉输入引脚速度GPIO_OSPEED的选择也有讲究。对115200波特率的串口来说50MHz的翻转速度完全够用甚至2MHz都够但对于SPI或定时器高频输出就要按实际信号频率选。速度配得过高会引入更多噪声过低则信号上升沿变缓影响通信质量。不过重映射本身不改变信号速度需求这个参数还是按原功能的需求来定。3.3 gpio_pin_remap_config库函数的调用细节GD32标准外设库中重映射配置由gpio_pin_remap_config函数完成原型如下void gpio_pin_remap_config(uint32_t remap, ControlStatus newvalue);第一个参数是重映射选项第二个参数是ENABLE或DISABLE。部分映射和完全映射是独立的两个宏需要分两次调用/* 使能USART1部分映射 */ gpio_pin_remap_config(GPIO_USART1_REMAP, ENABLE); /* 切换到完全映射时先清除部分映射再使能完全映射 */ gpio_pin_remap_config(GPIO_USART1_REMAP, DISABLE); gpio_pin_remap_config(GPIO_USART1_FULL_REMAP, ENABLE);这个函数内部本质是对AFIO_PCF0寄存器做读-改-写操作而不是直接赋值。所以在连续调用多次重映射配置时不需要担心覆盖之前已经设置的其他外设重映射位。不同子系列库的重映射宏命名有差异。GD32F10x中有GPIO_USART0_REMAP、GPIO_USART1_REMAP、GPIO_USART1_FULL_REMAPGD32F30x中由于USART编号不同宏可能变成GPIO_USART0_FULL_REMAP之类。写代码前务必打开对应型号固件库的gd32f10x_gpio.h或gd32f30x_gpio.h头文件搜remap关键字看一眼实际宏定义不要照搬别的型号代码。4. 从部分映射切换到完全映射完整代码与寄存器操作4.1 部分映射实例USART0重映射到PB6/PB7下面是一段完整的USART0部分映射初始化代码平台是GD32F103C8T6。这段代码把USART0从默认的PA9/PA10换到PB6/PB7然后初始化串口参数并发送一个测试字符。#include gd32f10x.h void usart0_partial_remap_init(void) { /* 1. 打开所有需要用到的外设时钟 */ rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_AFIO); rcu_periph_clock_enable(RCU_USART0); /* 2. 配置PB6为TX复用推挽PB7为RX上拉输入 */ gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6); gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_7); /* 3. 使能USART0部分重映射切换到PB6/PB7 */ gpio_pin_remap_config(GPIO_USART0_REMAP, ENABLE); /* 4. 初始化USART0波特率1152008N1 */ usart_deinit(USART0); usart_baudrate_set(USART0, 115200U); usart_word_length_set(USART0, USART_WL_8BIT); usart_stop_bit_set(USART0, USART_STB_1BIT); usart_parity_config(USART0, USART_PM_NONE); usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE); usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE); usart_receive_config(USART0, USART_RECEIVE_ENABLE); usart_transmit_config(USART0, USART_TRANSMIT_ENABLE); usart_enable(USART0); }把这段代码下载到板子后用串口助手接PB6TX、PB7RX应该能正常收到数据。注意这里不需要再初始化PA9/PA10即使它们默认复用为USART0一旦重映射使能USART0的物理引脚连接就被切换到了PB6/PB7。4.2 完全映射实例USART1重映射到PD8/PD9接着上面的工程假设硬件上将USART1默认引脚PB10/PB11占用了改用完全映射到PD8/PD9。注意PD8/PD9只在100脚及以上的封装中存在48脚的C8T6没有这两个引脚要用VET6或ZET6验证。void usart1_full_remap_init(void) { /* 1. 打开时钟 */ rcu_periph_clock_enable(RCU_GPIOD); rcu_periph_clock_enable(RCU_AFIO); rcu_periph_clock_enable(RCU_USART1); /* 2. 配置PD8为TX复用推挽PD9为RX上拉输入 */ gpio_init(GPIOD, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8); gpio_init(GPIOD, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_9); /* 3. 切换映射先清除部分映射再使能完全映射 */ gpio_pin_remap_config(GPIO_USART1_REMAP, DISABLE); gpio_pin_remap_config(GPIO_USART1_FULL_REMAP, ENABLE); /* 4. 初始化USART1其他配置同前 */ usart_deinit(USART1); usart_baudrate_set(USART1, 115200U); usart_word_length_set(USART1, USART_WL_8BIT); usart_stop_bit_set(USART1, USART_STB_1BIT); usart_parity_config(USART1, USART_PM_NONE); usart_hardware_flow_rts_config(USART1, USART_RTS_DISABLE); usart_hardware_flow_cts_config(USART1, USART_CTS_DISABLE); usart_receive_config(USART1, USART_RECEIVE_ENABLE); usart_transmit_config(USART1, USART_TRANSMIT_ENABLE); usart_enable(USART1); }如果库函数头文件中没有GPIO_USART1_REMAP这个宏说明子系列对USART编号的定义不同需要查阅当前型号的固件库。比如某些型号把USART2对应的重映射宏定义为GPIO_USART2_REMAP而这里的USART1在其他上下文里指的是另一个外设编号。这也是GD32和STM32移植时最大的坑之一章节5.3会展开。4.3 寄存器级操作直接改AFIO_PCF0的读改写过程库函数封装得再好调试时也离不开寄存器级理解。AFIO_PCF0寄存器中的USART1_REMAP位是bit3USART1_FULL_REMAP位是bit4。切换到完全映射的直接寄存器操作如下uint32_t pcf0; /* 读取当前AFIO_PCF0值 */ pcf0 AFIO_PCF0; /* 清除USART1_REMAP位(bit3)和USART1_FULL_REMAP位(bit4) */ pcf0 ~(AFIO_PCF0_USART1_REMAP | AFIO_PCF0_USART1_FULL_REMAP); /* 设置完全重映射USART1_FULL_REMAP 1 */ pcf0 | AFIO_PCF0_USART1_FULL_REMAP; /* 写回寄存器 */ AFIO_PCF0 pcf0;这三个步骤——读、改、写——在寄存器操作中缺一不可。如果直接对寄存器赋值会把其他外设已经设置好的重映射位覆盖掉导致莫名其妙的功能失效。库函数gpio_pin_remap_config内部正是做了这样的读-改-写操作只是对使用者透明了。调试技巧在Keil或IAR的寄存器窗口添加AFIO_PCF0表达式执行重映射前后对比寄存器的值可以直观确认映射是否切换成功。我通常会在重映射代码后面加一个断点把AFIO_PCF0的实时值记录下来比对数据手册中的位定义排查映射模式是否与预期一致。5. 重映射配置失败的排查链路三个实测坑5.1 坑一AFIO时钟未使能重映射寄存器写了等于白写这个问题我在第3章提过但因为它实在太高发值得单独作为排查案例讲完整过程。现象描述程序运行后PB6引脚有波形但串口助手上数据显示乱码或完全无数据。用调试器单步执行到重映射函数后查看AFIO_PCF0寄存器发现USART0_REMAP位没有置1。排查链路首先怀疑gpio_pin_remap_config函数没有被正确调用检查代码调用顺序没问题然后查看函数内部的寄存器操作发现它是在对AFIO_PCF0执行位运算理论上应该生效接着查看AFIO外设的时钟使能情况发现在代码中只调用了rcu_periph_clock_enable(RCU_GPIOB)和rcu_periph_clock_enable(RCU_USART0)根本没有使能RCU_AFIO。根因分析AHB/APB总线上的外设如果时钟没打开从总线视角看它就不存在。软件写入的数据到不了目标寄存器读回来永远是复位值。这个坑之所以高发是因为编译器不报错、运行不进入异常一切看起来都正常只有功能不工作。修复方法补上rcu_periph_clock_enable(RCU_AFIO)这一行问题立即消失。从此我形成了固定习惯只要代码里出现gpio_pin_remap_config第一步就检查AFIO时钟是否使能。5.2 坑二部分映射和完全映射同时置位映射模式冲突现象描述先使用USART1部分映射PC10/PC11串口工作正常后来在初始化中追加了完全映射使能PD8/PD9结果整个串口完全不工作。调试发现AFIO_PCF0中USART1_REMAP和USART1_FULL_REMAP两个位都是1。排查链路查数据手册中的映射表发现USART1_REMAP1且USART1_FULL_REMAP1这个组合在映射表中没有定义属于保留状态。芯片的行为只有两种可能要么输出不确定要么内部信号通路出现冲突USART1的实际输出引脚既不在PC10/PC11也不在PD8/PD9而是处于一种悬空或弱驱动的状态。根因分析两个位描述的是同一个外设的引脚映射选择不是两个可以叠加的独立开关。当一个外设有多个重映射位时这些位的所有组合共同决定最终映射结果部分和完全只是两种不同的组合模式不是两种可以叠加的效果。修复方法在任何一次重映射配置前先把同一外设相关的所有重映射位全部清除再设置目标模式。代码上可以用一次读-改-写完成gpio_pin_remap_config(GPIO_USART1_REMAP, DISABLE); gpio_pin_remap_config(GPIO_USART1_FULL_REMAP, DISABLE); gpio_pin_remap_config(GPIO_USART1_FULL_REMAP, ENABLE);这个经验适用于所有带多个重映射位的外设尤其是TIMER0、CAN这类重映射位更多的外设。配置前先清理配置后验证是避免这类问题的通用方法。5.3 坑三GD32与STM32的固件库不兼容重映射宏不能照搬作为一个大量使用STM32代码转为GD32的开发者这个坑我踩了不止一次。网上很多现成的STM32重映射示例代码直接抄到GD32工程中经常出现编译错误或行为异常。现象描述从STM32工程移植USART重映射代码到GD32F103中编译报错提示找不到GPIO_Remap_USART1这个宏。实际在GD32固件库中这个宏的名字变成了GPIO_USART0_REMAP或GPIO_USART1_REMAP与引脚编号和库版本有关。更深层的差异在于外设编号体系。STM32F103中的USART1对应GD32F103中的USART0STM32的USART3对应GD32的USART1。这导致同样是USART1重映射两个平台指向的外设和寄存器位完全不是一回事。如果不了解这个对应关系直接把代码拷过来初始化的是错误的外设引脚配置和重映射位也对不上排查起来极其痛苦。还有一个常见差异是库函数名。GD32标准外设库中重映射宏是在gpio_pin_remap_config函数的头文件中以宏定义形式给出的每个宏对应一个具体的寄存器位STM32标准外设库则倾向于用GPIO_Remap_开头的宏。名称体系完全不同。规避方法以GD32型号对应的数据手册和固件库头文件为唯一依据不要靠记忆或抄代码。在头文件中搜索remap把所有可用宏列出来对照数据手册的重映射表逐一确认。移植代码时先按目标平台的命名规则重写重映射相关部分而不是整体替换。做了一次这样的完整迁移后你会对GD32和STM32外设命名的差异有很深的体感。后面再遇到类似问题基本扫一眼代码就能判断是哪一类命名问题。最后再分享一个实测中的小经验重映射配置完成后如果还是不确定引脚有没有生效可以写一个GPIO翻转测试程序在目标引脚上输出一个方波用示波器或逻辑分析仪直接量。这个方法比单纯读寄存器更直观也更容易定位是映射层的问题还是外设初始化的问题。把重映射的生效验证拆成引脚有信号和外设功能正常两步排查效率会高很多。
返回列表