超详细!必看!!STM32-系统滴答SysTick

本文详细解析了SysTick定时器的工作原理、组成部分、时钟选择、装载值设置、以及其查询方式延时和中断功能延时的实现。重点讨论了为何选择外部时钟,以及如何配置以实现高精度定时和中断触发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


  

一、SysTick是什么?

   Systick定时器是一个24bit的倒计时(向下计数)定时器,功能就是实现简单的延时。注意:系统滴答定时器主要用于简单的定时!如果需要复杂的中断处理,请使用其他定时器。
   SysTick 是一种系统定时器,通常在嵌入式系统中使用。它是 ARM Cortex-M 处理器的一个特殊定时器,用于提供系统级的定时服务。SysTick 可以用于生成定时中断,以便执行特定的任务或进行系统级的时间跟踪。
   例如:计数初值为100,经过一个时钟周期后,计数值减一,即99,98,97……1,0;计数至0后,又重新开始从100开始倒计数至0。​ 可以借此做精准延时。

二、SysTick框架图

因为SysTick是属于内核的一部分,其被捆绑在NVIC中,用于产生SYSTICK异常。
在这里插入图片描述

三、SysTick组成

​ SysTick包含四个寄存器,都是32位的寄存器(下图未标注的位端为未使用或不常用字段),分别是:

1. SysTick->CTRL

用于控制 SysTick 计时器的启停、时钟源选择、定时溢出标志等。

在这里插入图片描述

2. SysTick->LOAD

SysTick->LOAD 是 SysTick 计时器的重载值寄存器,用于设置计数器的初始值。当 SysTick 计数到 0 时,它会自动重新加载 SysTick->LOAD 中的值,并开始新的倒计时。
在这里插入图片描述

3. SysTick->VAL

SysTick->VAL 是 SysTick(系统滴答定时器)的当前计数值寄存器,用于存储当前倒计时的值。它的值会在 SysTick 运行时不断递减,每个时钟周期减 1,直到 0 时触发溢出,并重新加载 SysTick->LOAD 的值继续计时。
在这里插入图片描述

4. SysTick->CALIB

SysTick->CALIB 是 SysTick 参考时钟校准值寄存器,用于提供 系统时钟的校准值,帮助软件程序计算精确的时间间隔。该寄存器主要包含出厂校准值,用于指示 SysTick 计时器每 10ms 需要多少个时钟周期。
在这里插入图片描述

四、SysTick时钟知识点

1. 首先明白时钟频率(Hz)与时间(S)的转换。

   ●1Hz代表每秒周期震动1次, 60Hz代表每秒周期震动60次。假如滴答时钟的频率是72MHZ,72MHz表示每秒钟有72,000,000个时钟周期。那让滴答时钟计1次,时间过去了1/72μs,也就是一个时钟周期为1/72000000 s =1/72 us。
●定时1us,就需要72个时钟周期。
●定时1s,就需要72000个时钟周期。

2. 为什么需要装载预期值-1?

   答:装载值就是装载的时钟周期个数。假如是向下计数,SysTick 定时器的计数是从 LOAD 装载值寄存器的值递减到零的,所以如果你希望实现 n 个时钟周期的延时,你需要将 LOAD 寄存器设置为 n - 1。如系统时钟频率为72MHz,经过8分频后,频率为9MHz。即1s震动9000 000个周期。所以装载值为8999 999,计数器从8999 999减到0,它会溢出并重新从8999 999开始计数,从而达到周期为9000000 个时钟周期的目标。则正好为1s的时间,即实现定时1s。

3. 为什么选择经过8分频的外部时钟,而不选择内部时钟?

答:选择使用外部时钟而不是内部时钟,是为了保证定时器的精度和稳定性。
   内部时钟是由微控制器内部提供的时钟源,通常频率相对较低。在某些情况下,使用内部时钟作为SysTick的时钟源可能会导致定时器的溢出时间过长,无法满足精确的延时需求。
   外部时钟,例如外部晶体振荡器或主芯片提供的外部时钟信号,具有较高的频率和稳定性。使用外部时钟作为SysTick的时钟源可以提供更高的精度和可靠性。对于需要较准确的延时操作或时间计量的应用,选择外部时钟是更好的选择。
   因此,在该代码中选择使用外部时钟来配置SysTick定时器,以确保精确和稳定的延时功能。

4. 时钟源选择

(1)库函数— SysTick_CLKSourceConfig(时钟源)

●函数代码如下:

//#define SysTick_CLKSource_HCLK_Div8    ((uint32_t)0xFFFFFFFB)   //经过8分频的外部时钟
//#define SysTick_CLKSource_HCLK         ((uint32_t)0x00000004)   //内部时钟                                   
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)   //时钟源选择库函数

(2)寄存器

SysTick->CTRL &=~(1<<2); //选择外部时钟,必须清零默认是1内核时钟
SysTick->CTRL |=(1<<2); //选择内核时钟。

在这里插入图片描述

5. 延时范围

   ●如系统时钟频率为72MHz,经过8分频后为9MHz。1s的时钟周期个数为9000 000,1ms的时钟周期个数为9000,1us的时钟周期个数为9。
   ●VAL寄存器以及LOAD寄存器都是24位的,它的最大值是1111 1111 1111 1111 1111 1111,转化乘十进制后是16777215。即装载的最大十周周期个数为16777215。
