问题记录
在尝试使用Clion在STM32平台上开发调试时,需要通过OpenOCD结合ST-Link等调试器进行烧录和调试。但通过STM32CubeMX生成代码后,发现出现以下现象:
- 程序能够正常编译并下载到开发板上,且运行符合预期。
- 调试时GDB Server能够正常连接,可以正常查看函数调用栈。但却在
SystemClock_Config
函数配置参数时,调用的HAL_RCC_OscConfig
等函数均返回为HAL_ERROR
,而导致死循环在Error_Handler
。
- OpenOCD的重置reset选项配置的为初始化init。
既然问题在正常下载时没有复现(排除硬件问题),且这段CubeMX生成代码比较成熟,出问题的可能不大。所以主要查看为什么调试会导致时钟配置有误。
根因分析
首先,STM32使用手册提到,PLL使能后,主PLL的配置参数不可更改。也就是说,重复配置PLL是不允许的。
OpenOCD 的启动流程(/usr/share/openocd/scripts/target/stm32f4x.cfg)入手分析。首先,ARM Cortex 内核限制 SWD 时钟频率不能超过内核频率的 1/6,而单片机上电后如果不做任何配置,时钟来源为 HSI 的 16MHz,这样 SWD 频率就最高是 2.666MHz。因此,如果不做时钟上的更改,OpenOCD 默认只能使用 2000kHz 的 adapter speed。
adapter speed 2000
adapter srst delay 100
if {[using_jtag]} {jtag_ntrst_delay 100
}
reset_config srst_nogate
由于选择的重置选项是reset-init,所以,下面这行配置脚本会执行。
而如果让 OpenOCD 启动调试,为了加快 SWD 时钟,OpenOCD 会把 MCU 里面的 PLL 打开并设置到 64MHz,然后将系统时钟来源切换为 PLL.
在复位时提高系统时钟(16MHz → 64MHz),以提升调试速度。
$_TARGETNAME configure -event reset-init {mww 0x40023804 0x08012008 ;# 配置 PLLmww 0x40023C00 0x00000102 ;# 设置 Flash 等待状态mmw 0x40023800 0x01000000 0 ;# 启用 PLLsleep 10mmw 0x40023808 0x00001000 0 ;# 配置总线分频mmw 0x40023808 0x00000002 0 ;# 切换到 PLL 时钟adapter speed 8000
}
那么,为什么会出现时钟不正常呢?我们分析 HAL 库设置系统时钟的流程。按照正常流程,HAL 库会启动 HSE,然后配置 PLL 参数,并将 PLL 时钟源设为 HSE,最后将系统时钟来源设置为 PLL。如果单片机上电后不做任何额外的时钟配置,这部分程序是可以正常工作的,单片机能够在正确的时钟上运行。
然而,巧合的是,启用调试器后,在运行用户代码前,系统时钟就处于已经配置 PLL 的状态。HAL 库如果检测到这种状态,则不会对 PLL 配置进行更改,只会检测现有的 PLL 配置与欲应用的配置是否相符,如果不相符,则返回 HAL_ERROR。
__weak HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct) {... // 初始化 HSI,HSE 等,在此省略if ((RCC_OscInitStruct->PLL.PLLState) != RCC_PLL_NONE) {// 如果 PLL 当前不是系统时钟if (__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_CFGR_SWS_PLL) {// 正常上电时,__HAL_RCC_GET_SYSCLK_SOURCE 应该返回 RCC_CFGR_SWS_HSI,从而进入这个分支... // 对 PLL 进行配置} else {// 如果启动调试器,__HAL_RCC_GET_SYSCLK_SOURCE 应该返回 RCC_CFGR_SWS_PLL,就会进入这个分支// 对 PLL 配置进行检查,如果与给定的配置不符,就返回 ERRORpll_config = RCC->PLLCFGR;if((READ_BIT(pll_config, RCC_PLLCFGR_PLLSRC) != RCC_OscInitStruct->PLL.PLLSource) ||(READ_BIT(pll_config, RCC_PLLCFGR_PLLM) != RCC_OscInitStruct->PLL.PLLM) ||(READ_BIT(pll_config, RCC_PLLCFGR_PLLN) != RCC_OscInitStruct->PLL.PLLN) ||(READ_BIT(pll_config, RCC_PLLCFGR_PLLP) != RCC_OscInitStruct->PLL.PLLP) ||(READ_BIT(pll_config, RCC_PLLCFGR_PLLQ) != RCC_OscInitStruct->PLL.PLLQ)){return HAL_ERROR;}}}
}
后面的情况也正如开头所讲,返回错误值,卡死在错误处理函数内。
解决方法
- 调整OpenOCD的重置选项至Run,关于重置选项的用途,请参考:https://openocd.org/doc/html/Reset-Configuration.html
run
Let the target runhalt
Immediately halt the targetinit
Immediately halt the target, and execute the reset-init script
- 在
main
函数的 USER CODE 1 段内加入以下代码
int main(void)
{/* USER CODE BEGIN 1 */__HAL_RCC_HSI_ENABLE(); // 启用 HSI__HAL_RCC_SYSCLK_CONFIG(RCC_SYSCLKSOURCE_HSI); // 切换系统时钟为 HSI/* USER CODE END 1 */HAL_Init();SystemClock_Config();// 进行其他初始化
}
参考文章
https://blog.vinkvin.com/post/96/
https://blog.t123yh.xyz:3/index.php/archives/922