STM32F4 时钟树概述
STM32F4 相对于 STM32F1 来说,时钟部分复杂了很多, STM32F4 的时钟配置,我们提供两个函数: Sys_Clock_Set 和 Stm32_Clock_Init。其中 Sys_Clock_Set 是核心的系统时钟配置函
数,由 Stm32_Clock_Init 调用,实现对系统时钟的配置。外部程序,一般调用 Stm32_Clock_Init函数来配置时钟。
sys文件夹中
在 STM32F4 中,有 5 个最重要的时钟源,为 HSI、 HSE、 LSI、 LSE、 PLL。 其中 PLL 实际是分为两个时钟源,分别为主 PLL 和专用 PLL。 从时钟频率来分可以分为高速时钟源和低速
时钟源,在这 5 个中 HSI, HSE 以及 PLL 是高速时钟, LSI 和 LSE 是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中 HSE 和
LSE 是外部时钟源,其他的是内部时钟源。
①、 LSI 是低速内部时钟, RC 振荡器,频率为 32kHz 左右。 供独立看门狗和自动唤醒单元使用。
②、 LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。 这个主要是 RTC 的时钟源。
③、 HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为 4MHz~26MHz。
我们的开发板接的是 8M 的晶振。 HSE 也可以直接做为系统时钟或者 PLL 输入。
④、 HSI 是高速内部时钟, RC 振荡器, 频率为 16MHz。 可以直接作为系统时钟或者用作 PLL
输入。
⑤、 PLL 为锁相环倍频输出。 STM32F4 有两个 PLL:
1) 主 PLL(PLL)由 HSE 或者 HSI 提供时钟信号,并具有两个不同的输出时钟。
第一个输出 PLLP 用于生成高速的系统时钟(最高 168MHz)
第二个输出 PLLQ 用于生成 USB OTG FS 的时钟(48MHz),随机数发生器的时钟和 SDIO
时钟。
2) 专用 PLL(PLLI2S)用于生成精确时钟,从而在 I2S 接口实现高品质音频性能。
STM32F4 的时钟树图(非常重要)
上图从左往右看,就是整个 STM32F4 的时钟走向。这里,我们挑选出 9 个重要的地方进行介绍(图 5.2.2.1 中标出的①~⑨)。
① 这是进入 PLL 之前的一个时钟分频系数(M),取值范围是: 2~63,一般取 8。注意,这个分频系数,对主 PLL 和 PLLI2S 都有效。
② 这是 STM32F4 的主 PLL,该部分控制 STM32F4 的主频率(PLLCLK)和 USB/SDIO/随机数发生器等外设的频率(PLL48CK)。其中, N 是主 PLL vco 的倍频系数,其取值
范围是: 64~432; P 是系统时钟的主 PLL 分频系数,其取值范围是: 2、 4、 6 和 8(仅
限这四个值); Q 是 USB/SDIO/随机数产生器等的主 PLL 分频系数,其取值范围是:
2~15; R 没用到。
③ 这是 STM32F4 I2S 部分的 PLL,该部分主要用于设置 STM32F4 I2S 内部输入时钟频率。其中, N 是用于 PLLI2S vco 的倍频系数,其取值范围是: 192~432; R 是 I2S 时钟的分
频系数,其取值范围是: 2~7; P 和 Q 没用到。
④ 这是 PLL 之后的系统主时钟(PLLCLK), STM32F4 的主频最高是 168Mhz,所以我们一般设置 PLLCLK 为 168Mhz(M=8,N=336,P=2),通过 SW 选择 SYSCLK=PLLCLK
即可得到 168Mhz 的系统运行频率。
⑤ 这是 PLL 之后的 USB/SDIO/随机数发生器时钟频率,由于 USB 必须是 48Mhz 才可以正常工作,所以这个频率一般设置为 48Mhz(M=8,N=336,Q=7)。
⑥ 是 I2S 的时钟,通过 I2SSRC 选择内部 PLLI2SCLK 还是外部 I2SCKIN 作为时钟。 探索者 STM32F4 开发板使用的是内部 PLLI2SCLK。
⑦ 这是 Cortex 系统定时器,也就是 SYSTICK 的时钟。上图清楚的表明 SYSTICK 的来源是 AHB 分频后再 8 分频(这个 8 分频是可以设置的,即 8 分频,或者不分频,一般使
用 8 分频), 我们一般设置 AHB 不分频,则 SYSTICK 的频率为: 168M/8=21Mhz。前面介绍的延时函数,就是基于 SYSTICK 来实现的。
⑧ 这里是 STM32F4 很多外设的时钟来源,即两个总线桥: APB1 和 APB2,其中 APB1是低速总线(最高 42Mhz), APB2 是高速总线(最高 84Mhz)。另外定时器部分,如
果所在总线(APB1/APB2)的分频系数为 1,那么就不倍频,如果不为 1(比如 2/4/8/16),那么就会 2 倍频(Fabpx*2)后,作为定时器时钟输入。
⑨ 这是 STM32F4 内部以太网 MAC 时钟的来源。对于 MII 接口来说,必须向外部 PHY芯片提供 25Mhz 的时钟,这个时钟,可以由 PHY 芯片外接晶振,或者使用 STM32F4
的 MCO 输出来提供。然后, PHY 芯片再给 STM32F4 提供 ETH_MII_TX_CLK 和ETH_MII_RX_CLK 时钟。对于 RMII 接口来说,外部必须提供 50Mhz 的时钟驱动 PHY
和 STM32F4 的 ETH_RMII_REF_CLK,这个 50Mhz 时钟可以来自 PHY、有源晶振或者 STM32F4 的 MCO。 我们的开发板使用的是 RMII 接口,使用 PHY 芯片提供 50Mhz
时钟驱动 STM32F4 的 ETH_RMII_REF_CLK。
关于时钟的详细介绍,在《STM32F4xx 中文参考手册》第 6.2 节(106~113 页)有详细介绍。有不明白的地方,可以对照手册仔细研究。 最后,提醒下大家, STM32F4 默认的情况下
(比如串口 IAP 时或未初始化时钟时),使用的是内部 16M 的 HSI 作为时钟的,所以不需要外部晶振也可以下载和运行代码的。
从上图可以看出 STM32F4 的时钟设计的比较复杂,各个时钟基本都是可控的,任何外设都有对应的时钟控制开关,这样的设计,对降低功耗是非常有用的,不用的外设不开启时钟,
就可以大大降低其功耗。
下面开始 Sys_Clock_Set 函数的介绍, 该函数用于配置 STM32F4 的时钟,包括系统主时钟、USB/SDIO/随机数发生器时钟、 APB1 和 APB2 时钟等。 该函数代码如下:
//时钟设置函数 //Fvco=Fs*(plln/pllm); //Fsys=Fvco/pllp=Fs*(plln/(pllm*pllp)); //Fusb=Fvco/pllq=Fs*(plln/(pllm*pllq)); //Fvco:VCO 频率 //Fsys:系统时钟频率 //Fusb:USB,SDIO,RNG 等的时钟频率 //Fs:PLL 输入时钟频率,可以是 HSI,HSE 等. //plln:主 PLL 倍频系数(PLL 倍频),取值范围:64~432. //pllm:主 PLL 和音频 PLL 分频系数(PLL 之前的分频),取值范围:2~63. //pllp:系统时钟的主 PLL 分频系数(PLL 之后的分频),取值范围:2,4,6,8.(仅限这 4 个值!) //pllq:USB/SDIO/随机数产生器等的主 PLL 分频系数(PLL 之后的分频),取值范围:2~15. //外部晶振为 8M 的时候,推荐值:plln=336,pllm=8,pllp=2,pllq=7. //得到:Fvco=8*(336/8)=336Mhz // Fsys=336/2=168Mhz // Fusb=336/7=48Mhz //返回值:0,成功;1,失败。 u8 Sys_Clock_Set(u32 plln,u32 pllm,u32 pllp,u32 pllq) { u16 retry=0; u8 status=0; RCC->CR|=1<<16; //HSE 开启 while(((RCC->CR&(1<<17))==0)&&(retry<0X1FFF))retry++;//等待 HSE RDY if(retry==0X1FFF)status=1; //HSE 无法就绪 else { RCC->APB1ENR|=1<<28; //电源接口时钟使能 PWR->CR|=3<<14; //高性能模式,时钟可到 168Mhz RCC->CFGR|=(0<<4)|(5<<10)|(4<<13);//HCLK 不分频;APB1 4 分频;APB2 2 分频. RCC->CR&=~(1<<24); //关闭主 PLL RCC->PLLCFGR=pllm|(plln<<6)|(((pllp>>1)-1)<<16)|(pllq<<24)|(1<<22); //配置主 PLL,PLL 时钟源来自 HSE RCC->CR|=1<<24; //打开主 PLL while((RCC->CR&(1<<25))==0);//等待 PLL 准备好 FLASH->ACR|=1<<8; //指令预取使能. FLASH->ACR|=1<<9; //指令 cache 使能. FLASH->ACR|=1<<10; //数据 cache 使能. FLASH->ACR|=5<<0; //5 个 CPU 等待周期. RCC->CFGR&=~(3<<0); //清零 RCC->CFGR|=2<<0; //选择主 PLL 作为系统时钟 while((RCC->CFGR&(3<<2))!=(2<<2));//等待主 PLL 作为系统时钟成功. } return status; }
在 Sys_Clock_Set 函数中,我们设置了 APB1 为 4 分频, APB2 为 2 分频, HCLK 不分频,同时选择 PLLCLK 作为系统时钟。该函数有 4 个参数,具体意义和计算方法,见函数前面的说
明。 一般我们推荐设置为: Sys_Clock_Set(336,8,2,7),即可设置 STM32F4 运行在 168Mhz 的频率下, APB1 为 42Mhz, APB2 为 84Mhz, USB/SDIO/随机数发生器时钟为 48Mhz。
以上代码中, RCC 和 FLASH 都是 MDK 定义的一个结构体,包含 RCC/FLASH 相关的寄存器组。其寄存器名与《STM32F4xx 中文参考手册》 里面定义的寄存器名字是一摸一样的,所
以在你不明白某个寄存器干什么用的时候,可以到《STM32F4xx 中文参考手册》里面查找一下,你就可以迅速查到这个寄存器的作用以及每个位所代表的意思。 特别注意,由于 FLASH 速度
远远跟不上 CPU 的运行频率,所以这里我们设置了 FLASH 的等待周期为 5, 很明显, FLASH会大大拖慢程序的运行,不过 STM32F4 有自适实时存储器加速器(ART),通过这个加速器,可
以让 STM32F4 获得相当于 0 FLASH 等待周期的运行效果。关于 STM32F4 的 FLASH 以及 ART等的介绍,请大家参考《STM32F4xx 中文参考手册》第 3.3 节(59 页开始)。
接下来,我们再看下 Stm32_Clock_Init 函数,该函数代码如下:
//系统时钟初始化函数 //plln:主 PLL 倍频系数(PLL 倍频),取值范围:64~432. //pllm:主 PLL 和音频 PLL 分频系数(PLL 之前的分频),取值范围:2~63. //pllp:系统时钟的主 PLL 分频系数(PLL 之后的分频),取值范围:2,4,6,8.(仅限这 4 个值!) //pllq:USB/SDIO/随机数产生器等的主 PLL 分频系数(PLL 之后的分频),取值范围:2~15. void Stm32_Clock_Init(u32 plln,u32 pllm,u32 pllp,u32 pllq) { RCC->CR|=0x00000001; //设置 HISON,开启内部高速 RC 振荡 RCC->CFGR=0x00000000; //CFGR 清零 RCC->CR&=0xFEF6FFFF; //HSEON,CSSON,PLLON 清零 RCC->PLLCFGR=0x24003010; //PLLCFGR 恢复复位值 RCC->CR&=~(1<<18); //HSEBYP 清零,外部晶振不旁路 RCC->CIR=0x00000000; //禁止 RCC 时钟中断 Sys_Clock_Set(plln,pllm,pllp,pllq);//设置时钟 //配置向量表 #ifdef VECT_TAB_RAM MY_NVIC_SetVectorTable(1<<29,0x0); #else MY_NVIC_SetVectorTable(0,0x0); #endif }
该函数主要进行了时钟配置前的一些设置工作,然后通过调用 Sys_Clock_Set 函数,实现对 STM32F4 的时钟配置。最后,根据代码运行的位置( FLASH or SRAM), 调用函数
MY_NVIC_SetVectorTable 进行中断向量表偏移设置。MY_NVIC_SetVectorTable 函数的代码如下:
//设置向量表偏移地址 //NVIC_VectTab:基址 //Offset:偏移量 void MY_NVIC_SetVectorTable(u32 NVIC_VectTab,u32 Offset) { SCB->VTOR=NVIC_VectTab|(Offset&(u32)0xFFFFFE00); //设置 NVIC 的向量表偏移寄存器,VTOR 低 9 位保留,即[8:0]保留。 }
该函数是用来配置中断向量表基址和偏移量,决定是在那个区域。当在 RAM 中调试代码 的时候,需要把中断向量表放到 RAM 里面, 这就需要通过这个函数来配置。关于向量表的详
细介绍请参考《Cortex M3 与 M4 权威指南》第 4.5.3 节(117 页)或者《Cortex M3 权威指南》第七章,第 113 页的向量表一章。 关于 SCB->VTOR 寄存器,请参考《STM32F3 与 F4 系列 Cortex
M4 内核编程手册》第 4.4.4 节(212 页),有详细描述。