news 2026/1/15 7:12:40

STM32F103C8T6 禁用 JTAG 保留 SWD 释放 PB3/PB4 全解析(HAL 库 / 寄存器 / CubeMX 三种实现方式)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F103C8T6 禁用 JTAG 保留 SWD 释放 PB3/PB4 全解析(HAL 库 / 寄存器 / CubeMX 三种实现方式)

一、前言:为什么要禁用 JTAG 保留 SWD?

1.1 STM32F103C8T6 调试接口硬件特性

STM32F103C8T6 是入门级 ARM Cortex-M3 内核 MCU,Flash=64KB、RAM=20KB,因性价比成为工业控制、物联网、创客项目的首选。其调试接口默认支持JTAGSWD两种模式,引脚映射如下表:

调试模式占用引脚功能说明
JTAGPA13(JTMS/SWDIO)、PA14(JTCK/SWCLK)、PA15(JTDI)、PB3(JTDO)、PB4(NJTRST)5 引脚调试,支持在线编程 / 调试
SWDPA13(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 的主流调试方式。两者核心差异如下表:

特性JTAGSWD
引脚数量5 个2 个
传输速率较低(依赖时钟频率)较高(双向串行传输)
硬件复杂度高(需 5 路布线)低(仅 2 路布线)
兼容性支持所有 ARM 芯片仅支持 Cortex-M 系列
功耗较高较低(适合低功耗场景)
2.2 AFIO 外设与引脚重映射核心逻辑

STM32 的 GPIO 引脚分为 “通用功能” 和 “复用功能”,调试接口属于复用功能。要将 PB3/PB4 从 JTAG 复用功能切换为通用 GPIO,需通过AFIO(Alternate Function IO)外设配置引脚重映射。

AFIO 的核心作用是:

  1. 配置 GPIO 的复用功能映射(如 JTAG/SWD、串口、定时器等);
  2. 控制外部中断的引脚选择;
  3. 实现 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] 值二进制左移偏移配置名称释放引脚调试功能适用场景
0x0000<<24JTAG+SWD 均使能(默认)JTAG/SWD 均可用初始状态,PB3/PB4 被占用
0x1001<<24保留(完全 SWJ 无 NJTRST)PB4(NJTRST)JTAG/SWD 均可用仅释放 PB4,不推荐
0x2010<<24JTAG 禁用 + SWD 使能PB3(PJTDO)、PB4(NJTRST)、PA15(JTDI)SWD 可用推荐!释放 PB3/PB4,保留调试
0x3011<<24保留(未使用)不推荐使用
0x4100<<24JTAG+SWD 均禁用PB3/PB4/PA15/PA13/PA14仅需释放所有调试引脚,无需调试
0x5~0x7101~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 V2SWDIO→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 软件环境
软件名称版本建议下载地址
STM32CubeMX6.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.5https://www.st.com/en/embedded-software/stm32cube-fw-f1.html
ST-Link 驱动V2.0+https://www.st.com/en/development-tools/stsw-link009.html

软件安装注意事项

  1. CubeMX 安装时选择 “默认路径”,避免中文路径;
  2. MDK 安装后需注册(学生 / 个人可申请免费许可证);
  3. CubeMX 中安装 F1 系列 HAL 库:打开 CubeMX→Help→Manage embedded software packages→选择 STM32F1→安装对应版本;
  4. 确保 MDK 中添加stm32f103xb.h(CMSIS 核心头文件),支持寄存器位定义。
3.3 调试链路搭建
  1. 连接 ST-Link V2 到电脑 USB 口,安装驱动后,在设备管理器中可看到 “ST-Link Debugger”;
  2. 将 ST-Link 的 SWDIO/SWCLK/GND 连接到 STM32F103C8T6 最小系统板的对应引脚;
  3. 给最小系统板供电(5V),确保电源指示灯亮起;
  4. 打开 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)是控制调试接口的关键,操作步骤为:

  1. 使能 AFIO 时钟(RCC->APB2ENR |= RCC_APB2ENR_AFIOEN);
  2. 清空SWJ_CFG[2:0]位(AFIO->MAPR &= ~(0x7 << 24)),避免原有配置干扰;
  3. SWJ_CFG[2:0]设置为0x2AFIO->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(通过调试器查看寄存器)
