时钟信号是指有固定周期并与运行无关的信号量,它用于决定逻辑单元中的状态何时更新。如时钟边沿触发信号意味着所有的状态变化都发生在时钟边沿到来时刻。在边沿触发机制中,只有上升沿或下降沿才是有效信号,才能控制逻辑单元状态量的改变。至于到底是上升沿还是下降沿作为有效触发信号,则取决于逻辑设计的技术。
时钟源是产生时钟信号的硬件(系统),如晶体/陶瓷谐振器。
在“PN0003 STM32F10XXX(Cortex-M3) MDK-RAM 点亮流水灯 笔记”中有抽象的提到关于时钟信号的作用。用一个较实际的例子来说明时钟信号的作用应该会让人觉得更为亲切:计数器只有在有效驱动时钟信号(如上升沿)CK_CNT到来时才发生计数操作。CK_CNT的频率(如1MHZ)决定了计数器计数一次所花的时间t1(1us)。若计数1000次发生了溢出/下溢从而间接的决定时基的大小1ms,由多个时基从而就可以得到现实生活中的10ms,1s之类的时间段了。时钟驱动计数器见《RM008stm32f10xx参考手册》 Page255 图99 ~ 106。
外部时钟在芯片外部通过芯片引脚和内部电路发生关系。外部时钟信号可由外部晶体或外部时钟源提供,外部晶体为震荡模式,是使用片内震荡电路和外部接的晶体来产生时钟信号,外部时钟源是一个可以产生时钟信号的器件,比如时钟发生器。
内部时钟在芯片内部。一般都是由RC震荡器产生。
跟时钟相关的模块都被集中到了“时钟树图”之上,所以学会读“时钟树图”还稍带必要性。就像“每次”到食堂吃早饭一样,吃鸡蛋之前都要先喝几口粥,但也不能将粥都先喝完了,不然后面吃鸡蛋的时候会被哽噎到。“时钟树图”就是那碗粥。
系统时钟 (SYSCLK)是核心。因为片内太多模块的时钟信号都是从这里“散发”出去的,它散发到需要时钟信号的每一个模块形成新的时钟信号以驱动这些模块工作。但SYSCLK并非就是由某个时钟源头发出时钟信号,它的来由可以来自“几大”时钟信号,再追踪这“几大”时钟信号就能够追踪到时钟源了。追踪到时钟源就差不多了,因为那已经是硬件成分了。可以在PCB原理图的时候仔细研究一下子,将其原理搞清楚。
Figure1:时钟树
Figure 1时钟树的SYSCLK时钟信号在深红色方框处。它的去处分为如图所示的1,2两个方向。假设SYSCLK已经存在(已经有了时钟信号)。
暂且不管I2Sx模块具体是什么。因为这里我只关心SYSCLK时钟去处为I2SxCLK,从图中可以看出SYSCLK直接作为了I2SxCLK。当然作为外设,在配置好SYSCLK的情况下,还需要使能I2SxCLK时钟后才能使用I2SxCLK时钟信号。
SYSCLK最大允许的频率为72MHz。
SYSCLK的来源在时钟树中用粉红色方框标记。从图中可以看出,SYSCLK来源于时钟源(除PLL):
时钟初始化配置主要配置“SYSCLK的来源(HIS OrHSE Or PLL(HIS/2 Or HSE))”以及“各模块时钟源频率(HCLK[AHB分频], PCLKx)”。
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; } |
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; } } |
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; } |
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) { } } |