STM32启动时钟配置函数的分析

关于STM32时钟的问题
2023.11.01 更新
        是我搞错了,今天破案了,主函数中执行RCC_Init是为了重新配置系统的时钟,例如原始时钟使用的是外部晶振倍频至某个频率,这里可以将其进行调整,比如时钟源切换选择系统时钟。系统初始化执行System_Init是刚开始自动配置时钟的,以STM32F1系列的启动文件为例,系统开始先执行System_Init配置时钟,这个函数的结果有两种,第一,配置成功,使用HSE外部高速晶振作为时钟源,通过PLL锁相环倍频至72MHz为系统提供时钟;第二,配置失败,失败的结果就是,时钟使用HSI内部低速8MHz晶振作为系统内部时钟,失败的很明显的一个结果就是系统的运行速率比之前慢了9倍(配置成功是72MHz,失败是8MHz),从延时函数最容易看出来。(这段话的叙述可能不是很标准,我所解释的是大致情况,具体情况要根据时钟树进行分析)

 

 
        在读别人建立大型的STM32工程时,会看到主函数中会首先执行一个RCC_Init()的函数,跳转过去之后发现是配置时钟的相关函数。而我自己在写STM32相关代码的时候,似乎从来没有注意过这个问题,我猜想在STM32程序开始执行的时候,也就是复位之后,会首先执行时钟配置的函数,于是我就详细的了解了STM32程序的执行流程。
        首先,STM32从上电复位到main函数的过程。主要有以下步骤:

1.初始化堆栈指针 SP=_initial_sp,初始化 PC 指针=Reset_Handler;

2.初始化中断向量表;

3.配置系统时钟;

4.调用 C 库函数_main 初始化用户堆栈,然后进入 main 函数。

        在学习STM32程序的执行顺序之前,我们需要首先了解STM32的启动方式,这个在STM32的参考手册里有的,如下图:

        一、STM32有三种启动方式:

        1.从主闪存存储器启动:主闪存存储器被映射到启动空间(0x0000 0000),但仍然能够在它原有的地址(0x0800 0000)访问它,即闪存存储器的内容可以在两个地址区域访问,0x0000
0000或0x0800 0000。(从STM32内置的Flash启动(0x0800 0000-0x0807 FFFF),一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。)
        2.从系统存储器启动:系统存储器被映射到启动空间(0x0000 0000),但仍然能够在它原有的地址(互联型产品原有地址为0x1FFF B000,其它产品原有地址为0x1FFF F000)访问它。(一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的ISP程序中,提供了串口下载程序的固件,可以通过这个ISP程序将用户程序下载到系统的Flash中。)
        3.从内置SRAM启动:只能在0x2000 0000开始的地址区访问SRAM。(从内置SRAM启动(0x2000 0000-0x3FFFFFFF),既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。)(SRAM掉电丢失)
        启动模式只决定程序烧录的位置,加载完程序之后会有一个重映射(映射到0x00000000地址位置);真正产生复位信号的时候,CPU还是从开始位置执行。值得注意的是STM32上电复位以后,代码区都是从0x00000000开始的,三种启动模式只是将各自存储空间的地址映射到0x00000000中。
        二、STM32的启动文件

        每个STM32工程都会有启动文件的,在建立工程的时候,已经将启动文件放在工程内了,这里我查看的是STM32f10x系列的启动文件,接下来我将一段一段的分析启动文件。

        启动文件的名称是“system_stm32f10x.c”。

 1. 这个文件提供了2个函数和1个全局变量:

SystemInit():设置系统时钟(系统时钟源、PLL锁相环(用于倍频)、AHB/APBx预分频器、Flash设置),这个函数被用作复位之后和主程序分支之前。

SystemCoreClock variable:系统内核时钟变量,包含内核时钟,它被用于用户应用程序设置SysTick定时器或配置其他参数。

