芯片异常复位问题(看门狗异常复位)

问题描述:从 Bootloader 跳转到 APP 后,执行GPIO_config()开启蓝牙导致芯片 3.3V 电压被微微拉低,进而引发类似看门狗复位,而注释掉串口打印usart_send_string后程序却能正常运行

问题代码如下:

int main( void ) { nvic_vector_table_set(NVIC_VECTTAB_FLASH, 0x4000); //重映射向量表 /* 驱动初始化 */ BSP_Init(); // 注册 cJSON 内存接口到 FreeRTOS 线程安全堆 cJSON_Hooks hooks; hooks.malloc_fn = pvPortMalloc; hooks.free_fn = vPortFree; cJSON_InitHooks(&hooks); fwdgt_counter_reload();//喂狗 W25Q128_Init(); RTC_BKP6 = BKP_APP; //处于APP程序 RTC_BKP7 = BKP_APP; //TBOX升级模式进入非升级模式--->默认走CAN升级 usart_send_string(UART_PRINTF,"Enter the APP1111111~\n"); GPIO_config(); //提前配置蓝牙引脚,会将芯片3.3V拉掉导致---->现象像看门狗复位 #ifdef CAN0_EN /* initialize transmit message */ can_struct_para_init( CAN_TX_MESSAGE_STRUCT, &g_transmit_message ); //存储发送帧结构体初始化 g_transmit_message.tx_sfid = 0x00; //标准格式帧标识符 g_transmit_message.tx_efid = 0x18FEEEBB; //扩展格式帧标识符 g_transmit_message.tx_ft = CAN_FT_DATA; //帧类型:数据帧/远程帧 g_transmit_message.tx_ff = CAN_FF_EXTENDED; //帧格式:标准帧/扩展帧 g_transmit_message.tx_dlen = 8; //数据长度 /* initialize receive message */ can_struct_para_init( CAN_RX_MESSAGE_STRUCT, &g_receive_message ); //接收帧结构体初始化 #endif xTaskCreate( ( TaskFunction_t )start_task, ( const char * )"start_task", (uint16_t )128, (void* )NULL, (UBaseType_t )1, ( TaskHandle_t * )&StartTask_Handler ); vTaskStartScheduler();//在 vTaskStartScheduler() 调用之前,不能调用 xQueueSendFromISR()。 while( 1 ) { } }

一、 原因分析

1. 电源瞬态冲击与负载叠加(Root Cause)
  • 蓝牙启动的瞬间浪涌电流(Inrush Current):蓝牙模块在被拉高使能引脚(PC10)开启的一瞬间,其内部射频电路启动以及外围旁路电容充电,会产生一个较大的瞬态浪涌电流
  • 串口发送的动态功耗:当执行usart_send_string时,USART0 发送引脚(TX)高频翻转,且串口驱动电路、MCU 内部串口外设都在工作,这会消耗一定的动态电流,并在 3.3V 电源轨上引入高频噪声。
  • 负载叠加导致电压跌落(Voltage Dip):如果板载 3.3V LDO 或 DCDC 电源芯片的瞬态响应较差,或者 MCU 附近的去耦电容容量不足,那么在串口正处于发送/刚发送完的瞬态活跃期时,紧接着拉高 PC10 开启蓝牙,两者叠加的电流负载超出了电源系统的承载极限,导致 3.3V 电压瞬时跌落。
2. 为什么现象像“看门狗复位”?
  • 3.3V 电压跌落可能并没有低到触发芯片硬件的欠压复位(LVR/BOR)阈值。
  • 但电压波动会导致 MCU 的高速时钟(PLL 锁相环)失锁,或者导致Flash 在高频读取指令时出现校验/数据错误(Flash Read Corruption)
  • 这会使 MCU 直接触发HardFault_Handler(硬件陷入死循环)或 CPU 挂起。
  • 因为 CPU 挂起无法继续喂狗,最终导致看门狗(FWDGT)超时溢出引发复位。复位后程序重新运行,再次走到该位置,再次跌落,形成“反复复位”的死循环。
3. 为什么注释掉usart_send_string程序就正常了?

注释掉串口打印后,在GPIO_config()执行时,串口处于空闲状态(TX 引脚保持高电平),无动态开关损耗,MCU 的整体功耗降到最低,3.3V 电源轨处于非常稳定的状态。此时单凭蓝牙模块上电的瞬态冲击,不足以将 3.3V 电压拉低到让 PLL 失锁或 Flash 出错的临界值,因此系统能够顺利扛过这一波浪涌,程序得以正常运行。


二、 优化建议

1. 软件优化策略
  • 错峰上电(Power Sequencing):避免在系统刚启动、外设初次初始化的敏感阶段开启大功耗外设。应将蓝牙模块的使能延时,放在 FreeRTOS 调度器启动并稳定运行后(例如延时 500ms)再开启。
  • 无抖动 GPIO 配置:在配置 GPIO 为输出模式前,先向输出寄存器(OCTL)写入默认的电平值(低电平,关闭蓝牙),避免在模式切换瞬间产生不需要的电平窄脉冲(Glitch)。
  • 复位源排查:可以在main()入口处读取RCU_RSTFR寄存器,判断究竟是看门狗复位(FWDGTRST)还是低电压/外部复位,以便更精准地定位硬件表现。
2. 硬件优化建议(供参考)
  • 在蓝牙模块的 VCC 输入端增加大容量低 ESR 的电容(如 10μF ~ 47μF 贴片钽电容或多层陶瓷电容 MLCC),用于吸收上电瞬间的浪涌电流。
  • 如果蓝牙模块使能引脚控制的是电源 MOSFET,可在 MOSFET 的栅极(Gate)增加 RC 延迟电路以实现“软启动(Soft-Start)”,减缓上电瞬间的电流变化率(dI/dt)。

三、 预览代码(不修改工程文件)

1. 修改 main.c 中的GPIO_config

在初始化阶段,将蓝牙引脚 PC10 配置为输出低电平(默认关闭),并且通过先写寄存器值的方式防止引脚配置瞬间产生高电平抖动。

void GPIO_config() { /* 无线模组使能引脚 */ rcu_periph_clock_enable(RCU_GPIOB); gpio_bit_reset(GPIOB, GPIO_PIN_7); // 显式初始化输出寄存器为低 gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_7); gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7); /* 无线模组供电引脚 */ gpio_bit_reset(GPIOB, GPIO_PIN_6); // 显式初始化输出寄存器为低 gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_6); gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6); /* 蓝牙使能引脚 */ rcu_periph_clock_enable(RCU_GPIOC); gpio_bit_reset(GPIOC, GPIO_PIN_10); // ★ 核心改动 1:配置输出前,先将输出寄存器清零,确保引脚默认输出低电平(关闭蓝牙) gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_10); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10); // ★ 核心改动 2:此处注释掉立即开启蓝牙的代码,改为后续在 RTOS 任务中延时开启 // gpio_bit_set(GPIOC, GPIO_PIN_10); }
2. 修改 uTask.c 中的BLE_task

BLE_task启动后,先进行一段延时,避开主系统启动和串口打印造成的电流高峰,然后再将 PC10 拉高使能蓝牙。

static void BLE_task(void *pvParameters) { static uint8_t t_cnt = 0; static uint16_t t_heart = 0; WIRELESS_RXD wireless_rxxd = {0}; // ★ 核心改动 3:分步延时启动蓝牙,实现错峰上电 vTaskDelay(pdMS_TO_TICKS(500)); // 1. 等待 RTOS 调度器运行稳定(500ms) gpio_bit_set(GPIOC, GPIO_PIN_10); // 2. 此时系统负载较轻,拉高 PC10 开启蓝牙模块 vTaskDelay(pdMS_TO_TICKS(2500)); // 3. 继续延时 2.5s(与原本的 3000ms 总延时保持一致,不影响后续时序) // 蓝牙设备密钥查询等后续逻辑 BLE_Device_Key_Inquiry(Inquire_all, sizeof(Inquire_all)); while (1) { // 原有循环逻辑... } }