stm32------(5)系统时钟配置

发布时间 2023-05-02 17:38:05作者: 王川贝壳子

一、概述

  系统时钟,是整个芯片的心脏,如果没有了它,就等于人没有了心跳;
  在实际工程应用中,每当使用一个外设时,首先需要做的就是打开该外设对应的时钟;这样的好处就是,如果不使用一个外设的时候,就把它的时钟关掉,从而可以降低系统的功耗,达到节能,实现低功耗的效果(低功耗); 
  寄存器是由D触发器组成的,只有送来了时钟,触发器才能被改写值。任何MCU的任何外设都需要有时钟,8051也是如此;STM32为了让用户更好地掌握功耗,对每个外设的时钟都设置了开关,让用户可以精确地控制,
  关闭不需要的设备,达到节省供电的目的。51单片机不用配置IO时钟,只是因为默认使用同一个时钟,这样是方便,但是这样的话功耗就降低不了。51中某个功能不需要,
  但是它还是一直运行。stm32中当你想关闭某个IO的时候,关闭它相对应的时钟使能就是了;ARM的芯片都是这样,外设通常都是给了时钟后,才能设置它的寄存器(即才能使用这个外设),
  这么做的目的是为了省电,使用了所谓时钟门控的技术。

 

二、关于时钟

  1.时钟分类(stm32所有型号的时钟分为4类)

  ①、HSI 是高速内部时钟(High Speed Internal Clock Signal)
  ②、HSE是高速外部时钟(High Speed External Clock Signal)
  ③、LSI 是低速内部时钟(Low  Speed Internal Clock Signal)
  ④、LSE是低速外部时钟(Low  Speed External Clock Signal)

 

  2.时钟源(stm32) 

  ①、HSI内部高速时钟,RC振荡器,频率为8MHz,当HSE故障时,系统时钟会自动切换到HSI,直到HSE启动成功,相对HSE精度小,受温度影响较大,会有温漂。
  ②、HSE外部高速时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz,多使用8MHz/12MHz。
  ③、PLL锁相环倍频时钟,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。

  STM32具有以下两个次级时钟源:

  ①、LSI内部低速时钟,RC振荡器,频率为30~60kHz不等,一般取40kHz,该 RC 用于驱动独立看门狗,也可选择提供给 RTC 用于停机/待机模式下的自动唤醒。
  ②、LSE外部低速时钟,接频率为32.768kHz的石英晶体,主要做RTC时钟源;
  (使用32.768kHz是因为2的15次方为32768,32.768kHz的晶振产生的时钟信号经过15次分频后,便会产生频率为1Hz的信号,即为秒脉冲信号

  

  3.时钟树(stm32)

  

  注:
  1)对于不同系列的芯片,时钟树之间会存在或大或小的差异,具体需要查看手册中RCC的章节;
  2)MCO接口的两个作用:其一,可以观察波形是否正常;其二,可以作为其他部件的时钟;

 

