zoukankan      html  css  js  c++  java
  • 【STM32】晶振,主时钟,外设频率介绍

    首先,我用的是STM32F407,下方所有图片都是出自这芯片的文档,如果型号和我不同,需要找到对应的芯片说明文档,也许会有出入

    先看一张时钟图

    这里会着重说明高速的部分,低速(不管内部还是外部)只给RTC时钟使用

    题外话,MCO1、MCO2,你可以往外面输出时钟

    以下开始正题

    图片红圈处是主时钟,供给许多东西使用,例如外设(UART、SPI...),简直就像大型音乐演奏的指挥者

    所有你想用到的外设,初始化第一步,就是使能时钟(向主时钟请求)

    例如下方的SPI初始化代码

    void SPI3_Init(void)
    {	 
      GPIO_InitTypeDef  GPIO_InitStructure;
      SPI_InitTypeDef  SPI_InitStructure;
    	
      RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);//使能GPIOC时钟
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);//使能SPI3时钟
     
      ...
      ...  	 
    }  

    是不是能看到clock(时钟)的字样?又看到ENABLE(使能)了吧?

    再回到时钟图,红圈处的主时钟,总共有三种来源

    【1】HSI内部高速晶振

    【2】HSE外部高速晶振

    【3】PLL锁相环

    其中PLL的来源,还是要由『内部高速晶振』或是『外部高速晶振』提供

    主时钟的SW,可以选择来源是三种的哪一种

    内部晶振固定是16M,外部的话,要取决你接的晶振是多少,当然,有个范围,图上写了,4M~26M

    选择了『内部高速晶振』或是『外部高速晶振』,这没什么好说的,主时钟的频率就和晶振一样

    下面说明,如果选择了PLL,应该要设定哪些东西

    首先很重要的一点,提供给PLL的时钟一定要是1M,不可以是2M、4M...之类的

    所以呢,就有了图上的绿圈,除M(代码为PLL_M)

    如果是内部晶振(16M)提供,这个除M的值,就必须设16,16M / 16 = 1M

    如果是外部晶振提供,假设接了8M的晶振,这个除M的值,就要设8,8M / 8 = 1M

    这是规定

    最终,PLL就一定会得到1M的时钟,然后才开始做倍频的工作

    倍频,会经过一个乘N(代码为PLL_N)

    之后,再经过一个除P(代码为PLL_P),分频后,就会把结果提供给主时钟了

    分频Q(除Q)(代码为PLL_Q),会给另一个东西提供时钟,其他的忘了,但是SDIO就是其中一个

    下面来看代码

    『代码图片』和『代码块』,都是出自sysytem_stm32f4xx.c

    代码块的代码有点多,但是只要认真看,其实会发现,ST把所有STM32F4系列的都加进来了

    只要看和自己有关的就好了,我一开始也说了,我用的是STM32F407,我只关注宏定义『STM32F40_41xxx』即可

    static void SetSysClock(void)
    {
    #if defined (STM32F40_41xxx) || defined (STM32F427_437xx) || defined (STM32F429_439xx) || defined (STM32F401xx)
    /******************************************************************************/
    /*            PLL (clocked by HSE) used as System clock source                */
    /******************************************************************************/
      __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
      
      /* 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)
      {
        /* Select regulator voltage output Scale 1 mode */
        RCC->APB1ENR |= RCC_APB1ENR_PWREN;
        PWR->CR |= PWR_CR_VOS;
    
        /* HCLK = SYSCLK / 1*/
        RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
    
    #if defined (STM32F40_41xxx) || defined (STM32F427_437xx) || defined (STM32F429_439xx)      
        /* PCLK2 = HCLK / 2*/
        RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
        
        /* PCLK1 = HCLK / 4*/
        RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
    #endif /* STM32F40_41xxx || STM32F427_437x || STM32F429_439xx */
    
    #if defined (STM32F401xx)
        /* PCLK2 = HCLK / 2*/
        RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
        
        /* PCLK1 = HCLK / 4*/
        RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
    #endif /* STM32F401xx */
       
        /* Configure the main PLL */
        RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
                       (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
    
        /* Enable the main PLL */
        RCC->CR |= RCC_CR_PLLON;
    
        /* Wait till the main PLL is ready */
        while((RCC->CR & RCC_CR_PLLRDY) == 0)
        {
        }
       
    #if defined (STM32F427_437xx) || defined (STM32F429_439xx)
        /* Enable the Over-drive to extend the clock frequency to 180 Mhz */
        PWR->CR |= PWR_CR_ODEN;
        while((PWR->CSR & PWR_CSR_ODRDY) == 0)
        {
        }
        PWR->CR |= PWR_CR_ODSWEN;
        while((PWR->CSR & PWR_CSR_ODSWRDY) == 0)
        {
        }      
        /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
        FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
    #endif /* STM32F427_437x || STM32F429_439xx  */
    
    #if defined (STM32F40_41xxx)     
        /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
        FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
    #endif /* STM32F40_41xxx  */
    
    #if defined (STM32F401xx)
        /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
        FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_2WS;
    #endif /* STM32F401xx */
    
        /* Select the main PLL as system clock source */
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        RCC->CFGR |= RCC_CFGR_SW_PLL;
    
        /* Wait till the main PLL is used as system clock source */
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != 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 */
      }
    #elif defined (STM32F411xE)
    #if defined (USE_HSE_BYPASS) 
    /******************************************************************************/
    /*            PLL (clocked by HSE) used as System clock source                */
    /******************************************************************************/
      __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
      
      /* Enable HSE and HSE BYPASS */
      RCC->CR |= ((uint32_t)RCC_CR_HSEON | RCC_CR_HSEBYP);
     
      /* 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)
      {
        /* Select regulator voltage output Scale 1 mode */
        RCC->APB1ENR |= RCC_APB1ENR_PWREN;
        PWR->CR |= PWR_CR_VOS;
    
        /* HCLK = SYSCLK / 1*/
        RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
    
        /* PCLK2 = HCLK / 2*/
        RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
        
        /* PCLK1 = HCLK / 4*/
        RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
    
        /* Configure the main PLL */
        RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
                       (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
        
        /* Enable the main PLL */
        RCC->CR |= RCC_CR_PLLON;
    
        /* Wait till the main PLL is ready */
        while((RCC->CR & RCC_CR_PLLRDY) == 0)
        {
        }
    
        /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
        FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_2WS;
    
        /* Select the main PLL as system clock source */
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        RCC->CFGR |= RCC_CFGR_SW_PLL;
    
        /* Wait till the main PLL is used as system clock source */
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != 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 */
      }
    #else /* HSI will be used as PLL clock source */
      /* Select regulator voltage output Scale 1 mode */
      RCC->APB1ENR |= RCC_APB1ENR_PWREN;
      PWR->CR |= PWR_CR_VOS;
      
      /* HCLK = SYSCLK / 1*/
      RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
      
      /* PCLK2 = HCLK / 2*/
      RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
      
      /* PCLK1 = HCLK / 4*/
      RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
      
      /* Configure the main PLL */
      RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | (PLL_Q << 24); 
      
      /* Enable the main PLL */
      RCC->CR |= RCC_CR_PLLON;
      
      /* Wait till the main PLL is ready */
      while((RCC->CR & RCC_CR_PLLRDY) == 0)
      {
      }
      
      /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
      FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_2WS;
      
      /* Select the main PLL as system clock source */
      RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
      RCC->CFGR |= RCC_CFGR_SW_PLL;
      
      /* Wait till the main PLL is used as system clock source */
      while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
      {
      }
    #endif /* USE_HSE_BYPASS */  
    #endif /* STM32F40_41xxx || STM32F427_437xx || STM32F429_439xx || STM32F401xx */  
    }
    

     

    在函数setSysClock(设置系统时钟)里面,一开始就会发现一行代码

    /* Enable HSE */
      RCC->CR |= ((uint32_t)RCC_CR_HSEON);

    这是选择使用HSE(外部晶振),但,这还不是主时钟的三个选择之一,只是选择制造时钟的来源,下面的代码才是真正决定主时钟是使用HSE、HSI、PLL哪一个

    /* Select the main PLL as system clock source */
      RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
      RCC->CFGR |= RCC_CFGR_SW_PLL;

    上面写着RCC_CFGR_SW_PLL,配合文章一开始的图片,红圈处,不是有个SW吗?这就是选择主时钟来源

    再结合这三行代码『RCC->CR』『RCC->CFGR』这些代码(注释 『/* Enable HSE */ 』这些不算代码啊)

    意思是:我使用外部晶振,然后外部晶振提供给PLL去倍频,最后主时钟采用PLL提供的频率

    在函数setSysClock里面,还可以找到关于PLL的设置,也就是

    PLL_M、PLL_N、PLL_P

    这些功用之前也说了,对照一开始的图片,找到PLL里面的『除M』『乘N』『除P』

    也就理解了

    在STM32F407里面,没有关于/R的东西,看看其他F4系列的,应该能找到

    接下来,我会把重点放在外设上,也就是红圈处主时钟的右边,APBx分频这部分(有些外设需要的时钟不同,但主时钟只能提供一种频率,这时要靠各自的分频,实现不同的频率)

    但是在APBx之前,还有一个AHB分频

    整个思路是这样的,假设我要用SPI:『主时钟提供时钟』->『经过AHB分频』->『经过APBx分频,APBx总线拥有自己的一个时钟频率』->『SPI自己还可以再一次分频』

    相关代码如下(此代码还是上面那个很长的代码,里面的一小部分,注释是我自己加上的)

    /* HCLK = SYSCLK / 1*/
        RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // AHB分频
    
    #if defined (STM32F40_41xxx) || defined (STM32F427_437xx) || defined (STM32F429_439xx)      
        /* PCLK2 = HCLK / 2*/
        RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2分频
        
        /* PCLK1 = HCLK / 4*/
        RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; // APB1分频
    		
    #endif /* STM32F40_41xxx || STM32F427_437x || STM32F429_439xx */

    这里就看出了两个分频了,一个是AHB,另一个是APBx(APB1和APB2暂且算同一个)

    上面以SPI为例,那么『SPI自己还可以再一次分频』,这代码在哪呢?

    这就要自己写SPI初始化函数了,设定相关的参数,其中一条,就是分频,如下图绿色底那行所示

    在STM32F4系列,有两个APB,分别是APB1、APB2

    APB是总线,至于哪些外设,分别是挂在哪个总线上呢?

    我截取一张头文件stm32f4xx_rcc.h的图片来看,就知道了

     

    这就是外设的分布,例如串口2(USART2),在410行,也就是挂在APB1上

    串口1(USART1),在433行,它就是挂在APB2上了

    文章上面一点,我不是贴了一个SPI 1的初始化代码的图片吗(图片显示行号4~39)?

    SPI 1是挂在APB2上的,时钟使能写的函数名称和参数,都写着APB2的

    另外,APB2的上限速度,是比APB1块的

    一开始的时钟图,也写了APB2的上限是84M,比APB1整整多了一倍

    总算结束了,但是请注意,本篇是以STM32F407为例,不同型号的芯片,最好还是参考各自的说明文档,以免出错

    谢谢你的观看,如果有错,也希望能提出来,一起交流进步

  • 相关阅读:
    技术期刊 · 白日照耀开鸿蒙 | 深入鸿蒙 ACE UI 框架解析;无限循环的 useEffect 类型;用 Three.js 实现 3D 房间;图神经网络入门;超基础的机器学习入门-原理篇
    青岛敏捷之旅,来了!
    痞子衡嵌入式:借助Serial Plot软件测量i.MXRT系列FlexSPI驱动Flash页编程执行时间分布
    痞子衡嵌入式:超级下载算法RT-UFL v1.0在Segger Ozone下的使用
    痞子衡嵌入式:超级下载算法RT-UFL v1.0在Keil MDK下的使用
    痞子衡嵌入式:超级下载算法RT-UFL v1.0在IAR EW for Arm下的使用
    痞子衡嵌入式:超级下载算法RT-UFL v1.0在MCUXpresso IDE下的使用
    《痞子衡嵌入式半月刊》 第 42 期
    痞子衡嵌入式:i.MXRT全系列下FlexSPI外设AHB Master ID定义与AHB RX Buffer指定的异同
    CDP客户数据管理平台体系化搭建
  • 原文地址:https://www.cnblogs.com/PureHeart/p/11330967.html
Copyright © 2011-2022 走看看