• STM32时钟系统配置程序源码深入分析


    一、分析程序的目的

    最近我在移植实时系统是遇到了一些问题,所以决定深入了解系统时钟的配置过程,当然想要学好stm32的小伙伴也有必要学习好时钟系统的配置,所以我将学习的过程再次记录,有写得不好的地方,望小伙伴指出。

    之前我已经记录过一篇关于时钟系统的文章,对程序中不了解的地方可以看我之前的笔记“STM32时钟系统的配置寄存器和源码分析”。

    这里我用的芯片是STM32F103C8T6,用的库函数是厂家提供的案例中提取出来的,这里可能和其他型号的mcu有细微差别,但是原理都是一样的。

    二、程序执行的过程

    当系统复位信号发生的时候,程序将执行复位中断函数,而在复位中断函数中是先执行SystemInit函数后在执行__main函数,如下图所示:

    系统调用SystemInit函数后完成系统时钟的配置,系统时钟配置的过程如下所示:

    从图中可知,在系统时钟配置的第三步有多个函数可以选择,这里可以根据自己的需求选择相应的配置流程,只需要在stm32f10x.h文件中定义相应的宏即可(默认配置为72MHz),如下图所示:

    在分析程序之前,需要了解一下相关寄存器的地址以及相应寄存器的作用,如下所示:

    typedef struct
    {
      __IO uint32_t CR;    // HSI、HSE、CSS、PLL等的使能和就绪标志位
      __IO uint32_t CFGR;    // PLL等的时钟源选择,分频系数设定
      __IO uint32_t CIR;    // 清除/使能时钟就绪中断
      __IO uint32_t APB2RSTR;    // APB2线上外设复位寄存器
      __IO uint32_t APB1RSTR;    // APB1线上外设复位寄存器
      __IO uint32_t AHBENR;    // DMA、SDIO等时钟使能
      __IO uint32_t APB2ENR;    // APB2线上外设时钟使能
      __IO uint32_t APB1ENR;    // APB1线上外设时钟使能
      __IO uint32_t BDCR;    // 备用域控制寄存器
      __IO uint32_t CSR;    // 控制状态寄存器
    } RCC_TypeDef;
    
    

    以上的寄存器都是相对RCC寄存器进行偏移的,如下图所示:

    通过查找stm32f10x.h文件中的定义可以知道寄存器RCC的地址,如下所示:
    RCC = RCC_BASE = AHBPERIPH_BASE + 0x1000 = PERIPH_BASE(0x40000000) + 0x20000 = 0x40021000

    三、SystemInit函数

    程序如下所示:

    /* 将RCC时钟配置重置为默认重置状态 */
    void SystemInit (void)
    {
      /* 打开HSION位(内部高速时钟使能) */
      RCC->CR |= (uint32_t)0x00000001;
    
      /* 复位SW、HPRE、PPRE1、PPRE2、ADCPRE和MCO位 */
      RCC->CFGR &= (uint32_t)0xF8FF0000;  
    
      /* 复位 HSEON, CSSON 和 PLLON 位  */
      RCC->CR &= (uint32_t)0xFEF6FFFF;
    
      /* 复位 HSEBYP 位 */
      RCC->CR &= (uint32_t)0xFFFBFFFF;
    
      /* 复位 PLLSRC, PLLXTPRE, PLLMUL 和 USBPRE/OTGFSPRE 位 */
      RCC->CFGR &= (uint32_t)0xFF80FFFF;
    
      /* 禁用所有中断并清除挂起位 */
      RCC->CIR = 0x00000000;
        
      /* 配置系统时钟频率、HCLK、PCLK2和PCLK1预分频器 */
      /* 配置闪存延迟周期并启用预取缓冲区 */
      SetSysClock();
    
    }
    

    从上面的代码可以看出,和库函数中的RCC_DeInit所执行的代码一下,所以在用户程序中需要从新配置系统时钟的话,不需要通过上面的代码将时钟配置为默认状态,只要调用RCC_DeInit函数即可。如下图所示:

    有不明白的地方只需要和相应的寄存器对应一下即可,相关的寄存说明请看“STM32时钟系统的配置寄存器和源码分析”。

    四、SetSysClock函数

    static void SetSysClock(void)
    {
    #ifdef SYSCLK_FREQ_HSE
      SetSysClockToHSE();
    #elif defined SYSCLK_FREQ_20MHz
      SetSysClockTo20();
    #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
    
    

    这是根据文件中的宏定义选择相应的系统时钟配置函数,有需要更改的直接定义相应的宏即可,系统默认是的72MHz

    五、SetSysClockTo72函数

    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 != HSEStartUp_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;
        
        /*!< PLLCLK = 8MHz * 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 | RCC_CFGR_PLLMULL9);
    
        /*!< 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 */    
    
        /*!< Go to infinite loop */
        while (1)
        {
        }
      }
    }
    
    • 使能外部高速时钟

      // #define  RCC_CR_HSEON                        ((uint32_t)0x00010000)
      
      RCC->CR |= ((uint32_t)RCC_CR_HSEON);
      
      do
      {
          HSEStatus = RCC->CR & RCC_CR_HSERDY;
          StartUpCounter++;  
      } while((HSEStatus == 0) && (StartUpCounter != HSEStartUp_TimeOut));
      
      if ((RCC->CR & RCC_CR_HSERDY) != RESET)
      {
          HSEStatus = (uint32_t)0x01;
      }
      else
      {
          HSEStatus = (uint32_t)0x00;
      } 
      

      从定义为文件中可知RCC_CR_HSEON为0x00010000,也就是CR寄存器的第17位为1。HSEStartUp_TimeOut为0x0500表示HSE启动超时,也就是说如下图所示:

      注意:执行完上面程序后,接着判断外部时钟是否就绪,只要当外部时钟就绪后才执行后面的流程,否成启动失败,程序将卡在while位置

    • 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;    
      
      

      由于CPU的速度比flash的速度要快,所以这里需要让cpu等待两个时钟

    • 设置AHB、APB1、APB2预分频的值

      // RCC_CFGR_HPRE_DIV1 = 0x00000000
      // RCC_CFGR_PPRE2_DIV1 = 0x00000000
      // RCC_CFGR_PPRE1_DIV2 = 0x00000400
      
      /*!< HCLK = SYSCLK */
      RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
      /*!< PCLK2 = HCLK */
      RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
        
      /*!< PCLK1 = HCLK/2 */
      RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
      
      

      从注释中可知AHB和APB2的预分频为1,APB1的预分频为2(因为PCLK1的最大频率为36MHz)

    • 设置PLL的时钟源和倍频

      // RCC_CFGR_PLLSRC = 0x00010000
      // RCC_CFGR_PLLXTPRE = 0x00020000
      // RCC_CFGR_PLLMULL = 0x003C0000
      // RCC_CFGR_PLLMULL9 = 0x001C0000
      
      /*!< PLLCLK = 8MHz * 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 | RCC_CFGR_PLLMULL9);
      
      

      第一行代码的作用是将CFGR的[16:21]寄存器复制为0,第二行是将HSE设置为PLL的时钟源,HSE分频器不分频,PLL倍频系数设置为9

    • 使能PLL时钟

      // RCC_CR_PLLON = 0x01000000
      // RCC_CR_PLLRDY = 0x02000000
      
      /*!< Enable PLL */
      RCC->CR |= RCC_CR_PLLON;
      
      /*!< Wait till PLL is ready */
      while((RCC->CR & RCC_CR_PLLRDY) == 0)
      {
      }
      
      

      使能PLL时钟,并等待PLL时钟就绪

    • 设置PLL作为系统时钟源

      // RCC_CFGR_SW = 0x00000003
      // RCC_CFGR_SW_PLL = 0x00000002
      // RCC_CFGR_SWS = 0x0000000C
      
      /*!< 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)
      {
      }
      

      设置PLL作为系统时钟源,并判断是否成功

    注意: SetSysClockTo72函数的作用是配置HCLK为72MHz、PCLK1为36MHz、PCLK2为72MHz,如下图所示:

    六、时钟配置系统的库函数

    头文件是stm32f10x_rcc.h,源文件是stm32f10x_rcc.c

    1. 时钟使能配置

      // HSE时钟使能
      void RCC_HSEConfig(uint32_t RCC_HSE);
      // HSI时钟使能
      void RCC_HSICmd(FunctionalState NewState);
      // PLL时钟使能
      void RCC_PLLCmd(FunctionalState NewState);
      // 启用或禁用指定的RCC中断
      void RCC_ITConfig(uint8_t RCC_IT, FunctionalState NewState)
      // 使能LSI时钟
      void RCC_LSICmd(FunctionalState NewState);
      // 使能RTC时钟
      void RCC_RTCCLKCmd(FunctionalState NewState)
      // 使能AHB外围时钟
      void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState)
      // 使能高速APB(APB2)外围时钟
      void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
      // 使能低速APB(APB1)外围时钟
      void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)
      // 使能时钟安全系统
      void RCC_ClockSecuritySystemCmd(FunctionalState NewState)
      
      
    2. 时钟相关配置

      // 配置PLL时钟源,仅当PLL禁用时,才能使用此功能。
      void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul)
      // 配置系统时钟(SYSCLK)。
      void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource)
      // 配置AHB时钟(HCLK)
      void RCC_HCLKConfig(uint32_t RCC_SYSCLK)
      // 配置低速APB时钟(PCLK1)
      void RCC_PCLK1Config(uint32_t RCC_HCLK)
      // 配置高速APB时钟(PCLK2)
      void RCC_PCLK2Config(uint32_t RCC_HCLK)
      // 配置USB时钟(USBCLK)
      void RCC_USBCLKConfig(uint32_t RCC_USBCLKSource)
      // 配置ADC时钟(ADCCLK)
      void RCC_ADCCLKConfig(uint32_t RCC_PCLK2)
      // 配置外部低速振荡器(LSE)
      void RCC_LSEConfig(uint8_t RCC_LSE)
      // 配置RTC时钟(RTCCLK)
      void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource)
      
      
    3. 其他时钟配置

      // 调整内部高速振荡器(HSI)校准
      void RCC_AdjustHSICalibrationValue(uint8_t HSICalibrationValue)
      // 获取时钟源
      uint8_t RCC_GetSYSCLKSource(void)
      // 等待HSE时钟启动
      ErrorStatus RCC_WaitForHSEStartUp(void)
      // 获取对应的时钟频率
      void RCC_GetClocksFreq(RCC_ClocksTypeDef* RCC_Clocks)
      // 强制复位高速APB(APB2)外围设备
      void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
      // 强制复位低速APB(APB1)外围设备
      void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)
      // 强制重置备份域
      void RCC_BackupResetCmd(FunctionalState NewState)
      // 选择要在MCO引脚上输出的时钟源
      void RCC_MCOConfig(uint8_t RCC_MCO)
      // 检查是否设置了指定的RCC标志
      FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG)
      // 清除RCC重置标志
      void RCC_ClearFlag(void)
      // 检查是否发生了指定的RCC中断
      ITStatus RCC_GetITStatus(uint8_t RCC_IT)
      // 清除RCC中断挂起位
      void RCC_ClearITPendingBit(uint8_t RCC_IT)
      
      

    七、通过库函数配置时钟系统

    // #include "stm32f10x_flash.h" 
    // 需要引用flash库文件
    
    void HSE_SetClk(uint32_t RCC_PLLMul_x)
    {
        ErrorStatus HSEStaus;
    	// 将RCC时钟配置重置为默认重置状态
    	
    	RCC_DeInit();
        // 使能外部时钟(HSE)
    	
        RCC_HSEConfig(RCC_HSE_ON);
    	// 获取时钟状态
    	
        HSEStaus = RCC_WaitForHSEStartUp();
    	
        if (HSEStaus == SUCCESS)
        {
    		//使能预取值
    		FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
    		FLASH_SetLatency(FLASH_Latency_2);
    		
    		// Check the parameters
    		RCC_HCLKConfig(RCC_SYSCLK_Div1);
    		RCC_PCLK1Config(RCC_HCLK_Div2);
    		RCC_PCLK2Config(RCC_HCLK_Div1);
    		
    		// 配置 PLLCLK = HSE * RCC_PLLMul_X
    		RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_x);
    		
    		// 使能PLL
    		RCC_PLLCmd(ENABLE);
    		
    		// 等待PLL稳定
    		while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
    		
    		// 选择系统时钟
    		RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
    		
    		while (RCC_GetSYSCLKSource() != 0x08);
    
        }
    	else 
    	{
    		
    		while (1)
    		{
    			// 启动失败,让程序卡死在此处,防止程序跑飞
    			
    		}
    		
    	}
    
    }
    
    

    从以上程序可知,不论是通过对寄存器还是通过库函数配置系统时钟的流程都是一样的,所以在后续需要更改系统时钟时可以通过上面的这个库函数试试。

    参考文献

    (野火STM32)16-RCC(第3节)使用HSE配置系统时钟并使用MCO输出监控系统时钟:https://www.bilibili.com/video/BV1ja41187Jq?spm_id_from=333.999.0.0
    【STM32】系统时钟RCC详解(超详细,超全面):https://blog.csdn.net/as480133937/article/details/98845509
    《STM32中文参考手册》

  • 相关阅读:
    一文学会JavaScript计时事件
    三维重建一种实现算法
    switch里面,一开头就放default是什么意思
    Docker版部署RocketMQ开启ACL验证
    循环中第一个li中的order_status==0得时候剩下的不可点击,第一个li中的order_status不等于0得时候剩下的可以点击
    C++学习第二十六天----内联函数与引用变量
    2021JavaScript面试题(最新)不定时更新(2021.11.6更新)
    java计算机毕业设计妇女健康保健系统MyBatis+系统+LW文档+源码+调试部署
    SpringBoot详解
    shopify motion主题如何更换视频
  • 原文地址:https://www.cnblogs.com/jzcn/p/16352893.html