【stm32f407】时钟树以及SystemInit剖析

来源:互联网 发布:淘宝lol台服账号购买 编辑:程序博客网 时间:2024/06/05 10:56

一. 时钟树

众所周知,时钟系统是CPU的脉搏,就像人的心跳一样。所以时钟系统的重要性就不言而喻了。 STM32F4的时钟系统比较复杂,不像简单的51单片机一个系统时钟就可以解决一切。于是有人要问,采用一个系统时钟不是很简单吗?为什么STM32要有多个时钟源呢?因为首先STM32本身非常复杂,外设非常的多,但是并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及RTC只需要几十k的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的MCU一般都是采取多时钟源的方法来解决这些问题。

首先让我们来看看STM32F4的时钟系统图

STM32F4中,有5个最重要的时钟源,为HSIHSELSILSEPLL。其中PLL实际是分为两个时钟源,分别为主PLL和专用PLL。从时钟频率来分可以分为高速时钟源和低速时钟源,在这5个中HSIHSE以及PLL是高速时钟,LSILSE是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中HSELSE是外部时钟源,其他的是内部时钟源。下面我们看看STM32F4的这5个时钟源,我们讲解顺序是按图中红圈标示的顺序

①、LSI是低速内部时钟,RC振荡器,频率为32kHz左右。供独立看门狗和自动唤醒单元使用。

②、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。这个主要是RTC的时钟源。

③、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~26MHzHSE也可以直接做为系统时钟或者PLL输入。

④、HSI是高速内部时钟,RC振荡器,频率为16MHz。可以直接作为系统时钟或者用作PLL输入。

⑤、PLL为锁相环倍频输出。STM32F4有两个PLL:

1) 主PLL(PLL)HSE或者HSI提供时钟信号,并具有两个不同的输出时钟。

第一个输出PLLP用于生成高速的系统时钟(最高168MHz

第二个输出PLLQ用于生成USB OTG FS的时钟(48MHz),随机数发生器的时钟和SDIO时钟。

2)专用PLL(PLLI2S)用于生成精确时钟,从而在I2S接口实现高品质音频性能。

这里我们着重看看主PLL时钟第一个高速时钟输出PLLP的计算方法。如图:

PLL时钟的时钟源要先经过一个分频系数为M的分频器,然后经过倍频系数为N的倍频器出来之后的时候还需要经过一个分频系数为P(第一个输出PLLP)或者Q(第二个输出PLLQ)的分频器分频之后,最后才生成最终的主PLL时钟。

例如我们的外部晶振选择8MHz。同时我们设置相应的分频器M=8,倍频器倍频系数N=336

分频器分频系数P=2,那么主PLL生成的第一个输出高速时钟PLLP为:

PLL=8MHz* N/ (M*P)=8MHz* 336 /(8*2) = 168MHz

如果我们选择HSEPLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟

168MHz。这对于我们后面的实验都是采用这样的配置

上面我们简要概括了STM32的时钟源,那么这5个时钟源是怎么给各个外设以及系统提供时钟的呢?这里我们选择一些比较常用的时钟知识来讲解。

1中我们用A~G标示我们要讲解的地方。

A.  这里是看门狗时钟输入。从图中可以看出,看门狗时钟源只能是低速的LSI时钟。

B.  这里是RTC时钟源,从图上可以看出,RTC的时钟源可以选择LSILSE,以及

HSE分频后的时钟,HSE分频系数为2~31

C.  这里是STM32F4输出时钟MCO1MCO2MCO1是向芯片的PA8引脚输出时钟。它有四个时钟来源分别为:HSI,LSE,HSEPLL时钟。MCO2是向芯片的PC9输出时钟,它同样有四个时钟来源分别为:HSE,PLLSYSCLK以及PLLI2S时钟。MCO输出时钟频率最大不超过100MHz

D.  这里是系统时钟。SYSCLK系统时钟来源有三个方面:HSI,HSEPLL。在我们实际应用中,因为对时钟速度要求都比较高我们才会选用STM32F4这种级别的处理器,所以一般情况下,都是采用PLL作为SYSCLK时钟源。根据前面的计算公式,大家就可以算出你的系统的SYSCLK是多少。

