STM32G474 “程序无法运行/PWM无输出” 排查笔记

日期:2026-09-15
芯片:STM32G474RET6

现象链

  1. TIM6中断能正常触发(回调函数里的计数器 a 在自增),一开始误以为一切正常
  2. 但TIM1的PWM输出(PA8/PA9/PA10)示波器测不到任何波形
  3. 换成完全不依赖HAL的裸机GPIO翻转程序测试,PA8依然恒定3.3V,无任何跳变 —— 说明问题不在应用层代码
  4. 暂停调试查看PC寄存器/反汇编窗口,发现程序停在 0x1FFF4xxx 地址,而不是Flash地址 0x08000xxx
  5. 反汇编显示卡在一个死循环,不断轮询读某寄存器状态位(LDR → LSLS → BPL 循环跳回自身)

走过的弯路(先排除,再定位)

依次排查了下面这些软件层面的东西,虽然部分确实是bug,但都不是本次故障的根本原因:

  • NVIC中断优先级/使能 —— 没问题
  • HAL_TIMEx_PWMN_Start 三次都误传了 TIM_CHANNEL_1(本该是CH1/CH2/CH3)—— 是个真bug,顺手修了
  • 缺少 __HAL_TIM_MOE_ENABLE(高级定时器主输出使能,STM32的安全设计,必须手动开)—— 也顺手补了,是必要条件但非本次故障原因
  • SystemClock_Config 里改HSE/HSI来回试 —— 全部无效,因为问题发生在 main() 执行之前,改任何用户代码都不可能影响它

根本原因

选项字节 nSWBOOT0 被勾选(Checked: BOOT0 taken from PB8/BOOT0 pin),即启动模式由物理PB8引脚电平决定,而PB8被外部电路拉到了3.3V(高电平)。

STM32启动逻辑:BOOT0逻辑值=1 → 芯片从内置System Bootloader(ROM,地址 0x1FFF0000 附近)启动,根本不会执行用户Flash里的任何代码。所以不管怎么改 main.c 都是徒劳 —— 程序连 Reset_Handler 都没走到用户区

关键教训

测物理PB8引脚电压不能直接判断问题,因为如果 nSWBOOT0未勾选的(用选项字节 nBOOT0 位而不是物理引脚决定启动模式),那PB8电压再高也无关。所以另一块"好板子"PB8同样是3.3V却能正常工作,因为它的 nSWBOOT0 配置是未勾选状态。

解决步骤

STM32CubeProgrammer(不是Keil,Keil连不上被卡死的芯片时优先用这个工具):

  1. 连接芯片
    • 若连不上,先确认Keil已完全 Stop Debug Session,释放SWD占用
    • Reset mode可尝试切换为 Hardware reset,更容易抢到连接时机
  2. 进入 OB (Option Bytes) → Detailed view
  3. 取消勾选 nSWBOOT0(改为 “BOOT0 taken from the option bit nBOOT0”)
  4. 确认 nBOOT0 = 1(勾选,即从主Flash启动)
  5. Apply
  6. 断电重新上电(选项字节生效必须重新上电,软件复位不够)

后续注意

之后调试时若发现 nSWBOOT0 又被改回勾选状态,检查Keil的 Debug → Settings → Flash Download 里是否带有旧的Option Bytes配置,每次下载时把设置覆盖回去了,需要同步改掉。

通用诊断方法论

当"改了代码但完全没反应,无论怎么改都一样"时,第一反应应该是确认程序是否真的跑在用户Flash里,而不是钻进业务逻辑排查。

  1. 调试器暂停,看 PC寄存器 / Disassembly窗口 当前地址:
    • 0x08000000 开头 → 用户Flash,正常
    • 0x1FFF0000 开头 → System Bootloader,说明启动模式配置错了,去查BOOT0/选项字节,不要查应用代码
    • 0x20000000 开头 → 在RAM,可能是调试配置问题或程序跑飞
  2. 一旦确认卡在Bootloader地址,直接去查选项字节里的 nSWBOOT0 / nBOOT0,而不是纠结晶振、时钟树、外设初始化顺序这些 —— 那些改了也没用,因为程序压根没执行到那里。

涉及的其他知识点(顺带修复的真bug)

1. TIM1互补通道启动错误

// 错误写法(三次都传CHANNEL_1)
HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_1);

// 正确写法
HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_3);

2. 高级定时器必须手动使能主输出

STM32高级定时器(TIM1/TIM8)有独立的MOE(Main Output Enable)位,是硬件安全设计,防止意外输出驱动信号,必须手动置位:

__HAL_TIM_MOE_ENABLE(&htim1);

3. TIM6中断触发不代表整个系统正常

TIM6中断能触发只说明TIM6这一个外设的时钟和中断链路是通的,不能作为"系统时钟/启动流程整体正常"的证据。当排查"为什么另一个外设不工作"时,不能因为某个外设正常就排除系统级问题(如启动模式错误)。

最后如果DeBug一直进入这个东西的话,看看外部晶振是不是不起振了,换一下内部晶振(2026.9.19)

void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