几乎每个数字电路都需要一种方法来同步其内部电路或与其他电路同步。时钟是一种产生周期性信号的设备,它是数字电子学中最普遍的心跳源形式。
然而,相同的时钟信号不能用于馈送现代微控制器(如 STM32 微控制器)提供的所有组件和外设。此外,功耗是与给定外设的时钟速度直接相关的一个关键方面。能够选择性地禁用或降低某些 MCU 部件的时钟速度,可以优化整体器件功耗。这要求时钟以层次组织,使开发人员可以选择不同的速度的时钟源。
本章简要介绍了 STM32 MCU 的复杂时钟分配网络。其目的是为读者提供必要的工具来理解和管理 clock tree,展示 HAL_RCC 模块的主要功能。后面进一步专门介绍电源管理。
1、时钟分配
时钟是一种通常产生方波信号的器件,占空比为 50%,如上图所示。(这里的方波是“理想的”,实际的波形具有梯形形式。)时钟信号在 VL 和 VH 电压电平之间振荡,对于 STM32 微控制器,该电压电平仅为 VDD 电源电压的一小部分。时钟最基本的参数是频率,它表示它在一秒钟内从 VL 切换到 VH 的次数。频率以赫兹表示。
大多数STM32 MCU可以交替由两个不同的时钟源提供时钟:内部RC振荡器(称为高速内部(HSI))或外部专用晶体振荡器(称为高速外部(HSE))。选择外部晶体而不是内部 RC 振荡器有几个原因:
与内部 RC 网络相比,外部晶体提供更高的精度,内部 RC 网络的额定精度为 1%,特别是当 PCB 工作温度远离 25°C 的环境温度时。(1% 的精度似乎是一个不错的折衷方案,特别是如果您考虑到可以节省 PCB 空间和专用晶体的成本,而专用晶体是一种价格不可忽视的器件。但是,对于时间受限的应用程序,1% 可能是一个巨大的变化。例如,一天由 86,400 秒组成。等于 1% 的误差意味着在最坏的情况下,我们可以损失(或赚取)长达 864 秒,即 14.4 分钟!如果温度升高,情况可能会变得更糟。这就是为什么如果您要使用 RTC,则必须使用外部低速晶体的原因。)
某些外设,尤其是高速外设,只能由以给定频率运行的专用外部晶振进行计时。
与高速振荡器一起,另一个时钟源可用于偏置低速振荡器,而低速振荡器又可以由外部晶体(称为 Low Speed External (LSE))或内部专用 RC 振荡器(称为 Low Speed Internal (LSI))提供时钟。低速振荡器用于驱动实时时钟 (RTC) 和独立看门狗 (IWDT) 外设。
高速振荡器的频率并不构成 Cortex-M 内核和其他外设的实际频率。复杂的分配网络(也称为时钟树)负责 STM32 MCU 内部时钟信号的传播。使用多个可编程锁相环 (PLL) 和预分频器,可以根据需要增加/减少源频率(参见下图),具体取决于我们想要达到的性能、给定外设或总线的最大速度以及整体全局功耗。(MCU 的功耗与其频率大致呈线性关系;频率越高,消耗的功率就越大)
1.1、STM32 时钟树概述
STM32 MCU 的时钟树可以具有真正的铰接结构。即使在“更简单”的 STM32F0 MCU 中,内部时钟网络也可以有多达四个 PLL/预分频器级,并且系统时钟多路复用器(也称为系统时钟开关 (SW))可以由多个备用源馈送。
此外,深入解释每个 STM32 系列的时钟树是一项复杂的任务,这也需要我们将注意力集中在特定的零件编号上。这是因为时钟树结构主要受以下关键方面的影响:
微控制器的 STM32 主系列。例如,所有 STM32F0 MCU 都只提供一条外设总线 (APB1),该总线可以以相同的 Cortex-M 内核最大频率进行计时。其他 STM32 微控制器通常提供两条外设总线,其中只有一个 (APB2) 可以达到最大 CPU 时钟速度。相反,STM32F7 微控制器中可用的外设总线都无法达到最大内核频率,而 STM32G4 MCU 中的 APB1 和 ABP2 都可以达到最大内核时钟速度。可以注意到,对于“旧”STM32F103,仅使用外部 HSE 振荡器即可达到最大时钟速度。
MCU 提供的外设类型和数量。时钟树的复杂性随着可用外设的数量而增加。此外,一些外设需要专用的时钟源和速度,这会影响 PLL 级的数量。
MCU 的销售类型和封装,决定了提供外设的有效类型和数量。
上图显示了最简单的 STM32 微控制器之一 STM32F030R8 的时钟树。对于 STM32 平台的许多新手来说,这个数字完全没有意义并且很难解码,特别是如果他们也是嵌入式微控制器的新手。最相关的路径以红色标出:从 HSI 振荡器到 Cortex-M0 内核、AHB 总线和 DMA 的路径。这就是我们从那时起一直默默地 “使用” 的路径,而没有过多地处理其可能的配置。让我们介绍该路径中最相关的部分。
该路径从内部 8MHz 振荡器开始。如前所述,它是由 ST 在工厂校准的 RC 振荡器,在 25 °C 的环境温度下具有 1% 的精度。 然后,HSI clock 可以按原样用于馈送 System Clock Switch (SW) (图中以蓝色突出显示的 path),或者由于中间预分频器,它可以用于在 PLL 乘法器被 2 除以后馈送 PLL 乘法器。因此,主 PLL 可以将 4MHz 时钟乘以 12 倍,以获得 48MHz 的最大系统时钟频率 (SYSCLK)。SYSCLK 源头可用于馈送 I2C1 外设(替代 HSI)和另一个中间预分频器 AHB 预分频器,后者可用于降低高(速度)时钟 (HCLK),进而偏置 AHB 总线、内核和 SysTimer。
为什么有这么多中间 PLL/Prescaler 级?如前所述,时钟速度决定了整体性能,但它也会影响 MCU 的总功耗。能够选择性地打开/关闭 MCU 的某些部分 - 或降低时钟速度 - 可以根据所需的有效计算能力降低功耗。L0/1/4/5 MCU 引入了更多的 PLL/预分频器阶段,为开发人员提供了对整体 MCU 消耗的更多控制。结合专用的硬件设计,这使得创建电池供电的设备,甚至可以使用相同的电池运行数年。
时钟树配置通过名为 Reset and Clock Control (RCC) 的专用外设执行,该过程主要由三个步骤组成: 1. 如果使用 HSE,则选择高速振荡器源(HSI 或 HSE)并正确配置。2. 如果我们想以高于高速振荡器提供的频率馈送 SYSCLK,那么我们需要配置主 PLL (提供 PLLCLK 信号)。否则,我们可以跳过此步骤。3. 系统时钟开关 (SW) 配置,选择正确的时钟源 (HSI、HSE 或 PLLCLK)。然后,我们选择正确的 AHB、APB1 和 APB2(如果可用)预分频器设置,以达到高速时钟(HCLK - 即馈送内核、DMA 和 AHB 总线的时钟)的所需频率,以及高级外设总线 1 (APB1) 和 APB2(如果可用)总线的频率。
了解 PLL 和 prescaler 的允许值可能是一场噩梦,尤其是对于更复杂的 STM32 MCU。只有某些组合对给定的 STM32 微控制器有效,其不当配置可能会损坏 MCU 或至少导致故障错误的 clock configuration 可能会导致异常行为、奇怪和不可预测的 resets 等。幸运的是,STM32 工程师提供了一个很棒的工具来简化时钟配置:CubeMX。
1.1.1、STM32L/U 系列中的多速内部 RC 振荡器
时钟源及其分配网络对 MCU 的整体功耗有不可忽视的影响。如果我们需要一个高于或低于内部 HSI 时钟源的 SYSCLK 频率(大多数 STM32 MCU 为 8MHz,其他一些为 16MHz),我们必须通过使用 PLL Source Mux 和中间预分频器来增加/减少它。不幸的是,这些组件会消耗能量,这可能会对电池供电的设备产生巨大影响。
STM32L/U MCU 专为低功耗应用而设计,它们通过提供名为 MultiSpeed Internal (MSI) RC 振荡器的专用内部时钟源来解决这一特定问题。MSI 是一款低功耗 RC 振荡器,具有 ±1%@25°C 的工厂预校准精度,在 0-85°C 范围内可提高高达 ±3%。MSI 的主要特点是它提供多达 12 种不同的频率,而无需添加任何外部组件。例如,STM32L476中的 MSI 提供 100kHz 至 48MHz 的内部时钟源。MSI 时钟在从 Reset、Standby 和 Shutdown 低功耗模式重启后用作 SYSCLK。从 Reset 重新启动后,MSI 频率设置为默认值(例如,STM32L476中的默认 MSI 频率为 4MHz)。
上表总结了 STM32L476 MCU 中所有可能的 clock sources 的最相关特性。如您所见,当 MCU 由 MSI 计时时(不使用 PLL 多路复用器),可实现最佳功耗。此外,与 HSI 相比,这个 clock source 保证了最短的 start time 。有趣的是,稳定 LSE 时钟最多需要 2 秒:如果启动速度对您的应用程序真的很重要,那么使用单独的线程来启动 LSE 是一个可以考虑的选项。
除了与低功耗相关的优势外,当 MSI 用作 PLL Source Mux 的源时,它提供了一个非常精确的时钟源,USB OTG FS 设备可以使用该源,而无需使用外部专用晶体,同时馈送主 PLL 以最大速度运行系统。
1.2、使用 CubeMX 配置 Clock Tree
上图显示了到目前为止看到的相同 F0 MCU 的 clock tree。clock tree 最相关的 paths 也以红色和蓝色突出显示。这应该简化了与前图的比较。创建新项目时,默认情况下,CubeMX 选择 HSI 振荡器作为默认时钟源。HSI 也被选为 System Clock Mux(蓝色路径) 的默认时钟源,如图所示。这意味着,对于我们在这里考虑的 MCU,Cortex-M 内核频率将等于 8MHz。
CubeMX 还建议我们两件事:在此 MCU 中,高速(高速)时钟 (HCLK) 和 APB1 总线的最大频率等于 48MHz(蓝色标签)。为了提高 CPU 内核频率,我们首先需要选择 PLLCLK 作为 System Clock Switch 的源时钟,然后选择正确的 PLL multiplier factor。但是,CubeMX 提供了一种快速的方法:您只需在 HCLK 字段中输入“48”,然后按 Enter 键即可。CubeMX 将自动安排设置,选择正确的时钟树路径(图中的红色路径)。
如果您的电路板依赖于外部 HSE/LSE 晶体,则必须先在 RCC 外设中启用它,然后才能将其用作相应振荡器的主时钟源。一旦启用外部振荡器,就可以指定其频率(在标有 “input frequency” 的蓝色框内)并配置主 PLL 以达到所需的 SYSCLK 速度(参见上图)。否则,外部振荡器 input frequency 可以直接用作 System Clock Switch 的源 clock。我们需要相应地配置 RCC 外设以启用外部时钟源。这可以从 CubeMX 的 Pinout 视图中完成,如下图所示。
对于 HSE 和 LSE 振荡器,CubeMX 提供三个配置选项:
Disable:外部振荡器不可用/未使用,而使用相应的内部振荡器。
Crystal/Ceramic Resonator:使用外部晶体/陶瓷谐振器,并从中得出相应的主频率。这意味着 RCC_OSC_IN 和 RCC_OSC_OUT 引脚用于连接 HSE,并且相应的信号 I/O 不可用于其他用途(如果我们使用外部低速晶体,则也使用相应的 RCC_OSC32_IN 和 RCC_OSC32_OUT I/O)。
BYPASS Clock Source:使用外部 clock source。clock source 由另一个活动器件生成。这意味着 RCC_OSC_OUT 未使用,可以将其用作常规 GPIO。在 ST 的几乎所有开发板中,ST-LINK 接口的主时钟输出 (MCO) 引脚用作目标 STM32 MCU 的外部时钟源。启用此选项允许将 ST-LINK MCO 用作 HSE。
RCC 外设还允许启用主时钟输出 (MCO),这是一个可用于为另一个外部设备提供时钟的引脚,允许为另一个 IC 节省外部晶体。一旦 MCO 被启用,就可以使用 Clock Configuration 视图选择它的 clock source,如下图所示。
2、HAL_RCC 模块概述
到目前为止,我们已经看到 Reset and Clock Control (RCC) 外设负责 STM32 MCU 的整个 clock tree 的配置。HAL_RCC 模块包含 CubeHAL 的相应描述符和例程,以便从特定的 RCC 实现中抽象出来。我们现在将简要概述它的主要功能以及配置 clock tree 期间涉及的步骤。
配置 clock tree 最相关的 C 结构是 RCC_OscInitTypeDef 和 RCC_ClkInitTypeDef。第一个用于配置 RCC 内部/外部振荡器源(HSE、HSI、LSE、LSI),以及一些额外的时钟源(如果由 MCU 提供)。例如,F0 系列的一些 STM32 MCU(STM32F07x、STM32F0x2 和 STM32F09x 的)提供 USB 2.0 支持,此外还有一个内部专用且经过工厂校准的高速振荡器,运行频率为 48MHz,用于偏置 USB 外设。如果是这种情况,则 RCC_OscInitTypeDef 结构也用于配置这些额外的 clock sources。RCC_OscInitTypeDef 结构体还有一个字段,该字段是 RCC_PLLInitTypeDef 结构体的实例,该字段配置用于提高源时钟速度的主 PLL。它反映了主 PLL 的硬件结构,可以根据 STM32 系列由多个字段组成(在 STM32F2/4/7 MCU 中,它可以具有相当复杂的结构)。相反,RCC_ClkInitTypeDef 结构用于配置系统时钟开关 (SWCLK)、AHB 总线和 APB1/2 总线的源时钟。
CubeMX 旨在为 MCU 的时钟树生成正确的代码初始化。所有必要的代码都打包在 SystemClock_Config() 例程中,到目前为止我们在生成的项目中遇到过。例如,以下 SystemClock_Config() 的实现反映了运行频率为 48MHz 的 STM32F030R8 MCU 的 clock tree 配置:
void SystemClock_Config(void) {RCC_OscInitTypeDef RCC_OscInitStruct;RCC_ClkInitTypeDef RCC_ClkInitStruct;RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.HSICalibrationValue = 16;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL12;RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1;HAL_RCC_OscConfig(&RCC_OscInitStruct);RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1);HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);/* SysTick_IRQn interrupt configuration */HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}
首先选择 HSI 作为源振荡器并启用主 PLL,通过 PLL 多路复用器将 HSI 设置为其时钟源。然后 clock frequency 增加 12 倍(设置 PLLMUL 字段)。设置 SYSCLK 频率。PLLCLK 被选为 clock source 。同样,SYSCLK 频率被选为 AHB 总线的源,相同的 HCLK 频率 (RCC_HCLK_DIV1) 被选为 APB1 总线的源。其他代码行设置 SysTick 计时器,这是 Cortex-M 内核中提供的一个特殊计时器,用于同步一些内部 HAL 活动(或驱动 RTOS 的调度器)。HAL 基于 SysTick 计时器每 1 毫秒生成一次中断的约定。由于我们正在配置 SysTick 时钟,使其以 48MHz 的最大内核频率运行(这意味着 SYSCLK 每秒执行 48000000 个时钟周期),我们可以设置 SysTick 定时器,使其每 48000000 个周期/1000ms = 48000 个时钟周期生成一个中断。
SysTick 计时器是一个 24 位 downcounter 计时器,也就是说,它从配置的最大值(在本例中为 48000)计数到零,然后再次自动重新启动。timer 的 source clock 确定此 timer 的计数速度。由于这里我们指定 SysTick 定时器的 clock source 是 HCLK,那么计数器将每 1ms 达到零。
2.1、计算运行时的时钟频率
有时,了解 CPU 内核的运行速度非常重要。如果我们的固件设计为始终以既定频率运行,那么我们可以使用符号常量轻松地在固件中对该值进行硬编码。然而,这始终是一种糟糕的编程风格,如果我们动态地管理 CPU 频率,它是完全不适用的。CubeHAL 提供了一个可用于计算 SYSCLK 频率的函数:HAL_RCC_GetSysClockFreq()。但是,必须特别小心地处理此功能。让我们看看为什么。
HAL_RCC_GetSysClockFreq() 不返回实际的 SYSCLK 频率 (如果没有已知且精确的外部参考,它永远无法以可靠的方式执行此操作),但它的结果基于以下算法:
如果 SYSCLK 源是 HSI 振荡器,则返回基于 HSI_VALUE 宏的值;
如果 SYSCLK 源是 HSE 振荡器,则根据 HSE_VALUE 宏返回值;
如果 SYSCLK 源是 PLLCLK,则根据特定的 STM32 MCU 实现,返回一个基于 HSI_VALUE/HSE_VALUE 乘以 PLL 因子的值。
HSI_VALUE 和 HSE_VALUE 宏在 stm32xxx_hal_conf.h 文件中定义,它们是硬编码值。HSI_VALUE 由 ST 在芯片设计期间定义,我们可以信任相应宏的值(除了那 1% 的准确率)。相反,如果我们使用外部振荡器作为 HSE 源,我们必须提供 HSE_VALUE 宏的实际值,否则 HAL_RCC_GetSysClockFreq() 函数返回的值是错误的。这也会影响 SysTick 定时器的 tick 频率 (即生成 timer 中断需要多长时间)。
我们还可以使用 SystemCoreClock CMSIS 全局变量来检索 core frequency。
如果我们决定在不使用 CubeHAL 例程的情况下手动操作 clock tree 配置,我们必须记住,每次更改 SYSCLK 频率时,都需要调用 CMSIS 函数 SystemCoreClockUpdate(),否则某些 CMSIS 例程可能会给出错误的结果。此函数由 HAL_RCC_ClockConfig() 例程自动调用。
请注意,Cortex-M 内核不是由 SYSCLK 频率计时的,而是由 HCLK 频率计时的,HCLK 频率可以通过 AHB 预分频器降低。所以,回顾一下,core frequency 等于 HAL_RCC_GetSysClockFreq()/AHB-prescaler。
2.2、启用主时钟输出
如前所述,根据所使用的 IC 封装,STM32 MCU 允许将时钟信号路由到一个或两个输出 I/O,称为主时钟输出 (MCO)。这是通过使用函数
void HAL_RCC_MCOConfig(uint32_t RCC_MCOx, uint32_t RCC_MCOSource, uint32_t RCC_MCODiv);
例如,要将 PLLCLK 路由到 STM32F401RE MCU 中的 MCO1 引脚(对应 PA8 引脚),我们必须按以下方式调用上述函数:
HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_PLLCLK, RCC_MCODIV_1);
请注意,当将 MCO 引脚配置为输出 GPIO 时,其速度(即转换速率)会影响输出时钟的质量。此外,对于更高的时钟频率,必须按以下方式启用补偿单元:
HAL_EnableCompensationCell();
2.3、启用时钟安全系统
时钟安全系统(CSS)是 RCC 外设的一项功能,用于检测外部 HSE 的故障。CSS 是一些关键应用中的重要功能,其中 HSE 的故障可能会对用户造成伤害。通过 NMI 异常(无法禁用的 Cortex-M 异常)注意到故障检测这一事实证明了它的重要性。
当检测到 HSE 故障时,MCU 会自动切换到 HSI 时钟,该时钟被选为 SYSCLK 时钟的源。因此,如果需要更高的内核频率,我们需要在 NMI 异常处理程序中执行适当的初始化。
为了启用 CSS,我们使用 HAL_RCC_EnableCSS() 例程,我们需要按以下方式定义 NMI 异常的处理程序:
void NMI_Handler(void) {HAL_RCC_NMI_IRQHandler();
}
捕获 HSE 时钟故障的正确方法是定义回调:无需启用 NMI 异常,因为它是自动启用的,不能禁用。
void HAL_RCC_CSSCallback(void) {//Catch the HSE failure and take proper actions
}
3、HSI 校准
我们在前面看到的 SystemClock_Config() 例程中保留了一行代码,没有注释。它用于执行 HSI 振荡器的微调校准。但它到底有什么作用呢?
如前所述,由于制造工艺的不同,内部 RC 振荡器的频率可能因芯片而异。因此,HSI 振荡器由 ST 进行工厂校准,在室温下的精度为 1%。复位后,出厂校准值自动加载到 RCC 配置寄存器 (RCC_CR) 的第二个字节 (HSICAL) 中(下图显示了该寄存器在 STM32F401RE中的实现)。
内部 RC 振荡器的频率可以微调,以便在更宽的温度和电源电压范围内实现更高的精度。修剪位用于此目的。五个微调位 RCC_CR->HSITRIM[4:0] 用于微调。默认剪裁值为 16。此修剪值的增加/减少会导致 HSI 频率的增加/减少。HSI 振荡器以 HSI 时钟速度的 0.5% 为步长进行微调: 写入 17 到 31 范围内的微调值会增加 HSI 频率。
写入 0 到 15 范围内的修整值会降低 HSI 频率。
写入等于 16 的修剪值将导致 HSI 频率保持其默认值。
HSI 可以按照以下步骤进行校准:
1. 设置内部高速 RC 振荡器 System clock;
2. 测量每个微调值的内部 RC 振荡器频率;
3. 计算每个 trimming 值的频率误差(根据已知的参考);
4. 最后,将 Trimming Bits 设置为最佳值 (对应于最低频率误差)。
内部振荡器频率不是直接测量的,而是根据使用 timer 计数的 clock pulse 数与典型值进行比较计算得出的。为此,必须提供非常精确的参考频率,例如外部 32.768 kHz 晶体提供的 LSE 频率或电源的 50 Hz/60 Hz。
ST 提供了几个应用说明,更好地描述了这一程序(例如,AN4067是关于 STM32F0 系列中的校准程序https://bit.ly/1R8kEbf)。