STM32F103五分钟入门系列SystemInit()函数SetSysClock()函数

Posted 自信且爱笑‘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103五分钟入门系列SystemInit()函数SetSysClock()函数相关的知识,希望对你有一定的参考价值。

学习板:STM32F103ZET6

强推系列:
STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结

STM32F103五分钟入门系列(二)GPIO的七大寄存器+GPIOx_LCKR作用和配置

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区

参考:

STM32F103五分钟入门系列(六)时钟框图+相关寄存器总结+系统时钟来源代码(寄存器)

前言

上一篇博客总结了时钟框图、时钟相关寄存器。在博客的最后又总结了如何在main.c中重新设置系统时钟并通过MCO(PA8复用引脚)输出系统时钟,再通过示波器察看系统时钟的波形。上一篇博客也总结到,在main.c中重新设置系统时钟时,如果设置不成功,就依旧采用默认的系统时钟;当然重新设置系统时钟时,AHB、APB1、APB2等分频系数、flash等不需要再重新设置,因为程序编译时已经在底层的system.stm32f10x.c中设置了这些,如果外设没有特殊要求,在main.c中重新设置时钟时这些就不用再设置了。

本博客对system.stm32f10x.c中时钟相关的函数进行总结,通过本篇博客应该就可以清楚知道默认的系统时钟是怎么来的?如何在底层中修改系统时钟?如果外部晶振接的不是8MHZ,那么此时默认的系统时钟是多少、怎么计算?

一、SystemInit()

打开system_stm32f10x.c文件,找到SystemInit()函数
在这里插入图片描述

现一句句分析:

1、打开HSI时钟

 	RCC->CR |= (uint32_t)0x00000001  //打开HSI时钟

在这里插入图片描述

HSI在这里打开后就不会再关闭了,所以也不用等待它使能就绪。HSI打开的目的很明确,就是在HSE、PLL作为系统时钟源发生故障时,通过CSS的中断来自动将HSI切换为系统时钟源,所以HSI一直开启。

2、关闭MCO、ADCPRE、PPRE2、PPRE1、HPRE、SWS、SW

在这里插入图片描述

	RCC->CFGR &= (uint32_t)0xF0FF0000;  //关闭MCO、ADCPRE、PPRE2、PPRE1、HPRE、SWS、SW

在这里插入图片描述

还是觉得这个底层有问题,位27应该要置0(我的板子是HD,不是CL),但是根据寄存器,位27是保留位,应该与1进行与运算,所以这个地方有问题,但不影响使用。

这行代码设置的东西:
在这里插入图片描述

即:关闭MCO时钟输出、AHB不分频、APB1不分频、APB2不分频、ADC 2分频、SHI作为系统时钟

3、使能PPL和CSS、关闭HSE

	RCC->CR &= (uint32_t)0xFEF6FFFF;   //使能PPL和CSS、关闭HSE

在这里插入图片描述

4、HSEBYP清零

	RCC->CR &= (uint32_t)0xFFFBFFFF; 

在这里插入图片描述
位18置零。表示HSE没有被旁路

5、PLLSRC、PLLXTPRE、PLLMUL、USBPRE

	RCC->CFGR &= (uint32_t)0xFF80FFFF;

在这里插入图片描述

位16~22清零,表示:USB预分配系数1.5、PLLMUL 2倍频输出、SHE不分频做PLL的时钟来源、PLL输入时钟源为HSI/2。如下图紫色部分所示。
在这里插入图片描述

6、CSSC、PLLRDYC

在这里插入图片描述

	 RCC->CIR = 0x009F0000;

在这里插入图片描述

此代码清除CSS时钟的系统中断、清除PLL就绪中断

7、进入SysClock();

在这里插入图片描述

接下来就进入SysClock()函数了。

