大家好,我是老耿,高职青椒一枚,一直从事单片机、嵌入式、物联网等课程的教学。 对于高职的学生层次,同行应该都懂的,老师在课堂上教学几乎是没什么成就感的。正因如此,才有了借助 CSDN 平台寻求认同感和成就感 的想法。在这里,我准备陆续把自己花了很多心思的教学设计分享出来,主要面向广大师生朋友,单片机老鸟就略过吧。欢迎点赞+关注,各位的支持是本人持续输出的动力,多谢多谢!
众所周知,衡量一款处理器的性能,最重要的一个指标就是主频,对于STM32来说也不例外。主频的背后其实是一套复杂的时钟系统,而这套系统关乎所有外设的工作。因此,在我们继续深入学习之前,有必要了解STM32时钟系统的脉络,进而才能理解所有跟时间有关的机制和配置。其实,一开始我们用到的delay延时功能就是通过配置时钟系统实现的,现在是时候对它一探究竟了。
【学习目标】
- 认识STM32的系统时钟树,理解时钟的产生过程;
- 了解系统时钟配置函数的实现脉络;
- 知道SysTick定时器的地位和作用;
- 了解SysTick寄存器的功能;
- 理解延时函数的实现原理
与STM32时钟有关的信息量不小,为了不让篇幅太长,本章打算分两个部分来讲解,本文是第一部分。
一、STM32的时钟系统
时钟系统是CPU的脉搏,就像人的心跳一样,其重要性就不言而喻了。STM32的时钟系统比较复杂,不像51单片机一个系统时钟就可以解决一切。于是有人要问,采用一个系统时钟不是很简单吗?为什么STM32要有多个时钟源呢?首先,STM32的外设非常多,但并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及RTC只需要几十kHz的时钟即可。其次,同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的MCU一般都是采取多时钟源的方法来解决这些问题。
下面,让我们来看看STM32的时钟系统图吧,如图1所示。这张图是从《STM32中文参考手册》中摘过来的,初学者一看可能会无所适从,不知该从哪儿入手。别慌,我们在图上标注了一些序号(Ⅰ~Ⅴ、A~E)和一条主线(①~⑦),大家结合下面的分析,先好好领悟这些带有标注的地方,剩下的地方便可以触类旁通啦。
图1 STM32F103系列时钟框图
1.1 时钟源
在STM32中,有五个时钟源:HSI、HSE、LSI、LSE、PLL,分别对应图1中的Ⅰ~Ⅴ。从时钟频率来分可以分为高速时钟源和低速时钟源,HIS、HSE和PLL是高速时钟,LSI和LSE是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中HSE和LSE是外部时钟源,其他的是内部时钟源。下面我们依次来看每个时钟源的特点:
I. HSI是高速内部时钟,采用RC振荡器,频率为8MHz。
II. HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为 4MHz~16MHz,我们的开发板接的是12M的晶振。
III. LSI是低速内部时钟,RC振荡器,频率为40kHz。独立看门狗的时钟源只能是LSI,同时LSI还可以作为RTC的时钟源。
IV. LSE是低速外部时钟,接频率为32.768kHz的石英晶体,这个主要是RTC的时钟源,我们的开发板上没接这个晶振。
V. PLL为锁相环倍频输出,其时钟输入源可选HSI/2、HSE或HSE/2。倍频可选2~16倍,但输出频率最大不超过72MHz。
1.2 时钟分配
上面我们简要概括了STM32的时钟源,那么这5个时钟源是怎么给各个外设以及系统提供时钟的呢?这里我们按照图1中A ~E的顺序进行讲解。
A. MCO是STM32的一个时钟输出IO(PA8),它可以选择一个时钟信号输出,可以是PLL输出的2分频、HSI、HSE或系统时钟。这个时钟可以用来给外部其他系统提供时钟源。
B. 这里是RTC时钟源,从图上可以看出,RTC的时钟源可以选择LSI或LSE,以及HSE的128分频。
C. USB的时钟,来自PLL时钟源。STM32中有一个全速功能的USB模块,其串行接口引擎需要一个频率为48MHz的时钟源。该时钟源只能从PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用USB模块时,PLL必须使能,并且时钟频率配置为48MHz或72MHz。
D. STM32的系统时钟SYSCLK,它是供STM32中绝大部分部件工作的时钟源。系统时钟可选择为PLL输出、HSI或HSE。系统时钟最大频率为 72MHz,当然你也可以超频,不过一般情况为了系统稳定性是没有必要冒风险去超频的。
E. 指其他所有外设了。从时钟图上可以看出,其他所有外设的时钟最终来源都是 SYSCLK。SYSCLK通过AHB分频器分频后送给各模块使用。这些模块包括:
-
AHB总线、内核、内存和DMA使用的HCLK时钟。
-
通过8分频后送给Cortex的系统定时器时钟,也就是SysTick了,它也是下一节要重点讲解的内容,延时的产生就跟它有关。
-
直接送给Cortex的空闲运行时钟FCLK。
-
送给APB1分频器。APB1分频器有两路输出:一路供APB1外设使用 (PCLK1,最大频率36MHz),另一路送给定时器2~7使用。
-
送给APB2分频器。APB2分频器也有两路输出:一路供APB2外设使用 (PCLK2,最大频率72MHz),另一路送给定时器1和8使用。
关于APB1和APB2总线的区别,主要在于挂载的外设不同,这一点我们在之前的章节中已经详细阐述过了,大家可以再翻看一遍,加深理解,这里就不再赘述了。
在以上的时钟输出中,有很多是带使能控制的,例如AHB总线时钟、内核时钟、各种APB1外设、APB2外设等等。当需要使用某模块时,记得一定要先使能对应的时钟,我们在初始化GPIO时使用了 RCC_APB2PeriphClockCmd() 库函数,起的就是这个作用。
1.3 时钟主线
现在来看图1中序号①~⑦的这条路径,我们称之为时钟主线。理解了这条主线,也就明白了STM32的系统时钟和各总线时钟是如何一步步确定的。而图中的其他路径,也就迎刃而解了。
我们把区域①单独拎出来分析,如图2所示。来自于开发板上12MHz晶振的HSE信号,会到达一个梯形的模块,这其实是一个选择器,能够从多路输入信号中选择一路通过。可以看到,这个选择器的输入信号有两路,一路是HSE,另一路则是HSE/2。究竟选哪一路由PLLXTPRE信号决定,而该信号是时钟配置寄存器CFGR的第17位,固件库对其默认的配置是0。因此,经过区域①,得到了12MHz输出信号,该信号再进入区域②。
图2 时钟主线之HSE和PLL
时钟线、对应寄存器及其功能在《STM32中文参考手册》6.3.2小节中有详细描述,上图只是把其中一部分整理后放在了一起,就是希望大家学会看图的方法,理解其中的来龙去脉。后面②~⑦的分析方法都是类似的,这里我们汇总如下:
● 区域②是PLL时钟源选择,默认选择的是HSE(12MHz)。
● 区域③是一个锁相环信号倍频器,它可以把来自PPLSRC的信号进行倍频处理,可选2倍~16倍。我们在配置工程模板的时候将其设置为了 6 倍频,这样就得到了12MHz*6 = 72MHz的PLLCLK,72MHz也是ST官方推荐的稳定运行时钟。这里也可设置更大的倍频系数得到更高的频率,但不建议超频。
● 区域④是系统时钟来源选择,可选HIS(内部高速时钟)、PLLCLK(锁相环时钟)、HSE(外部高速时钟),默认选择的是PLLCLK,这样得到的系统时钟SYSCLK与PLLCLK一致,也为72MHz。
● 区域⑤是AHB预分频器,系统时钟 SYSCLK经过这里分频后,得到AHB总线时钟HCLK,分频因子可以是1/2/4/8/16/64/128/256/512,默认配置为1,即HCLK = SYSCLK = 72MHz。STM32片上大部分外设的时钟都是经过HCLK 分频得到的。
● 区域⑥是APB2预分频器,HCLK经过这里分频后,得到APB2总线时钟PCLK2,分频因子可以是1/2/4/8/16,默认配置为1,即PCLK2 = HCLK = 72MHz。PCLK2属于高速的总线时钟,片上高速的外设就挂载在这条总线上,比如GPIO、USART1、TIM1/8等。
● 区域⑦是APB1预分频器,HCLK经过这里分频后,得到APB1总线时钟PCLK1,分频因子可以是1、2、4、8、16,默认配置为2,即PCLK1 = HCLK/2 = 36MHz。PCLK1属于低速的总线时钟,片上低速的外设就挂载在这条总线上,比如USART2/3/4/5、SPI2/3、I2C1/2等。
1.4 溯源系统时钟配置
上面我们分析了时钟主线,这条主线确定了72MHz的系统时钟SYSCLK、72MHz的APB2总线时钟、36MHz的APB1总线时钟,其中所有的设置都通过时钟配置寄存器完成的。在实际开发中,我们并不需要亲自配置寄存器,固件库已经写好了配置函数,并且会在上电初始化中完成配置。接下来,我们一起追溯一下固件库里是如何实现系统时间配置的。
如图3所示,STM32上电或复位后,先执行 startup_stm32f10x_hd.s 这个启动文件,该文件调用了 SystemInit() 函数(图中①)。SystemInit() 这个函数定义在 system_stm32f10x.c 中(图中②),可以看到了它又调用了 SetSysClock() 函数。继续进入 SetSysClock() 函数(图中③),是一堆条件编译结构,取决于定义了哪个宏。从图中④我们可以看到, SYSCLK_FREQ_72MHz 这个宏被保留,所以 SetSysClockTo72() 这个函数(图中⑤)将被执行,而该函数内部就是按照上述时钟主线去配置寄存器的过程,源码比较复杂,感兴趣的读者可以去阅读和研究。
图3 系统时钟函数溯源
(第一部分完,共两部分)