SystemCoreClockUpdate():更新变量SystemCoreClock和在程序执行期间调用可以更改核心时钟。 

 2. 每个设备复位之后,HSI内部高速时钟(8MHz)被用于系统时钟源。然后在“startup_stm32f10x_xx.s”文件中调用 SystemInit() 函数,以配置分支到主程序之前的系统时钟。

 3. 如果用户选择的系统时钟源启动失败,则SystemInit()  函数将不做任何事情,HSI仍然用作系统时钟源。 用户可以在SetSysClock()函数中添加一些代码来处理这个问题。 

        4. HSE晶振的默认值设置为8 MHz(或25 MHz,开启使用的产品),请参阅“stm32f10x.h”文件中的“HSE_VALUE”定义。当HSE直接或通过PLL用作系统时钟源时,您使用不同的晶体,您必须根据自己的 HSE 值进行调整配置。

        总结一下,复位之后HSI内部高速时钟(8MHz)被用于系统时钟源,然后调用SystemInit() ,通过它配置时钟。

        5. 对System_Init函数的分析(包含寄存器的解释),这里我将其复制过来了,进行逐句解释:

void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */  //复位RCC时钟配置为默认值

/* Set HSION bit */          
RCC->CR |= (uint32_t)0x00000001;  竟时钟是心脏嘛。

//设置HSION位,就是将RCC_CR寄存器末位也就是HSION位置1,就是开启HSI内部高速晶振(8MHz),这里不开启的话,后面的程序就没法执行,毕竟时钟是心脏嘛。

/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */  
#ifndef STM32F10X_CL                    
RCC->CFGR &= (uint32_t)0xF8FF0000;             
#else                            

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

    #endif /* STM32F10X_CL */                    
    //复位SW、HPRE、PPRE1、PPRE2、ADCPRE、MCO,Reset是重置、清零的意思,所以此处就是将RCC_CFGR寄存器这些位设置为0,#ifdef是如果定义的意思,就是如果你定义的是STM32F10X_CL型号的单片机,这里就将RCC_CFGR寄存器设置为0xF8FF0000,否则就设置为0xF0FF0000,其实将这两个16进制值转化为2进制再与手册里的寄存器对照,就可以实现上 述的复位目标了。SW[1:0]占2位配置时钟切换,复位00即为HSI作为系统时钟;HPRE[7:4]占4位配置AHB高速时钟,AHB是高速总线的意思,这里复位值0000,就是SYSCLK不分频(SYSCLK是系统时钟,分频就是除以相应的倍数放慢时钟频率); PPRE1[10:8]占3位配置APB1低速时钟,APB1是低速总线,复位值000,表示HCLK不分频,这里涉及一个知识点(APB上的时钟源来自分频之后的系统时钟SYSCLK,其实就是HCLK),所以这里是HCLK不分频;PPRE2[13:11]占3位配置APB2高速时钟,与PPRE1相似,此处复位值000,也是HCLK不分频;ADCPRE[15:14]占2位用于确定ADC的时钟频率,ADC的时钟源来自APB2高速时钟,复位值00,表示PCLK二分频(PCLK2就表示APB2的时钟)。MCO[26:24]占3位用于配置微控制器时钟输出,这里意思是,单片机MCU输出至外部的时钟,就是通过某些端口将内部的时钟信号输出,复位值000表示不向外界输出时钟信号。容易得知,SW、HPRE、PPRE1、PPRE2、ADCPRE、MCO复位之后,此时系统的时钟配置是这样,SYSCLK为8MHz,HCLK为8MHz,PCLK1、PCLK2也为8MHz,ADC的时钟是4MHz,MCU不向外部输出时钟信号。MCO位控制输出我猜想是如果有单片机级联的情况,可以将它们的时钟统一。

/* Reset HSEON, CSSON and PLLON bits */  
RCC->CR &= (uint32_t)0xFEF6FFFF;     

               //复位HSEON,CSSON,PLLON,如果你读懂了上面CFGR寄存器的配置,这里也很容易理解,这里只说结果,失能HSE外部高速时钟,就是关 闭外部高速时钟HSE;关闭时钟检测器,时钟检测器是用来检测外部HSE高速时钟的,如果外部高速时钟出现问题,则会将系统时钟切换至HSI,CIR中断寄存器内也有相应的标志位;关闭PLL锁相环,PLL锁相环是用来倍频的,这里HSI直接作为SYSCLK了,所以PLL就不需要了,故而失能PLL。

/* Reset HSEBYP bit */            
RCC->CR &= (uint32_t)0xFFFBFFFF;

//复位HSEBYP,HSEBYP指外部高速时钟旁路,置零,表示外部3-25MHz晶振没有旁路。