8、总结(重要

从之前的七小节得到以下结论:

现在的状态:

1、HSI:打开,并作为系统时钟
2、HSE:关闭
3、MCO:关闭
4、PLL:打开
5、CSS:打开
6、CSS系统中断标志位:清除
7、PLL就绪中断:清除
8、HSE旁路:未旁路
9、USB预分频:1.5,USB采用SYSCLK/1.5时钟
10、AHB预分频:1,AHB输出后为系统时钟的大小
11、APB1预分频:1,到APB1外设的时钟PCLK1=SYSCLK
12、APB2预分频:1,到APB2外设的时钟PCLK2=SYSCLK
13、ADC预分频:2,到ADC的时钟ADCCCLK=SYSCLK/2
14、PLLMUL选择器输出倍频:2
15、SHE可直接提供PLL时钟来源
16、HSI/2可直接提供PLL时钟来源

在时钟框图中标注(绿色)
在这里插入图片描述

注意以下几点:

1、APB1低速外设总线的时钟最大为36MHZ,所以这个地方之后可能需要更改倍频系数

2、ADC的最大时钟为14MHZ,所以这个地方之后可能需要更改倍频系数

二、SetSysClock()

底层更改系统时钟的方法

SystemInit()函数执行到最后就进入SetSysClock()函数了
在这里插入图片描述
点击进入SetSysClock()
在这里插入图片描述
在这里插入图片描述

可以看到定义不同的标识符,就执行对应的函数,从名称上也能看出是在配置哪个频率的系统函数

打开定义,注意此时只有72MHZ的函数和标识被定义,其他的都被注释了,所以只能用72MHZ相关的标识和函数打开定义(如果打不开,就一个个的试吧)
在这里插入图片描述在这里插入图片描述

所以也看出来了,要想在底层中修改系统时钟,就在这里修改,如更改为36MHZ,就将SYSCLK_FREQ_36MHz解除注释,将SYSCLK_FREQ_72MHz再注释掉:

在这里插入图片描述

现分别看看这几个时钟配置函数

(一)SetSysClockTo72()函数

在这里插入图片描述
在这里插入图片描述

1、使能HSE时钟

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

在这里插入图片描述

即:

	RCC->CR |= 0x00010000;

在这里插入图片描述

该行代码是使能HSE时钟

2、等待HSE时钟就绪

  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

分析一下这几行代码
在这里插入图片描述

HSEStatus = RCC->CR & 0x00020000;

在这里插入图片描述

当HSE就绪后,位17变为1,此时 HSEStatus!=0
StartUpCounter != HSE_STARTUP_TIMEOUT是保证在一定时间内HSE就绪,不能无期限的等待它就绪,当就绪后,while后面的判断条件就不成立了,跳出循环。
当然保证HSE不会出错的情况下,这几行代码还可以改为:

while((RCC->CR & 0x00020000)==0)
{

}

3、flash设置

在这里插入图片描述

上图中框中代码是判断HSE是否就绪,如果就绪就执行接下来的代码。如果没有就绪,也不会重新执行HSE使能了,说明HSE出现了问题,系统时钟配置也就无法成功。

/* 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;    

flash相关文件:stm32f10xxx闪存编程参考手册.pdf
这三行代码的定义的标识不好理解,直接换成十六进制数:

/* Enable Prefetch Buffer */
    FLASH->ACR |= 0x10;

    /* Flash 2 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~)0x03);
    FLASH->ACR |= (uint32_t)0x02;    

注意:stm32中二进制数是低位对齐,高位补零的,所以可以用一个8位二进制数对32位寄存器运算,只需前面补零就行。

第一行代码:

在这里插入图片描述

该行代码使能预取缓冲区

第二行代码:
在这里插入图片描述

该行代码是为了将位1:0置0,当然位31:8这些保留位也变为了0。

第三行代码:
在这里插入图片描述

该行的目的是把位1置1

在这里插入图片描述

此时位2:0位010,意思是设置设置SYSCLK周期与闪存访问时间的比例为2(目前是把系统时钟设置为72MHZ)
这里还有有点问题的,在第二行代码中,没有把位2置0,(底层有好几处不影响使用、但逻辑错误的地方)所以可以改为:

 	 FLASH->ACR &= (uint32_t)((uint32_t)~)0x07);//第二行修改

所以flash设置为:每两个时钟周期,访问一次闪存。

4、设置分频系数(APB1)

 	    /* 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;

前两行的标识都为0,,或运算后CFGR寄存器的状态不变,就不用看了。直接第三行:

在这里插入图片描述在这里插入图片描述

或运算后CFGR寄存器的位10变为1,即PPRE1为100

在这里插入图片描述
即设置APB1的分频系数为/2。

此时时钟框图状态:(绿色标注为SystemInit()后的状态,蓝色标注为新加状态)
在这里插入图片描述

5、设置倍频系数(PLLMUL)

在这里插入图片描述

    /*  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);

不好理解,把定义的标识重新用16进制数表示:

	  RCC->CFGR &= ~(0x00010000 | 0x00020000 |0x003C0000);           
	  RCC->CFGR |= 0x00010000| 0x001C0000;

还是不好理解,再简化,将或运算和取反运算求出来:

	 RCC->CFGR &=0xffc0ffff;
     RCC->CFGR |=0x001d0000;

第一行
在这里插入图片描述目的是将位21:16置0。

第二行
在这里插入图片描述
目的是将位20、19、18、16置1

经过这俩行代码,位21:18=0111,位16=1
在这里插入图片描述在这里插入图片描述

即这俩行代码设置PLL时钟源为HSE提供、PLLMUL为9倍频。

时钟框图中标注:(绿色为SystemInit()函数设置后状态,蓝色为本函数新设置的状态)

在这里插入图片描述

6、使能PLL时钟

	RCC->CR |= RCC_CR_PLLON;

将标识写为16进制数:

	RCC->CR |= 0x01000000;

在这里插入图片描述

所以该行代码使能PLL时钟
接下来的代码等待PLL时钟就绪,之前已经总结过了,不在赘述。

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

7、将PLL设置为系统时钟

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

将标识改为16进制数:

    RCC->CFGR &= (uint32_t)((uint32_t)~(0x00000003));
    RCC->CFGR |= (uint32_t)0x00000002;    

即:

    RCC->CFGR &= 0xfffffffc;
    RCC->CFGR |= 0x00000002;    

第一行
在这里插入图片描述

将CFGR寄存器的位1:0设置为00

第二行
将CFGR寄存器的位1:0设置为10
在这里插入图片描述

这俩行代码将PLL设置为系统时钟。

之后就是等待PLL时钟就绪:

    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }

8、总结(重要

目前的时钟状态:
在这里插入图片描述

72MHZ系统时钟来源:HSE(8M)经过9倍频得到PLL时钟,PLL时钟做系统时钟输入
如果外接的晶振为24MHZ,则只需将PLLMUL的倍频系数9改为3即可。还有一些晶振比如15M,不管如何设置分频、倍频都无法达到72MHZ,此时系统时钟只能选用<72MHZ的一个值。

(二)SetSysClockTo56()函数

代码与SetSysClockTo72()函数基本一样,唯一不同的是设置PLLMUL的倍频系数。

1、设置倍频系数(PLLMUL)

在这里插入图片描述

    /* PLL configuration: PLLCLK = HSE * 7 = 56 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_PLLMULL7);

将标识写成16进制的形式:

    /* PLL configuration: PLLCLK = HSE * 7 = 56 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(0x00010000 | 0x00020000 | 0x003C0000));
    RCC->CFGR |= (uint32_t)(0x00010000 | 0x00140000);

还是不好理解,将或运算和取反运算算出来:

    /* PLL configuration: PLLCLK = HSE * 7 = 56 MHz */
    RCC->CFGR &= 0xffc0ffff;
    RCC->CFGR |= 0x00150000;

第一行:
在这里插入图片描述

将CFGR寄存器的位21:16置0

第二行:
在这里插入图片描述

将CFGR寄存器的位18、为16置1.

经过这俩行代码后,位21:18=0101,位16=1,位17=0
在这里插入图片描述
在这里插入图片描述

所以这俩行代码将HSE直接设置为PLL时钟源的输入,PLLMUL倍频系数为7,此时PLL时钟为7*8MHZ=56MHZ

之后就是使能PLL时钟、等待PLL时钟就绪、将PLL设置为系统时钟、等待系统时钟就绪,这些代码与SetSysClockTo72()函数的代码一样。

在这里插入图片描述

2、总结(重要

SetSysClockTo56()函数的设置与SetSysClockTo72()函数基本相同,唯一不同的地方是PLLMUL倍频系数的设置。

56MHZ时钟来源为:HSE(8MHZ)经过7倍频(PLLMUL)生成PLL时钟,PLL做为系统时钟输出。

时钟框图:(绿色为SystemInit()函数设置后的结果,蓝色为本函数设置的结果)
在这里插入图片描述

(三)SetSysClockTo48()函数

代码与SetSysClockTo72()函数基本一样,唯一不同的是设置PLLMUL的倍频系数。

1、设置倍频系数(PLLMUL)

在这里插入图片描述

    /*  PLL configuration: PLLCLK = HSE * 6 = 48 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_PLLMULL6);

将标识改为16进制数:

    /* PLL configuration: PLLCLK = PREDIV1 * 6 = 48 MHz */ 
    RCC->CFGR &= (uint32_t)~(0x00010000| 0x00020000 | 0x003C0000);
    RCC->CFGR |= (uint32_t)()0x00010000 | 0x00100000);  

将或运算和取反运算算出来:

    /* PLL configuration: PLLCLK = PREDIV1 * 6 = 48 MHz */ 
    RCC->CFGR &= 0xffc0ffff;
    RCC->CFGR |= 0x00110000;  

第一行:
在这里插入图片描述
将CFGR寄存器位21:16置为0

第二行:

在这里插入图片描述

将位20和位16置1

此时位21:18=0100,位17=0,位16=1。
在这里插入图片描述
在这里插入图片描述

所以这俩行代码将HSE直接设置为PLL时钟源的输入,PLLMUL倍频系数为6,此时PLL时钟为6*8MHZ=48MHZ

之后就是使能PLL时钟、等待PLL时钟就绪、将PLL设置为系统时钟、等待系统时钟就绪,这些代码与SetSysClockTo72()函数的代码一样。
在这里插入图片描述

2、总结(重要

SetSysClockTo48()函数的设置与SetSysClockTo72()函数基本相同,唯一不同的地方是PLLMUL倍频系数的设置。

48MHZ时钟来源为:HSE(8MHZ)经过6倍频(PLLMUL)生成PLL时钟,PLL做为系统时钟输出。

时钟框图:(绿色为SystemInit()函数设置后的结果,蓝色为本函数设置的结果)
在这里插入图片描述

(四)SetSysClockTo36()函数

代码与SetSysClockTo72()函数基本一样,不同的是设置PLLMUL的倍频系数、PLLXTPRE选择器设置。

1、设置倍频系数(PLLMUL)、设置选择器(PLLXTPRE)

在这里插入图片描述

    /*  PLL configuration: PLLCLK = (HSE / 2) * 9 = 36 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_PLLXTPRE_HSE_Div2 | RCC_CFGR_PLLMULL9);

将标识改为16进制数:

    /*  PLL configuration: PLLCLK = (HSE / 2) * 9 = 36 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(0x00010000 | 0x00020000 | 0x003C0000));
    RCC->CFGR |= (uint32_t)(0x00010000 |0x00020000| 0x001C0000);

将或运算和取反运算算出来:

    /*  PLL configuration: PLLCLK = (HSE / 2) * 9 = 36 MHz */
    RCC->CFGR &= 0xffc0ffff;
    RCC->CFGR |= 0x001f0000;

第一行:
在这里插入图片描述

将CFGR寄存器的位21:16置0

第二行:
在这里插入图片描述

将位20:16置1

执行这两行代码后,位21:18=0111;位17=1;位16=1;
在这里插入图片描述

所以是将HSE经过2分频后再9倍频作为PLL时钟来源,然后PLL时钟做系统时钟输入。此时PLL时钟为:9×(8MHZ/2)=36MHZ

之后就是使能PLL时钟、等待PLL时钟就绪、将PLL设置为系统时钟、等待系统时钟就绪,这些代码与SetSysClockTo72()函数的代码一样。
在这里插入图片描述

2、总结(重要

SetSysClockTo36()函数的设置与SetSysClockTo72()函数基本相同,唯一不同的地方是PLLMUL倍频系数的设置。

36MHZ时钟来源为:HSE(8MHZ)2分频后再经过9倍频作为PLL时钟来源,然后PLL时钟作为系统时钟来源。

时钟框图:(绿色为SystemInit()函数设置后的结果,蓝色为本函数设置的结果)
在这里插入图片描述

(五)SetSysClockTo24()函数

代码与SetSysClockTo72()函数基本一样,不同的是设置PLLMUL的倍频系数、PLLXTPRE选择器设置。

1、设置倍频系数(PLLMUL)

在这里插入图片描述

    /*  PLL configuration:  = (HSE / 2) * 6 = 24 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_PLLXTPRE_HSE_Div2 | RCC_CFGR_PLLMULL6);

将标识

以上是关于STM32F103五分钟入门系列SystemInit()函数SetSysClock()函数的主要内容,如果未能解决你的问题,请参考以下文章

STM32F103五分钟入门系列(十五)输入捕获(精雕细琢-.-)

STM32F103五分钟入门系列SystemInit()函数SetSysClock()函数

STM32F103五分钟入门系列按键实验(库函数+寄存器)

STM32F103五分钟入门系列NVIC中断优先级管理

STM32F103你学不会系列(十七)电容触摸按键实现

STM32F103(二十)DAC(贼详细)