尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32 CAN Bootloader脱机失效排查:调试器掩盖的复位、时钟与跳转陷阱

STM32 CAN Bootloader脱机失效排查:调试器掩盖的复位、时钟与跳转陷阱 先说一个典型的翻车现场STM32G0B1KCT6 这颗片子Cortex-M0 内核128KB Flash带一个经典 bxCAN 控制器。我拿它做 CAN bootloader方案是网上的开源 open bootloader 加自己改的上下位机协议。在 Keil 里 F5 全速跑bootloader 能收 CAN 升级帧、能擦 Flash、能跳转应用跑起来也好好的。结果呢把 ST-Link 一拔断电再上电板子直接“装死”——CAN 总线上一帧报文都不回应用也起不来。这种问题最折磨人它不是“完全不行”而是“只在特定环境下不行”。这篇文章就专门聊这个故障从“调试器下正常、脱机失效”这个现象出发把复位、时钟、看门狗、中断向量表、CAN 配置这些点一个个过一遍最后给出一个可直接照抄的排查路径和跳转代码。搞 CAN bootloader 的朋友尤其是刚开始上手 G0 系列、对内置 bootloader 期望过高的人建议认真看完。1. 项目背景与故障现象调试器里的“假正常”1.1 这个 bootloader 做了什么先说清楚项目形态。平台是 STM32G0B1KCT6LQFP32 封装主频 64MHzFlash 128KB。Bootloader 占 Flash 起始区域我习惯把应用跳转地址定在 0x08004000也就是 bootloader 预留 16KB。CAN 通信速率 500kbps标准帧上位机用 CAN 分析仪加 PC 软件。整个升级流程大致分这几步上电后 bootloader 初始化时钟、CAN、Flash 接口。等待 CAN 升级指令比如报文 ID 0x101DLC 8前 4 字节是约定好的魔术字。收到指令后擦除应用区全部页。逐包接收固件数据写入 Flash。全部写完做一次 CRC 校验通过则跳转不通过则报错并重新等待。跳转地址 0x08004000应用启动。这套流程听起来很常规实际跑起来也确实能跑通。“能跑通”指的是在调试器接管的情况下。调试模式下一路顺风不代表脱机也能正常工作这一点很多人一开始根本没意识到。1.2 故障的准确描述先分环节再谈排查“调试下正常、脱机不正常”不是单一现象它至少能拆成三种完全不同的表现现象 A脱机上电后CAN 总线上完全没有任何应答bootloader 好像根本没起来。现象 BCAN 通信正常升级帧也能收固件也烧进去了但烧完跳转后应用不运行或者运行后立刻 HardFault。现象 C上电后板子上的 LED 反复闪烁或者干脆熄灭像是系统在不断复位每次都走不完初始化流程。这三种现象对应的原因差距很大。现象 A 多半出在时钟、看门狗、启动流程上现象 B 多半出在跳转代码和应用工程的启动配置上现象 C 基本就是看门狗复位循环或者供电问题。拿到故障的第一件事不是翻代码而是先判断属于哪一类把范围缩小。2. 为什么“插着调试器”会掩盖真实问题调试态与脱机态的六大差异2.1 复位与时钟调试器给的“确定性”调试器连接时内核复位是由调试工具发起的比如 ST-Link 的“Connect under Reset”模式。它会先拉低 NRST、建立 SWD 连接、再把内核释放整个时序完全确定。但脱机上电是另一回事电源爬坡需要时间、外部晶振起振需要时间、NRST 释放后内部 RC 振荡器稳定也需要时间。任何一个环节出问题行为都会变化。最典型的是外部晶振。G0 系列的 HSE 起振失败后系统时钟会自动回退到 HSI 16MHz。如果 bootloader 代码里默认 HSE 能稳定起振并且按 64MHz 算了 PLL 和 CAN 分频那脱机时一旦 HSE 没起来CAN 波特率直接差 4 倍总线上自然一帧都收不到。调试器连接时为什么没这个问题因为调试器通过 SWD 和内核保持通信它的时序控制和电平状态本身就相当于给电路加了一层“外挂稳定器”很多临界状态被掩盖了。所以我排查这类问题时第一件事永远是确认脱机时系统时钟到底跑在多少。没有示波器就配 MCO 引脚输出时钟有示波器就直接量 Pin 波形。不要一上来就盯着 CAN 底层代码找半天。2.2 看门狗与中断被冻结的危机这是调试掩盖问题最严重的领域。很多人不知道Cortex-M0 内核的调试接口可以通过 DBGMCU 寄存器把独立看门狗 IWDG、窗口看门狗 WWDG、以及定时器/外设在内核暂停时冻结。调试器 halt 内核后IWDG 停止计数看门狗形同虚设。但脱机运行时IWDG 一旦使能在 LS 时钟驱动下立刻开始倒计时不会给你任何准备时间。如果你的 bootloader 里有某段耗时操作超过看门狗周期比如擦除 Flash 期间忘了喂狗或者阻塞等待某个标志超时脱机时系统就会不断复位。调试器下为什么没事因为调试器里的“全速运行”虽然也是运行但很多调试工具默认会把看门狗冻结位写进去。你看着 App 跑得好好的其实 IWDG 根本没在工作。另外中断也是一样。调试器暂停时外设中断不会触发你观察到的“正常”可能只是“还没来得及触发就已经被跳过”。脱机后中断一开各种中断嵌套和优先级问题立刻浮现。2.3 供电、SWD 引脚与硬件状态看不见的“外援”调试器不只是“看变量”的工具它还是一个实际电路环节。ST-Link 的 3.3V 输出能给板子供电Debug 模式下很多自制板等于白嫖了调试器的电源。一旦拔掉调试器板子独立供电如果电源电路余量不足、纹波偏大CAN 收发器在临界电压下就会工作不稳定。更隐蔽的是 SWD 引脚复用问题。SWCLK 和 SWDIO 在调试时被调试器驱动电平确定。但如果板子上这两个引脚还被复用成普通 GPIO比如控制CAN收发器的 STB 待机引脚独立运行时就可能因为初始化代码还没跑到或者初始化顺序不对导致收发器一直处于待机模式。调试器连着的时候看不出来拔掉就原形毕露。这一类问题我把它叫“硬件层面的外援消失”它和软件没有直接关系但会让你怀疑人生。3. 分步排查实操从现象到根因3.1 第一步给 bootloader 加“打点”让硬件自己告诉你跑到哪了没有 LED 的话先加一个。即使有串口也要加 LED因为 CAN bootloader 场景下串口不一定存在而 LED 人人都能看。我的习惯是分阶段闪烁上电初始化完成亮一下、进入 CAN 等待状态慢闪、收到升级指令快闪、Flash 擦除完成长灭、跳转前点亮不再操作。编译烧进去拔掉调试器断电再上电看 LED 的状态就能判断 bootloader 卡在哪个阶段。这一步能把“系统完全没跑起来”和“跑起来了但没收到 CAN 报文”区分开。如果 LED 从初始化的第一闪都没有问题在芯片启动阶段比如外部晶振导致卡死、电源复位异常、或者读保护选项字节被改乱。如果 LED 正常走到“进入等待”状态但 CAN 无应答问题大概率在 CAN 初始化、过滤器、或者收发器硬件上。不要跳过这一步直接上逻辑分析仪抓总线纯属浪费生命。3.2 第二步验证跳转链路用 GPIO 拉高拉低做“握手”如果现象是 B能烧写但跳转后应用不跑验证跳转链路最直接的办法是在 bootloader 跳转前把一个 GPIO 拉高应用启动后立刻把这个 GPIO 拉低。用示波器或者万用表都能看。如果看到了下降沿说明跳转成功应用确实跑起来了问题在应用内部。如果 GPIO 一直保持高电平说明应用根本没启动就可以顺着跳转函数、应用向量表、链接脚本往下查。我当时排查时发现跳转后 RIPReset Vector根本没执行到最后定位到是跳转前没把系统时钟恢复到默认的 HSI。bootloader 里开了 PLL 到 64MHz跳转后应用的 SystemInit 里虽然会重新初始化时钟但在那之前有一些早期的初始化代码已经用了错误的时钟源直接卡死在等待 PLL LOCK 的循环里。3.3 第三步盯住看门狗和时钟配置两个“隐形杀手”先说看门狗。检查 bootloader 代码里有没有开启 IWDG如果有看喂狗位置。Flash 擦除是一个比较耗时的操作G0 的 Flash 页大小和擦除时间虽然不算长但如果在擦除循环里没喂狗超时复位就来了。脱机时这种复位会一直循环表现出来就是 LED 反复重启、CAN 完全没有稳定输出。再检查时钟。bootloader 的 SystemInit 里如果用 CubeMX 生成默认会尝试配置 HSE 和 PLL。如果板子 HSE 起振有问题初始化不会 “报错”它会自己回退到 HSI。问题在于你的 CAN 分频值是基于什么时钟算出来的。我建议 bootloader 这种对时序敏感的程序初期先直接用 HSI 运行把所有外设分频都按 HSI 16MHz 来算等确认 HSE 稳定再切换。这样可以排除掉一个最大的变量。3.4 第四步CAN 起不来先查过滤器和采样点CAN 报文收不到除了时钟问题最常见的就是过滤器配置。G0 的 bxCAN 过滤器支持掩码模式和列表模式很多人直接从例程里抄了过滤器代码结果过滤 ID 和上位机发的 ID 不一致。还有一个坑是bootloader 和应用如果共用一套 CAN 初始化代码在跳转前关闭 CAN 时没有把过滤器复位应用启动后自己重新初始化 CAN 时过滤器可能仍然保留 bootloader 的配置。虽然 CAN 外设复位会清掉过滤器但如果你只是关掉了 CAN 并没有复位外设残留问题就会存在。采样点也是一个容易被忽视的参数。500kbps 下PCLK1 为 64MHz 时我常用的配置是 BRP8BS113BS22SJW1这样时间量子数是 16采样点 (113)/(1132)87.5%。这个采样点偏后适合较长总线但如果总线很短、节点很少用 81.25% 会更稳对应 BRP8、BS112、BS23。不要照抄某个“通用配置”就完事根据你实际总线的长度和节点数调否则某些环境下会间歇性丢帧。3.5 第五步烧写失败不一定是 Flash 问题先怀疑丢包现象是“调试时能烧、脱机时烧写失败”的人大概率是踩了丢包的坑。G0 的 bxCAN 只有两个硬件 FIFO每个 FIFO 有 3 个邮箱且不支持 DMA。bootloader 擦除 Flash 期间如果关闭了中断上位机连续发送的固件包就会直接覆盖或者溢出丢失几包写进去的固件不完整CRC 校验必然失败。这个问题最容易出现在“调试模式”下因为调试时你是一包一包手动发送或者发送节奏很慢丢包概率低。脱机后上位机自动连续快速发送丢包立刻暴露。解决办法有两个思路一是 bootloader 在擦除前先把整个固件文件接收完放到 RAM 缓冲区全部收齐并 CRC 校验通过后再开始擦 Flash二是上位机加超时重传机制bootloader 每收到一包回一帧确认上位机没收到确认就重发。第一种方案对 RAM 有要求G0B1KCT6 的 RAM 足够放下普通的应用镜像实际用下来更省心。4. 核心代码与配置参考一个健壮的跳转函数长什么样4.1 跳转前的“外设软复位”不要指望应用替你擦屁股很多人写的跳转函数只有三行关闭中断、取向量、跳转。这在 bootloader 没初始化任何外设的时候够用但一旦 bootloader 里开了 CAN、串口、定时器、DMA、看门狗直接跳转就是埋雷。应用启动时面对的是一个被 bootloader 用过的系统外设状态、中断配置、时钟树、栈指针全是未知数。我现在的跳转函数长这样typedef void (*pFunction)(void); void jump_to_app(uint32_t app_addr) { uint32_t app_msp; pFunction app_reset; // 1. 关闭全局中断防止跳转过程中断嵌套 __disable_irq(); // 2. 关闭 SysTick清空计数器 SysTick-CTRL 0; SysTick-LOAD 0; SysTick-VAL 0; // 3. 复位所有外设AHB/APB1/APB2 域 // 这一步会清掉 CAN、USART、TIM、DMA 等全部残留状态 RCC-AHBRSTR 0xFFFFFFFF; RCC-AHBRSTR 0x00000000; RCC-APBRSTR1 0xFFFFFFFF; RCC-APBRSTR1 0x00000000; RCC-APBRSTR2 0xFFFFFFFF; RCC-APBRSTR2 0x00000000; // 4. 恢复系统时钟到 HSI 16MHz并关闭 PLL RCC-CR | RCC_CR_HSION; while ((RCC-CR RCC_CR_HSIRDY) 0); RCC-CFGR 0; while ((RCC-CFGR RCC_CFGR_SWS) ! 0); RCC-CR ~RCC_CR_PLLON; while ((RCC-CR RCC_CR_PLLRDY) ! 0); // 5. 清空 NVIC 所有中断使能和挂起位 for (uint32_t i 0; i 8; i) { NVIC-ICER[i] 0xFFFFFFFF; NVIC-ICPR[i] 0xFFFFFFFF; } // 6. 把中断向量表挪到应用地址 SCB-VTOR app_addr; // 7. 取应用的 MSP 和 Reset_Handler 地址 app_msp *(volatile uint32_t *)app_addr; app_reset (pFunction)(*(volatile uint32_t *)(app_addr 4)); // 8. 设置 MSP跳转 __set_MSP(app_msp); app_reset(); while (1); }这段代码里第 3、4 步是很多“标准例程”里没有的但它们恰恰是解决“跳转后应用跑飞”的关键。第 3 步把所有外设复位一遍相当于给应用一个干净的硬件环境第 4 步恢复时钟到默认的 HSI避免应用早期初始化代码在错误时钟下运行。第 6 步设置 VTOR 也很重要虽然 G0 是 M0但 VTOR 是真实存在的不设置的话应用一旦触发中断会从 bootloader 的向量表里取中断向量后果就是 HardFault 或者跳到一个完全不相干的地方。4.2 CAN 初始化与采样点计算示例CAN 初始化看起来简单但采样点算不对总线一长就掉链子。G0 的 bxCAN 挂在 APB1 总线上我的板子 PCLK1 是 64MHzHSI 经 PLL 倍频。500kbps 配置如下void can_init(void) { GPIO_InitTypeDef gpio {0}; CAN_InitTypeDef can {0}; CAN_FilterTypeDef filter {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_CAN1_CLK_ENABLE(); gpio.Pin GPIO_PIN_11 | GPIO_PIN_12; gpio.Mode GPIO_MODE_AF_PP; gpio.Pull GPIO_NOPULL; gpio.Speed GPIO_SPEED_FREQ_HIGH; gpio.Alternate GPIO_AF4_CAN1; // G0 上 CAN1_TX/RX 复用功能 HAL_GPIO_Init(GPIOA, gpio); hcan.Instance CAN1; hcan.Init.AutoBusOff ENABLE; hcan.Init.AutoWakeUp DISABLE; hcan.Init.ABOM ENABLE; hcan.Init.NART DISABLE; hcan.Init.RFLM DISABLE; hcan.Init.TXFP DISABLE; hcan.Init.Mode CAN_MODE_NORMAL; hcan.Init.SJW CAN_SJW_1TQ; // 500kbps, PCLK164MHz, BRP8, BS113, BS22 - 87.5% hcan.Init.Prescaler 8; hcan.Init.SyncJumpWidth CAN_SJW_1TQ; hcan.Init.TimeSeg1 CAN_BS1_13TQ; hcan.Init.TimeSeg2 CAN_BS2_2TQ; HAL_CAN_Init(hcan); }这个配置下如果想让采样点降到 81.25%把 TimeSeg1 改成 12TQ、TimeSeg2 改成 3TQ 就行其他不用动。总线上节点比较近、线缆短的话87.5% 和 81.25% 体感差别不大但如果经常偶发丢帧用 CAN 分析仪看错误帧类型如果全是位填充错误、CRC 错误大概率就是采样点和总线波特率微观偏差问题微调一两个 TQ 就能解决。4.3 应用工程的 VTOR 与启动准备跳转函数设置好只是半边应用侧也要配合。很多人的应用工程是从零新建的链接脚本里 Flash 起始地址仍然是 0x08000000bootloader 跳过去自然跑飞。应用工程必须做两件事第一Linker 脚本的 Flash 起始地址改成 bootloader 预留的地址。Keil 里在 Options for Target 的 Target 页修改 IROM1 起始地址和大小IAR 里修改链接配置文件ST 官方例程用的是 scatter 文件。这个改错会导致整个应用烧写地址错位跳转进去执行的根本不是 Reset_Handler。第二在应用代码的最早阶段设置 SCB-VTOR。即便 bootloader 跳转前已经设置过了应用自己的 SystemInit 或者 C 启动代码也可能会重新覆盖所以保险起见main 里第一行就写#define APP_BASE_ADDR 0x08004000 SCB-VTOR APP_BASE_ADDR;这行必须在任何外设初始化、任何中断使能之前执行。放在 SystemInit 里也行但必须确保这个宏定义是正确的。G0 是 M0支持 VTOR不需要像老 M0 那样搞向量表拷贝省事很多。5. 常见问题速查表与避坑心得5.1 现象—原因—解决方案对照表我把踩过的坑和收集到的同类问题做成了一张表排查时建议直接对着看故障现象可能原因排查/解决方向脱机上电 LED 只闪一下反复重启IWDG 复位循环、供电不足检查喂狗位置特别是 Flash 擦除期间用示波器看复位引脚LED 正常但 CAN 无任何报文HSE 未
返回列表