1 概念
(1) 时钟源与时钟信号
时钟信号是指有固定周期并与运行无关的信号量,它用于决定逻辑单元中的状态何时更新。如时钟边沿触发信号意味着所有的状态变化都发生在时钟边沿到来时刻。在边沿触发机制中,只有上升沿或下降沿才是有效信号,才能控制逻辑单元状态量的改变。至于到底是上升沿还是下降沿作为有效触发信号,则取决于逻辑设计的技术。
时钟源是产生时钟信号的硬件(系统),如晶体/陶瓷谐振器。
(2) 时钟信号的作用
在“PN0003 STM32F10XXX(Cortex-M3) MDK-RAM 点亮流水灯 笔记”中有抽象的提到关于时钟信号的作用。用一个较实际的例子来说明时钟信号的作用应该会让人觉得更为亲切:计数器只有在有效驱动时钟信号(如上升沿)CK_CNT到来时才发生计数操作。CK_CNT的频率(如1MHZ)决定了计数器计数一次所花的时间t1(1us)。若计数1000次发生了溢出/下溢从而间接的决定时基的大小1ms,由多个时基从而就可以得到现实生活中的10ms,1s之类的时间段了。时钟驱动计数器见《RM008stm32f10xx参考手册》 Page255 图99 ~ 106。
(3) 外部时钟与内部时钟
外部时钟在芯片外部通过芯片引脚和内部电路发生关系。外部时钟信号可由外部晶体或外部时钟源提供,外部晶体为震荡模式,是使用片内震荡电路和外部接的晶体来产生时钟信号,外部时钟源是一个可以产生时钟信号的器件,比如时钟发生器。
内部时钟在芯片内部。一般都是由RC震荡器产生。
2 读时钟树图
跟时钟相关的模块都被集中到了“时钟树图”之上,所以学会读“时钟树图”还稍带必要性。就像“每次”到食堂吃早饭一样,吃鸡蛋之前都要先喝几口粥,但也不能将粥都先喝完了,不然后面吃鸡蛋的时候会被哽噎到。“时钟树图”就是那碗粥。
系统时钟 (SYSCLK)是核心。因为片内太多模块的时钟信号都是从这里“散发”出去的,它散发到需要时钟信号的每一个模块形成新的时钟信号以驱动这些模块工作。但SYSCLK并非就是由某个时钟源头发出时钟信号,它的来由可以来自“几大”时钟信号,再追踪这“几大”时钟信号就能够追踪到时钟源了。追踪到时钟源就差不多了,因为那已经是硬件成分了。可以在PCB原理图的时候仔细研究一下子,将其原理搞清楚。
Figure1:时钟树
(1) SYSCLK去处
Figure 1时钟树的SYSCLK时钟信号在深红色方框处。它的去处分为如图所示的1,2两个方向。假设SYSCLK已经存在(已经有了时钟信号)。
[1] 去往I2Sx[x=2,3]
暂且不管I2Sx模块具体是什么。因为这里我只关心SYSCLK时钟去处为I2SxCLK,从图中可以看出SYSCLK直接作为了I2SxCLK。当然作为外设,在配置好SYSCLK的情况下,还需要使能I2SxCLK时钟后才能使用I2SxCLK时钟信号。
[2] 去往AHB预分频器
SYSCLK的第二个去向就是AHB预分频器,AHB预分频器可对SYSCLK时钟进行1,2…,512分频。经AHB预分频器分频后的时钟信号会去往以下模块:- 至SDIO (FSMC)成为SDIOCLK(FSMCCLK),只要使能SDIO (FSMCCLK)模块时钟,SDIOCLK (FSMCCLK)就被激活即可驱动SDIO (FSMC)模块工作。跟SYSCLK拥有一样的频率。
- 至AHB总线、核心存储器和DMA模块成为HCLK,在使能HCLK后HCLK就被打开去驱动此模块工作。
- 经8分频后至(成为)Cortex系统时钟。即假设SYSCLK为72MHz,则Cortex系统时钟为9MHz。
- 至APB1预分频器。APB1预分频器可对经AHB预分频器后SYSCLK信号进行1, 2,…,16分频,然后再分往两个去处。去往APB1外设的PCLK1时钟最大只能为36MHz即AHB与APB1预分频器的分频系数不能同时为1,在让PCLK1驱动APB1外设工作时需要提前使能外设各自对应的时钟。去往TIMx[x=2,3,…7]作为TIMXCLK,若APB1预分频系数为1则TIMXCLK的频率率与所在APB总线频率一致,否则,定时器的时钟频率被设为与其相连的APB总线频率的2倍。在使用TIMx时需要使能对应的时钟TIMxCLK。
- 进入APB2预分频器的时钟信号除新增ADC模块后同上 (APB1)。
- 经2分频后进入SDIO的AHB接口,时钟频率大小为HCLK/2。使用此模块时钟需要提前对其使能。
(2) SYSCLK来源
SYSCLK最大允许的频率为72MHz。
SYSCLK的来源在时钟树中用粉红色方框标记。从图中可以看出,SYSCLK来源于时钟源(除PLL):
- HIS(内部高速时钟):它是由被称为“HSI RC时钟源”的东西产生的频率为8MHz的时钟信号可直接作为SYSCLK的来源。
- HSE(外部高速时钟): HSE信号的时钟源可以分为“外部时钟源”和外部晶体/陶瓷谐振器。后者是使用“片内震荡电路”和“外部接的晶体”组成的时钟源来产生的名为HSE的时钟信号;前者是通过如时钟产生器的东西产生HSE,4 ~ 16MHz的HSE可被直接作为SYSCLK的来源。
- PLL时钟来源于HSE或HSI/2,然后将其倍频送给SYSCLK。当HSI被用于作为PLL时钟的输入时,系统时钟能得到的最大频率是64MHz。当HSE作为PLL时钟时,选择倍频时要考虑到SYSCLK最大频率不得超过72MHz。
(3) 其它时钟源
- LSE(外部低速时钟):跟HSE时钟一样时钟源分为两种,提供的频率为32.768KHz。主要是为实时时钟(RTC)或其它定时模块提供一个低功耗且精确的时钟源。RTC还可以有由HSE/128提供。
- LSI(内部低速时钟):时钟源为“LSI RC”振荡器,LSI频率大约为40KHz(30KHz ~ 60KHz),为独立看门狗和自动唤醒单元提供时钟。
- 如果需要在应用中使用USB接口,PLL必须被设置为输出48或72MHZ时钟,用于提供48MHz的USBCLK时钟。
(4) 时钟总结
- 时钟信号的作用。
- 低速时钟一般没有那么精确,都设有校准位。
- 配置时钟的操作都在对应RCC寄存器内。时钟的稳定由硬件设置,开启和关闭某一时钟作为SYSCLK可在RCC_CR、RCC_CFGR寄存器内设置。
3 时钟初始化配置
时钟初始化配置主要配置“SYSCLK的来源(HIS OrHSE Or PLL(HIS/2 Or HSE))”以及“各模块时钟源频率(HCLK[AHB分频], PCLKx)”。
- RCC_CR: 含HSE、HLI、PLL时钟的开启位及三大时钟的稳定状态指示位,及HLI的校准位。
- RCC_CFGR: 含“系统时钟源状态位”、“系统时钟选择位”、“PLL时钟来源位”、“PLL时钟输出倍频位”及“其它时钟(HCLK、HSE、PCLKx)分频系数位”。
(1) 时钟复位
设置RCC时钟在系统复位时的默认配置。void RCC_Reset(void) { //复位时选择HIS作为系统时钟 RCC->CR |= (unsigned int)0x00000001; //复位SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO位 //即HSI作为系统时钟,SYSCLK不分频,HCLK不分频(PCLK1分频系数),HCLK不分频(PCLK2分频系数),… RCC->CFGR &= (unsigned int)0xF8FF0000; //复位HSEON, CSSON and PLLON 位即关闭HSE、CSS、PLL时钟 RCC->CR &= (unsigned int)0xFEF6FFFF; //复位HSEBYP位即外部4-16MHz振荡器没有旁路 RCC->CR &= (unsigned int)0xFFFBFFFF; //复位PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE位 //即设置PLL来源,分频,倍频、USB等分频 RCC->CFGR &= (unsigned int)0xFF80FFFF; //清除所有时钟就绪的中断,写1清除 RCC->CIR = 0x009F0000; } |
(2) 开启HSE
开启HSE时钟就是将RCC_CR中的HSEON位开启,并判断RCC_CR中的HSERDY是否为1,为1就说明HSE已经稳定就绪可被输出使用了,如果超过某个时间后HSERDY依旧为0则作超时处理。void HSE_Enable(void) { //外部4-16MHz振荡器没有旁路 RCC |= (unsigned int)0x00010000 } #define HSEStartUp_TimeOut ((unsigned short)0x0500) /* Time out for HSE start up */ //返回0表面HSE时钟正常启动 char RCC_WaitHseStartUp(void) { unsigned short counter = 0; do{ ; }while( (RCC->CR & (unsigned int)0x00200000) && ++counter != HSEStartUp_TimeOut ); if(counter != HSEStartUp_TimeOut){ return 1; }else { return 0; } } |
(3) HCLK、PCLKx分频
在RCC_WaitHseStartUp()函数返回为0的情况下继续配置时钟。void HP_CLKx_Set(void) { //使能预取缓冲 FLASH->ACR |= 0x10; //Flash 2 等待状态 FLASH->ACR &= (unsigned int)(( unsigned int)~ 0x03); FLASH->ACR |= (unsigned int) 0x02; //HCLK = SYSCLK,HPRE[3:0] =0xx,AHB不分频 RCC->CFGR |= (unsigned int)0x00000000; //PCLK2 = HCLK,PPRE2[2:0]=0xx,PCLK2预分频系数为1 RCC->CFGR |= (unsigned int) 0x00000000; //PCLK1 = HCLK/2,PPRE1[2:0]=100,PCLK1预分频系数为2 RCC->CFGR |= (unsigned int) 0x00000400; } |
(4) 选择PLL时钟为SYSCLK时钟源
凡是打开一个时钟后都得等待这个时钟的启动与稳定。SYSCLK的时钟源切换也需要等待。void PLL_To_SYSCLK(void) { //设置HSE为PLL的时钟信号输入源,并且设置9倍倍频输出PLL //PLLCLK = HSE * 9 RCC->CFGR &= (unsigned int)(( unsigned int)~( 0x00010000 | 0x00020000 | 0x003C0000));//消除其它时钟作为PLL输入时的设置 RCC->CFGR |= (unsigned int)( 0x00010000 | 0x001C0000); //使能PLL时钟 RCC->CR |= (unsigned int)0x01000000; //等待PLL时钟启动至稳定 while((RCC->CR & 0x02000000) == 0) { } //选择PLL时钟作为SYSCLK信号源 RCC->CFGR &= (unsigned int)( ~ (0x00000003)); //消除某时钟源作为SYSCLK的标记 RCC->CFGR |= (unsigned int) 0x00000002; //直到PLL时钟被切换为SYSCLK的时钟信号 while ((RCC->CFGR & (unsigned int) 0x0000000C) != (uint32_t)0x08) { } } |