E.  这里我们指的是以太网PTP时钟,AHB时钟,APB2高速时钟,APB1低速时钟。这些时钟都是来源于SYSCLK系统时钟。其中以太网PTP时钟是使用系统时钟。AHB,APB2APB1时钟是经过SYSCLK时钟分频得来。这里大家记住,AHB最大时钟为168MHz, APB2高速时钟最大频率为84MHz,APB1低速时钟最大频率为42MHz

F.  这里是指I2S时钟源。I2S的时钟源来源于PLLI2S或者映射到I2S_CKIN引脚的外部时钟。I2S出于音质的考虑,对时钟精度要求很高。STM32F4开发板使用的是内部PLLI2SCLK

G.  这是STM32F4内部以太网MAC时钟的来源。对于MII接口来说,必须向外部PHY芯片提供25Mhz的时钟,这个时钟,可以由PHY芯片外接晶振,或者使用STM32F4 MCO输出来提供。然后,PHY 芯片再给STM32F4提供ETH_MII_TX_CLKETH_MII_RX_CLK时钟。对于RMII接口来说,外部必须提供50Mhz的时钟驱动PHYSTM32F4ETH_RMII_REF_CLK,这个50Mhz时钟可以来自PHY、有源晶振或者STM32F4MCO。我们的开发板使用的是RMII 接口,使用PHY 芯片提供50Mhz时钟驱动STM32F4 

ETH_RMII_REF_CLK

H.  这里是指外部PHY提供的USB OTG HS60MHZ)时钟。


二. STM32F4时钟初始化配置

STM32F4时钟系统初始化是在system_stm32f4xx.c中的SystemInit()函数中完成的。对于系

统时钟关键寄存器设置主要是在SystemInit函数中调用SetSysClock()函数来设置的。我们可以先看看SystemInit ()函数体:

[cpp] view plain copy
  1. void SystemInit(void)  
  2. {  
  3.   /* FPU settings ------------------------------------------------------------*/  
  4.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)  
  5.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */  
  6.   #endif  
  7.   /* Reset the RCC clock configuration to the default reset state ------------*/  
  8.   /* Set HSION bit */  
  9.   RCC->CR |= (uint32_t)0x00000001;  
  10.   
  11.   /* Reset CFGR register */  
  12.   RCC->CFGR = 0x00000000;  
  13.   
  14.   /* Reset HSEON, CSSON and PLLON bits */  
  15.   RCC->CR &= (uint32_t)0xFEF6FFFF;  
  16.   
  17.   /* Reset PLLCFGR register */  
  18.   RCC->PLLCFGR = 0x24003010;  
  19.   
  20.   /* Reset HSEBYP bit */  
  21.   RCC->CR &= (uint32_t)0xFFFBFFFF;  
  22.   
  23.   /* Disable all interrupts */  
  24.   RCC->CIR = 0x00000000;  
  25.   
  26. #ifdef DATA_IN_ExtSRAM  
  27.   SystemInit_ExtMemCtl();   
  28. #endif /* DATA_IN_ExtSRAM */  
  29.            
  30.   /* Configure the System clock source, PLL Multiplier and Divider factors,  
  31.      AHB/APBx prescalers and Flash settings ----------------------------------*/  
  32.   SetSysClock();  
  33.   
  34.   /* Configure the Vector Table location add offset address ------------------*/  
  35. #ifdef VECT_TAB_SRAM  
  36.   SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */  
  37. #else  
  38.   SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */  
  39. #endif  
  40. }  

SystemInit函数开始先进行浮点运算单元设置,然后是复位PLLCFGR,CFGR寄存器,同时

通过设置CR寄存器的HSI时钟使能位来打开HSI时钟。此代码就是RCC->CR |=(uint32_t)0x00000001;打开HSI振荡器

默认情况下如果CFGR寄存器复位,那么是选择HSI作为系统时钟,这点大家可以查看RCC->CFGR寄存器的位描述最低2位可以得知,当低两位配置为00的时候(复位之后),会选择HSI振荡器为系统时钟。也就是说,调用SystemInit函数之后,首先是选择HSI作为系统时钟。下面是RCC->CFGR寄存器的位1:0配置描述(CFGR寄存器详细描述请参考《STM32F4中文参考手册》6.3.31CFGR寄存器配置表)

如图:

在设置完相关寄存器后,接下来SystemInit函数内部会调用SetSysClock函数。这个函数比