三、实验分析

  keil软件版本:V5.35.00
  st官网:STM32 固件 - 意法半导体STMicroelectronics
  单片机型号:STM32F103VET6
  1、标准库中配置系统时钟分析
  1)系统时钟配置的相关代码在这里哦

  

  

  

  2)配置详情及描述(stm32)  

  
  1 /**
  2   * @brief  Sets System clock frequency to 72MHz and configure HCLK, PCLK2 
  3   *         and PCLK1 prescalers. 
  4   * @note   This function should be used only after reset.
  5   * @param  None
  6   * @retval None
  7   */
  8 static void SetSysClockTo72(void)
  9 {
 10   __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
 11   
 12   /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/    
 13   /* Enable HSE */    
 14   RCC->CR |= ((uint32_t)RCC_CR_HSEON);                            //使能HSE,等待HSE稳定
 15  
 16   /* Wait till HSE is ready and if Time out is reached exit */
 17   do                                                            //等待HSE启动稳定,并做超时处理
 18   {
 19     HSEStatus = RCC->CR & RCC_CR_HSERDY;
 20     StartUpCounter++;  
 21   } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
 22 
 23   if ((RCC->CR & RCC_CR_HSERDY) != RESET)                        //判断HSE启动是否成功,置位标志位
 24   {
 25     HSEStatus = (uint32_t)0x01;
 26   }
 27   else
 28   {
 29     HSEStatus = (uint32_t)0x00;
 30   }  
 31 
 32   if (HSEStatus == (uint32_t)0x01)                                //如果HSE启动成功
 33   {
 34     /* Enable Prefetch Buffer */
 35     FLASH->ACR |= FLASH_ACR_PRFTBE;                                //使能FLASH预存取缓冲区
 36 
 37     /* Flash 2 wait state */                                    //SYSCLK 周期与闪存访问时间的比例设置,这里统一设置成 2
 38     FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);        //设置成 2 的时候, SYSCLK 低于 48M 也可以工作,如果设置成 0 或者 1 的时候,
 39     FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;                //如果配置的 SYSCLK 超出了范围的话,则会进入硬件错误,程序就死了
 40                                                                 //0: 0 < SYSCLK <= 24M
 41                                                                 //1: 24< SYSCLK <= 48M
 42                                                                 //2: 48< SYSCLK <= 72M
 43  
 44                                                                 //设置AHB、APB2、APB1预分频因子
 45     /* HCLK = SYSCLK */
 46     RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
 47       
 48     /* PCLK2 = HCLK */
 49     RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
 50     
 51     /* PCLK1 = HCLK/2 */
 52     RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
 53 
 54 #ifdef STM32F10X_CL
 55     /* Configure PLLs ------------------------------------------------------*/
 56     /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
 57     /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */
 58         
 59     RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
 60                               RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
 61     RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
 62                              RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
 63   
 64     /* Enable PLL2 */
 65     RCC->CR |= RCC_CR_PLL2ON;
 66     /* Wait till PLL2 is ready */
 67     while((RCC->CR & RCC_CR_PLL2RDY) == 0)
 68     {
 69     }
 70     
 71    
 72     /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ 
 73     RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
 74     RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | 
 75                             RCC_CFGR_PLLMULL9); 
 76 #else    
 77     /*  PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
 78     RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC         //设置PLL时钟来源,设置PLL倍频因子,PLLCLK = HSE * 9 = 72MHz
 79     
 80                                         | RCC_CFGR_PLLXTPRE 
 81                                         
 82                                         |RCC_CFGR_PLLMULL));
 83                                         
 84     RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE 
 85     
 86                             | RCC_CFGR_PLLMULL9);
 87 #endif /* STM32F10X_CL */
 88 
 89     /* Enable PLL */
 90     RCC->CR |= RCC_CR_PLLON;                                    //使能PLL
 91 
 92     /* Wait till PLL is ready */
 93     while((RCC->CR & RCC_CR_PLLRDY) == 0)                        //等待PLL稳定
 94     {
 95     }
 96     
 97     /* Select PLL as system clock source */
 98     RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));            //选择PLL作为系统时钟来源
 99     RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    
