STM32-仿真调试时的SystemInit陷阱
我在开始STM32的仿真调试时,遇到一个问题,就是调试时程序一直停在SystemInit()中的等待晶振中,怎么也出不来。
SystemInit()前面部分的代码,都能走过,就是在执行到最后一个函数时出问题了。
最后一个函数是:SetSysClock();
执行到下面这个循环之后,出不来了:
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
这里,我就有疑问了:
1,我希望的是直接进main函数,那么,这个SystemInit()函数是从哪里来的?
2,为什么会进入死循环?
我全工程搜索“SystemInit”,发现在startup_stm32f0xx.s中有这样的代码:
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
看来,系统是先执行SystemInit,然后才执行main的啊。
接下来是第二个问题,为什么进入死循环?
看看注释:/* Wait till HSE is ready and if Time out is reached exit */
等待HSE准备就绪且超时时间到达。超时时间且不去管它,这个HSE是什么?
HSE(High Speed External Clock signal),高速外部时钟信号,是接外部时钟源的。
相应的还有HSI(High Speed Internal Clock signal),高速内部时钟信号,是stm32芯片自带的。
看到这个概念,我就明白问题所在了:是我用的板子,没有接外部晶振啊!
所以,等待HSE准备就绪,这是永远不能达成的条件啊。
所以,这里需要修改一下,不再等待HSE了,其实是不使用HSE了,而是修改为使用HSI。
当我准备修改文件的时候,发现了一个问题,我居然修改不了这个文件!
敲了字母,它不出现在代码中!?
上网一查,原来是system_stm32f0xx.c这个文件是只读的。
好吧,从windows的文件夹中找到文件,查看属性,
见下图:
去掉“只读”即可。
不依赖于HSE,使用HSI,我修改后的代码如下:
/**
* @brief Configures the System clock frequency, AHB/APBx prescalers and Flash
* settings.
* @note This function should be called only once the RCC clock configuration
* is reset to the default reset state (done in SystemInit() function).
* @param None
* @retval None
*/
static void SetSysClock(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK configuration ----------------------------------------*/
// /* Enable HSE */
// RCC->CR |= ((uint32_t)RCC_CR_HSEON);
//
// /* Wait till HSE is ready and if Time out is reached exit */
// do
// {
// HSEStatus = RCC->CR & RCC_CR_HSERDY;
// StartUpCounter++;
// } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
// RCC_HSEConfig(RCC_HSE_OFF);//外部晶振关闭!
RCC->CR |= ((uint32_t)RCC_CR_HSION);//使用内部晶振
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01)
{
/* Enable Prefetch Buffer and set Flash Latency */
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE_DIV1;
/* PLL configuration = HSE * 6 = 48 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLMULL6);
/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)RCC_CFGR_SWS_PLL)
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
//设置系统时钟8MHz
RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI);
while(0x00 != RCC_GetSYSCLKSource());//等待设置成功 8--PLL 4--HSE 0--
HSI
RCC_HCLKConfig(RCC_SYSCLK_Div1);//HCLK 8MHz
RCC_PCLKConfig(RCC_HCLK_Div1);//PLCK 8MHz
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_SYSCFG,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 | RCC_AHBPeriph_GPIOB,ENABLE);
RCC_ADCCLKConfig(RCC_ADCCLK_PCLK_Div4);//ADC1时钟频率 2MHz
}
}
这样修改之后,再进入在线调试,果然走过了SystemInit(),然后进入了main()。
这样,就解决了在线调试总是进不来main()的问题了。
不过,我还是有个疑问:为什么,这样的代码,在调试时有问题,而在全速运行的时候就没有问题呢?
再次仔细查看这段代码:
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
其实并不是一个死循环,跳出的条件有两个:HSE准备好了,或者超时。
由于我的板子没有接外接晶振,第一个条件是不能达到的,那么,第二个条件其实是可以达到的啊,为什么我会以为是个死循环呢?
让我们来看看 HSE_STARTUP_TIMEOUT 是个什么值吧:
查看定义,是这样的:
#define HSE_STARTUP_TIMEOUT ((uint16_t)0x0500) /*!< Time out for HSE start up */
其实,不是死循环,只是循环次数值太大(1280=0x500),单步调试,不能点击走这么多的循环次数(另外,在这里,想进行断点执行跳过循环也不管用,不清楚是什么原因,是因为还没有到执行到main()吗?若有知道原因的高手,请指点,谢谢!)。
这样,我就考虑到了有几个办法解决这个问题了:
1,改小HSE_STARTUP_TIMEOUT,例如:1
评估:危险!我们尽量不要去修改厂家提供的宏。万一以后需要用HSE呢?另外还要考虑这个值是否有其它地方的调用。
2,调试时,修改StartUpCounter变量值,为4ff,则很快达到0x500,跳出循环。
评估:可行,但是比较麻烦,每次运行都需要修改一次。
若不想修改任何代码,这倒也是一个选择。
3,像前文说的那样,修改SystemInit,默认选择HSI。
评估:可行。不过,代码修改量比较大。或许我们还有更好的选择?
4,修改startup_stm32f0xx.s,不执行SystemInit了
如下修改:
IMPORT __main
; IMPORT SystemInit
; LDR R0, =SystemInit
; BLX R0
LDR R0, =__main
BX R0
ENDP
实测,可行。修改时注意,这个文件也是只读的,需要去掉只读属性后才能修改代码。
改动量较小。不过风险可不小,因为我还不能准确评估去掉 SystemInit 那部分代码的影响。
可行的原因分析:系统复位后,HSI振荡器默认被选为系统时钟。
5,去掉SystemIit() 中对 SetSysClock() 的调用;
实测,可行。
改动最较小,只是把那句调用代码注释掉即可。且通过分析SetSysClock()函数,可以知道,若没有启用HSE,则相当于没有执行任何有效操作。可以说,对于使用HSI的情况,逻辑上没有任何差别。
最终,我采用了第5种修改方法,调试运行,一切正常。