较长,我们就把函数一些关键代码行截取出来给大家讲解一下。这里我们省略一些宏定义标识符值的判断而直接把针对STM32F407比较重要的内容贴出来:

[cpp] view plain copy
  1. /** 
  2.   * @brief  Configures the System clock source, PLL Multiplier and Divider factors,  
  3.   *         AHB/APBx prescalers and Flash settings 
  4.   * @Note   This function should be called only once the RCC clock configuration   
  5.   *         is reset to the default reset state (done in SystemInit() function).    
  6.   * @param  None 
  7.   * @retval None 
  8.   */  
  9. static void SetSysClock(void)  
  10. {  
  11. /******************************************************************************/  
  12. /*            PLL (clocked by HSE) used as System clock source                */  
  13. /******************************************************************************/  
  14.   __IO uint32_t StartUpCounter = 0, HSEStatus = 0;  
  15.     
  16.   /* Enable HSE */  
  17.   RCC->CR |= ((uint32_t)RCC_CR_HSEON);  
  18.    
  19.   /* Wait till HSE is ready and if Time out is reached exit */  
  20.   do  
  21.   {  
  22.     HSEStatus = RCC->CR & RCC_CR_HSERDY;  
  23.     StartUpCounter++;  
  24.   } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));  
  25.   
  26.   if ((RCC->CR & RCC_CR_HSERDY) != RESET)  
  27.   {  
  28.     HSEStatus = (uint32_t)0x01;  
  29.   }  
  30.   else  
  31.   {  
  32.     HSEStatus = (uint32_t)0x00;  
  33.   }  
  34.   
  35.   if (HSEStatus == (uint32_t)0x01)  
  36.   {  
  37.     /* Select regulator voltage output Scale 1 mode, System frequency up to 168 MHz */  
  38.     RCC->APB1ENR |= RCC_APB1ENR_PWREN;  
  39.     PWR->CR |= PWR_CR_VOS;  
  40.   
  41.     /* HCLK = SYSCLK / 1*/  
  42.     RCC->CFGR |= RCC_CFGR_HPRE_DIV1;  
  43.         
  44.     /* PCLK2 = HCLK / 2*/  
  45.     RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;  
  46.       
  47.     /* PCLK1 = HCLK / 4*/  
  48.     RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;  
  49.   
  50.     /* Configure the main PLL */  
  51.     RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |  
  52.                    (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);  
  53.   
  54.     /* Enable the main PLL */  
  55.     RCC->CR |= RCC_CR_PLLON;  
  56.   
  57.     /* Wait till the main PLL is ready */  
  58.     while((RCC->CR & RCC_CR_PLLRDY) == 0)  
  59.     {  
  60.     }  
  61.      
  62.     /* Configure Flash prefetch, Instruction cache, Data cache and wait state */  
  63.     FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;  
  64.   
  65.     /* Select the main PLL as system clock source */  
  66.     RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));  
  67.     RCC->CFGR |= RCC_CFGR_SW_PLL;  
  68.   
  69.     /* Wait till the main PLL is used as system clock source */  
  70.     while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);  
  71.     {  
  72.     }  
  73.   }  
  74.   else  
  75.   { /* If HSE fails to start-up, the application will have wrong clock 
  76.          configuration. User can add here some code to deal with this error */  
  77.   }  
  78.   
  79.   
  80. /******************************************************************************/  
  81. /*                          I2S clock configuration                           */  
  82. /******************************************************************************/  
  83.   /* PLLI2S clock used as I2S clock source */  
  84.   RCC->CFGR &= ~RCC_CFGR_I2SSRC;  
  85.   
  86.   /* Configure PLLI2S */  
  87.   RCC->PLLI2SCFGR = (PLLI2S_N << 6) | (PLLI2S_R << 28);  
  88.   
  89.   /* Enable PLLI2S */  
  90.   RCC->CR |= ((uint32_t)RCC_CR_PLLI2SON);  
  91.   
  92.   /* Wait till PLLI2S is ready */  
  93.   while((RCC->CR & RCC_CR_PLLI2SRDY) == 0)  
  94.   {  
  95.   }  
  96. }  

RCC->CR |= ((uint32_t)RCC_CR_HSEON);

