
你有没有遇到过这种怪事芯片跑得正常烧录也一切顺利你想把 PA15、PB3、PB4 这几个引脚拿出来点个灯、接个外设照着手册里 GPIO 的例程配置成输出模式结果示波器一量引脚电平纹丝不动。代码查了一遍又一遍没找到问题最后翻到参考手册的调试端口章节才恍然大悟——这三个引脚默认根本不属于 GPIO而是被 JTAG 调试接口长期占着。AT32F4xx 系列在这一点上和绝大多数 Cortex-M4 芯片一样上电复位后调试端口控制器默认接管了 PA13、PA14、PA15、PB3、PB4 这五个引脚。你想把这些引脚做成 GPIO 复用功能就必须先处理和 JTAG/SWD 配置相关的释放动作。这篇文章不打算重复网上到处都能查到的“GPIO 八种工作模式”而是把 AT32F4xx 在引脚复用这件事上最容易踩的坑一次说透调试端口为什么默认占用引脚、释放 JTAG/SWD 的正确配置顺序、复用功能的 AF 编号怎么填、以及最让人头疼的“一配就锁死、SWD 连不上”的完整恢复排查链路。无论你是刚从 STM32 生态转过来还是第一次打算把 PA15/PB3/PB4 抠出来做普通 IO这篇都值得收藏备用。1. 先搞清楚一件事PA15/PB3/PB4 为什么默认不是普通 GPIO1.1 调试端口控制器的引脚仲裁逻辑很多刚开始接触 AT32F4xx 的工程师会有一个惯性思维查阅数据手册的 GPIO 章节看到某个引脚有“复用功能表”就觉得只要把 GPIO 寄存器配置成复用模式外设信号就能从引脚上出来。这个想法在半数情况下会翻车因为手册 GPIO 章节之外还有一个容易被忽略的“调试端口”章节那里定义了一组引脚的上电默认归属。这些引脚的归属关系是这样的芯片内部有一个 PIN MUX 仲裁逻辑同一时刻只能有一方控制 PAD 的输入输出通路。上电复位瞬间调试端口控制器Debug Port优先级最高它默认把 PA13、PA14、PA15、PB3、PB4 全部申请到自己名下。你可以把 PAD 理解成一间只有一个大门的办公室GPIO 外设和调试控制器都想在这扇门上班但上电默认把钥匙交给了调试控制器。你想拿回钥匙就得去 AFIOAlternate Function IO那里修改“门禁权限表”也就是配置 SWJ_CFG 相关寄存器位。这个底层机制解释了为什么“明明配置了 GPIO 输出示波器却量不到电平”——并不是你的代码没跑而是你写的电平根本没被送到 PAD 上。GPIO 模块的 ODR 寄存器确实写入成功了但引脚内部的多路开关还停留在调试端口通路GPIO 信号被挡在了里面。引脚JTAG 角色SWD 角色释放后可用作PA13JTMSSWDIOGPIO 或复用功能PA14JTCKSWCLKGPIO 或复用功能PA15JTDI—GPIO 或复用功能PB3JTDOSWOTrace 输出GPIO 或复用功能PB4JNTRST—GPIO 或复用功能这里特别提醒一句PB3 在调试链路里除了 JTDO还兼作 SWOSerial Wire Output是做指令 Trace 和事件跟踪的好帮手。如果你把 PB3 释放成 GPIOSWO 功能也一起没了。很多人在“释放引脚”和“保留调试能力”之间犹豫就是因为没意识到 PB3 身上还挂着这层功能。1.2 不同型号的复用地图与 AF 编号AT32F4xx 是一个跨多款型号的大家族F403A、F407、F413、F415、F425、F435、F437 等虽然 GPIO 复用的整体逻辑一致但 AFAlternate Function编号和可复用的外设组合并不完全相同。固件库里用gpio_pin_mux_config(GPIOx, GPIO_PINS_x, GPIO_MUX_x)来指定 AF 编号第三个参数就是数据手册中“Alternate Function Mapping”表里的列编号。以 PA15 举例这颗引脚在不同系列上可能对应不同的复用组合比如定时器通道、SPI 片选、串口接收等。具体到某个型号PA15 的 AF1、AF5、AF7 分别对应什么外设信号都必须以该型号数据手册里的那张复用映射表为准不能想当然地从 STM32 的经验照搬。PA15 功能示例以实际型号手册为准AF 编号说明JTDI默认调试功能上电默认占用需要先释放SPI1_NSS 等 SPI 类信号查手册对应 AF 列一般出现在 AF5 附近TMR1 通道等定时器信号查手册对应 AF 列不同系列差异较大USART 类信号查手册对应 AF 列需要结合具体引脚编号个人经验是拿到一块新 AT32 板子第一件事不是写代码而是把该型号数据手册的 Pin Definitions 章节、固件库里的gpio_mux_sel_type枚举定义、以及at32f4xx_gpio.c里的 pin mux 表格打印出来对照着看。花十分钟做这张“引脚能力地图”后面改硬件、调驱动能省下半天时间。1.3 复用模式与普通输出模式的区别GPIO 的GPIO_MODE_MUX复用模式和GPIO_MODE_OUTPUT普通输出模式是两个完全不同的概念但几乎每隔一段时间就会有人把两者混用。普通输出模式下引脚电平由你操作 ODR 寄存器直接控制适合点灯、拉高拉低、控制使能脚这类场景。复用模式下引脚输出信号不再由 CPU 直接写 ODR 决定而是由串口、定时器、SPI 这些外设的信号线接管。如果你只是把 USART1_TX 对应的引脚配置成 OUTPUT 模式然后使能串口发送数据数据根本不会出现在这个引脚上——因为外设的 TX 信号没被接到 PAD。输入方向的复用也是一样。UART_RX、SPI_MISO 这些输入信号同样需要把引脚配置成 MUX 模式GPIO 输入通路才会和外设的输入引脚对接上。很多初学者只记得“输出要配模式”输入就随便选 INPUT结果外设收到的数据全是错的或者在浮空引脚上出现随机电平。配置复用模式时有一个容易忽略的细节推挽/开漏、上下拉的选择同样要正确。比如复用为 UART 输出时默认用推挽复用为 I2C 时引脚必须设为开漏并加上拉UART RX 引脚通常配置成浮空输入或上拉输入避免悬空时电平乱跳。这些细节如果留到硬件回来之后再慢慢调会浪费不少调试时间。2. 释放 JTAG/SWD 的完整配置从时钟到 Remap 再到 AF2.1 先确认你需要哪种释放组合释放调试端口不是只有“全关”和“全开”两个选项AT32F403A/407 这一代固件库通过gpio_pin_remap_config提供了两档释放程度宏观上可以理解为配置选择效果最适合的场景默认状态不调用 remapJTAG 和 SWD 都可用产品开发初期代码频繁改动禁用 JTAG保留 SWDPA15/PB3/PB4 释放为普通引脚PA13/PA14 继续作为 SWD 下载调试口引脚不够用但仍需在线调试禁用 JTAG 和 SWD五个调试引脚全部释放为普通引脚量产阶段、引脚极其紧张且确定不再需要调试接口我强烈建议开发阶段优先选择“禁用 JTAG、保留 SWD”。大多数项目的引脚缺口来自 PA15、PB3、PB4 这三个把这三个释放出来已经能解决 80% 的复用需求同时保住了 SWD 的最小调试通路。等到把所有功能都验证完毕、准备出量产固件时再考虑全释放。另外要提醒一点不同 AT32 系列固件库的宏名略有差异。F403A/407 和 F413 这类型号里调试端口释放参数可能写作GPIO_REMAP_SW_JTAG_NOJTAG、GPIO_REMAP_SW_JTAG_DISABLEF435/437 等新系列则可能走的是另一套配置接口。拿到工程后第一件事永远是翻固件库头文件确认你手上这个版本的真实函数名和宏名不要在 StackOverflow 上看到一个名字就照抄。2.2 完整的释放与复用初始化代码下面这个示例基于 AT32F403A/407 固件库完成“释放 PB3/PB4并配置为普通 GPIO 输出”的工作。虽然只是输出模式但整个流程已经涵盖了所有关键步骤时钟、释放、初始化。#include at32f403a_407.h void switch_pb3_pb4_to_gpio(void) { gpio_init_type gpio_init_struct; /* 1. 使能 GPIO 时钟以及 AFIO 时钟 —— 这一步很关键 */ crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_AFIO_PERIPH_CLOCK, TRUE); /* 2. 禁用 JTAG保留 SWDPB3、PB4、PA15 恢复为普通引脚 */ gpio_pin_remap_config(GPIO_REMAP_SW_JTAG_NOJTAG, TRUE); /* 3. 按普通 GPIO 输出初始化 */ gpio_default_para_init(gpio_init_struct); gpio_init_struct.gpio_mode GPIO_MODE_OUTPUT; gpio_init_struct.gpio_out_speed GPIO_OUT_SPEED_50MHZ; gpio_init_struct.gpio_pull GPIO_PULL_NONE; gpio_init_struct.gpio_pins GPIO_PINS_3 | GPIO_PINS_4; gpio_init(GPIOB, gpio_init_struct); /* 4. 验证PB3、PB4 输出高/低电平翻转 */ gpio_bits_set(GPIOB, GPIO_PINS_3 | GPIO_PINS_4); }这段代码里有几个点值得展开。AFIO 时钟AT32F403A/407 这一代的 remap 操作依赖 AFIO 外设必须显式使能CRM_AFIO_PERIPH_CLOCK。很多人从 STM32F4 切过来习惯了“F4 不需要使能 AFIO 时钟”结果 remap 配置写不进寄存器引脚依旧不工作。这个坑不亲自踩一遍真的很难想到。先释放再初始化执行顺序上建议把gpio_pin_remap_config放在 GPIO 初始化之前。虽然大多数情况下先初始化再释放也能最终正常工作但调试口释放后引脚控制权瞬间从调试控制器转交给 GPIO如果 GPIO 还没初始化完成引脚会处于一种短暂的不确定状态。标准写法就是先释放、后配置逻辑上也更清晰。2.3 配置顺序不对的后果顺序问题看似是个小细节实际引发的故障现象却千奇百怪。下面是我见过和实际遇到过的几种典型情况。不使能 AFIO 时钟就调 remap这是最常见的错误。寄存器写入无效引脚状态完全没变。因为 AT32F403A/407 的gpio_pin_remap_config内部操作的是 AFIO 寄存器时钟没开写等于白写。而且这种故障很难从现象上判断是“没释放成功”还是“GPIO 配置错了”只能一步一步排查寄存器值。还有一种风险是把 total disableJTAG 和 SWD 同时禁用写进代码后立刻下载。注意这个行为是单程票——程序一旦运行到 remap 执行的那一行当前调试连接会在瞬间断开因为调试控制器已经对 PA13/PA14 失去了控制权。后面你想再通过调试器读 Flash、看寄存器芯片已经不再应答。很多人第一次遇到“烧完固件就再也连不上”的情况就是死在这一步。所以 2.2 的示例里我特意先写NOJTAG而不是DISABLE不是为了省一行代码而是为了让你手里始终保有一条能救命的 SWD 通路。3. 三个典型复用场景实测串口、定时器 PWM 与 SPI3.1 释放 PB3/PB4 后做调试串口输出把 PB3/PB4 复用作串口收发是释放调试引脚后最常见的需求之一。以 AT32F403A 为例串口外设的默认引脚往往不在这两个脚上需要额外用gpio_pin_mux_config把信号引过来。void uart_pb3_pb4_init(void) { gpio_init_type gpio_init_struct; crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_AFIO_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_USART2_PERIPH_CLOCK, TRUE); /* 释放 JTAG保留 SWD */ gpio_pin_remap_config(GPIO_REMAP_SW_JTAG_NOJTAG, TRUE); gpio_default_para_init(gpio_init_struct); gpio_init_struct.gpio_mode GPIO_MODE_MUX; gpio_init_struct.gpio_out_speed GPIO_OUT_SPEED_50MHZ; gpio_init_struct.gpio_pull GPIO_PULL_NONE; /* TX 引脚 */ gpio_init_struct.gpio_pins GPIO_PINS_3; gpio_init(GPIOB, gpio_init_struct); /* 查数据手册确认 PB3 对应串口的 AF 编号替换 GPIO_MUX_x */ gpio_pin_mux_config(GPIOB, GPIO_PINS_3, GPIO_MUX_x); /* RX 引脚 */ gpio_init_struct.gpio_pins GPIO_PINS_4; gpio_init(GPIOB, gpio_init_struct); /* 查数据手册确认 PB4 对应串口的 AF 编号 */ gpio_pin_mux_config(GPIOB, GPIO_PINS_4, GPIO_MUX_x); /* 后续串口参数初始化略 */ }这里最大的坑就是那个GPIO_MUX_x。不同型号上 PB3/PB4 能复用的串口编号和 AF 编号可能完全不同写错的表现是串口既不发送也不接收看起来像是串口初始化失败实际上是 AF 根本没有选中目标外设。如果你用逻辑分析仪去抓引脚会发现 TX 引脚一直保持空闲电平没有任何帧头。只要意识到是 AF 选择问题翻一下数据手册的复用表马上就能解决。3.2 PA15 复用为 PWM 输出PA15 是 JTDI 引脚释放后拿来输出 PWM 的场景也很多。定时器复用不外乎几件事先释放 PA15、再配置 GPIO 为 MUX 模式、接着配 AF 编号、最后初始化定时器。这里的经验主要在于输出初值和外设调度。void pa15_pwm_gpio_init(void) { gpio_init_type gpio_init_struct; crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_AFIO_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_TMR1_PERIPH_CLOCK, TRUE); gpio_pin_remap_config(GPIO_REMAP_SW_JTAG_NOJTAG, TRUE); gpio_default_para_init(gpio_init_struct); gpio_init_struct.gpio_mode GPIO_MODE_MUX; gpio_init_struct.gpio_out_speed GPIO_OUT_SPEED_50MHZ; gpio_init_struct.gpio_pull GPIO_PULL_NONE; gpio_init_struct.gpio_pins GPIO_PINS_15; gpio_init(GPIOA, gpio_init_struct); /* PA15 复用为定时器通道时AF 编号以手册为准 */ gpio_pin_mux_config(GPIOA, GPIO_PINS_15, GPIO_MUX_x); /* TMR1 的时钟分频、周期、比较值初始化略 */ }一个值得注意的现象是PA15 刚被释放、还没有对外输出有效 PWM 波形的瞬间引脚电平可能不是你想要的状态。定时器的比较输出使能需要几个时钟周期而 GPIO 已经提前切成了 MUX 模式。如果你的负载是电机驱动器或者对电平敏感的外设这个小毛刺会被放大。实际项目中可以在外部电路上加一个下拉电阻把默认电平稳住或者先让定时器输出通道处于禁止状态完成所有初始化后再打开比较输出。3.3 PB3/PB4 复用为 SPI 外设把 PB3/PB4 复用到 SPI 总线也是常见操作尤其是板子上 SPI 设备多、一组 SPI 不够用的时候。SPI 复用的难点往往不在 GPIO 配置而在 NSS 引脚的管理方式。SPI 的 NSS 有两种管理模式硬件 NSS 和软件 NSS。硬件模式下NSS 引脚的电平变化会直接影响 SPI 状态机一不小心就进入 Busy 状态导致通信卡死或数据错位。软件模式下NSS 信号完全由 GPIO 控制你把它当作普通 IO 拉高拉低即可。使用复用引脚做 SPI 时我基本都是把 NSS 配置成软件管理直接用任意一个 GPIO 去控制片选这样能避开很多莫名其妙的通信时序问题。具体到 PB3/PB4 这类原本属于调试口的引脚配置成 SPI 功能前同样要先保证调试口已经释放并且 GPIO 模式是 MUX 而不是 OUTPUT。常有人把片选引脚配成 OUTPUT 模式然后手动拉低这没问题但如果把 MISO 也配成 OUTPUT 去读数据读回来的数据永远是错的因为外设输入通路没有被接通。4. 踩坑实录一配就锁死SWD 连接失败的完整排查链路4.1 故障现场重现通信失败、Flash 读保护……到底发生了什么这个故障场景几乎每个做 AT32 项目的工程师都会遇到一次。现象非常统一写完固件烧录成功程序也跑起来了但下次想把 Keil 或者 J-Link 连上去调试时下载器报错类似 “SWD/JTAG Communication Failure”或者连接超时。遇到这个报错大多数人第一反应是怀疑下载器坏了或者是板子供电不稳。CPU 还在跑程序还正常这就说明芯片没坏、时钟没坏、电源没坏坏的是“调试通路”——因为你在程序里已经执行了禁用调试端口的代码SWCLK 和 SWDIO 在芯片上电后极短时间内就被你配置成普通 GPIO 或者复用功能。调试器再也无法通过这两根线访问内核。另外还有一种更隐蔽的情况当 PA13、PA14 被复用成普通 GPIO 而外部电路又恰好把它们拉到了特定电平时调试器的连接行为会变得非常诡异。比如 SWDIO 被外部强拉低调试器每次通信都失败比如引脚被配置成推挽输出高调试器的数据线上一直读到高电平无法完成握手。这些都会延长排查时间。4.2 按顺序排查从 ISP 到 Connect Under Reset 再到全擦除一旦出现 SWD 完全连不上的情况不要慌下面这张表是我实测下来比较靠谱的恢复优先级。恢复方案成功率操作要点适用阶段IDE 里开启 Connect under Reset低到中复位瞬间调试器抢在代码执行前连接开发初期代码量大且未禁用复位相关逻辑BOOT0 拉高进入 ISP全擦除 Flash高进入系统 Bootloader通过串口 ISP 工具擦除任何阶段最可靠上电瞬间反复点击下载很低碰运气抓窗口只适合应急已有 Bootloader 的远程升级高靠固件自身的升级通道恢复量产后最可靠的还是 ISP 全擦除。具体操作是把 BOOT0 引脚拉高复位芯片芯片进入系统存储区内置 Bootloader然后通过 AT32 配套的 ISP 下载工具连接串口执行全芯片擦除。擦除之后 Flash 里没有用户程序下次上电不会再执行释放 SWD 的代码SWD 自然就能连上了。有一点必须说清楚只要 Flash 里存着禁用 SWD 的程序哪怕你用 Connect under Reset 抓住了复位瞬间也不一定有用。因为芯片的复位向量指向 Flash 用户程序复位之后处理器会立刻执行那段禁用 SWD 的代码。窗口非常短调试器的握手协议根本来不及完成。所以不要把 Connect under Reset 当成万能药它只适合“代码还没跑到禁用 SWD 那一步”的场景比如延时释放。4.3 从源头避免“锁死”延迟释放、按键跳过、固件分离既然全释放是单程票聪明的做法是给这张票加一道保险。我的习惯是在固件里加入基于延时和按键判断的释放逻辑直观、可移植、不依赖额外硬件。void safe_release_swd(void) { volatile uint32_t delay_cnt; /* 上电留出 2~3 秒窗口方便开发阶段调试器连接 */ for (delay_cnt 0; delay_cnt 3000000; delay_cnt); /* 如果 SDK 板上 PA0 被外部跳线拉低则跳过释放保留完整调试能力 */ if (gpio_input_data_bit_read(GPIOA, GPIO_PINS_0) RESET) { return; /* 跳过释放SWD 继续可用 */ } /* 正常流程禁用 JTAG保留 SWD */ gpio_pin_remap_config(GPIO_REMAP_SW_JTAG_NOJTAG, TRUE); }这段代码的价值在于量产固件可以默认走完整释放但开发调试时只要用跳线帽把 PA0 拉低上电后就会跳过释放逻辑SWD 一直可用。哪怕你写了一个 bug 导致程序跑飞重新上电后按住跳线就能恢复调试不需要每次都走 ISP 擦除。固件分离是更彻底的做法调试版固件只禁用 JTAG、保留 SWD量产版固件再做全释放。两块固件用宏区分同样一套工程编译出两个 bin。这样既能保证开发效率又能保证最终产品把引脚利用率做到最高。5. 释放调试引脚之后替代调试方案与板级设计建议5.1 没有 SWD 之后怎么调试一旦把 SWD 也释放了断点调试这条路就基本断了。这时候最实用的替代方案是老牌、可靠的串口日志。你不需要什么花哨工具把 printf 重定向到串口加一个环形缓冲区程序的运行轨迹就能实时上报。我实测下来串口日志在“外设初始化是否成功、中断是否触发、状态机是否按预期跳转”这些场景下调试效率和断点调试差距不大。真正麻烦的是那些靠断点才能发现的时序问题——比如中断嵌套导致的数据竞争或者缓存一致性引发的诡异行为。遇到这种问题没有调试器会很难受所以这也是我反复建议“能留 SWD 就留 SWD”的原因。如果你已经把 SWD 释放了就尽量把一个串口封装成调试口输出分级日志。再配合命令行交互——比如输入命令读寄存器、翻转某个引脚、强制进入某个状态——基本上能覆盖大部分常规调试需求。这套方案比在线调试更容易自动化也方便在现场部署。5.2 板卡设计阶段的引脚规划引脚的占用和释放一定要在画板子之前就想清楚特别是调试引脚。我的建议是PCB 上无论是否需要都把 PA13/PA14 默认接到调试接口插座上。就算你在固件里最终会释放它们硬件上留一组 SWD 测试点或 4Pin 插座成本几乎为零但能在调试阶段节省大量时间。对于 PA15、PB3、PB4 这类可能被复用的调试引脚设计时建议中间串一个 0 欧电阻或者保留跳线。需要调试时焊上电阻保持调试通路需要把引脚给外设时去掉电阻即可。这个成本不到一毛钱却能在“改硬件”和“改固件”之间灵活切换尤其适合开发阶段反复迭代的场景。Boot0 跳线或拨码开关也建议常驻板卡上。哪怕你有那么一两块板子因为全释放“锁死”了只要 Boot0 能方便拉高芯片就永远有得救。很多开发板为了省一个按键把 Boot0 直接拉低一旦固件把调试口全关了这块板子就只能靠外部烧录器用 ISP 救或者直接扔进回收站非常可惜。5.3 最后再提醒一遍的三件事第一件释放调试引脚前把当前工程完整备份。不同固件库版本、不同 AT32 型号的宏名和 AF 编号都存在差异写错是常态。手里有备份改坏了大不了重来没有备份就只能靠 ISP 擦除后重新下载。第二件配置复用功能时AF 编号必须查数据手册不要在博客、论坛帖子里直接抄。同一颗引脚在不同系列上的 AF 映射可能完全不同我就在这个上面吃过亏。串口映射到错误 AF 之后外部看起来像硬件故障实际只是差了一个数字。第三件量产后非必要不要全释放。即使确定了量产固件也尽量保留 PA13/PA14 的 SWD 能力到最后一版。万一现场有设备需要升级、排查故障一个可连接的调试口比什么都管用。引脚不够用优先释放 PA15、PB3、PB4 已经能解决绝大多数扩展需求了。我手头这块 AT32F403A 的板子最初就是图省事一口气把 JTAG 和 SWD 全关了结果后面每次改程序都得先拿串口 ISP 擦一遍 Flash麻烦得够呛。后来学乖了调试阶段只禁用 JTAG、保留 SWD直到最后一个版本才考虑全释放。这个习惯帮我至少避免了三次“板子变砖”事故。如果你正准备把 PA15/PB3/PB4 拿出来做复用建议先按这篇文章里的模板跑通一次释放流程再决定后面到底要释放到哪一步。