STM32-时钟篇以及复位时时钟的配置过程
时钟就是一个芯片的心脏,CPU的处理速度也是由时钟来直接决定,相比51单一的时钟源,STM32具有强大的时钟系统,我们的所有的外设都离不开时钟,时钟是学习STM32非常重要的部分。
一、时钟源
首先时钟都是由时钟源产生的,STM32的时钟可以由三个时钟源产生。
二、时钟树
在STM32的官方参考手册里面,RCC时钟的这一节里面有一个时钟树的图,如下,如果可以看懂这个图的话,就对STM32的时钟系统有了很清晰的了解,下面将会分部分对时钟树比较重要的部分进行简单的讲解。
1. 系统时钟部分
系统时钟部分一般被配置为72MHz,如下图,一般是由HSE(8MHz)经过PREDIV1不进行分频,然后经过PLLMUL进行9倍频,然后得到72MHz的系统时钟SYSCLK。
2. 实时时钟部分
如下图,实时时钟的时钟RTCCLK可以来源于三个地方,分别是40kHz的LSI(内部低速振荡器)和32.768kHz的LSE(外部低速振荡器)以及由HSE(外部高速振荡器进行128分频得到的时钟)。
3. APB1、APB2、AHB三条总线的时钟
- AHB 总线时钟:AHB时钟即HCLK,直接来源于系统时钟可以选择进行分频,一般不分频,等于系统时钟的最高频率72MHz。
- APB1总线的时钟:即PCLK1,最高只能设置为36MHz,来源于AHB的时钟HCLK,可以选择进行分频,由于最高只能设置为36MHz,所以一般进行2分频得到PCLK1=36MHz。
- APB2总线的时钟:即PCLK2,最高可以设置为72MHz,来源于AHB的时钟HCLK,可以选择进行分频。
4. 定时器的时钟
定时器又分为高级定时器和通用定时器,高级定时器包括TIM1和TIM8,通用定时器包括TIM2~TIM7。
如图中所示,如果APB1总线的预分频系数为1,那么到通用定时器的时钟就等于APB1总线的时钟PCLK1,否则等于APB1总线的时钟PCLK1*2。例如,APB1的时钟PCLK1配置为最高36MHz,APB1 prescaler=1那么通用定时器的时钟TIMxCLK=PCLK1=36MHz;如果APB1 prescaler不等于 1,那么TIMxCLK=PCLK1×2=72MHz。
而与高级定时器有关的总线是APB2,和通用定时器类似,如果APB2总线的预分频系数为1,那么到通用定时器的时钟就等于APB2总线的时钟PCLK2,否则等于APB2总线的时钟PCLK2*2。有同学在这里可能会有一点点不了解,因为APB2总线的最高频率为72MHz,在这里乘以2,就是144MHz,这样理解是不对的,为什么呢?首先只有APB2 prescaler 不为1的后,才会乘以2,而系统时钟最高为72MHz,如果APB2 prescaler不为1,那么会被分频,然后到定时器的时钟再乘以2,也是始终不会超过72MHz的。
5. ADC的时钟
ADC的时钟没有什么好说的,就是来源于APB2,然后经过ADC的预分频器,得到ADCCLK,不过需要注意的是ADC的最高频率只能为14MHz,当APB2的频率为72MHz的时候,必须经过6分频,得到的是12MHz,通过自己实验以及在网上查找资料发现,即使没有经过6分频,ADC还是可以正常工作,但是需要注意的是,此时ADC会有误差。
三、系统复位时时钟的配置过程
在编写STM32的时候,会有一个启动文件startup_stm32f10x_md.s,关于启动文件的作用如下
- 初始化堆栈指针 SP;
- 初始化程序计数器指针 PC;
- 设置堆、栈的大小;
- 设置异常向量表的入口地址;
- 配置外部 SRAM 作为数据存储器(这个由用户配置,一般的开发板可没有外部 SRAM);
- 设置 C 库的分支入口__main(最终用来调用 main 函数);
- 在 3.5 版的启动文件还调用了在 system_stm32f10x.c 文件中的
SystemInit() 函数配置系统时钟,在旧版本的工程中要用户进入
main 函数自己调用 SystemInit() 函数。
这次我们的重点不放在启动文件上面,主要是系统初始化函数SystemInit() ,我们在启动文件中可以找到一段代码,意思就是首先调用了SystemInit函数,返回之后,就进入我们的主函数main。我们定位到SystemInit鼠标右键选择go to definition of ‘SysttemInit’或者按键盘上面的F12进入该函数。
由于本篇针对的是STM32F103实用系列,函数里面一些是关于其他系列如互联型等,所以可以将SystemInit函数进行了一些简化,代码如下(来源于野火):
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
/* 使能 HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* 等待HSE就绪并做超时处理 */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
// 如果HSE启动成功,程序则继续往下执行
if (HSEStatus == (uint32_t)0x01)
{
/* 使能预取指 */
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;
/* HCLK = SYSCLK = 72M */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK = 72M */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK = 36M*/
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
/* 锁相环配置: 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);
/* 使能 PLL */
RCC->CR |= RCC_CR_PLLON;
/* 等待PLL稳定 */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* 选择PLLCLK作为系统时钟*/
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* 等待PLLCLK切换为系统时钟 */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
}
else
{
/* 如果HSE 启动失败,用户可以在这里添加处理错误的代码 */
}
}
这样我们就可以清楚的知道,搞明白了这些,这样在以后学习定时器或者ADC以及其他的资源的时候,就不会感觉到有一些迷惑,总是觉得不太自信的赶脚。。。哈哈
HCLK = SYSCLK = 72M |
---|
PCLK2 = HCLK = 72M |
PCLK1 = HCLK = 36M |
PLLCLK = HSE * 9 = 72 MHz |