此宏RCC_CR_HSEON定义在stm32f4xx.h中,

#define  RCC_CR_HSEON                        ((uint32_t)0x00010000)

此宏是在第16bit1

此段代码的意思是:把外部高速时钟打开

do

  {

   HSEStatus = RCC->CR & RCC_CR_HSERDY;

   StartUpCounter++;

  }while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

#define RCC_CR_HSERDY                      ((uint32_t)0x00020000)

此段代码的意思是:在一个时间内看HSE是否就绪

RCC->APB1ENR |= RCC_APB1ENR_PWREN;

PWR->CR |= PWR_CR_VOS;

#define RCC_APB1ENR_PWREN                  ((uint32_t)0x10000000)

#define PWR_CR_VOS                         ((uint16_t)0x4000)

此段代码的意思就是:使能电源时钟

RCC->CFGR |= RCC_CFGR_HPRE_DIV1;

#define RCC_CFGR_HPRE_DIV1                 ((uint32_t)0x00000000)

此段代码的意思是:不进行分频

RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;

#define RCC_CFGR_PPRE2_DIV2                ((uint32_t)0x00008000)

此段代码的意思是:对AHB时钟进行2分频,所以APB2 = AHB/2

RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;

#define RCC_CFGR_PPRE1_DIV4                ((uint32_t)0x00001400)

此段代码的意思是:对AHB时钟进行4分频,所以APB1 = AHB/4

RCC->PLLCFGR = PLL_M | (PLL_N <<6) | (((PLL_P >> 1) -1) << 16) |

                   (RCC_PLLCFGR_PLLSRC_HSE) |(PLL_Q << 24);

#define PLL_M      8

#define PLL_N      336

#define PLL_P      2

#define PLL_Q      7

#define RCC_PLLCFGR_PLLSRC_HSE             ((uint32_t)0x00400000)

所以我们的主PLL时钟为:

PLL=8MHz * N/ (M*P)=8MHz* 336 /(8*2) =168MHz

在开发过程中,我们可以通过调整这些值来设置我们的系

RCC->CR |= RCC_CR_PLLON;

#define RCC_CR_PLLON                       ((uint32_t)0x01000000)

所以此段代码的意思是:把PLL开启

while((RCC->CR & RCC_CR_PLLRDY) ==0)

{}

#define  RCC_CR_PLLRDY                       ((uint32_t)0x02000000)

所以此段代码的意思是:等待PLL就绪

 

RCC->CFGR&= (uint32_t)((uint32_t)~(RCC_CFGR_SW));

RCC->CFGR |=RCC_CFGR_SW_PLL;

#define  RCC_CFGR_SW                         ((uint32_t)0x00000003)

#define  RCC_CFGR_SW_PLL                    ((uint32_t)0x00000002)  

以上是选择PLL作为系统时钟源

while((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);

    {

    }

同以上:等待就绪

I2S时钟配置

RCC->CFGR&= ~RCC_CFGR_I2SSRC;

#define  RCC_CFGR_I2SSRC                     ((uint32_t)0x00800000)


PLLI2S作为I2S时钟源

RCC->PLLI2SCFGR= (PLLI2S_N << 6) | (PLLI2S_R << 28);

#definePLLI2S_N   258

#definePLLI2S_R   3

以上代码的意思是:配置PLLI2S

RCC->CR |=((uint32_t)RCC_CR_PLLI2SON);

  while((RCC->CR & RCC_CR_PLLI2SRDY) ==0)

  {

  }

#define  RCC_CR_PLLI2SON                     ((uint32_t)0x04000000)

#define  RCC_CR_PLLI2SRDY                    ((uint32_t)0x08000000)

以上代码的意思是:使能PLLI2S,并且等待就绪

另外在开发过程中,我们可以通过调整这些值来设置我们的系统时钟。

这里还有个特别需要注意的地方,就是我们还要同步修改stm32f4xx.h 中宏定义标识符

HSE_VALUE的值为我们的外部时钟:

#if !defined(HSE_VALUE)

#defineHSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */

#endif /*HSE_VALUE */

这里默认固件库配置的是25000000,我们外部时钟为8MHz,所以我们根据我们硬件情况修改为8000000即可

三. 时钟配置工具

ST公司有个配置时钟的工具,如图所示:

配置起来很方便