STM32启动时钟配置函数的分析
1.初始化堆栈指针 SP=_initial_sp,初始化 PC 指针=Reset_Handler;
2.初始化中断向量表;
3.配置系统时钟;
4.调用 C 库函数_main 初始化用户堆栈,然后进入 main 函数。
在学习STM32程序的执行顺序之前,我们需要首先了解STM32的启动方式,这个在STM32的参考手册里有的,如下图:
一、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 */ /* 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