本文基于ST32F407ZGT6编写
——————————————
时钟系统是单片机的心脏,单片机初始化的第一步就是时钟系统的初始化。本文是基于STM32的库函数对时钟系统启动过程进行分析。
启动过程需要了解到的几个汇编语言:
- IMPORT :定义表示这是一个外部变量的标号,表示不是在本程序定义的。
- EXPORT :表示本程序里面定义的变量,是提供给其他模块调用的。
这两个关键字自是告诉编译器变量的来源,相当于C语言里的声明(与extern的作用差不多)
- LDR 和 STR ——字和无符号字节加载/存储指令
LDR指令用于从内存中读取单一字或字节数据存入寄存器中
STR指令用于将寄存器中的单一字或字节数据保存到内存
具体的例子:
LDR r0,[r1] ;将R1中的值存到r0中
STR r1,[r2] ; 将r1中的值存到r2所指定的地址中
- B跳转指令,常用的还有BL、BX、BLX
在startup_stm32f40_41xxx.s汇编启动文件中,有这样一段汇编代码
; Reset handlerSystemInit
;Reset_Handler 子程序开始
Reset_Handler PROC
;输出子程序 Reset_Handler 到外部文件
EXPORT Reset_Handler [WEAK]
;从外部文件中引入 main 函数
IMPORT SystemInit
;从外部文件引入 SystemInit 函数
IMPORT __main
;把 SystemInit 函数调用地址加载到通用寄存器 r0
LDR R0, =SystemInit
;跳转到 r0 中保存的地址执行程序(调用 SystemInit 函数)
BLX R0
;把 main 函数调用地址加载到通用寄存器 r0
LDR R0, =__main
;跳转到 r0 中保存的地址执行程序(调用 main 函数)
BX R0
;Reset_Handler 子程序结束
ENDP
; Dummy Exception Handlers (infinite loops which can be modified)
可以看到,程序是从复位开始跑的;这就是为什么我们下载完程序以后要按一下复位按键的原因。然后可以看到在进入main函数之前还会进入SystemInit函数,时钟初始化就是在这个函数里面进行的。
SystemInit函数:
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
//是否使用FPU(浮点计算)功能,如果使用那么要定义两个变量为1
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* Reset the RCC clock configuration to the default reset state ------------*/
/* Set HSION bit */
//CR最后一位置1,使能内部高速时钟;设置时钟为内部高速时钟启动
RCC->CR |= (uint32_t)0x00000001;
/* Reset CFGR register */
//复位时钟配置寄存器
RCC->CFGR = 0x00000000;
/* Reset HSEON, CSSON and PLLON bits */
//&=进行清零操作,把CR寄存器的16、19、24位清零
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset PLLCFGR register */
//0x24003010是PLLCFGR的默认复位值,中文参考手册有写
RCC->PLLCFGR = 0x24003010;
/* Reset HSEBYP bit */
//&=清零操作,给CR寄存器第18位清零
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Disable all interrupts */
//CIR时钟中断寄存器复位
RCC->CIR = 0x00000000;
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* Configure the System clock source, PLL Multiplier and Divider factors,
AHB/APBx prescalers and Flash settings ----------------------------------*/
//跳转到SetSysClock()函数
SetSysClock();
/* Configure the Vector Table location add offset address ------------------*/
//配置向量表位置,添加偏移地址
#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
}
这段代码注释已经放在了函数中了
这段代码主要是配置RCC_CR寄存器以及CFGR、PLLCFGR寄存器的复位。
1、先确定单片机是否使用浮点计算功能
2、然后将单片机时钟设计为内部高速时钟启动(HSI),因为单片机刚开始启动时采用内部高速时钟,响应速度快;然后就是把CR几个控制位关闭,以及相关寄存器CFGR、PLLCFGR复位。
3、接着跳转到了SetSysClock()函数:
SetSysClock()函数:
static void SetSysClock(void)
{
#if defined (STM32F40_41xxx) || defined (STM32F427_437xx) || defined (STM32F429_439xx) || defined (STM32F401xx)
/******************************************************************************/
/* PLL (clocked by HSE) used as System clock source */
/******************************************************************************/
/*定义了两个变量:
StartUpCounter:用于计数,与外部时钟准备时间(0x05000)作比较
HSEStatus:用于判断外部高速时钟是否已经准备好的标志位
*/
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* Enable HSE */
//把CR寄存器16位(HSE ON)置1,打开外部时钟使能位
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
do
{
/*退出循环的两个条件满足其一即可:
1、CR寄存器第17位置1表示HSE已经准备好了,即HSEStatus不等于0
2、当 StartUpCounter自增到5000 ,定时已到
*/
HSEStatus = RCC->CR & RCC_CR_HSERDY; //读取CR寄存器第17位(HSERDY),判断外部时钟是否准备好了
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
//确定CR寄存器第17位(HSERDY)已经置1了
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;//将HSEStatus取值0x01,相当于设定一个标志位
}
else
{
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01)//检查HSEStatus标志位
{
/* Select regulator voltage output Scale 1 mode */
// APB1ENR寄存器第28位(PWREN)置1,电源接口时钟使能
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
/*
配置PWR_CR寄存器,这个寄存器不是时钟的寄存器而是电源的寄存器,
这些位用来控制内部主调压器的输出电压,以便在器件未以最大频率工作时
使性能与功耗实现平衡具体内容看中文参考手册101页
这里把PWR_CR寄存器的14-15位(VOS)设置为11,即11:级别模式1(复位值)
*/
PWR->CR |= PWR_CR_VOS;
/* HCLK = SYSCLK / 1*/
//RCC_CFGR_HPRE_DIV1=0,所以一下相当于将CFGR复位按缺省值配置
/*CFGR寄存器是时钟配置寄存器,这个寄存器决定了STM32系统的时钟配置,
内容比较多,建议直接看《STM32中文参考手册118页》
如果你想要设置时钟为外部时钟还是内部时钟,时钟怎么分频就看这个寄存器了*/
RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
#if defined (STM32F40_41xxx) || defined (STM32F427_437xx) || defined (STM32F429_439xx)
/* PCLK2 = HCLK / 2*/
//将CFGR的15位置1,即PPRE2为100,PCLK2为HCLK二分频
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
/* PCLK1 = HCLK / 4*/
//将CFGR的10、12位置1,即PPRE1为101,PCLK1为HCLK四分频
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
#endif /* STM32F40_41xxx || STM32F427_437x || STM32F429_439xx */
/*以下代码是F401的,为了方便查看代码我把它注释掉了*/
//#if defined (STM32F401xx)
// /* PCLK2 = HCLK / 2*/
// RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
//
// /* PCLK1 = HCLK / 4*/
// RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
//#endif /* STM32F401xx */
/* Configure the main PLL */
/*这个语句比较复杂,我把它分出来就是把值0x0740 0d88赋值给PLLCFGR
就是把PLLCFGR的第3、7、8、10、12、14、22、24、25、26位置1(0X0740 5408)
第22位置1,选择 HSE 振荡器时钟作为 PLL 和 PLLI2S 时钟输入
其中:PLLQ=7;PLLP=2;PLLN=336;PLLM=8
计算公式:
● f(VCO 时钟) = f(PLL 时钟输入) × (PLLN / PLLM)
● f(PLL 常规时钟输出) = f(VCO 时钟) / PLLP
● f(USB OTG FS, SDIO, RNG 时钟输出) = f(VCO 时钟) / PLLQ
*/
RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
(RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
/* Enable the main PLL */
//CR寄存器第24位(PLLON)置1,开启PLL
RCC->CR |= RCC_CR_PLLON;
/* Wait till the main PLL is ready */
//查询CR寄存器第25位(PLLRDY)是否置1,以确定PLL是否已经开始
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/*以下代码是STM32F427_437x || STM32F429_439xx 的,为了方便查看代码我把它注释掉了*/
//#if defined (STM32F427_437xx) || defined (STM32F429_439xx)
// /* Enable the Over-drive to extend the clock frequency to 180 Mhz */
// PWR->CR |= PWR_CR_ODEN;
// while((PWR->CSR & PWR_CSR_ODRDY) == 0)
// {
// }
// PWR->CR |= PWR_CR_ODSWEN;
// while((PWR->CSR & PWR_CSR_ODSWRDY) == 0)
// {
// }
// /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
// FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
//#endif /* STM32F427_437x || STM32F429_439xx */
#if defined (STM32F40_41xxx)
/* Configure Flash prefetch, Instruction cache, Data cache and wait state */
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
#endif /* STM32F40_41xxx */
/*以下代码是F401的,为了方便查看代码我把它注释掉了*/
//#if defined (STM32F401xx)
// /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
// FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_2WS;
//#endif /* STM32F401xx */
/* Select the main PLL as system clock source */
//把CFGR的后两位清零,第二位置1,系统时钟由HSE更改为PLL
//如果想改时钟输入在这一步改即可
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= RCC_CFGR_SW_PLL;
/* Wait till the main PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
{
}
}
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 */
}
/*******下面还有一大段代码是STM32F411xE的,就不粘贴出来了***********/
}
具体注释已经在代码中
这段代码主要配置了RCC_CR、CFGR、PLLCFGR寄存器;
还有配置PWR_CR寄存器(这个寄存器不是RCC的寄存器而是电源的寄存器)。
这段代码比较长,涉及到相关寄存器每个位的操作,需要一边看代码一边看《STM32中文参考手册》才能搞懂。
关于STM32系统时钟的选择,PLL倍频的内容就在这段代码中。