老农最近搞基于STM32F407的IAP升级固件升级功能,遇到了这样一个问题:IAP引导程序和APP程序都是基于STM32CUBEMX的程序模板生成,单独调试运行APP功能也都正常,但是只要采用IAP方式将APP程序的bin文件烧写到程序存储区,再跳转到APP的地址运行就死掉了。
这个问题困扰了老农挺长时间,后来才发现问题所在:那就是STM32系列的单片机一旦完成PLL时钟设置后,是不能再次配置的。
本例中IAP引导程序首先对PLL进行了成功配置,当跳转到APP程序运行后,APP程序初始化部分又对PLL进行了一次配置,结果配置失败,因为程序是基于STM32CUBEMX的模板,配置失败后调用模板的Error_Handler()函数,该函数原型如下:
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
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 */
}
可以看到,函数内部会先禁止中断,然后就进入死循环。这就是为什么程序每次跳转到APP后死掉的原因。
搞清楚了问题所在,就有针对性的解决方法:一是偷懒的方法,将Error_Handler()函数的内部功能全部屏蔽,这样即使PLL配置出错,也能跳过去,但是必须要保证IAP引导程序和APP程序的时钟配置完全一致,否则还是会出问题。二是想办法重新配置PLL,这样即使IAP引导程序和APP程序的时钟配置不一致,程序也能顺利运行。
要想重新配置STM32的PLL,通过查阅相关资料,发现STM32的PLL一旦使能后配置参数便不能更改,除非在下次重新启动时再次配置相关参数。这样就成死循环了,似乎只能采用前面说的第一种方法了。
老农不死心,继续研究参考手册,发现有这么一段话:
The three PLLs are disabled by hardware when entering Stop and Standby modes, or when
an HSE failure occurs when HSE or PLL (clocked by HSE) are used as system clock.
重点在第一句,在进入停止或待机模式时PLL可以由硬件禁止。这样的话我们就可以在APP程序的时钟初始化之前首先设置STM32进入停止或待机模式,然后再唤醒,并重新对PLL时钟进行配置。
通过研究STM32的停止模式和待机模式,可以知道其进入和退出方式有一定差异,如下所示:
另外如果进入待机模式,退出后除备份区域和待机电路中的寄存器外,其他SRAM和寄存器内容全部丢失,程序将按照复位后的方式重新执行,这个并不是我们的初衷。如果每次唤醒都相当于重启,那我们的程序就进入死循环了。
老农结合自己的实际硬件,正好在某个管脚上有一个外部I2C设备输入方波可以用来做为唤醒源,选用停止模式。具体代码如下:
HAL_Init();
IIC_Init(); //外部设备初始化
SetCLKOandSQW(0,F1Hz,S_1Hz); //使能外部器件方波输出
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); //使能方波输入管脚中断
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI); //进入停止模式
Stm32_Clock_Init(168,6,2,7); //重新配置PLL并使能
HAL_NVIC_DisableIRQ(EXTI9_5_IRQn); //关闭方波输入管脚中断
为了验证效果,将Error_Handler函数中的死循环重新打开,再次烧写新的APP程序,这次APP能够正常运行,这说明通过采用进入停止模式再重新配置PLL时钟是可行的。
采用这种方法确实可以解决PLL的重配置,但是需要结合自己的硬件考虑是否具备唤醒停止模式或待机模式的能力。