第一章 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 主要时钟源

缩写英文全称中文全称材料
HSEHigh Speed External外部高速时钟晶体/陶瓷
HSIHigh Speed Internal内部高速时钟
LSELow Speed External外部低速时钟
LSILow Speed Internal内部低速时钟
PLLPhase 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.hsystem_stm32f4xx.c 两个文件的权限信息进行修改,需要去掉文件的只读属性。

2.2.2 配置外部晶振参数

通过原理图可知,开发板的所采用的时钟模块为 8MHz ,如下图:

image-20250413212201557

然后设定 stm32f4xx.h 文件中 HSE_VALUE 外部晶振为 8MHz,找到文件,操作如下图。

image-20250413212946921

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

设置入下图:

image-20250413213336776

第三章 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;     // 禁用计数器

}