学习系统时钟之前先问大家一个问题? + h% f$ y0 j# k0 d- ?2 B7 { 这是一个跑马灯的程序,为什么我们没有在主函数中配置系统时钟,却可以正常的执行流水灯代码呢? 我想大多数人都会说,已经配置了系统时钟,不需要在主函数中调用了。如果你回答不上来或者不清楚系统时钟是怎样配置的就好好看下文了。至此引出系统时钟的概念。 - @3 p) l8 @6 F( E$ M5 Y
我这里以STM32F429为例,其他的芯片的原理都是一样的。STM32F429主板上有两个无源晶振。第一个是主晶振,频率为8MHz,经过内部PLL倍频到168MHz,作为CPU内核的时钟使用。第二个晶振频率为32.768kHz,用于内部RTC电路。第二个晶振不是必须的,当需要使用STM32内部RTC时钟功能时才需要安装32.768k晶振。 CPU的内核时钟可以高达180MHz,但并不是说我们就必须要设置为最高180MHZ。你可以设置为小于等于180MHZ都可以。但是CPU内部的外设,比如ADC,GPIO,部分TIM是达不到这么高的速度的。CPU的时钟管理电路有好几个分频器,不同速度的外设给定不同的时钟。 主时钟选择的是HSE高速外部时钟。 系统时钟 SYSCLK =PLLCLK= 168MHz AHB总线时钟HCLK = SYSCLK=168MHz0 P) @% R( @, a& h) J& H APB1外设时钟PCLK1 = HCLK /4=42MHz5 \% ?4 G7 ~( w5 _ APB2外设时钟PCLK2 = HCLK /2=84MHz ; C! u1 P$ r5 N6 J* S2 D 时钟树单纯讲理论的话会比较枯燥,如果选取一条主线,并辅以代码,先主后次讲解的话会很容易,而且记忆还更深刻。我们这里选取库函数时钟系统时钟函数SetSysClock()这个函数在system_stm32f4xx.c中,以这个函数的编写流程来讲解时钟树,这个函数也是我们用库的时候默认的系统时钟设置函数。该函数的功能是利用HSE把时钟设置为: 有人会问F4不是主频:系统时钟 SYSCLK =PLLCLK= 168MHz8 b# j- |+ g7 g7 z3 m p# C AHB总线时钟HCLK = SYSCLK=168MHz APB1外设时钟PCLK1 = HCLK /4=42MHz' i+ _/ I2 @( J* ~6 t3 b2 b APB2外设时钟PCLK2 = HCLK /2=84MHz SYSCLK=180M,APB1=90M,APB2=180M吗? 确实你看到的大部分程序中F4都是这样配置的,但是并不代表必须这样配置,你可以想配置多少就是多少。只要不超多系统最高时钟的限制180MHZ。当然又会有人说了,不是可以超频吗?是可以超频但是99%都不会超频或者去用超频。下面我们来看时钟树: 8 P. @: e4 Z- w; j4 i" @5 b
3 j! g3 |9 ^5 a8 ^" n9 L9 J+ N ①LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗和自动唤醒单元使用。②LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。③HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为 4MHz~26MHz。当使用有源晶振时,时钟从 OSC_IN 引脚进入,OSC_OUT 引脚悬空,当选用无源晶振时,时钟从 OSC_IN 和 OSC_OUT进入,并且要配谐振电容。HSE 我们使用 8M的无源晶振。如果我们使用 HSE或者 HSE经过 PLL倍频之后的时钟作为系统时钟 SYSCLK。野火和正点原子的F429开发板接的是 25M 的晶振。安富莱的开发板是8M外接晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。④HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作 PLL输入。⑤PLL 为锁相环倍频输出。STM32F4 有三个 PLL:主 PLL(PLL)由 HSE 或者 HSI 提供时钟信号,并具有两个不同的输出时钟。另外两个 PLL(PLLI2S)和 PLL(PLLSAI)用于生成精确时钟,在 I2S 和 SAI1 上实现高品质音频性能。很少用先不看在 STM32F429 中,有 5 个最重要的时钟源,按照上图的顺序为LSI、LSE、 HSE、HSL、PLL。H开头的是高速时钟、L开头的是低速时钟。其中 PLL分为三个时钟源,分别为主 PLL 和 I2S 部分专用 PLLI2S 和 SAI 部分专用 PLLSAI。其中 HSE 和 LSE 是外部时钟源就是从外部通过接晶振的方式获取时钟源(就是需要外界晶振),其他的是内部时钟源(不需要外接晶振)。下面我们看看 STM32F429 的这 5 个时钟源,我们讲解顺序是按图中红圈标示的顺序: 这里我们着重看看主 PLL 时钟第一个高速时钟输出 PLLP 的计算方法。是主 PLL 的时钟图。 # j4 |$ f- j- F1 j" K: t1 P( H6 F' v
主 PLL 时钟的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器出来之后还需要经过一个分频系数为 P(第一个输出 PLLP)或者 Q(第二个输出 PLLQ)的分频器分频之后,最后才生成最终的主 PLL 时钟。例如我们的外部晶振选择 8MHz。同时我们设置相应的分频器 M=8,倍频器倍频系数 N=336,分频器分频系数 P=2,那么主 PLL 生成的第一个输出高速时钟 PLLP 为:8MHZ/8*336/2=168 PLL=8MHz * N/ (M*P)=8MHz* 336 /(8*2) = 168MHz) c4 n( {! W: z& M9 Z如果我们选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为 168MHz。看下面的程序也是这样的,说明计算无误。 " I9 H4 W5 F1 \! M
具体的实现请看下图: - l) C/ k8 y0 g# V 单纯地讲解系统的时钟框图的,请看具体的数据手册,我们这里只是简单的介绍这个系统时钟是怎么来的。 比如告诉你,我现在的外接晶振是25M。但是我想要配置成系统时钟是180MHZ的你会吗?你会自己去配置吗?看到这里我想你已经会配置了。比如我这设置PLL_M=25、PLL_N=360、PLL_P=2。 那么我的系统时钟SYSCLK就是:
如果我们选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为 180MHz。HCLK=SYSCLK=PLLCLK=180M,只需要设置AHB Prescaler分频因子为1。PCLK1=HCLK/2=90M,只需要设置AHB1 Prescaler分频因子为2。PCLK2=HCLK=180M,只需要设置AHB2 Prescaler分频因子为1就可以了。 3 y3 H. p+ P! c# G 代码分析至此我们已经大概的了解到了系统时钟是怎么一回事。现在我们来分析具体的代码。不然只分析没代码,学起来效果不好。 还记得我在前面我提到的问题吗?其实在按下单片机上面的复位按键之后,系统会这些首先执行启动代码的里面的程序。 7 m3 X" x [8 B0 O& W2 M! i 我们打开startup_stm32f429_439xx.s文件。当然我这里以F4为例,其他的也都是一样的。会看到在系统复位时候在执行main函数之前先执行了SystemInit函数。那我们现在看看SystemInit函数中都有啥。鼠标放在SystemInit函数上面右键选择Go To Defintion of SystemInit E& Z! {# R& x9 u0 {' Z, c5 p3 U
( `+ m6 R' m2 P9 ^2 d 就会跳转到system_stm32f4xx.c文件中的SystemInit函数上来了。 下面来一行一行分析: - o2 s% |' J& V/ j( ? 5 u: W+ g" A; U. Y. s 第592行:配置RCC寄存器的时钟控制RCC_CR寄存器第0位为1。打开HSI振荡器。 RCC->CR |= (uint32_t)0x00000001; : A# q& F! a% K& c 第595行:复位RCC寄存器的时钟配置CFGR寄存器,全为0。RCC->CFGR = 0x00000000; 第598行:复位RCC寄存器的时钟控制CR寄存器的HSEON、CSSON、PLLON为0。RCC->CR &= (uint32_t)0xFEF6FFFF; 第601行:复位RCC寄存器的配置PLLCFGR寄存器。具体的操作可以看下面对应的寄存器位值。RCC->PLLCFGR = 0x24003010; , X2 x+ z2 b1 P- ?' @9 Y. d& W 第604行:复位RCC寄存器的CR寄存器。RCC->CR &=(uint32_t)0xFFFBFFFF;第604行:失能RCC寄存器的CIR寄存器,关闭所有中断。RCC->CIR = 0x00000000;这两行就不一一列举了。' f' }9 ^! n" j: a 第615行:SetSysClock();这个函数主要是配置AHB、AHB1、AHB2的时钟频率。鼠标放在SetSysClock上面右键选择Go To Defintion of SetSysClock。 7 `# y' `* i6 ?& Q7 g: v : z" q0 w0 p7 ?; y6 i6 C9 z/ @; P9 | 到这里系统时钟就全部配置完成了。在这两个函数中有很多的宏定义,在看代码的时候一定要注意是操作了哪一位,在对应为去找STM32fx的中文参考手册就容易理解多了。6 F$ L& B* D; g# p7 I% R % X) ~* t# E) X8 ?3 n& {/ B
最后来一个总结,在我们按下开发板上面的复位按键之后,系统从启动文件中先执行SystemInit函数,在SystemInit函数中配置了PLL锁相环的各个分频因子,得到了系统时钟。在SystemInit函数中又调用了SetSysClock()函数,来配置AHB、AHB1、AHB2的时钟频率。 现在我们系统时钟配置你会了吗?需要注意的是,我们写在程序的时候一般不需要配置时钟,因为我们一般用的是被人写好的工程模板,直接拿来用就可以了。但是还是需要了解,时钟树的知识比较难以理解,相信大家看上面的内容都会配置了吧! 转载自:果果小师弟 / I' H: K1 ]! N4 N0 U |
定时器剩余通道是否可以做PWM输出呢?
基于STM32双定时器+ADC+DMA实战经验分享
基于STM32的定时器触发ADC时可能遇到的情形
【NUCLEO-U545RE-Q评测】5. 基本计时器
基于STM32的定时器不按设定超时产生中断
基于stm32用两个16位定时器级联成32位定时器经验分享
基于STM32利用TIMER事件和栈帧体验中断响应经验分享
基于STM32的Timer 结合 DMA 2D 通道实现不同波形输出
基于STM32软件定时器+中断方式模拟PWM经验分享
基于STM32高精度定时器中single-shot计数模式不工作