为什么要了解时钟树?

最近项目开发的时候,外部时钟源是16MHz,结果配置错了系统时钟,STM32F103的系统时钟频率最高为72MHz,错误地配置到了144MHz,但是AHB总线时钟又正确地配置到72MHz,最终导致了以下几种情况:

  • 程序会意外跑飞,然后进入Hard fault,甚至不知道发生了什么,就出现这样的错误;
  • 数据意外错误,int32负数乘法的时候,第30位数据异常; 以上的问题,较难排查,而且给人一种芯片不稳定的错觉,对的,没错,这就是超频的代价,高速行驶的汽车,更容易翻车,所以,对于芯片的时钟配置有一个全面的了解,有着弥足轻重的作用。

树的根

时钟树 先看一下这张从网上扒来的图,说到树,不由自主想到了二叉树,设备树,森林里的树,而时钟树,也不例外,和这些树一样,都有根,有叶子,做一下简单的类比; 是树汲取营养的地方,是树赖以生存的一个部位; 而时钟树的根就是时钟输入源,用于产生系统的时钟节拍,即系统时钟

系统时钟源

  • 高速内部时钟 HSI8MHz;
  • 高速外部时钟 HSE4MHz25MHz;
  • 锁相环时钟 PLL; 常用的配置方案例如:选用外部时钟HSE,频率为8MHz,经过锁相环时钟,PLL进行9倍频,则系统时钟频率:

SysClock = 8MHz*9 = 72Mhz

或者使用外部时钟HSE,频率为16MHz,则经过锁相环时钟,PLL2分频,然后9倍频,则系统时钟频率:

SysClock = 16MHz/2*9 = 72Mhz

可见,PLL时钟源的使用很灵活,可以灵活运用;

标准库的时钟配置

STM32标准库的启动文件中可以发现,在main()运行前,已经运行了SystemInit()函数,代码如下,IDE是MDK,代码如下,具体可以参考

Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
     IMPORT  __main
     IMPORT  SystemInit
                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP

标准库是默认按照外部高速时钟频率为8MHz进行配置的,SystemInit()函数原型位于system_stm32f10x.c,具体的调用关系如下,具体源码可以参考标准库;

    SystemInit()
        -->SetSysClock()
            -->SetSysClockTo72()

在函数SetSysClock()通过宏定义选择系统所需要配置的时钟频率;

static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
  SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
  SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
  SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
  SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
  SetSysClockTo56();  
#elif defined SYSCLK_FREQ_72MHz
  SetSysClockTo72();
#endif 
 /* If none of the define above is enabled, the HSI is used as System clock
    source (default after reset) */ 
}

函数SetSysClockTo72默认定义系统时钟为72MHz

#define SYSCLK_FREQ_72MHz   72000000

初始化后系统的状态: | | | | — | — | | SYSCLK | 72MHz | | AHB | 72MHz | | PCLK1 | 36MHz | | PCLK2 | 72MHz | | PLL | 72MHz |

外部时钟源16M

SetSysClockTo72

如果外部时钟源的频率是16M,需要进行哪些修改? 根据源码可以知,最终在SetSysClock中进行修改即可,因为要配置到72M的系统时钟频率,则直接进入函数SetSysClockTo72()进行修改; SetSysClockTo72源码如下;或者跳过源码直接看patch文件;

/**
  * @brief  Sets System clock frequency to 72MHz and configure HCLK, PCLK2 
  *         and PCLK1 prescalers. 
  * @note   This function should be used only after reset.
  * @param  None
  * @retval None
  */
static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK, HCLK, PCLK2 and PCLK1 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));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    HSEStatus = (uint32_t)0x01;
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }  

  if (HSEStatus == (uint32_t)0x01)
  {
    /* Enable Prefetch Buffer */
    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 */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

#ifdef STM32F10X_CL
    /* Configure PLLs ------------------------------------------------------*/
    /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */
        
    RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
                              RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
    RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
                             RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
  
    /* Enable PLL2 */
    RCC->CR |= RCC_CR_PLL2ON;
    /* Wait till PLL2 is ready */
    while((RCC->CR & RCC_CR_PLL2RDY) == 0)
    {
    }
    
   
    /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ 
    RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | 
                            RCC_CFGR_PLLMULL9); 
#else    
    /*  PLL configuration: 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);
#endif /* STM32F10X_CL */

    /* 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)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 */
  }
}
#endif

patch

需要修改两个地方:

  • stm32f10x.hHSE_VALUE的值;
  • system_stm32f10x.cSetSysClockTo72(void)函数的PLL配置; patch如下所示;

diff --git a/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/stm32f10x.h b/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/stm32f10x.h
index 8bf7624..e0ad316 100644
--- a/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/stm32f10x.h
+++ b/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/stm32f10x.h
@@ -116,7 +116,9 @@
  #ifdef STM32F10X_CL
   #define HSE_VALUE    ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */
  #else
-  #define HSE_VALUE    ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
+//  #define HSE_VALUE    ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */

diff --git a/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c b/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c
index 71efc85..4ff040a 100644
--- a/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c
+++ b/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c
@@ -1053,7 +1053,7 @@ static void SetSysClockTo72(void)
     /*  PLL configuration: 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);
+    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9 | RCC_CFGR_HPRE_DIV2);
 #endif /* STM32F10X_CL */

     /* Enable PLL */

这样就完成了基本配置的修改。

其他细节

  • RTC的时钟来源:LSE, LSI,LSE的128分频
  • 独立看门狗IWDGCLK的时钟来源:LSI
  • APB1总线的时钟,最大到36M
  • APB2总线的时钟,最大到72M
  • APB,APB1,APB2为外设提供时钟。