01、什么是时钟
参考视频:
【STM32】超清晰STM32时钟树动画讲解
简单来说,时钟是单片机的脉搏,是系统工作的同步节拍。单片机上至CPU,下至总线外设,它们工作时序的配合,都需要一个同步的时钟信号来统一指挥。时钟信号是周期性的脉冲信号。
举个例子来具体说明时钟的作用。
cpu内部由众多的逻辑电路组成,以下面一简单的逻辑电路为例。
A、B两线通过一个与门和异或门与寄存器相连,通过控制A、B两线的高低电平来控制寄存器的值。
在图中时刻,A、B两线为高电平,寄存器写入零。
假如受到外部影响(如GPIO口输入变化),B线变为低电平,那么此逻辑电路的理想情况如下。
但实际情况却有些出入,光速传播的电信号,我们大可以忽略不计其传播延时,整个B线瞬间就变为低电平,但逻辑门内部涉及mos管的充放电,需要一定的运算时间,虽然这个时间也很短,但和光速相比就是一段较长的时间。在这段时间内,与门的输出还未改变,仍然是输出高电平,而B线已经变为了低电平,这就导致异或门的输入信号为一高一低电平,从而输出高电平,导致寄存器被写入1。具体见下图。
直到与门完成了运算,异或门才会输出低电平。在这一过程中中·,寄存器出现了由于门电路运算延时而产生的错误的状态,这一状态虽然短暂,但依旧会导致依赖此寄存器的程序与其他逻辑电路产生更严重的错误。
为了解决这个问题,在寄存器前有一个边沿触发器。
此边沿触发器有一个输入端,一个输出端,以及一个控制端,其特性是当控制端出现一个由低电平变为高电平的上升沿时,输出端会变为输入端的电平,其他时候,任由输入端如何变化,输出端电平也不会改变。
因此,有了这边沿触发器后,我们为其控制端接入一个一定频率的方波信号,方波的一个周期内,门电路执行运算而产生的任何副作用都不会影响到寄存器的值,而这个好似“脉搏”的方波信号,就是我们所说的时钟信号。它在单片机中由“心脏”时钟源产生,又通过“动脉”时钟树传播到整个芯片中。
除开上述微观角度,宏观来讲,串口通信的波特率,定时器计时,IIC、SPI通信的时钟线,ADC的采样间隔,这些也都需要一个精确的方波信号,即时钟,来为其确定时序,告知这些外设时间的流逝。
02、时钟源
时钟源指能够主动发出时钟信号的元器件,STM32内部共有下面四个时钟源
HSI 内部高速时钟
时钟信号由内部RC震荡电路提供,时钟频率为8MHz,但是这个时钟频率会随着温度产生漂移,很不稳定,所以一般不使用此时钟信号。
HSE 外部高速时钟
时钟信号由外部晶振提供,时钟频率一般在4-16MHz,是经常会用到的时钟源。
这里的外部晶振可以是有源晶振,也可以是无源晶振,它们的区别在于与STM32的 连接方式,以及需不需要谐振电容。
LSI 内部低速时钟
时钟信号由内部RC振荡电路提供,时钟频率一般为40KHz,这个信号一般用于独立看门狗时钟。
LSE 外部低速时钟
时钟信号由外部晶振提供,时钟频率一般为32.768KHz,这个信号一般用于RTC实时时钟
03、时钟树
a、简述
简单来说时钟树是一个用于时钟信号传递的网状结构,通过时钟树,可以将时钟源的时钟信号传递到所需要的外设处,STM32有很多外设器件,每个器件的时钟信号不一样,所以要将一个固定的信号频率进行倍频/分频处理,达到每个外设需要的频率。时钟信号的分频就像树的分支一样,因此称为时钟树。
b、时钟树框架
不同芯片内部时钟树有些许差异,但大体框架如下图所示
c、框架分析
AHB部分
单片机内部有一个叫AHB(Advanced High Performance Bus)的总线结构,中文名为先进高性能总线,上面挂载着内存,DMA,处理器,以及各种外设,它们通过此总线进行数据的通信,而HCLK(High performance Bus Clock 高性能总线时钟)就是此总线上的时钟信号。HCLK直接连接了处理器内核,内存以及DMA,为其带来了时钟信号,因此我们可以得到时钟树下这样一条“树枝”。
这条树枝上的组件与HCLK直连,其时钟的频率与HCLK相同。
除此之外,内核内部有一个SysTick(System Tick 系统滴答定时器),用于给程序提供时间标准(详见时基部分内容)。与前文的直连不同,Systick与HCLK间还有一个分频器,分频器简单而言,就是为频率“做除法”。
APB部分
接下来,再介绍一下外设的时钟。
在STM32芯片中,像GPIO、串口、IIC等外设,并没有直接连接在AHB总线上,而是分门别类地连接到了两个APB总线上。APB全称Advanced Peripheral Bus 中文名先进外设总线,顾名思义,此总线是专门用于连接外设的。这两条总线通过一个桥接器连接在AHB上,以此与AHB上挂载的组件进行通信。AHB上的时钟信号由HCLK通过预分频器处理得到,这个时钟信号直接提供给了APB总线上除了定时器以外的所有外设。
而定时器由于需要较高的频率来保证计数的精度,故来自HCLK的时钟信号分频后,在经过一个倍频器传递给定时器。
此倍频器有*1与*2两种方式,但不能手动设置,而是取决于前面的APB预分频器(Prescaler),当预分频器配置为/1时,倍频器自动设置为*1,而预分频器设置为/2、/4、/8等其他选项时,倍频器自动设置为*2,以保证定时器有一个较高频率的时钟信号。
除此之外,挂载ADC外设的APB总线内(APB2)还有一个专门提供给ADC外设的预分频器(ADC所需的采样频率相较HCLK而言较低)
根据时钟型号不同,部分芯片此预分频器没有在CubeMX的时钟树界面显示,想要修改此值需要再ADC外设处配置
有的可以直接在时钟树处配置
FCLK部分
FCLK全称Free Running Clock 即自由运行时钟。
当我们需要节省电量,需要进入STM32低功耗模式之一的“停止模式”时,AHB总线会停止运行,HCLK会停止传输时钟脉冲,进而连接到AHB总线的外设全部停止运行。但是,休眠只是暂时的省电手段,我们还需要重新唤醒STM32。而此唤醒操作依靠的是外部中断,而FCLK正是为这个中断采样提供时钟信号。
在CubeMX中,虽然FCLK与HCLK直连,但这并不意味着FCLK来自于HCLK,而是为了表示二者的频率相同。FCLK的时钟信号实际来自于AHB的预分频器。
HCLK来源部分
时钟树的主干HCLK部分已经大致介绍完,下面讲解时钟源是如何给HCLK提供时钟信号的。
HCLK由SYSCLK(系统时钟)分频得来
而系统时钟有三个来源,分别是HSE(高速外部时钟)、HSI(高速内部时钟)、以及HSE/HSI通过PLL锁相环倍频所得到的时钟。这三路时钟信号通过System Clock Mux(系统时钟多路复用器)进行选择,输出一路时钟信号给系统时钟。
其中System Clock Mux有一个CSS(Clock Security System时钟安全系统)组件,它只有在系统时钟选择HSE或是用HSE倍频得来的时钟信号时才允许开启(Eable CSS以蓝色高亮显示)
由于在极端情况下,外部晶振时钟容易发生意外情况,当单片机选择HSE作为系统时钟,而HSE出现晶振短路等情况而无法正常提供时钟信号时,会导致单片机死机。为了防止该情况,stm32内部有一VSS用于实时监测HSE,当其发生意外时会自动将SYSCLK时钟信号的来源改为HSI,并产生中断,以便开发者应对。
MCO部分
MCO(Microcontroller Clock Output 微控制器时钟输出引脚),用于在引脚处输出选择的时钟信号
低速时钟部分
低速时钟源主要给RTC(Real Time Clock实时时钟)与IWDG(Independent Watch Dog 独立看门狗)两个组件提供时钟信号。
共有三个时钟源可供RTC使用,分别是HSE经过/128分频后得到的HSE_RTC、LSE、LSI。三者通过RTC Clock Mux进行选择,提供给RTC时钟信号。
由于RTC最大的特点是可以通过独立电源供电,以实现在单片机掉电后继续工作。而HSE_RTC与LSI在单片机掉电后都会受到影响,故一般选用LSE作为RTC的时钟信号。这也是为什么外部时钟的频率多为32.768KHZ,因为32768=2^15,便于RTC内部的预分频器使用。
其他外设部分(芯片不同,不一定有这些)
PTP
用于给以太网PTP协议的时钟
USB
I2S
04、时基
参考文章:
HAL和FreeRTOS的基础时钟
a、简介
HAL需要设置一个定时器作为基础时钟。基础时钟通过定时溢出中断产生嘀嗒信号,嘀嗒信号的缺省频率是1000Hz,也就是基础时钟的定时周期是1ms。基础时钟主要用于实现延时函数HAL_Delay(),或在一些有超时(timeout)设置的函数里确定延时。
时基的配置在SYS模块的Timbase Source选项中,默认使用SysTick(系统滴答定时器)。使用SysTick作为基础时钟后,在NVIC中会自动启用SysTick的中断,并且优先级默认设置为最低,如图2所示。可以修改SysTick的中断优先级,但是不能在图2中禁用SysTick中断。此优先级要根据实际情况调整,优先级越高,延时越精准,但同时有可能打断其他实时性任务。(注意,如果在优先级高于时基计数器中断的中断中调用HAL_Delay等需要用到定时器的函数时,由于正在执行的中断函数优先级更高,导致时基计数器中断函数不会进入,时基计数器不会变化,从而导致函数卡死)
b、基础时钟的初始化
在STM32CubeMX生成的初始化代码中,HAL_Init()是在main()函数中执行的第一行代码。HAL_Init()对SysTick定时器进行设置,使其定时中断周期为1ms。函数HAL_Init()的代码如下。
其中,执行的HAL_InitTick(TICK_INT_PRIORITY)是对SysTick定时器进行定时周期和中断的设置,HAL_InitTick()是在文件stm32f4xx_hal.c中用__weak修饰符定义的弱函数,代码如下:
作为弱函数,HAL_InitTick()可以被重新实现。在使用SysTick作为基础时钟时,使用的就是文件stm32f4xx_hal.c中的HAL_InitTick()。
函数HAL_Init()中最后调用的函数HAL_MspInit()也是一个弱函数,在HAL驱动中就是个空函数。在STM32CubeMX生成的初始化代码中,在文件stm32f4xx_hal_msp.c中重新实现了这个函数,功能就是启用了RCC的时钟信号,并设置中断优先级分组策略,也就是图2中用户设置的优先级分组策略
所以,执行函数HAL_Init()后,就设置了SysTick定时器的定时周期和中断优先级。默认的SysTick定时周期为1ms,产生的嘀嗒信号频率为1000Hz。
c、基础时钟的中断处理
在文件stm32f4xx_it.c中自动生成了SysTick定时器中断的ISR函数(中断服务子函数),其代码如下:
在SysTick定时器的定时溢出中断里就执行了函数HAL_IncTick(),这是在文件stm32f4xx_hal.c中实现的函数,函数的代码如下:
它的功能就是使得全局变量uwTick递增,这个变量就是嘀嗒信号的计数值。当嘀嗒信号频率为1000Hz时,uwTickFreq的值为1;当嘀嗒信号频率为100Hz时,uwTickFreq的值为10。
在文件stm32f4xx_hal.c中还定义了操作滴答定时器的两个函数,用于暂停和恢复滴答定时器,都是用__weak定义的弱函数,代码如下:
常用的延时函数HAL_Delay()就是利用嘀嗒信号来实现的,函数HAL_Delay()的代码如下:
程序中调用的函数HAL_GetTick()的功能就是返回全局变量uwTick,也就是嘀嗒信号的当前计数值。函数HAL_Delay()的输入参数Delay是以毫秒为单位的延时时间。延时的原理就是先读取嘀嗒信号的当前计数值保存到变量tickstart,计算在此基础上延时所需要的计数值差量wait,然后在while循环中不断用函数HAL_GetTick()读取滴答信号当前技术值,计算相对于tickstart的差量,当差量超过wait时就达到了延时时间。
在HAL库中使用SysTick作为基础定时器时,其作用就是用于产生嘀嗒信号计数值,然后用于延时计算。如果不需要用到延时计算,停掉SysTick定时器对系统运行是没有什么影响的。例如,在使用低功耗设计时,为了使系统进入睡眠模式后不被SysTick的中断唤醒,就停掉了SysTick定时器。
uwTick溢出相关见如下资料
关于STM32的hal库中滴答定时器uwTick溢出的思考和分析
d、Freertos抢占SysTick
当时基选择默认的SysTick的同时,开启FreeRTOS,CubeMX会弹出下面一条警告
原因是FreeRTOS抢占了SysTick,作为其调度器的时钟,如果选择YES,会导致HAL库时基与freertos时基共用一个时钟,从而导致,任务调度中断无法抢占时基中断,时基中断也无法抢占任务调度中断,使任务调度时间片不够精准,Systick计数也不够准确。故而需要避免,解决方法是在SYS配置中,选择其他TIM作为hal库的时基源。
注意,这样配置后,任务调度中断与时基中断优先级仍然相同,上述问题还是存在,需要根据实际情况调整二者的优先级。
e、使用其他定时器作为HAL库的基础时钟
在使用定时器TIM6作为HAL的基础时钟源并用STM32CubeMX生成代码后,\Src目录下新增了一个文件stm32f4xx_hal_timebase_TIM.c,这个文件里重新实现了文件stm32f4xx_hal.c中的3个弱函数,用定时器TIM6替代了SysTick的功能。文件stm32f4xx_hal_timebase_TIM.c的完整代码如下:
函数HAL_InitTick()是在HAL_Init()中被调用的,重新实现的这个函数对定时器TIM6进行了初始化配置,设置其中断优先级,配置其分频系数、计数周期等,使其定时器周期为1ms。
重新实现的函数HAL_ResumeTick()和HAL_SuspendTick()也是对TIM6的操作。
在使用定时器TIM6作为HAL的基础时钟源并用STM32CubeMX生成代码后,在文件stm32f4xx_it.c中没有了SysTick的ISR函数,有TIM6的ISR函数。TIM6的ISR函数代码如下
定时器的UEV事件的回调函数是HAL_TIM_PeriodElapsedCallback,在文件main.c中就重新实现了这个函数,其功能就是执行函数HAL_IncTick(),代码如下:
所以,在使用TIM6作为HAL的基础时钟时,TIM6完全替代了SysTick的功能。