STM32-仿真调试时的SystemInit陷阱

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种修改方法,调试运行,一切正常。

 

static void SetSysClockTo72(void) { __IO uint32_t StartUpCounter = 0, HSEStatus = 0; /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/ /* Enable HSE :使能 HSE */ RCC->CR |= ((uint32_t)RCC_CR_HSEON); /* Wait till HSE is ready and if Time out is reached exit */ //等待HSE就绪并做超处理 do { HSEStatus = RCC->CR & RCC_CR_HSERDY; StartUpCounter++; } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT)); if ((RCC->CR & RCC_CR_HSERDY) != RESET) { HSEStatus = (uint32_t)0x01; } else { HSEStatus = (uint32_t)0x00; } //启动成功执行下步的代码 if (HSEStatus == (uint32_t)0x01) { /*使能预取指 cpu在FLASH取代码*/ FLASH->ACR |= FLASH_ACR_PRFTBE; /* Flash 2 wait state :设置成两个等待周期 */ FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY); FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2; /* HCLK = SYSCLK =72M */ RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; /* PCLK2 = HCLK =72M */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; /* PCLK1 = HCLK =36M */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; //二分频 /* 锁相环配置: PLLCLK = HSE * 9 = 72 MHz */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL)); RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9); /* Enable PLL :使能PLL*/ RCC->CR |= RCC_CR_PLLON; /* Wait till PLL is ready :等待PLL稳定*/ while((RCC->CR & RCC_CR_PLLRDY) == 0) { } /* Select PLL as system clock source :选择PLL作为系统钟*/ 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 :等待PLLCLK切换为系统钟*/ while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { } } 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 */ //如果HSE 启动失败,用户可以在这里添加处理错误的代码 } } 由上述描述我了解到系统启动后最先开始对flsh代码读取,那么在标准库配置为什么又进行读取,是否多余
03-30
<think>嗯,用户的问题是关于在STM32的标准库中配置,为什么在系统启动后已经对Flash进行了设置,但在代码中再次配置Flash的等待周期,是否多余。我需要仔细分析这个问题,确保回答准确。 首先,系统启动,启动代码(如startup_stm32f10x.s)确实会进行些初始化,包括钟和Flash设置。但不同型号的MCU默认钟可能不同,比如有些可能使用内部HSI,而用户代码可能需要切换到HSE并超频到72MHz。这候,Flash的等待周期需要根据实际运行的钟重新配置。 当使用标准库函数配置系统,比如SetSysClockTo72(),代码中会再次设置Flash的等待周期,这是因为当系统钟频率提高后,Flash的访问速度可能跟不上,需要增加等待周期以避免总线错误。例如,当从默认的8MHz切换到72MHz,必须调整Flash的等待状态,否则可能导致读取错误。 另外,启动代码中的初始化可能只是基础的配置,而用户代码中的配置是根据实际需求调整到更高的频率,因此需要重新设置Flash。此外,不同厂商或型号的启动文件可能不同,标准库为了保证兼容性和正确性,会在钟配置中显式设置Flash,确保无论启动代码如何,都能正确运行。 总结起来,虽然启动代码可能已经配置了Flash,但用户代码中的再次配置是为了适应更高的钟频率,确保稳定运行,并非多余。需要分清楚启动代码的基础配置和用户代码的具体需求之间的区别,以及不同情况下Flash配置的必要性。</think>在STM32标准库的钟配置代码中,对Flash预取和等待周期的设置看似重复,但实际上是有必要的。以下是逐步分析: 1. **启动阶段的Flash默认配置** - 系统上电后,芯片首先执行启动文件(如`startup_stm32f10x.s`),此内核以内部HSI钟(8MHz)运行。 - **Flash访问速度与钟频率相关**:当系统钟较低(如8MHz),Flash可能只需1个等待周期或无等待。 2. **用户代码中的钟超频需求** - 当通过`SetSysClockTo72()`将钟提升至72MHz,**必须重新配置Flash等待周期**。 - 计算公式: $$ \text{等待周期数} = \left\lceil \frac{\text{SYSCLK (MHz)}}{24} \right\rceil - 1 $$ 例如72MHz: $$ \text{等待周期} = \lceil 72/24 \rceil -1 = 2 $$ 3. **标准库代码的兼容性设计** - 不同型号STM32的启动文件可能不同(如F1系列有不同Flash容量版本)。 - 标准库的钟配置函数需要**显式覆盖所有可能的初始状态**,确保无论启动文件如何配置,超频后的Flash参数正确。 4. **代码流程的关键操作** ```c FLASH->ACR |= FLASH_ACR_PRFTBE; // 启用预取缓冲区 FLASH->ACR &= ~FLASH_ACR_LATENCY; // 清除原有等待周期 FLASH->ACR |= FLASH_ACR_LATENCY_2; // 设置2个等待周期(72MHz必需) ``` 5. **不配置的后果** - 若未正确设置等待周期,当CPU以72MHz访问Flash,会因为Flash响应速度不足导致**总线错误**或**数据读取异常**。 **总结**:虽然启动文件可能初始化了Flash,但用户代码中的二次配置是**针对超频后的安全操作**,并非冗余。这是STM32钟配置的标准实践,确保硬件在不同工作频率下的稳定性。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值