
06_STM32_时钟篇
第一章 STM32F407时钟系统架构
1.1 关于时钟
1.1.1 时钟信号
从电气属性上来说,时钟就是具有周期性的脉冲信号,这种信号是占空比为 50% 的方波。
1.1.2 单片机上的时钟
时钟是单片机运行的基础,时钟信号控制着单片机内部每个部分执行指令的节奏,让单片机内部的外设组件可以进行同步的协同工作,并且与外部设备通讯时达到同步的一个功能。
时钟系统就像 CPU 的“心跳”,决定 cpu 运行速度。整个 MCU 只有一个主时钟源,具体通过“选择器、倍频器、分频器”等模块进行处理后,分配到各个外设。
只有有了时钟,才能驱动指令的执行,才能控制各模块的功能处理(比如 GPIO、串口、ADC 等),没有时钟,这些模块都无法正常工作。
此外,在STM32中的时钟,还具备动态调整时钟频率的强大功能,通过调节频率,可以控制整个芯片的性能与能耗的关系,例如我们电脑上的 安静模式 、均衡模式 、野兽模式。
1.2 关于时钟树
STM32F407 的时钟系统是一个“分布式树状结构”,通过选择、倍频、分频等多个过程,把主时钟源经过一系列处理后传输给各个外设模块。
通俗理解:就像自来水厂出来的水(主时钟源),通过不同管道(分频器、倍频器)送到不同小区(外设模块),每一处都有自己的流量调节开关(选择器)。
STM32 支持多种时钟源,分为 内部时钟源 和 外部时钟源,分别可开启或关闭,每个时钟源都可以启用(enable),也可以关闭(disable)。
1.2.1 主要时钟源
缩写 | 英文全称 | 中文全称 | 材料 |
---|---|---|---|
HSE | High Speed External | 外部高速时钟 | 晶体/陶瓷 |
HSI | High Speed Internal | 内部高速时钟 | |
LSE | Low Speed External | 外部低速时钟 | |
LSI | Low Speed Internal | 内部低速时钟 | |
PLL | Phase lock loop | 锁相环倍频后得到的时钟 |
H : High 高
L : Low 低
S : Speed 速度
I : Interior 内部
E: External 外部
1.2.2 时钟树结构图
在上面的时钟树图中,有几个比较重要的器件,需要读者提前了解,才能开始后续的内容,分别是 选择器 、 倍频器 以及 分频器 ,器件外观入下图所示,接下来,将对这三个器件进行讲解。
1.2.3 选择器
选择器在 STM32 的时钟树中扮演着“决策者”的角色。它负责从多个时钟源中挑选一个作为主系统时钟(SYSCLK)。这个“主时钟”将决定整个系统的运行节奏。
在 STM32F407 中,SYSCLK 的来源通常有以下三种:
-
HSI(High-Speed Internal):高速内部时钟,芯片内自带的 16MHz RC 振荡器;
-
HSE(High-Speed External):高速外部时钟,通常接 8MHz 外部晶振;
-
PLLCLK(Phase-Locked Loop Clock):锁相环倍频输出时钟,常用于提升系统主频。
举个例子:选择器就像 MCU 的“大脑”,要决定今天用哪种“心跳频率”来安排一整天的工作。
在系统启动或特定运行阶段,可以通过配置相关寄存器(如 RCC->CFGR
中的 SW 位)来选择适合当前应用的时钟源。
比如:
-
上电启动时先用 HSI(启动快);
-
稳定后切换为 HSE 或 PLL(更精准高效)。
1.2.4 倍频器
倍频器(PLL)用于提升时钟频率,以满足主频要求较高的应用场景。STM32 中的锁相环 PLL(Phase Locked Loop)模块就是一个典型的倍频器。
例如:
-
原始时钟是 HSE=8MHz;
-
通过 PLL 设置后可以将其提升至 168MHz;
-
达到 STM32F407 的最高主频。
通俗理解:就像汽车的变速箱,可以让“原始动力”变得更强劲,从而推动整车跑得更快。
倍频器的实现原理是“相位锁定技术”:
-
将输入时钟与一个可控振荡器进行比较;
-
控制振荡器频率,使其与输入信号保持固定相位关系;
-
最终输出一个频率更高但稳定的时钟信号。
这是实现高速、稳定运行的关键手段。
1.2.5 分频器
分频器是用于降低时钟频率的装置。不是所有模块都需要高频率时钟,某些外设(如低速串口、定时器等)工作时只需要较低的频率,这时就需要将主系统时钟“降速”。
STM32 的典型分频输出如下:
-
HCLK:内核总线主时钟;
-
PCLK1:低速外设总线(如 USART2、I2C1);
-
PCLK2:高速外设总线(如 USART1、SPI1)。
类比:自来水主管道的压力太大,家里的水龙头需要装减压阀,才能正常用水。
例如:
-
主系统时钟为 16MHz;
-
若 PCLK1 分频为 16,则输出 1MHz 时钟信号给低速外设。
分频的系数通常可设置为 2、4、8、16 等整数倍,通过寄存器 RCC->CFGR
中的 HPRE(AHB分频器)、PPRE1(APB1分频器)、PPRE2(APB2分频器)进行配置。
模块 | 功能说明 | 举例 |
---|---|---|
选择器 | 选择系统主时钟源 | HSI / HSE / PLL |
倍频器 | 提高时钟频率 | HSE=8MHz → PLL=168MHz |
分频器 | 降低时钟频率 | SYSCLK=16MHz → PCLK=1MHz |
三者配合完成 STM32 的主时钟系统配置,为 MCU 的各个模块和外设提供稳定、精准、可调的时钟信号。
1.3 HSE 外部高速时钟
1.3.1HSE时钟简介
HSE:High Speed External Clock signal,即高速的外部时钟。
来源:无源晶振(4-16M),通常使用8M。
作用:可不分频或2分频(频率/2)作为PLL锁相环的输入,还可直接不分频作为系统时钟,128分频作为外设RTC时钟的输入。
1.3.2 HSE时钟寄存器
HSE晶体可以通过设置时钟控制寄存器里RCC_CR中的HSEON位被启动和关闭,在时钟控制寄存器RCC_CR中的HSERDY位用来指示高速外部振荡器是否稳定。在启动时,直到这一位被硬件置’1’,时钟才被释放出来。
1.4 HSI 内部高速时钟
1.4.1 HSI时钟介绍
HSI:High Speed Internal Clock signal,高速的内部时钟。
来源:芯片内部,大小为 8M,当HSE故障时,系统时钟 会自动切换到HSI,直到HSE启动成功。
功能:可直接作为系统时钟或在2分频后作为PLL输入。HSI RC振荡器能够在不需要任何外部器件的条件下提供系统时钟。它的启动时间比HSE晶体振荡器短。然而,即使在校准之后它的时钟频率精度仍较差
1.4.2 HSI时钟寄存器
时钟控制寄存器中的HSIRDY位用来指示HSI RC振荡器是否稳定。在时钟启动过程中,直到这一位被硬件置’1’,HSI RC输出时钟才被释放。HSI RC可由时钟控制寄存器中的HSION位来启动和关闭。如果HSE晶体振荡器失效,HSI时钟会被作为备用时钟源
1.5 LSE外部低速时钟
1.5.1 LSE时钟简介
LSE: low Speed External Clock signal,低速的外部时钟。
来源:LSE晶体是一个32.768kHz的低速外部晶体或陶瓷谐振器。它为实时时钟或者其他定时功能提供一个低功耗且精确的时钟源。
作用:直接作为RTC是时钟来源
1.5.3 LSE时钟寄存器
LSE晶体通过在备份域控制寄存器(RCC_BDCR)里的LSEON位启动和关闭。在备份域控制寄存器(RCC_BDCR)里的LSERDY指示LSE晶体振荡是否稳定。在启动阶段,直到这个位被硬置’1’后,LSE时钟信号才被释放出来。
1.6 LSI 内部低速时钟
1.6.1 LSI 时钟简介
LSI: low Speed Internal Clock signal,低速的内部时钟。
来源:内部芯片,LSI RC担当一个低功耗时钟源的角色,它可以在停机和待机模式下保持运行,LSI时钟频率大约40kHz(在30kHz和60kHz之间)。
作用:为独立看门狗和自动唤醒单元(RTC)提供时钟。
1.6.2 LSI时钟寄存器
LSI RC可以通过控制/状态寄存器(RCC_CSR)里的LSION位来启动或关闭在控制/状态寄存器(RCC_CSR)里的LSIRDY位指示低速内部振荡器是否稳定。在启动阶段,直到这个位被硬件设置为’1’后,此时钟才被释放。
1.7 PLLCLK锁相环
1.7.1 PLLCLK锁相环时钟简介
锁相环时钟:PLLCLK
来源:选择HIS振荡器除2或HSE振荡器为PLL的输入时钟,和选择倍频因子,必须在其被激活前完成。一旦PLL被激活,这些参数就不能被改动。
作用:内部PLL可以用来倍频HSI RC的输出时钟或HSE晶体输出时钟(倍频数2~16倍),而经过倍频变成PLLCLK可以作为系统时钟源。
1、PLL 配置
PLL 有两个:主 PLL 和专用的 PLLI2S。它们都可以使用 HSE 或 HSI 作为时钟输入信号。
主PLL 需要将时钟输送到 系统时钟、USB、RNG、SDIO,入下图所示。
2、PLL内部工作原理
输入分频:HSE 或 HSI 时钟首先经过分频因子 M(2~63),进入 VCO(电压控制振荡器)。VCO 的输入时钟必须在 1 MHz 到 2 MHz 之间。例如,如果 HSE 设置为 8 MHz,M 设置为 8,则 VCO 的输入时钟为 1 MHz。
VCO 倍频:VCO 输入时钟经过倍频因子 N 进行倍频,输出的 VCO 时钟必须在 192 MHz 到 432 MHz 之间。例如,如果 N 设置为 336,则 VCO 输出时钟为 336 MHz。如果要超频系统时钟,可以调整 N。
输出分频:VCO 输出时钟会经过两个分频因子:
-
分频因子 p:输出到系统时钟,可取 2、4、6、8,例如设置为 2,则得到 PLLCLK = 168 MHz。
-
分频因子 Q:输出到USB/OTG FS/RNG/SDIO ,可取 4~15,但 USB 必须使用 48 MHz,因此 Q 设置为 7(即 336 MHz ÷ 48 MHz = 7)。
根据以上配置,PLL 时钟可以通过以下公式计算:
VCOCLK_IN = PLLCLK_IN / M = HSE / 8 = 1M
VCOCLK_OUT = VCOCLK_IN * N = 1M * 336 = 336M
PLLCLK_OUT=VCOCLK_OUT/P=336/2=168M
USBCLK = VCOCLK_OUT/Q=336/7=48
1.7.2 PLLCLK锁相环时钟寄存器
寄存器RCC_CR:PLLNO、PLLRDY
寄存器RCC_PLLCFGR:PLLM、PLLN、PLLP、PLLQ
1.8 SYSCLK 系统时钟
1.8.1 SYSCLK 系统时钟简介
系统时钟:SYSCLK,最高为 168M
来源:HSI(高速的内部时钟)、HSE(高速的外部时钟)、PLLCLK(锁相环输出时钟)。
如果系统时钟是由 HSE 经过 PLL 倍频之后的 PLLCLK 得到,当 HSE 出现故障的时候,系统时钟会切换为 HSI=16M,直到 HSE 恢复正常为止。
1.8.2 SYSCLK 系统时钟寄存器
时钟配置寄存器 CFGR 的 SW[1:0]作为系统时钟切换控制位。
1.9 AHB总线 HCLK时钟
1.9.1 AHB总线 HCLK时钟简介
HCLK时钟: 是 AHB(Advanced High-performance Bus)总线的时钟,负责为系统中所有连接到 AHB 总线的外设提供时钟。
来源:HCLK 时钟通常由系统时钟(如 PLL 或 HSI)提供,经过适当的分频后生成。
作用:HCLK 时钟为 AHB 总线上的外设提供时钟信号,确保这些外设的正常工作。其频率通常与系统时钟相关,分频因子取决于系统配置。
1.9.1 AHB总线 HCLK时钟寄存器
HCLK 时钟的分频因子可以通过 RCC_CFGR 寄存器进行配置。通过设置该寄存器中的相关位,可以调整 AHB 总线的时钟频率。
AHB分频:可以通过 RCC_CFGR 寄存器中的 HPRE 位来配置,选择分频因子,具体的分频值(如 1、2、4、8 等)会影响 HCLK 的输出频率。
1.10 APB1总线 PCLK1时钟
1.10.1 APB1总线 PCLK1时钟简介
PCLK1: 是 APB1 总线的时钟,最高频率为 42 MHz。
来源:PCLK1 时钟通常由 HCLK 时钟经过分频得到。通常配置为 PCLK1 = HCLK / 4
,即 42 MHz。
作用:PCLK1 为 APB1 总线上的外设提供时钟,包括定时器、串口、I2C、SPI 等外设。当 PCLK1 进行 2 倍频时,可以提供高达 84 MHz 的时钟信号,主要用于为 APB1 总线上的定时器等外设提供时钟。
1.10.2 APB1总线 PCLK1时钟寄存器
PCLK1 时钟的分频因子可以通过 RCC_CFGR 寄存器中的 PPRE1[12:10] 位进行配置。该位设置决定了 PCLK1 的频率,通过选择不同的分频值,可以将 HCLK 时钟分频为 PCLK1。
1.11 APB1 总线 PCLK2时钟
1.11.1 APB1 总线 PCLK2时钟简介
PCLK2:是 APB2 总线的时钟,通常为 HCLK 或其他来源时钟经过分频后的输出。
来源:PCLK2 时钟通常由 HCLK 直接提供,或者经过 PLL 时钟的分频,具体取决于系统配置。
作用:PCLK2 时钟为 APB2 总线上的外设提供时钟,例如高级定时器、ADC、USART、SPI 等。
1.11.2 APB1 总线 PCLK2时钟寄存器
PCLK2 时钟的分频因子可以通过 RCC_CFGR 寄存器中的 PPRE2[15:13] 位进行配置。类似于 PCLK1,设置该位可以选择适当的分频值,来调整 PCLK2 的频率。
1.12 RTC时钟
1.12.1 RTC时钟简介
RTC 时钟: 用于实时时钟(RTC)功能,负责提供准确的时间和日期计时功能。
来源:RTC 时钟通常使用 LSE(外部低速时钟) 或 LSI(内部低速时钟) 作为时钟源。LSE 是一个低功耗的 32.768 kHz 晶体振荡器,而 LSI 是内部 RC 振荡器,提供约 40 kHz 的时钟频率。
作用:RTC 时钟用于提供定时功能,常用于系统断电或进入低功耗模式时继续保持时间准确性。
1.12.1 RTC时钟寄存器
RCC_BDCR 寄存器:用于配置 RTC 时钟源的选择。
- RTCEN:使能 RTC 时钟。
- RTCSRC:选择 RTC 时钟源(LSE 或 LSI)
1.13 MCO 时钟输出
1.13.1 MCO 时钟输出简介
MCO(Microcontroller Clock Output) 是一个时钟输出接口,允许微控制器将某些时钟信号输出到外部设备,以便这些设备同步工作。
作用:MCO 允许用户将系统时钟(如 HSE、HSI、PLL 等)输出到外部引脚,以便调试、测试或与其他外部设备同步时钟。
1.13.2 MCO 时钟输出寄存器
RCC_CFGR 寄存器:通过 MCO[1:0] 位选择时钟源。
第二章 时钟初始化
2.1 系统初始化函数
1.2.1 SystemInit 函数解析
STM32F4 的时钟系统初始化通过 system_stm32f4xx.c
文件中的 SystemInit()
函数完成。主要的系统时钟配置操作是通过 SystemInit()
调用 SetSysClock()
来实现的。我们可以首先来分析 SystemInit()
函数的代码:
1、 源码注释
/**
* @}
*/
/** @addtogroup STM32F4xx_System_Private_Functions
* @{
*/
/**
* @brief 系统初始化函数
* 初始化嵌入式 Flash 接口,配置 PLL,并更新系统时钟频率变量。
* @param 无
* @retval 无
*/
void SystemInit(void)
{
/* FPU 设置 ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
// 设置 CP10 和 CP11 为完全访问权限
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));
#endif
/* 重置 RCC 时钟配置为默认复位状态 ------------*/
/* 设置 HSION 位,启用 HSI(内部高速时钟) */
RCC->CR |= (uint32_t)0x00000001;
/* 重置 CFGR 寄存器,清除时钟源配置 */
RCC->CFGR = 0x00000000;
/* 重置 HSEON, CSSON 和 PLLON 位,禁用外部高速晶振 (HSE)、时钟安全系统 (CSS) 和 PLL */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* 重置 PLLCFGR 寄存器,清除 PLL 配置 */
RCC->PLLCFGR = 0x24003010;
/* 重置 HSEBYP 位,禁用外部高速晶振旁路 */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* 禁用所有中断 */
RCC->CIR = 0x00000000;
#if defined(DATA_IN_ExtSRAM) || defined(DATA_IN_ExtSDRAM)
// 如果有外部 SRAM 或 SDRAM,则调用扩展内存控制函数
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* 配置系统时钟源、PLL 倍频和分频因子,
配置 AHB/APB 预分频器和 Flash 设置 ----------------------------------*/
SetSysClock();
/* 配置向量表位置和偏移地址 ------------------*/
#ifdef VECT_TAB_SRAM
// 向量表在内部 SRAM 中
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;
#else
// 向量表在内部 Flash 中
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
#endif
}
2、代码解析
FPU 设置:如果目标系统中包含 FPU(浮点单元),则通过设置 CPACR
寄存器来允许访问浮点寄存器。
RCC 配置复位:清除 RCC(时钟控制)寄存器的设置,恢复为默认状态,确保所有时钟源和 PLL 配置是从一个已知的状态开始。
HSE 和 PLL 配置:通过配置外部时钟源 HSE 和 PLL 来提供更精确的系统时钟。
向量表配置:指定中断向量表的位置,是放在 SRAM 还是 Flash 中,依据具体应用选择。
1.2.2 SetSysClock函数解析
1、源码注释
static void SetSysClock(void)
{
/******************************************************************************/
/* 使用 HSE 作为系统时钟源并配置 PLL */
/******************************************************************************/
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* 启用 HSE (外部高速晶振) */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* 等待 HSE 就绪,超时退出 */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
/* 检查 HSE 是否准备好 */
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
// HSE 启动成功
HSEStatus = (uint32_t)0x01;
}
else
{
// HSE 启动失败
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01)
{
/* 启用电源电压调节器并设置工作模式 */
RCC->APB1ENR |= RCC_APB1ENR_PWREN; // 启用电源控制
PWR->CR |= PWR_CR_VOS; // 设置电源电压调节器
/* 配置 HCLK、PCLK1、PCLK2 的时钟分频器 */
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; /* HCLK = SYSCLK / 1 */
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; /* PCLK1 = HCLK / 4 */
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; /* PCLK2 = HCLK / 2 */
/* 配置 PLL (由 HSE 时钟源驱动) */
RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) - 1) << 16) |
(RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
/* 启用 PLL */
RCC->CR |= RCC_CR_PLLON;
/* 等待 PLL 就绪 */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* 配置 Flash 预取、指令缓存、数据缓存和等待状态 */
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_LATENCY_5WS;
/* 选择 PLL 作为系统时钟源 */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= RCC_CFGR_SW_PLL;
/* 等待 PLL 成为系统时钟源 */
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL)
{
}
}
else
{
/* 如果 HSE 启动失败,可以在此处处理错误,或进行重试 */
}
}
当然我们也可以修改这个函数,让他切换是时钟源,操作如下:
1.选择PLL作为系统时钟源
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= RCC_CFGR_SW_PLL;
2.选择HSI作为系统时钟源
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= RCC_CFGR_SW_HSI;
3.选择HSE作为系统时钟源
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= RCC_CFGR_SW_HSE;
2、代码解析
HSE 启动:首先启用 HSE(外部高速晶振),然后通过循环等待 HSE 稳定。若启动失败,可以根据需求添加错误处理。
电源控制:启用电源调节器并设置电压模式。
时钟分频配置:配置 AHB、APB 时钟分频器,以确定各个外设时钟的频率。
PLL 配置:配置 PLL(锁相环)来增加时钟频率,具体包括倍频因子和时钟源选择。HSE 作为 PLL 的输入时钟源。
Flash 设置:设置 Flash 相关的缓存和延迟配置,以优化性能。
PLL 作为系统时钟:最后,选择 PLL 作为系统时钟源,等待 PLL 成为系统时钟源。
2.2 为硬件适配系统时钟(重点)
在前面的学习中,我们已经知道什么是外部高速时钟,并且也已经知道外部高速时钟是系统中的主要时钟来源,但是由于外部时钟并不是芯片内部的时钟,所以芯片没法知道这个晶振的参数大小,就需要我们来自己进行设定一些参数,让芯片可以正常的运行。
在下面的操作中,需要设定两个文件,分别是 stm32f4xx.h
用于设定晶振大小, 以及 system_stm32f4xx.c
文件,用于设定锁相环的分频系数。
2.2.1 准备工作
在进行设定之前,我们需要将 stm32f4xx.h
和system_stm32f4xx.c
两个文件的权限信息进行修改,需要去掉文件的只读属性。
2.2.2 配置外部晶振参数
通过原理图可知,开发板的所采用的时钟模块为 8MHz ,如下图:
然后设定 stm32f4xx.h
文件中 HSE_VALUE
外部晶振为 8MHz,找到文件,操作如下图。
2.2.3 配置锁相环预分配系数
配置 "system_stm32f4xx.c" 文件 PLL 倍频锁相环,将系统时钟设置为 168MHz
*-----------------------------------------------------------
* PLL_M | 8
*-----------------------------------------------------------
* PLL_N | 336
*-----------------------------------------------------------
* PLL_P | 2
*-----------------------------------------------------------
* PLL_Q | 7
*-----------------------------------------------------------
#define PLL_M 8
#define PLL_Q 7
#if defined (STM32F40_41xxx)
#define PLL_N 336
#define PLL_P 2
#endif
------------------------------------------------------------
// 计算公式: PLL 输出频率 = (外部时钟频率 * PLL_N) / PLL_M / PLL_P
// 例如:8MHz * 336 / 8 / 2 = 168MHz
设置入下图:
第三章 SysTick定时器
3.1 相关介绍
什么是SysTick?
SysTick定时器也叫SysTick滴答定时器,它是Cortex-M3内核的一个外设,被嵌入在NVIC 中,用于产生SYSTICK异常(异常号:15)。SysTick是一个24位的倒计数系统节拍计时器System Tick timer,每计数一次所需时间为1/SYSTICK,SYSTICK是系统定时器时钟,它可以直接取系统时钟,还可以通过系统时钟8分频后获取。当计数到0时,它就会从Load寄存器中自动重装定时初值,重新向下递减计数,如此循环往复。如果开启SysTick中断的话,当定时器计数到0,将产生一个中断信号。因此只要知道计数的次数就可以准确得到它的延时时间。只要不把CTRL寄存器中的ENABLE清0,它就永不停止。
SysTick作用?
在单任务引用程序中,因为其架构就决定了它执行任务的串行性,这就引出一个问题:当某个任务出现问题时,就会牵连到后续的任务,进而导致整个系统崩溃。要解决这个问题,可以使用实时操作系统(RTOS)。因为RTOS以并行的架构处理任务,单一任务的崩溃并不会牵连到整个系统。这样用户出于可靠性的考虑可能就会基于RTOS来设计自己的应用程序。SYSTICK存在的意义就是提供必要的时钟节拍,为RTOS的任务调度提供一个有节奏的“心跳”。微控制器的定时器资源一般比较丰富,比如STM32存在8个定时器,为啥还要再提供一个SYSTICK?原因就是所有基于ARM Cortex_M3内核的控制器都带有SysTick定时器,这样就方便了程序在不同的器件之间的移植。而使用RTOS的第一项工作往往就是将其移植到开发人员的硬件平台上,由于SYSTICK的存在无疑降低了移植的难度。SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等。要注意的是,当处理器在调试期间被喊停(halt)时,则SysTick定时器亦将暂停运作。
SysTick 叫做系统滴答时钟、系统定时器,属于 Cortex-M4 内核中的一个外设(外围设备),并且是24bit 向下递减 的计数器。
《STM32中文参考手册》P108 RCC 向 Cortex 系统定时器 (SysTick) 馈送 8 分频的 AHB 时钟 (HCLK)。SysTick 可使用此时钟作为时钟源,也可使用 HCLK 作为时钟源,具体可在 SysTick 控制和状态寄存器中配置。
注意:
- 定时器的位数越多,定时时间更长。
- 通过计数值间接计算定时时间,不能像操作系统直接调用函数实现延时或定时功能。
计数值,就是要进行多少个计数。
3.2 操作介绍
3.2.1 寄存器介绍
SysTick寄存器说明在《Cortex-M3权威指南》(SysTick定时器章节)有说明:
1、SysTick->CTRL:控制及状态寄存器
位0:(使能)ENABLE位。和所有外设一样,在使用之前,都需要将使能位置1,也就是开启Systic定时器,写0则关闭。
位1:TICKINT位。它是和中断相关的位,SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。Systick中断的优先级也可以设置。将这一位置1后,VAL寄存器中的值减到0时就会进入中断。如果写0,VAL寄存器中的值减到0时不会进入中断,会重新开始下一轮的计数。
位2:CLKSOURE位。它是用来选择时钟源的。Systick定时器有俩个时钟源,一个是内部时钟源,它的频率较高,一般和HCLK相同,另一个是外部时钟源,它的频率较低,是HCLK的八分之一。
位16:COUNTFALG位。这是一个状态位,它在计数的过程中是0,当VAL寄存器中的值减为0时,该位就会由硬件自动置1,并且该位只能读,不能写。
2、SysTick->LOAD:重装载数值寄存器
3、SysTick->VAL:当前数值寄存器
4、SysTick->CALIB:校准数值寄存器
3.2.2 库函数介绍
同样的,STM官方对Systick的使用也提供了库函数,让我们在使用的时候更加方便,不用挨个配置相关的寄存器。
1、时钟源选择库函数
在STM官方提供的库函数里的misc.c源文件中定义了时钟源选择库函数。
代码如下:
#define SysTick_CLKSource_HCLK_Div8 ((uint32_t)0xFFFFFFFB)
#define SysTick_CLKSource_HCLK ((uint32_t)0x00000004)
#define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
((SOURCE) == SysTick_CLKSource_HCLK_Div8))
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
/* Check the parameters */
assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
{
SysTick->CTRL |= SysTick_CLKSource_HCLK;
}
else
{
SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
}
}
2、计数初值配置函数
#define SysTick_LOAD_RELOAD_Pos 0
#define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos)
#define SysTick_CTRL_CLKSOURCE_Pos 2
#define SysTick_CTRL_CLKSOURCE_Msk (1ul << SysTick_CTRL_CLKSOURCE_Pos)
#define SysTick_CTRL_TICKINT_Pos 1
#define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos)
#define SysTick_CTRL_ENABLE_Pos 0
#define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos)
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */
SysTick->VAL = 0; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0); /* Function successful */
}
3.3 延时函数
开篇就提到,Systick定时经常用来作延时使用,它本来的目的就是为了节省资源,节省定时器资源,所以我们在使用它定时的时候也要节省中断资源,采用部通过中断的方式来进行定时。
3.3.1 延时函数初始化
代码如下:
// 延时函数初始化
void Delay_Init(uint32_t SYSCLK)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); // 设置时钟源为HCLK/8
DelayTime_us = SYSCLK / 8; // 计算每个us的时钟周期数
}
首先在程序中通过时钟源选择函数选择了外部时钟,对于STMF103ZET6芯片而言,此时的Systick时钟频率是HCLK时钟频率的八分之一,也就是72MHZ/8=9MHZ。
这里延时初始化的主要作用就是计算俩个因子fac_us和fac_ms。
fac_us:
这因子是一个值,它的意义是时间是1微秒,会有几个Systick时钟周期。SystemCoreClock的值是系统时钟频率,也就是72MHZ。
除以8000000可以看作是俩步,先除以8进行八分之一分频,得到的是9MHZ频率,再除以1000000得到的就是1微秒内Systick时钟周期的个数。
fac_ms:
这因子也是一个值,它的意义是时间是1毫秒,会有几个Systick时钟周期。因为us与ms的进率是1000,所以fac_ms的值就是在fac_us的值的基础上乘1000。
3.3.2 微秒级延时
// 延时函数 us
void Delay_us(uint32_t us)
{
uint32_t temp = us * DelayTime_us; // 计算延时的时钟周期数
SysTick->VAL = 0; // 清空计数器
SysTick->LOAD = temp; // 设置计数器的初始值
SysTick->CTRL = SysTick->CTRL | 0x01; // 使能计数器
while((SysTick->CTRL>>16 & 0x01) == 0); // 等待计数到达
SysTick->VAL = 0;
SysTick->CTRL = SysTick->CTRL & ~0x01; // 禁用计数器
}
- 感谢你赐予我前进的力量