/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;

               //复位PLLSRC、PLLXTPRE、PLLMUL、 USBPRE/OTGFSPRE,即PLL输入时钟为HSI时钟2分频后的时钟;PLLXTPRE没看懂,留坑;PLLMUL是PLL锁相环倍频系数,这里复位值0000,倍频系数保留;USBPRE/OTGFSPRE没看懂也用不到,留坑

#ifdef STM32F10X_CL  //#ifdef是条件宏定义,大概意思就是,如果满足后面的条件,执行下面代码,如果不满足,就删除下面代码,我使用的型号是Flash为64kb的F103C8T6,即为MD。

/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;

/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000;

/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;

/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#else  //上面的型号没有MD,那么就执行#else后面的代码,下面代码的意思就是使能所有的终端并且清除所有标志位,这里指的是时钟中断寄存器RCC_CIR。
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */

               //再具体一点就是清除CSSF安全系统中断标志位CSSF(也在这个寄存器中)、清除PLL就绪中断PLLRDY、清除HSE就绪中断、清除HSI就绪中断、清除LSE就绪中断、清除LSI就绪中断,后16位均是0,就是将标志位都置为0。


#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif

//上面这段代码对于MD系列的不执行。

 

/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();

//这里就是配置系统时钟频率、AHB高速总线的时钟、APB1、APB2的总线的时钟,具体详细配置请见下文

#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif  //这里不懂,留坑
}

6.接下来我们来看SetSysClock()函数

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

上面这段函数还是条件宏,这里执行的是SYSCLK_FREQ_72MHz,72MHz的选项在一开始就被选好了,如下图:

     //这里用到的还是条件宏,这里C8T6单片机的属于MD系列,所以选择的是72MHz这一项。


/* If none of the define above is enabled, the HSI is used as System clock
source (default after reset) */
}

展开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);  

//使能外部高速时钟,就是开启HSE外部高速时钟。

/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;  //这里就是通过一个与运算读取标志位HSERDY
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));  

               //等待HSE外部时钟开启成功,时钟开启需要时间的,通过检测相应时钟标志位可以知道时钟是否开启。这里检测的是HSEStatus,这里用到的是do while语句,只要外部时钟一直未开启且等待时间没到HSE_STARTUP_TIMEOUT,就一直执行while里的语句,计时就通过StartUpCounter累加完成。

if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}

//这里就是如果外部高速时钟准备好了,就将HSEStatus赋值为1;

 

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;

 

//Flash这里先不看,因为我不会,留坑
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
//这里就简单了,配置AHB时钟为系统时钟,DIV1是系统时钟不分频的意思。
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
//配置APB2总线上的时钟为HCLK,也是不分频
/* PCLK1 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

//配置APB1总线上的时钟为HCLK,这里是2分频,因为APB1总线上的时钟最快只有36MHz,这里必须将72MHz分频了

#ifdef STM32F10X_CL  //这里不看,与我无关
/* Configure PLLs ------------------------------------------------------*/
/* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
/* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */

RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);

/* Enable PLL2 */
RCC->CR |= RCC_CR_PLL2ON;
/* Wait till PLL2 is ready */
while((RCC->CR & RCC_CR_PLL2RDY) == 0)
{
}


/* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 |
RCC_CFGR_PLLMULL9);
#else  //看这里,与你有关
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */  //锁相环PLL配置,8MHz经9倍频至72MHz,很好理解的吧。
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));  

//这里利用与运算和或运算,同时配置PLL时钟源......,这里没看懂,应该是清除这些寄存器吧,下一步是写,我猜的
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);

//这里就是配置PLL时钟源为HSE,9倍频。
#endif /* STM32F10X_CL */ 

/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;

//使能PLL

/* Wait till PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
//等待PLL准备好
/* Select PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;

//选择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 */
}
}

总结一下,这里设置72MHz时钟作为系统时钟的过程为:开启HSE、等待;配置FLASH、HCLK、PCLK1、PCLK2;配置锁相环PLL;使能锁相环、等待;将PLL选择为系统时钟源、等待。

具体流程还得看系统时钟树,我这里理解为反向配置,后面的AHB、APB总线配置好了之后,再来配置PLL锁相环。

完结!!!

2023 11 07

posted @ 2023-10-29 23:16  不再熬夜啦  阅读(620)  评论(0编辑  收藏  举报