100 
101     /* Wait till PLL is used as system clock source */            //读取时钟切换状态位,确保PLLCLK被选为系统时钟
102     while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
103     {
104     }
105   }
106   else                                                            //如果HSE启动失败,可以在这里添加错误代码
107   { /* If HSE fails to start-up, the application will have wrong clock 
108          configuration. User can add here some code to deal with this error */
109   }
110 }
system_stm32f10x.c

  3)另外补充一个例子(gd32)

  
 1 /*!
 2     \brief      configure the system clock to 72M by PLL which selects HXTAL as its clock source
 3     \param[in]  none
 4     \param[out] none
 5     \retval     none
 6 */
 7 static void system_clock_72m_hxtal(void)
 8 {
 9     uint32_t timeout = 0U;
10     uint32_t stab_flag = 0U;
11 
12     /* enable HXTAL */
13     /*开启外部高速时钟*/
14     RCU_CTL0 |= RCU_CTL0_HXTALEN;
15 
16     /* wait until HXTAL is stable or the startup time is longer than HXTAL_STARTUP_TIMEOUT */
17     /*等待外部高速时钟稳定,(当外部晶振稳定后,芯片将自动设置相关标志位,软件只需要不断读取这个标志位就可以知道时钟是否稳定)*/
18     do {
19         timeout++;
20         stab_flag = (RCU_CTL0 & RCU_CTL0_HXTALSTB);
21     } while((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout));
22     /* if fail 若外部高速时钟异常,上面等待超时,进入这里*/
23     if(0U == (RCU_CTL0 & RCU_CTL0_HXTALSTB)) {
24         return;
25     }
26     
27     
28     /*运行到这里,说明外部高速时钟正常启动,下面按照时钟树,来配置系统和各个模块的时钟*/
29     /* HXTAL is stable */
30     /* AHB = SYSCLK */
31     RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;        //系统时钟到AHB总线时钟不进行分频
32     /* APB2 = AHB/2 */
33     RCU_CFG0 |= RCU_APB2_CKAHB_DIV2;    //APB2是AHB的2分频
34     /* APB1 = AHB/2 */
35     RCU_CFG0 |= RCU_APB1_CKAHB_DIV2;    //APB1是AHB的2分频
36     
37 
38     /* PLL = HXTAL * 6 = 72 MHz */
39     /*现在外部晶振是12M,通过PLL倍频为72M*/
40     RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PLLMF | RCU_CFG0_PLLMF4 | RCU_CFG0_PLLPREDV);
41     RCU_CFG1 &= ~(RCU_CFG1_PLLPRESEL | RCU_CFG1_PLLMF5 | RCU_CFG1_PREDV);
42     
43     RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | (RCU_PLL_MUL6 & (~RCU_CFG1_PLLMF5)));        //PLLSEL设置为1,PLL设置为6
44 //    RCU_CFG0 |= (RCU_CFG0_PLLPREDV);                        //HXTAL或CK_IRC48M时钟二分频    (CFG0第17位,与CFG1中的PREDV[0]位是一样的)
45     RCU_CFG1 |= (RCU_PLLPRESEL_HXTAL);                        //HXTAL选为PLL时钟源         PREDV 选择1分频
46     RCU_CFG1 |= (RCU_PLL_MUL6 & RCU_CFG1_PLLMF5);            //PLLMF的第五位设置为0
47     
48     
49     /* enable PLL */
50     /*上面的配置完成,使能PLL*/
51     RCU_CTL0 |= RCU_CTL0_PLLEN;
52 
53     /* wait until PLL is stable */
54     while(0U == (RCU_CTL0 & RCU_CTL0_PLLSTB)) {
55     }
56 
57     /* select PLL as system clock */
58     /*设置SCS[1:0],将时钟切换到刚配置好的PLL这条系统时钟线路*/
59     RCU_CFG0 &= ~RCU_CFG0_SCS;
60     RCU_CFG0 |= RCU_CKSYSSRC_PLL;
61 
62     /* wait until PLL is selected as system clock */
63     /*等待PLL这条系统时钟配置能稳定给系统提供时钟*/
64     while(0U == (RCU_CFG0 & RCU_SCSS_PLL)) {
65     }
66     
67     /*到此处,系统完成了从8M到200M的切换*/
68 }
system_gd32f3x0.c

 

  2、重新定义并初始化函数

  由上文代码可知,程序运行起来会首先执行启动文件,调用systeminit()函数,最终初始化系统时钟;
  如需改变时钟配置,可以在系统时钟配置文件中修改;但是为了不破坏库函数的完整性,可重新定义并调用初始化函数;
  一般情况下,使用HSE经分频倍频后来配置系统时钟;
  1)以HSE配置系统时钟为例,按照时钟树流程来完成函数: 
   
 1 /**
 2   * @brief  HSE_SetSysClk program.
 3   * @param  RCC_PLLMul_x: specifies the PLL multiplication factor[2~16]
 4   * @retval None
 5   */
 6 void HSE_SetSysClk(uint32_t RCC_PLLMul_x)
 7 {
 8     ErrorStatus HSEStatus;
 9     
10     
11     RCC_DeInit();                                //把RCC寄存器复位
12     
13     RCC_HSEConfig(RCC_HSE_ON);                    //使能HSE
14     
15     HSEStatus = RCC_WaitForHSEStartUp();        //等待HSE稳定,返回HSE状态
16     
17     if(HSEStatus == SUCCESS)
18     {
19         /*预取缓冲器包含两个数据块,每个数据块有8个字节; 预取指令(数据)块直接映像到闪存中,
20           因为数据块的大小与闪存的宽度相同, 所以读取预取指令块可以在一个读周期完成;
21           设置预取缓冲器可以使CPU更快地执行, CPU读取一个字的同时下一个字已经在预取缓冲器中等候,
22           即当代码跳转的边界为8字节的倍数时, 闪存的加速比例为2;
23           flash等待周期数要系统时钟频率对应, 《stm32f10xxx闪存编程手册》中可以进行了解 */
24         FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);    //使能预取指
25         FLASH_SetLatency(FLASH_Latency_2);                        //2个等待周期
26         
27         RCC_HCLKConfig(RCC_SYSCLK_Div1);                        //配置AHB总线时钟HCLK
28         RCC_PCLK1Config(RCC_HCLK_Div2);                            //配置APB1总线时钟PCLK1,PCLK1 = HCLK/2
29         RCC_PCLK2Config(RCC_HCLK_Div1);                            //配置APB2总线时钟PCLK2,PCLK2 = HCLK
30         
31         RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_x);        //配置锁相环时钟(PLLCLK = HSE * RCC_PLLMul_x = 72MHz),RCC_PLLMul_x为倍频因子,PLL需要先配置再使能
32         RCC_PLLCmd(ENABLE);                                        //使能PLL
33         while(RESET == RCC_GetFlagStatus(RCC_FLAG_PLLRDY));        //等待PLL稳定
34         
35         RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);                //选择PLLCLK为系统时钟    
36         while(0x08 != RCC_GetSYSCLKSource());                    //等待PLLCLK置位系统时钟完成
37     }
38     else
39     {
40         /*如果HSE启动失败,可以在这里添加处理错误的代码*/
41     }
42 }
43 
44 /*在main中调用即可*/
45 /**
46   * @brief  Main program.
47   * @param  None
48   * @retval None
49   */
50 int main(void)
51 {
52     LED_GPIO_Config();
53     HSE_SetSysClk(RCC_PLLMul_9);
54 //    HSI_SetSysClk(RCC_PLLMul_16);
55     MCO_GPIO_Config();
56     RCC_MCOConfig(RCC_MCO_SYSCLK);
57     
58     for(;;)
59     {
60         LED1_TOGGLE;
61         Delay(0xFFFFF);
62         LED1_TOGGLE;
63             Delay(0xFFFFF);
64     }
65 }
rcc_clk_config_operation.c
  使能MCO,使用示波器抓取波形
    
 1 /**
 2   * @brief  MCO_GPIO_Config program.
 3   * @param  void
 4   * @retval enable mco for see clock
 5   */
 6 void MCO_GPIO_Config()
 7 {
 8     GPIO_InitTypeDef  GPIO_InitStructure;
 9 
10     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
11     
12     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
13     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
14     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
15     
16     GPIO_Init(GPIOA,&GPIO_InitStructure);
17 
18 }
19 
20 
21     RCC_MCOConfig(RCC_MCO_SYSCLK);
mco_gpio_config.c
  波形如下:(使用HSE 8*9 = 72MHz; 使用HSE(超频) 8*16 = 128MHz)

    

  2)以HSI作为系统时钟

   
 1 /**
 2   * @brief  HSI_SetSysClk program.
 3   * @param  RCC_PLLMul_x: specifies the PLL multiplication factor[2~16]
 4   * @retval None
 5   */
 6 void HSI_SetSysClk(uint32_t RCC_PLLMul_x)
 7 {
 8     __IO uint32_t HSIStatus = 0;                //加 __IO 防止编译器优化
 9     
10 
11     RCC_DeInit();                                //把RCC寄存器复位
12 
13 //    RCC_HSICmd(ENABLE);                            //使能HSI,复位时候已经使能了HSI,这里可以不再使能
14     
15     HSIStatus = RCC->CR & RCC_CR_HSIRDY;        //等待HSI稳定,读取hsirdy寄存器
16     
17     if(HSIStatus == RCC_CR_HSIRDY)
18     {
19         /*预取缓冲器包含两个数据块,每个数据块有8个字节; 预取指令(数据)块直接映像到闪存中,
20           因为数据块的大小与闪存的宽度相同, 所以读取预取指令块可以在一个读周期完成;
21           设置预取缓冲器可以使CPU更快地执行, CPU读取一个字的同时下一个字已经在预取缓冲器中等候,
22           即当代码跳转的边界为8字节的倍数时, 闪存的加速比例为2;
23           flash等待周期数要系统时钟频率对应, 《stm32f10xxx闪存编程手册》中可以进行了解 */
24         FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);    //使能预取指
25         FLASH_SetLatency(FLASH_Latency_2);                        //2个等待周期
26         
27         RCC_HCLKConfig(RCC_SYSCLK_Div1);                        //配置AHB总线时钟HCLK
28         RCC_PCLK1Config(RCC_HCLK_Div2);                            //配置APB1总线时钟PCLK1,PCLK1 = HCLK/2
29         RCC_PCLK2Config(RCC_HCLK_Div1);                            //配置APB2总线时钟PCLK2,PCLK2 = HCLK
30         
31         RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_x);    //配置锁相环时钟(PLLCLK = HSI * RCC_PLLMul_x = 64MHz),RCC_PLLMul_x为倍频因子,PLL需要先配置再使能
32         RCC_PLLCmd(ENABLE);                                        //使能PLL
33         while(RESET == RCC_GetFlagStatus(RCC_FLAG_PLLRDY));        //等待PLL稳定
34         
35         RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);                //选择PLLCLK为系统时钟    
36         while(0x08 != RCC_GetSYSCLKSource());                    //等待PLLCLK置位系统时钟完成
37     }
38     else
39     {
40         /*如果HSI启动失败,可以在这里添加处理错误的代码*/
41     }
42 }
rcc_clk_config_operation_hsi.c
  波形如下:(使用HSI 8/2 * 16 = 64MHz; 使用HSI  8/2*8 = 32MHz)

    

 

四、总结

  系统时钟配置需要根据具体的时钟框图,先设置好时钟源,AHB、APB1、APB2的分频系数;再设置好PLL倍频和分频;最后使用SW切换选择系统时钟来源即可。

 

参考:

           1、《STM32F10X-中文参考手册》
           2、[野火EmbedFire]《STM32库开发实战指南——基于野火指南者开发板》;
   3、野火F103-指南者bilibili教程视频【150集-野火F103霸道/指南者视频教程】-中级篇_哔哩哔哩_bilibili
   4、32系统时钟配置 - Darren_pty - 博客园 (cnblogs.com)
   5、STM32F4_RCC系统时钟配置及描述 - strongerHuang - 博客园 (cnblogs.com)
           6、STM32F2系列系统时钟默认配置 - MyBooks - 博客园 (cnblogs.com)
           7、STM32入门系列-STM32时钟系统,时钟使能配置函数 - STM32嵌入式开发 - 博客园 (cnblogs.com)

 

小弟才疏学浅,如有错误或不足之处,还请大佬批评指正,深表感谢!