验证函数返回非 0x2AFIO 时钟未使能确认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 宏,需满足两个前提:

  1. 启用HAL_AFIO_MODULE_ENABLED(在stm32f1xx_hal_conf.h中);
  2. 包含必要的头文件(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_ENABLEDstm32f1xx_hal_conf.h中启用HAL_AFIO_MODULE_ENABLED
HAL_GPIO_WritePin未定义未启用HAL_GPIO_MODULE_ENABLEDstm32f1xx_hal_conf.h中启用HAL_GPIO_MODULE_ENABLED
PB3/PB4 输出无响应AFIO 时钟未使能检查__HAL_RCC_AFIO_CLK_ENABLE()是否调用(必须在宏前执行)
验证函数返回非 0x2HAL 库版本过旧升级 HAL 库到STM32Cube_FW_F1_V1.8.5及以上
4.3 方式 3:CubeMX+MDK 可视化配置(新手首选)

CubeMX 是 ST 推出的可视化配置工具,无需手动编写寄存器 / HAL 代码,通过图形界面即可完成调试接口配置(底层已自动明确SWJ_CFG位偏移),适合新手。

4.3.1 CubeMX 中 Debug 模式配置步骤

步骤 1:新建 CubeMX 工程

  1. 打开 STM32CubeMX,点击 “Access to MCU Selector”;
  2. 在搜索框输入STM32F103C8T6,选中后点击 “Start Project”;
  3. 在 “Pinout & Configuration” 界面,左侧选择System Core → SYS
  4. 右侧 “Debug” 选项中,选择Serial Wire(仅保留 SWD,禁用 JTAG):
    • 原默认值为JTAG-DP + SW-DP(JTAG+SWD 均使能);
    • 选择Serial Wire后,CubeMX 会自动配置AFIO->MAPRSWJ_CFG=0x2(bit26~bit24);

步骤 2:配置 PB3/PB4 为普通 GPIO

  1. 在 “Pinout & Configuration” 界面,找到 PB3、PB4 引脚;
  2. 点击 PB3,选择GPIO_Output(继电器);
  3. 点击 PB4,选择GPIO_Output(指示灯);
  4. 点击 PB3/PB4 的 “GPIO Settings”,配置如下:
    • Mode:Output Push Pull(推挽输出);
    • Pull:No Pull-up and No Pull-down(无上拉下拉);
    • Speed:High(高速);
    • User Label:分别命名为RELAYLED(便于识别)。

步骤 3:配置系统时钟

  1. 切换到 “Clock Configuration” 界面;
  2. 选择HSECrystal/Ceramic Resonator(外部 8MHz 晶振);
  3. 配置 PLL 倍频为 9(8MHz×9=72MHz);
  4. APB1 Prescaler 选择/2(36MHz),APB2 Prescaler 选择/1(72MHz);
  5. 确认SYSCLK=72MHzHCLK=72MHzPCLK1=36MHzPCLK2=72MHz

步骤 4:生成 MDK 工程

  1. 切换到 “Project Manager” 界面;
  2. 配置Project Name(如Relay_LED_SWD)、Project Location(纯英文路径);
  3. Toolchain/IDE选择MDK-ARM V5
  4. 切换到 “Code Generator” 界面,勾选Generate peripheral initialization as a pair of .c/.h files per peripheral
  5. 点击 “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 生成路径含中文改为纯英文路径,重新生成工程
验证函数返回非 0x2CubeMX 版本过旧升级 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_ENABLEDstm32f1xx_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 正负极接线
验证函数返回非 0x2AFIO_MAPR 寄存器被其他外设修改检查是否启用了 JTAG 相关外设(如 TRACE)禁用 TRACE 功能,仅保留 SWD

六、实战案例:继电器 + 指示灯复用 PB3/PB4

6.1 硬件电路设计(接线 / 保护 / 供电)
模块接线细节保护措施
继电器模组VCC→5V、GND→GND、IN→PB3(串 1kΩ 限流电阻)并联 1N4148 续流二极管(防止反向电动势)
指示灯模组LED 正极→PB4(串 220Ω 限流电阻)、负极→GND限流电阻(避免过流烧毁 LED)
ST-LinkSWDIO→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); #endif

relay.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); #endif

led.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_CFGPB3/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 位位置使能时钟宏核心配置宏
F1AFIO->MAPRbit26~bit24__HAL_RCC_AFIO_CLK_ENABLE()__HAL_AFIO_REMAP_SWJ_NOJTAG()
F4SYSCFG->MEMRMPbit2~bit0__HAL_RCC_SYSCFG_CLK_ENABLE()__HAL_SYSCFG_REMAP_SWJ_NOJTAG()
F7/H7SYSCFG->SWJCRbit0~bit2__HAL_RCC_SYSCFG_CLK_ENABLE()__HAL_SYSCFG_REMAP_SWJ_DISABLE_JTAG()

适配原则:

  1. F4/F7/H7 用SYSCFG替代 F1 的AFIO
  2. 使能对应外设时钟(SYSCFG);
  3. 核心配置仍为禁用 JTAG、保留 SWD(SWJ_CFG=0x2);
  4. 验证时提取对应位域的值(如 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 项目的稳定性和可维护性,为复杂外设集成(如多路继电器、传感器阵列)奠定基础。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/3 4:35:01

5.React状态管理

多更新的组件状态 在组件状态中&#xff0c;我们了解到了React中组件的状态及其用法。组件状态的主要作用就是由状态设置触发组件的局部UI渲染&#xff0c;状态用法也很简单。 有时候有些组件对于状态的更新操作很多&#xff0c;这就让我们很难短时间理清组件更新逻辑。示例如…

作者头像 李华
网站建设 2026/1/3 2:37:46

Wan2.2-T2V-A14B支持多种艺术风格迁移的实现方式

Wan2.2-T2V-A14B&#xff1a;如何实现多艺术风格视频生成 在短视频内容爆炸式增长的今天&#xff0c;品牌方、创作者和影视团队面临的最大挑战之一不再是“有没有创意”&#xff0c;而是“如何快速、低成本地将创意可视化”。传统视频制作流程动辄数周周期、高昂成本&#xff0…

作者头像 李华
网站建设 2026/1/3 2:37:38

哔哩下载姬实战手册:从零到精通的B站视频管理技巧

还记得那个让你抓狂的场景吗&#xff1f;收藏夹里心爱的视频突然下架&#xff0c;精心整理的UP主内容无法离线观看&#xff0c;或者急需某个视频素材却发现网络不稳定。这些痛点正是哔哩下载姬要帮你解决的现实问题。 【免费下载链接】downkyi 哔哩下载姬downkyi&#xff0c;哔…

作者头像 李华