一、前言:为什么要禁用 JTAG 保留 SWD?
1.1 STM32F103C8T6 调试接口硬件特性
STM32F103C8T6 是入门级 ARM Cortex-M3 内核 MCU,Flash=64KB、RAM=20KB,因性价比成为工业控制、物联网、创客项目的首选。其调试接口默认支持JTAG和SWD两种模式,引脚映射如下表:
| 调试模式 | 占用引脚 | 功能说明 |
|---|---|---|
| JTAG | PA13(JTMS/SWDIO)、PA14(JTCK/SWCLK)、PA15(JTDI)、PB3(JTDO)、PB4(NJTRST) | 5 引脚调试,支持在线编程 / 调试 |
| SWD | PA13(SWDIO)、PA14(SWCLK) | 2 引脚调试,兼容 JTAG 功能,更节省引脚 |
从表中可见:JTAG 模式占用了 PB3、PB4 两个通用 GPIO 引脚,而 STM32F103C8T6 总共仅 37 个 GPIO(实际可用约 30 个),在继电器、指示灯、串口等外设密集的项目中,PB3/PB4 的占用会直接导致硬件资源不足。
1.2 PB3/PB4 被 JTAG 占用的痛点
在实际项目中,开发者常遇到以下问题:
- 硬件设计阶段未考虑 JTAG 占用,导致 PB3/PB4 焊接了继电器 / 指示灯,但代码中无法驱动;
- 直接将 PB3/PB4 配置为普通 GPIO,编译无错误但硬件无响应;
- 禁用 JTAG 后误关闭 SWD,导致芯片 “锁死”(无法下载代码);
- HAL 库宏使用错误,出现 “隐式声明”“未定义符号” 等编译 / 链接错误;
- 寄存器操作时因
SWJ_CFG位偏移理解偏差,导致配置失效。
1.3 禁用 JTAG + 保留 SWD 的核心价值
| 核心价值 | 具体说明 |
|---|---|
| 资源复用 | 释放 PB3/PB4 作为普通 GPIO,驱动继电器、指示灯、传感器等外设 |
| 调试不丢失 | 保留 SWD(PA13/PA14),仍可通过 ST-Link 下载代码、在线调试(断点 / 变量监控) |
| 兼容性强 | SWD 模式仅需 2 个引脚,适配小型硬件设计(如贴片 ST-Link、排针简化) |
| 稳定性高 | 寄存器 / HAL 库 / CubeMX 三种实现方式,适配不同开发习惯(新手 / 资深开发者) |
| 配置精准 | 明确SWJ_CFG位偏移细节,确保寄存器操作一次生效 |
二、STM32 调试接口底层原理(从硬件到寄存器)
2.1 JTAG 与 SWD 的技术差异
JTAG(Joint Test Action Group)是传统调试协议,需 5 个引脚,兼容性强但引脚占用多;SWD(Serial Wire Debug)是 ARM 推出的精简调试协议,仅需 2 个引脚,传输效率更高,是 STM32 的主流调试方式。两者核心差异如下表:
| 特性 | JTAG | SWD |
|---|---|---|
| 引脚数量 | 5 个 | 2 个 |
| 传输速率 | 较低(依赖时钟频率) | 较高(双向串行传输) |
| 硬件复杂度 | 高(需 5 路布线) | 低(仅 2 路布线) |
| 兼容性 | 支持所有 ARM 芯片 | 仅支持 Cortex-M 系列 |
| 功耗 | 较高 | 较低(适合低功耗场景) |
2.2 AFIO 外设与引脚重映射核心逻辑
STM32 的 GPIO 引脚分为 “通用功能” 和 “复用功能”,调试接口属于复用功能。要将 PB3/PB4 从 JTAG 复用功能切换为通用 GPIO,需通过AFIO(Alternate Function IO)外设配置引脚重映射。
AFIO 的核心作用是:
- 配置 GPIO 的复用功能映射(如 JTAG/SWD、串口、定时器等);
- 控制外部中断的引脚选择;
- 实现 GPIO 引脚的重映射(如串口 1 从 PA9/PA10 映射到 PB6/PB7)。
对于 STM32F1 系列,AFIO 的寄存器基地址为0x40010000,其中AFIO_MAPR(重映射和调试 I/O 配置寄存器)是控制调试接口的核心寄存器,地址为0x40010004。
2.3 SWJ_CFG 位的配置规则(寄存器级精准解析)
STM32F1 系列AFIO_MAPR寄存器中,SWJ_CFG位的正确偏移是bit26~bit24,这是配置生效的关键!
SWJ_CFG[2:0]位(bit26~bit24)决定了 JTAG/SWD 的使能状态,是释放 PB3/PB4 的核心。其配置规则如下表(STM32F1 系列专属):
| SWJ_CFG [2:0] 值 | 二进制 | 左移偏移 | 配置名称 | 释放引脚 | 调试功能 | 适用场景 |
|---|---|---|---|---|---|---|
| 0x0 | 000 | <<24 | JTAG+SWD 均使能(默认) | 无 | JTAG/SWD 均可用 | 初始状态,PB3/PB4 被占用 |
| 0x1 | 001 | <<24 | 保留(完全 SWJ 无 NJTRST) | PB4(NJTRST) | JTAG/SWD 均可用 | 仅释放 PB4,不推荐 |
| 0x2 | 010 | <<24 | JTAG 禁用 + SWD 使能 | PB3(PJTDO)、PB4(NJTRST)、PA15(JTDI) | SWD 可用 | 推荐!释放 PB3/PB4,保留调试 |
| 0x3 | 011 | <<24 | 保留(未使用) | 无 | 无 | 不推荐使用 |
| 0x4 | 100 | <<24 | JTAG+SWD 均禁用 | PB3/PB4/PA15/PA13/PA14 | 无 | 仅需释放所有调试引脚,无需调试 |
| 0x5~0x7 | 101~111 | <<24 | 保留(未使用) | 无 | 无 | 不推荐使用 |
核心结论:要释放 PB3/PB4 且保留 SWD 调试,需将SWJ_CFG[2:0]配置为0x2(二进制 010),对应寄存器操作是AFIO->MAPR |= (0x2 << 24)。
三、开发环境全流程准备
3.1 硬件环境
| 硬件名称 | 选型建议 | 作用说明 |
|---|---|---|
| STM32F103C8T6 最小系统板 | 核心板(带 SWD 排针、5V 供电) | 主控芯片 |
| ST-Link V2 调试器 | 正版 / 兼容版(带 SWD 接线) | 代码下载 + 在线调试 |
| 继电器模组 | 5V 低电平触发(1 路,接 PB3) | 验证 PB3 的通用 GPIO 功能 |
| 指示灯模组 | LED+220Ω 限流电阻(接 PB4) | 验证 PB4 的通用 GPIO 功能 |
| 杜邦线 | 公对公 / 公对母(若干) | 连接 SWD / 继电器 / 指示灯 |
| 5V 电源适配器 | 输出 2A 以上 | 为继电器模组供电(避免 MCU 供电不足) |
硬件接线表:
| 设备 | 引脚连接 | 备注 |
|---|---|---|
| ST-Link V2 | SWDIO→PA13、SWCLK→PA14、GND→GND、3.3V→3.3V | 无需接 5V,避免电源冲突 |
| 继电器模组 | VCC→5V、GND→GND、IN→PB3 | 低电平触发时,IN 接 PB3 |
| 指示灯模组 | LED 正极→PB4(串 220Ω 电阻)、负极→GND | 共阴极 LED,高电平点亮 |
3.2 软件环境
| 软件名称 | 版本建议 | 下载地址 |
|---|---|---|
| STM32CubeMX | 6.9.0+ | https://www.st.com/en/development-tools/stm32cubemx.html#downloads |
| MDK-ARM(Keil5) | 5.38+ | https://www.keil.com/demo/eval/arm.htm |
| STM32F1 HAL 库 | STM32Cube_FW_F1_V1.8.5 | https://www.st.com/en/embedded-software/stm32cube-fw-f1.html |
| ST-Link 驱动 | V2.0+ | https://www.st.com/en/development-tools/stsw-link009.html |
软件安装注意事项:
- CubeMX 安装时选择 “默认路径”,避免中文路径;
- MDK 安装后需注册(学生 / 个人可申请免费许可证);
- CubeMX 中安装 F1 系列 HAL 库:打开 CubeMX→Help→Manage embedded software packages→选择 STM32F1→安装对应版本;
- 确保 MDK 中添加
stm32f103xb.h(CMSIS 核心头文件),支持寄存器位定义。
3.3 调试链路搭建
- 连接 ST-Link V2 到电脑 USB 口,安装驱动后,在设备管理器中可看到 “ST-Link Debugger”;
- 将 ST-Link 的 SWDIO/SWCLK/GND 连接到 STM32F103C8T6 最小系统板的对应引脚;
- 给最小系统板供电(5V),确保电源指示灯亮起;
- 打开 MDK,新建工程后,在 “Debug” 选项中选择 “ST-Link Debugger”,点击 “Settings”→“SW” 模式,确认 “Port” 为 SWD,点击 “Connect”,若显示 “Connected to Target” 则调试链路正常。
四、三种实现方式全解析(从底层到可视化)
4.1 方式 1:寄存器直接操作(无 HAL 依赖,最稳定)
寄存器操作是 STM32 底层开发的核心方式,不依赖 HAL 库,兼容性最强(适配所有 F1 系列固件版本),适合资深开发者或对稳定性要求高的场景。通过明确SWJ_CFG位偏移细节,确保配置精准生效。
4.1.1 AFIO_MAPR 寄存器底层原理
AFIO_MAPR 寄存器的SWJ_CFG[2:0]位(bit26~bit24)是控制调试接口的关键,操作步骤为:
- 使能 AFIO 时钟(
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN); - 清空
SWJ_CFG[2:0]位(AFIO->MAPR &= ~(0x7 << 24)),避免原有配置干扰; - 将
SWJ_CFG[2:0]设置为0x2(AFIO->MAPR |= (0x2 << 24)),实现 JTAG 禁用 + SWD 使能。
补充:CMSIS 标准位定义(推荐使用,可读性更高):
c
运行
// 从stm32f103xb.h中提取的标准定义 #define AFIO_MAPR_SWJ_CFG_Pos (24U) #define AFIO_MAPR_SWJ_CFG_Msk (0x7UL << AFIO_MAPR_SWJ_CFG_Pos) #define AFIO_MAPR_SWJ_CFG_JTAGDISABLE (0x2UL << AFIO_MAPR_SWJ_CFG_Pos) // 010: JTAG禁用,SWD使能4.1.2 代码编写与模块化集成(relay.c 示例,含验证逻辑)
以继电器模组的relay.c为例,完整代码如下:
c
运行
#include "stm32f103xb.h" // CMSIS核心头文件(含寄存器位定义) // 宏定义:PB3/PB4引脚 #define RELAY_PIN GPIO_PIN_3 #define LED_PIN GPIO_PIN_4 #define GPIOB_PORT GPIOB // 函数声明 void disable_jtag_enable_swd(void); // 核心配置函数 void relay_led_init(void); // GPIO初始化函数 void relay_control(uint8_t state); // 继电器控制 void led_control(uint8_t state); // 指示灯控制 uint8_t check_swd_config(void); // 配置验证函数 /** * @brief 禁用JTAG保留SWD,释放PB3/PB4 * @param 无 * @retval 无 */ void disable_jtag_enable_swd(void) { // 步骤1:使能AFIO时钟(必须!AFIO寄存器操作前提) RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // 步骤2:禁用JTAG,保留SWD(明确SWJ_CFG位偏移:bit26~bit24) AFIO->MAPR &= ~AFIO_MAPR_SWJ_CFG_Msk; // 清空SWJ_CFG位(推荐用位掩码) // 等价于:AFIO->MAPR &= ~(0x7 << 24); AFIO->MAPR |= AFIO_MAPR_SWJ_CFG_JTAGDISABLE; // 设置为JTAG禁用+SWD使能 // 等价于:AFIO->MAPR |= (0x2 << 24); // 步骤3:验证配置是否生效(可选,调试用) if(check_swd_config() != 0x2) { // 配置失败,可通过串口/LED提示(此处仅示例) while(1); // 配置失败则死循环 } } /** * @brief 验证SWJ_CFG配置是否生效 * @param 无 * @retval swj_cfg值:0x2=配置成功,其他=配置失败 */ uint8_t check_swd_config(void) { uint32_t mapr_value = AFIO->MAPR; uint8_t swj_cfg = (mapr_value >> 24) & 0x7; // 提取bit26~bit24的值 return swj_cfg; } /** * @brief 初始化PB3(继电器)、PB4(指示灯)为普通GPIO * @param 无 * @retval 无 */ void relay_led_init(void) { // 先配置调试接口,释放PB3/PB4 disable_jtag_enable_swd(); // 步骤1:使能GPIOB时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // 步骤2:配置PB3为推挽输出(清除原有配置+重新配置) GPIOB->CRL &= ~(GPIO_CRL_CNF3 | GPIO_CRL_MODE3); // 清除PB3配置 GPIOB->CRL |= (0x3 << GPIO_CRL_MODE3_Pos); // 推挽输出,50MHz GPIOB->CRL &= ~GPIO_CRL_CNF3; // 确认推挽输出模式 // 步骤3:配置PB4为推挽输出 GPIOB->CRL &= ~(GPIO_CRL_CNF4 | GPIO_CRL_MODE4); // 清除PB4配置 GPIOB->CRL |= (0x3 << GPIO_CRL_MODE4_Pos); // 推挽输出,50MHz GPIOB->CRL &= ~GPIO_CRL_CNF4; // 确认推挽输出模式 // 初始化后默认状态:继电器断开,指示灯熄灭 GPIOB->BSRR = GPIO_BSRR_BS3 | GPIO_BSRR_BS4; // PB3/PB4置高 } /** * @brief 控制继电器状态 * @param state: 0-吸合(低电平),1-断开(高电平) * @retval 无 */ void relay_control(uint8_t state) { if(state == 0) { GPIOB->BSRR = GPIO_BSRR_BR3; // PB3置低(吸合) } else { GPIOB->BSRR = GPIO_BSRR_BS3; // PB3置高(断开) } } /** * @brief 控制指示灯状态 * @param state: 0-熄灭(低电平),1-点亮(高电平) * @retval 无 */ void led_control(uint8_t state) { if(state == 1) { GPIOB->BSRR = GPIO_BSRR_BS4; // PB4置高(点亮) } else { GPIOB->BSRR = GPIO_BSRR_BR4; // PB4置低(熄灭) } }代码关键要点说明:
| 要点 | 实现方式 | 原因说明 | |
|---|---|---|---|
| SWJ_CFG 位偏移 | AFIO->MAPR &= ~(0x7 << 24) | SWJ_CFG 位在 bit26~bit24,偏移 24 位 | |
| SWJ_CFG 赋值 | `AFIO->MAPR | = (0x2 << 24)` | 配置为 JTAG 禁用 + SWD 使能,释放 PB3/PB4 |
| 寄存器操作规范性 | 优先使用 CMSIS 位定义(如AFIO_MAPR_SWJ_CFG_Msk) | 可读性更高,避免偏移理解偏差 | |
| 配置验证 | 添加check_swd_config()函数 | 立即发现配置错误,便于调试 |
4.1.3 编译 / 链接错误全排查
| 错误现象 | 错误原因 | 解决方案 | |
|---|---|---|---|
AFIO未定义 | 未包含stm32f103xb.h | 在代码开头添加#include "stm32f103xb.h"(CMSIS 核心头文件) | |
RCC_APB2ENR_AFIOEN未定义 | 未使用 CMSIS 标准头文件 | 替换为数值操作:`RCC->APB2ENR | = (1 << 0)`(AFIOEN 是 bit0) |
GPIO_CRL_MODE3_Pos未定义 | MDK 未添加器件库 | MDK 中选择 “STM32F103C8” 器件库,或手动定义:#define GPIO_CRL_MODE3_Pos 6 | |
| PB3/PB4 仍无法输出 | SWJ_CFG 位配置错误 | 检查AFIO->MAPR的 bit26~bit24 值是否为 0x2(通过调试器查看寄存器) | |
| 验证函数返回非 0x2 | AFIO 时钟未使能 | 确认RCC->APB2ENR的 bit0(AFIOEN)已置 1 |
4.2 方式 2:HAL 库标准实现(简洁易维护)
HAL 库是 ST 推出的标准化驱动库,代码可读性高、易维护,适合团队开发或新手进阶。核心是使用__HAL_AFIO_REMAP_SWJ_NOJTAG()宏实现 JTAG 禁用(该宏底层已明确SWJ_CFG位偏移)。
4.2.1 AFIO 宏的正确使用(__HAL_AFIO_REMAP_SWJ_NOJTAG)
STM32F1 HAL 库中,控制调试接口的宏定义在stm32f1xx_hal_afio.h中,核心宏如下(底层已明确位偏移):
| 宏名称 | 功能说明 | 底层寄存器操作 | SWJ_CFG 值 | |
|---|---|---|---|---|
__HAL_AFIO_REMAP_SWJ_ENABLE() | JTAG+SWD 均使能(默认) | `AFIO->MAPR &= ~AFIO_MAPR_SWJ_CFG_Msk; AFIO->MAPR | = 0x0 <<24` | 0x0 |
__HAL_AFIO_REMAP_SWJ_NOJTAG() | JTAG 禁用 + SWD 使能(推荐) | `AFIO->MAPR &= ~AFIO_MAPR_SWJ_CFG_Msk; AFIO->MAPR | = 0x2 <<24` | 0x2 |
__HAL_AFIO_REMAP_SWJ_DISABLE() | JTAG+SWD 均禁用 | `AFIO->MAPR &= ~AFIO_MAPR_SWJ_CFG_Msk; AFIO->MAPR | = 0x4 <<24` | 0x4 |
关键说明:HAL 库的宏已封装了正确的SWJ_CFG位偏移,无需手动计算,避免寄存器操作的人为偏差。
4.2.2 HAL 模块启用与头文件依赖
要使用 HAL 库的 AFIO 宏,需满足两个前提:
- 启用
HAL_AFIO_MODULE_ENABLED(在stm32f1xx_hal_conf.h中); - 包含必要的头文件(
stm32f1xx_hal.h/stm32f1xx_hal_afio.h)。
步骤 1:启用 HAL_AFIO_MODULE_ENABLED打开Core/Inc/stm32f1xx_hal_conf.h,确保以下代码未被注释:
c
运行
/** * @brief Select the HAL Module to enable */ #define HAL_MODULE_ENABLED #define HAL_AFIO_MODULE_ENABLED // 必须启用!取消注释 #define HAL_GPIO_MODULE_ENABLED #define HAL_RCC_MODULE_ENABLED // 其他模块按需启用...步骤 2:编写 HAL 库版代码(relay.c)
c
运行
#include "stm32f1xx_hal.h" // 核心HAL库头文件 #include "stm32f1xx_hal_afio.h" // AFIO宏定义头文件 // 宏定义:PB3/PB4引脚 #define RELAY_PIN GPIO_PIN_3 #define LED_PIN GPIO_PIN_4 #define GPIOB_PORT GPIOB // 函数声明 void relay_led_init(void); void relay_control(uint8_t state); void led_control(uint8_t state); uint8_t check_swd_config_hal(void); // HAL版配置验证 /** * @brief 禁用JTAG保留SWD,初始化PB3/PB4 * @param 无 * @retval 无 */ void relay_led_init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 步骤1:使能AFIO时钟(HAL库宏) __HAL_RCC_AFIO_CLK_ENABLE(); // 步骤2:禁用JTAG保留SWD(核心宏,底层已明确位偏移) __HAL_AFIO_REMAP_SWJ_NOJTAG(); // 步骤3:验证配置(可选) if(check_swd_config_hal() != 0x2) { while(1); // 配置失败死循环 } // 步骤4:使能GPIOB时钟 __HAL_RCC_GPIOB_CLK_ENABLE(); // 步骤5:初始化PB3/PB4为推挽输出 GPIO_InitStruct.Pin = RELAY_PIN | LED_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB_PORT, &GPIO_InitStruct); // 初始化默认状态:继电器断开,指示灯熄灭 HAL_GPIO_WritePin(GPIOB_PORT, RELAY_PIN, GPIO_PIN_SET); // 继电器断开 HAL_GPIO_WritePin(GPIOB_PORT, LED_PIN, GPIO_PIN_RESET); // 指示灯熄灭 } /** * @brief HAL版验证SWJ_CFG配置 * @param 无 * @retval swj_cfg值:0x2=成功,其他=失败 */ uint8_t check_swd_config_hal(void) { uint32_t mapr_value = AFIO->MAPR; return (mapr_value >> 24) & 0x7; // 提取bit26~bit24 } /** * @brief 控制继电器 * @param state: 0-吸合,1-断开 * @retval 无 */ void relay_control(uint8_t state) { HAL_GPIO_WritePin(GPIOB_PORT, RELAY_PIN, state ? GPIO_PIN_SET : GPIO_PIN_RESET); } /** * @brief 控制指示灯 * @param state: 0-熄灭,1-点亮 * @retval 无 */ void led_control(uint8_t state) { HAL_GPIO_WritePin(GPIOB_PORT, LED_PIN, state ? GPIO_PIN_SET : GPIO_PIN_RESET); }步骤 3:在 main.c 中调用初始化函数
c
运行
#include "main.h" #include "relay.h" int main(void) { // HAL库初始化 HAL_Init(); SystemClock_Config(); // 配置72MHz系统时钟 // 初始化继电器和指示灯 relay_led_init(); // 主循环:继电器吸合1s→断开1s,指示灯同步闪烁 while (1) { relay_control(0); // 继电器吸合 led_control(1); // 指示灯点亮 HAL_Delay(1000); // 延时1s relay_control(1); // 继电器断开 led_control(0); // 指示灯熄灭 HAL_Delay(1000); // 延时1s } }4.2.3 常见警告 / 错误解决
| 错误 / 警告现象 | 原因分析 | 解决方案 |
|---|---|---|
#223-D: function "__HAL_AFIO_REMAP_SWJ_NOJTAG" declared implicitly | 未包含stm32f1xx_hal_afio.h | 在 relay.c 开头添加#include "stm32f1xx_hal_afio.h" |
L6218E: Undefined symbol __HAL_AFIO_REMAP_SWJ_NOJTAG | 未启用HAL_AFIO_MODULE_ENABLED | 在stm32f1xx_hal_conf.h中启用HAL_AFIO_MODULE_ENABLED |
HAL_GPIO_WritePin未定义 | 未启用HAL_GPIO_MODULE_ENABLED | 在stm32f1xx_hal_conf.h中启用HAL_GPIO_MODULE_ENABLED |
| PB3/PB4 输出无响应 | AFIO 时钟未使能 | 检查__HAL_RCC_AFIO_CLK_ENABLE()是否调用(必须在宏前执行) |
| 验证函数返回非 0x2 | HAL 库版本过旧 | 升级 HAL 库到STM32Cube_FW_F1_V1.8.5及以上 |
4.3 方式 3:CubeMX+MDK 可视化配置(新手首选)
CubeMX 是 ST 推出的可视化配置工具,无需手动编写寄存器 / HAL 代码,通过图形界面即可完成调试接口配置(底层已自动明确SWJ_CFG位偏移),适合新手。
4.3.1 CubeMX 中 Debug 模式配置步骤
步骤 1:新建 CubeMX 工程
- 打开 STM32CubeMX,点击 “Access to MCU Selector”;
- 在搜索框输入
STM32F103C8T6,选中后点击 “Start Project”; - 在 “Pinout & Configuration” 界面,左侧选择
System Core → SYS; - 右侧 “Debug” 选项中,选择
Serial Wire(仅保留 SWD,禁用 JTAG):- 原默认值为
JTAG-DP + SW-DP(JTAG+SWD 均使能); - 选择
Serial Wire后,CubeMX 会自动配置AFIO->MAPR的SWJ_CFG=0x2(bit26~bit24);
- 原默认值为
步骤 2:配置 PB3/PB4 为普通 GPIO
- 在 “Pinout & Configuration” 界面,找到 PB3、PB4 引脚;
- 点击 PB3,选择
GPIO_Output(继电器); - 点击 PB4,选择
GPIO_Output(指示灯); - 点击 PB3/PB4 的 “GPIO Settings”,配置如下:
- Mode:
Output Push Pull(推挽输出); - Pull:
No Pull-up and No Pull-down(无上拉下拉); - Speed:
High(高速); - User Label:分别命名为
RELAY、LED(便于识别)。
- Mode:
步骤 3:配置系统时钟
- 切换到 “Clock Configuration” 界面;
- 选择
HSE为Crystal/Ceramic Resonator(外部 8MHz 晶振); - 配置 PLL 倍频为 9(8MHz×9=72MHz);
- APB1 Prescaler 选择
/2(36MHz),APB2 Prescaler 选择/1(72MHz); - 确认
SYSCLK=72MHz,HCLK=72MHz,PCLK1=36MHz,PCLK2=72MHz。
步骤 4:生成 MDK 工程
- 切换到 “Project Manager” 界面;
- 配置
Project Name(如Relay_LED_SWD)、Project Location(纯英文路径); Toolchain/IDE选择MDK-ARM V5;- 切换到 “Code Generator” 界面,勾选
Generate peripheral initialization as a pair of .c/.h files per peripheral; - 点击 “Generate Code”,生成完成后点击 “Open Project” 打开 MDK 工程。
4.3.2 代码生成与 MDK 工程适配
CubeMX 生成的代码中,调试接口配置会自动写入sys.c(底层已明确SWJ_CFG位偏移),GPIO 配置写入gpio.c,核心代码如下:
sys.c 中的 Debug 配置
c
运行
void MX_SYS_Init(void) { /* USER CODE BEGIN SYS_Init 0 */ /* USER CODE END SYS_Init 0 */ /* System interrupt init*/ /* SysTick_IRQn interrupt configuration */ HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0); /* USER CODE BEGIN SYS_Init 1 */ /* USER CODE END SYS_Init 1 */ /* Configure the debug port */ __HAL_AFIO_REMAP_SWJ_NOJTAG(); // CubeMX自动添加,底层已处理bit26~bit24 /* USER CODE BEGIN SYS_Init 2 */ /* USER CODE END SYS_Init 2 */ }gpio.c 中的 PB3/PB4 配置
c
运行
void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOB_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOB, RELAY_Pin|LED_Pin, GPIO_PIN_SET); /*Configure GPIO pins : RELAY_Pin LED_Pin */ GPIO_InitStruct.Pin = RELAY_Pin|LED_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); }在 main.c 中添加业务逻辑 + 配置验证
c
运行
#include "main.h" #include "gpio.h" #include "sys.h" // 新增:验证SWJ_CFG配置 uint8_t check_swd_config_cubemx(void) { uint32_t mapr_value = AFIO->MAPR; return (mapr_value >> 24) & 0x7; } int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); /* USER CODE BEGIN 2 */ // 验证CubeMX配置是否生效 if(check_swd_config_cubemx() != 0x2) { while(1); // 配置失败死循环 } /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { // 继电器吸合,指示灯点亮 HAL_GPIO_WritePin(GPIOB, RELAY_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, LED_Pin, GPIO_PIN_SET); HAL_Delay(1000); // 继电器断开,指示灯熄灭 HAL_GPIO_WritePin(GPIOB, RELAY_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, LED_Pin, GPIO_PIN_RESET); HAL_Delay(1000); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }4.3.3 配置验证与坑点规避
| 坑点现象 | 原因分析 | 规避方案 |
|---|---|---|
| CubeMX 中 Debug 选项灰色 | 未配置系统时钟 | 先在 “Clock Configuration” 中配置 HSE,再返回 SYS 配置 Debug |
| 生成代码后 SWD 无法调试 | Debug 选项选错(选了Disabled) | 重新选择Serial Wire,重新生成代码 |
| PB3/PB4 仍被占用 | 未保存 CubeMX 配置 | 配置完成后点击 “Save”,再生成代码 |
| MDK 编译报错 “头文件缺失” | CubeMX 生成路径含中文 | 改为纯英文路径,重新生成工程 |
| 验证函数返回非 0x2 | CubeMX 版本过旧 | 升级 CubeMX 到 6.9.0 + 版本 |
五、全场景错误排查手册(附解决方案)
| 错误类型 | 具体现象 | 根本原因 | 解决方案 |
|---|---|---|---|
| 编译警告 | #223-D: function "__HAL_AFIO_REMAP_SWJ_NOJTAG" declared implicitly | 未包含stm32f1xx_hal_afio.h | 添加#include "stm32f1xx_hal_afio.h" |
| 链接错误 | L6218E: Undefined symbol __HAL_AFIO_REMAP_SWJ_NOJTAG | 未启用HAL_AFIO_MODULE_ENABLED | 在stm32f1xx_hal_conf.h中启用该宏 |
| 功能错误 | PB3/PB4 配置为输出但无电平变化 | SWJ_CFG 位配置错误 / AFIO 时钟未使能 | 1. 明确位偏移为<<24;2. 执行__HAL_RCC_AFIO_CLK_ENABLE() |
| 调试错误 | ST-Link 提示 “Can not connect to target” | SWDIO/SWCLK 接线错误或 SWD 被禁用 | 1. 检查接线;2. 确认 SWJ_CFG=0x2(仅禁用 JTAG,保留 SWD) |
| GPIO 失效 | 初始化后 PB3/PB4 正常,运行一段时间后无响应 | 其他代码修改了 AFIO_MAPR 寄存器 | 1. 封装 AFIO 配置为独立函数;2. 定时验证 SWJ_CFG 值 |
| 继电器不动作 | PB3 有电平变化但继电器不吸合 | 继电器驱动电压不匹配(如 5V 继电器接 3.3V) | 增加三极管 / 光耦驱动,或更换 3.3V 继电器 |
| 指示灯不亮 | PB4 有电平变化但 LED 不亮 | 限流电阻过大或 LED 极性接反 | 更换 220Ω 限流电阻,调整 LED 正负极接线 |
| 验证函数返回非 0x2 | AFIO_MAPR 寄存器被其他外设修改 | 检查是否启用了 JTAG 相关外设(如 TRACE) | 禁用 TRACE 功能,仅保留 SWD |
六、实战案例:继电器 + 指示灯复用 PB3/PB4
6.1 硬件电路设计(接线 / 保护 / 供电)
| 模块 | 接线细节 | 保护措施 |
|---|---|---|
| 继电器模组 | VCC→5V、GND→GND、IN→PB3(串 1kΩ 限流电阻) | 并联 1N4148 续流二极管(防止反向电动势) |
| 指示灯模组 | LED 正极→PB4(串 220Ω 限流电阻)、负极→GND | 限流电阻(避免过流烧毁 LED) |
| ST-Link | SWDIO→PA13、SWCLK→PA14、GND→GND、3.3V→3.3V | 串 100Ω 电阻(防静电) |
| 电源 | 最小系统板→5V/1A、继电器模组→5V/2A(独立供电) | 加 0.1μF 去耦电容(滤除电源噪声) |
6.2 模块化编程(relay.c/led.c,含寄存器精准配置)
relay.h
c
运行
#ifndef __RELAY_H #define __RELAY_H #include <stdint.h> // 确保uint8_t识别 #define RELAY_PIN GPIO_PIN_3 #define RELAY_PORT GPIOB void relay_init(void); void relay_control(uint8_t state); uint8_t check_swd_config(void); #endifrelay.c
c
运行
#include "relay.h" #include "stm32f103xb.h" void disable_jtag_enable_swd(void) { // 1. 使能AFIO时钟 RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // 2. 禁用JTAG,启用SWD(明确位偏移) AFIO->MAPR &= ~(0x7 << 24); AFIO->MAPR |= (0x2 << 24); } uint8_t check_swd_config(void) { return (AFIO->MAPR >> 24) & 0x7; } void relay_init(void) { disable_jtag_enable_swd(); // 使能GPIOB时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // 配置PB3为推挽输出 GPIOB->CRL &= ~(GPIO_CRL_CNF3 | GPIO_CRL_MODE3); GPIOB->CRL |= (0x3 << GPIO_CRL_MODE3_Pos); GPIOB->CRL &= ~GPIO_CRL_CNF3; // 默认断开 GPIOB->BSRR = GPIO_BSRR_BS3; } void relay_control(uint8_t state) { if(state == 0) { GPIOB->BSRR = GPIO_BSRR_BR3; } else { GPIOB->BSRR = GPIO_BSRR_BS3; } }led.h
c
运行
#ifndef __LED_H #define __LED_H #include <stdint.h> #define LED_PIN GPIO_PIN_4 #define LED_PORT GPIOB void led_init(void); void led_control(uint8_t state); void led_toggle(void); #endifled.c
c
运行
#include "led.h" #include "stm32f103xb.h" void led_init(void) { // 依赖relay_init()已释放PB4,无需重复配置AFIO RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // 配置PB4为推挽输出 GPIOB->CRL &= ~(GPIO_CRL_CNF4 | GPIO_CRL_MODE4); GPIOB->CRL |= (0x3 << GPIO_CRL_MODE4_Pos); GPIOB->CRL &= ~GPIO_CRL_CNF4; // 默认熄灭 GPIOB->BSRR = GPIO_BSRR_BR4; } void led_control(uint8_t state) { if(state == 1) { GPIOB->BSRR = GPIO_BSRR_BS4; } else { GPIOB->BSRR = GPIO_BSRR_BR4; } } void led_toggle(void) { if((GPIOB->ODR & GPIO_ODR_ODR4) != 0) { GPIOB->BSRR = GPIO_BSRR_BR4; } else { GPIOB->BSRR = GPIO_BSRR_BS4; } }main.c
c
运行
#include "relay.h" #include "led.h" #include "stm32f103xb.h" // 简易延时函数(无HAL依赖) void delay_ms(uint32_t ms) { ms *= 72000; // 72MHz系统时钟,粗略延时 while(ms--); } int main(void) { // 初始化 relay_init(); led_init(); // 验证配置 if(check_swd_config() != 0x2) { while(1); // 配置失败 } // 主循环 while (1) { relay_control(0); // 吸合 led_control(1); // 点亮 delay_ms(1000); relay_control(1); // 断开 led_control(0); // 熄灭 delay_ms(1000); } }6.3 初始化顺序与 GPIO 失效规避
| 初始化顺序 | 正确顺序 | 错误顺序 | 后果 |
|---|---|---|---|
| 核心步骤 | 1. 使能 AFIO 时钟2. 配置 SWJ_CFG3. 使能 GPIOB 时钟4. GPIO 初始化 | 1. GPIO 初始化2. 配置 SWJ_CFG | PB3/PB4 仍被 JTAG 占用,初始化无效 |
| 模块初始化 | 1. relay_init()2. led_init() | 无顺序要求(均依赖 PB 端口) | 无影响 |
| 外设初始化 | 先调试接口配置,后 GPIO 初始化 | 先 GPIO 初始化,后调试接口配置 | GPIO 配置被调试接口覆盖,失效 |
| 验证逻辑 | 配置后立即验证 SWJ_CFG 值 | 无验证逻辑 | 配置错误无法及时发现 |
6.4 系统稳定性验证(24h 运行测试 + 寄存器验证)
| 测试项 | 测试方法 | 合格标准 |
|---|---|---|
| 继电器动作 | 连续 24h 吸合 / 断开(1 次 / 秒) | 无卡顿、无误动作 |
| 指示灯闪烁 | 同步继电器动作 | 无频闪、无熄灭 |
| SWD 调试 | 中途连接 ST-Link,下载新代码 | 正常连接、下载、调试 |
| 电源稳定性 | 测量 PB3/PB4 电平 | 高电平≈3.3V,低电平≈0V |
| 温度稳定性 | 环境温度 - 10℃~50℃ | 模块正常工作,无死机 |
| 寄存器验证 | 每小时读取 AFIO->MAPR 的 SWJ_CFG 值 | 始终为 0x2,无被篡改 |
| 抗干扰测试 | 靠近变频器 / 电机运行 | GPIO 电平无波动,继电器无误动作 |
七、进阶优化:GPIO 初始化后失效全维度规避
7.1 软件层面:时钟 / 引脚 / 初始化逻辑
| 优化点 | 具体措施 | |
|---|---|---|
| 时钟使能 | 每个 GPIO 端口独立使能时钟,避免依赖其他模块(如 `RCC->APB2ENR | = RCC_APB2ENR_IOPBEN`) |
| 引脚冲突 | 建立引脚映射表,新增外设前检查引脚占用(如 PB3/PB4 已用于继电器 / 指示灯,不再分配给其他外设) | |
| 初始化顺序 | 先配置调试接口(AFIO),再初始化 GPIO,避免配置覆盖;配置后立即验证 SWJ_CFG 值 | |
| 代码封装 | 禁止直接操作 GPIO/AFIO 寄存器,统一通过模块接口(如relay_control())操作;AFIO 配置函数加static修饰 | |
| 中断保护 | 若 GPIO 在中断中使用,关闭全局中断后操作(__disable_irq()/__enable_irq()),避免竞态条件 | |
| 配置防篡改 | 定时(如 100ms)读取 SWJ_CFG 值,若被篡改则重新配置 |
7.2 硬件层面:供电 / 接线 / 负载匹配
| 优化点 | 具体措施 |
|---|---|
| 供电稳定性 | 继电器模组独立供电(5V/2A),避免 MCU 供电不足;MCU 电源端加 100μF+0.1μF 滤波电容 |
| 接线规范 | GPIO 引脚到外设的走线≤10cm,采用杜邦线 / 排线,避免与动力线并行布线 |
| 负载匹配 | GPIO 输出最大电流为 25mA,驱动继电器时增加 NPN 三极管(如 8050),放大驱动能力 |
| 静电防护 | SWD/GPIO 引脚串 100Ω 电阻,工业级应用加 TVS 管(如 SMBJ3.3CA) |
| 续流保护 | 继电器线圈反向并联 1N4148 续流二极管,防止反向电动势损坏 GPIO |
| 电平匹配 | 5V 继电器与 3.3V GPIO 之间加电平转换(如 1kΩ 上拉电阻到 5V) |
7.3 逻辑层面:防止 GPIO 被意外修改
| 优化点 | 具体措施 |
|---|---|
| 全局变量保护 | 用static修饰 GPIO 控制函数和配置验证函数,避免外部非法调用 |
| 状态监控 | 维护 GPIO 状态变量(如uint8_t relay_state),操作前后对比,不一致则报警 |
| 错误处理 | 添加断言(assert_param()),检查 GPIO 配置参数合法性;配置失败时触发 LED 闪烁报警 |
| 日志输出 | 通过串口打印 GPIO/AFIO 寄存器值(如UART1_Printf("SWJ_CFG: 0x%02X\r\n", check_swd_config())),便于远程调试 |
| 看门狗复位 | 启用独立看门狗(IWDG),若系统死机则自动复位,恢复 GPIO 配置 |
八、总结与扩展
8.1 三种实现方式对比
| 实现方式 | 优点 | 缺点 | 适用人群 | 核心注意事项 |
|---|---|---|---|---|
| 寄存器操作 | 无 HAL 依赖、稳定、执行效率高 | 代码复杂度高、需明确位偏移 | 资深开发者、工业级项目 | 确保 SWJ_CFG 位偏移为<<24 |
| HAL 库实现 | 代码简洁、易维护、标准化 | 依赖 HAL 库、版本兼容问题 | 进阶开发者、团队项目 | 启用HAL_AFIO_MODULE_ENABLED,包含正确头文件 |
| CubeMX+MDK | 可视化配置、新手友好、自动生成 | 灵活性低、代码冗余 | 新手、快速原型开发 | 选择Serial Wire模式,验证 SWJ_CFG=0x2 |
8.2 跨 STM32 系列适配(F4/F7/H7)
不同 STM32 系列的调试接口配置核心逻辑一致(禁用 JTAG + 保留 SWD),但寄存器 / 宏名称略有差异,适配要点如下表:
| 系列 | 核心寄存器 | SWJ_CFG 位位置 | 使能时钟宏 | 核心配置宏 |
|---|---|---|---|---|
| F1 | AFIO->MAPR | bit26~bit24 | __HAL_RCC_AFIO_CLK_ENABLE() | __HAL_AFIO_REMAP_SWJ_NOJTAG() |
| F4 | SYSCFG->MEMRMP | bit2~bit0 | __HAL_RCC_SYSCFG_CLK_ENABLE() | __HAL_SYSCFG_REMAP_SWJ_NOJTAG() |
| F7/H7 | SYSCFG->SWJCR | bit0~bit2 | __HAL_RCC_SYSCFG_CLK_ENABLE() | __HAL_SYSCFG_REMAP_SWJ_DISABLE_JTAG() |
适配原则:
- F4/F7/H7 用
SYSCFG替代 F1 的AFIO; - 使能对应外设时钟(SYSCFG);
- 核心配置仍为禁用 JTAG、保留 SWD(SWJ_CFG=0x2);
- 验证时提取对应位域的值(如 F4 提取 bit2~bit0)。
8.3 工业级应用注意事项
| 注意事项 | 具体要求 |
|---|---|
| EMC 兼容 | 遵循 EMC 设计规范,GPIO/SWD 引脚加滤波电容(0.1μF)、串磁珠,避免电磁干扰 |
| 可靠性测试 | 进行高低温(-40℃~85℃)、振动(10~2000Hz)、盐雾测试,验证 GPIO 稳定性 |
| 冗余设计 | 关键 GPIO(如继电器控制)增加备用引脚,配置失败时自动切换 |
| 固件升级 | 保留 SWD 调试接口,支持在线固件升级(IAP);升级后重新验证 SWJ_CFG 配置 |
| 低功耗 | 闲置时将 PB3/PB4 配置为输入模式(GPIO_MODE_INPUT),降低功耗 |
| 故障诊断 | 预留串口 / 蓝牙日志输出,记录 GPIO/AFIO 寄存器值,便于现场故障排查 |
结语
本文从底层原理到实战案例,完整讲解了 STM32F103C8T6“禁用 JTAG 保留 SWD 释放 PB3/PB4” 的三种实现方式(寄存器 / HAL 库 / CubeMX),并补充了配置验证、错误排查、工业级优化等关键内容。
无论是新手还是资深开发者,均可根据项目需求选择合适的实现方式:寄存器操作适合对稳定性要求高的工业场景,HAL 库适合团队协作,CubeMX 适合快速原型开发。核心要点是:确保 SWJ_CFG 位偏移为<<24、使能 AFIO 时钟、配置后验证 SWJ_CFG=0x2,这三点是配置生效的关键。
掌握调试接口的配置技巧,不仅能复用 PB3/PB4 等稀缺 GPIO 资源,还能提升 STM32 项目的稳定性和可维护性,为复杂外设集成(如多路继电器、传感器阵列)奠定基础。