级别的定时器,一次最大定时时长为:16777215 / 9000000 s。
毫秒级别的定时器,一次最大的定时时长16777215/9000 ms,也就是1864.135毫秒,由于对于毫秒只能取整,也就是1864毫秒。
微秒级别的定时器,一次最大定时时长是16777215/9=1864135 us。
   这就是Systick定时器循环一次所能达到的最大定时时长。也就是装载值的最大范围。当然也可以通过循环嵌套来实现更长时间的定时。

五、SysTick两种功能

1. 查询方式延时功能

   只需要定时器工作一个周期,也就是从重装载值减到0的一个过程,执行一次后需要关闭定时器,不然它还会不停的从重装载值减到0然后又从重装载值减到0无限循环。

实现功能:实现us、ms级别的延时函数。(成功)
伪代码:

实现系统的us延时(参数)
{
   1.选择时钟 建议选择经过8分频后的外部时钟。
   2.写入重装载值,设为预期值-13.禁止中断。
   4.清空计数器。
   5.使能计数器。
   6.等待时间到达,等待标志位置17.关闭计数器。
   8.清空计数器。
}

(1)寄存器版


#include "stm32f10x.h"  // 适用于 STM32F1,其他芯片请更改头文件

static u32 s_fac_num = 0;  // SysTick 计数因子
static u32 us_fac_num = 0; // 微秒级延时因子
static u32 ms_fac_num = 0; // 毫秒级延时因子

void delay_init()
{
    SysTick->CTRL &= ~(1 << 2);  // 1. 选择外部时钟源 (HCLK/8)
    
    s_fac_num = SystemCoreClock / 8;    // 计算 SysTick 计数频率 SystemCoreClock= SYSCLK_FREQ_72MHz;     
    us_fac_num = s_fac_num / 1000000;   // 计算 1us 需要的计数值
    ms_fac_num = s_fac_num / 1000;      // 计算 1ms 需要的计数值
}

void delay_us(u32 nus)
{
    if (nus == 0) return;  // 避免无效调用

    SysTick->LOAD = nus * us_fac_num - 1;  // 1. 设定倒计时初值
    SysTick->CTRL &= ~(1 << 1);            // 2. 关闭中断模式
    SysTick->VAL = 0x00;                   // 3. 计数器清零
    SysTick->CTRL |= (1 << 0);             // 4. 启动计数

    while ((SysTick->CTRL & (1 << 16)) == 0);  // 5. 等待倒计时结束
    SysTick->CTRL &= ~0x07;  // 6. 彻底关闭 SysTick
    SysTick->VAL = 0x00;     // 7. 清空计数器
}

void delay_ms(u32 nms)
{
    while (nms--) delay_us(1000);  // 逐个调用 delay_us(1000) 避免 SysTick 溢出
}

(2)库函数版

#include "stm32f10x.h"

static uint32_t fac_us = 0;  // 微秒延时因子
static uint32_t fac_ms = 0;  // 毫秒延时因子

void my_delay_init(void)
{
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); // 选择时钟源 HCLK/8
    fac_us = SystemCoreClock / 8000000;  // 计算微秒因子 (72MHz / 8 = 9MHz)
    fac_ms = fac_us * 1000;              // 计算毫秒因子
}

void my_delay_us(uint32_t nus)
{
    uint32_t temp;
    SysTick->LOAD = nus * fac_us - 1; // 计算重装值
    SysTick->VAL = 0;                 // 清空当前值
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 开启 SysTick 计数
    do
    {
        temp = SysTick->CTRL;
    } while ((temp & SysTick_CTRL_COUNTFLAG_Msk) == 0); // 等待计数到 0

    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 关闭 SysTick 计数器
    SysTick->VAL = 0;  // 清零计数器
}

void my_delay_ms(uint32_t nms)
{
    while (nms--) my_delay_us(1000); // 1 ms = 1000 us
}

2. 中断功能延时

   利用中断,一定时间进一次中断,以此来实现一个时间片轮询的操作方式。这时候,就需要计数器一直计数了,所以不能计数完成后就关闭计数器了。

实现功能:每过1ms就发送一次’systick’。

(1)库函数版本

#include "systick.h"
#include "stm32f10x.h"
#include <stdio.h>
#include "usart1.h"
#include "led.h"

/*******************************
功能:初始化SysTick_ms
参数:u32 ms - 参数单位为ms
返回值:void
注意:1ms间隔
********************************/
void SysTick_Init(void)
{
    if(SysTick_Config(SystemCoreClock / 1000)){   //每过1ms触发一次中断!  
			while(1);
		}
}

void SysTick_Handler(void)
{
   printf("systick\r\n");
}

#include "stm32f10x.h"
#include "usart1.h"
#include "delay.h"
#include "string.h"
#include "systick.h"
#include "led.h"

int main(void)
{
	// 设置NVIC中断优先级分组为2
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	// 初始化延时和LED
	LED_init();
	delay_init();
	USART_Config(115200);
	printf("start\r\n");
	SysTick_Init();
	while (1)
	{
		LED1=!LED1;
		delay_ms(1000);
	}
}

在这里插入图片描述

六、附录

上述函数中,为什么SysTick的时钟频率需要经过8分频系统时钟?
在这里插入图片描述
答:因为在时钟树框图中,Cortex系统时钟需要系统时钟经过8分频。

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值