《一文带你搞懂STM32的SysTick系统定时器》

1.SysTick是什么

SysTick是ARM Cortex-M系列处理器内置的一个24位向下计数定时器,核心功能是提供统一的、可移植的定时服务,常用于操作系统(如RTOS)的任务调度、延时函数实现等场景。
 
其关键特性可总结为以下3点:
 
1. 硬件集成:无需外接定时器芯片,直接集成在Cortex-M内核中,简化硬件设计。
2. 自动重装:支持“自动重装模式”,计数器减到0后会自动加载预设的初始值并重新计数,可生成周期性中断。
3. 通用性:因属于内核外设而非芯片厂商自定义外设,基于SysTick的代码可在不同品牌的Cortex-M芯片(如STM32、Kinetis)间复用,提升移植性。

2.SysTick原理深度解析

SysTick的核心是ARM Cortex-M内核内置的24位递减计数器,通过“时钟源输入→计数器递减→中断触发/计数清零”的逻辑,实现定时或延时功能,其原理可拆解为4个核心模块。

2.1 核心组成:4个关键寄存器

SysTick的所有操作均通过配置4个内核寄存器实现,寄存器直接决定定时器的工作状态,具体功能如下:
 
- CTRL(控制及状态寄存器):核心控制位,用于开启/关闭SysTick、选择时钟源(内核时钟/内核时钟的1/8)、使能/禁止计数到0时的中断。
- LOAD(重装载寄存器):存储计数器的初始值。当计数器(VAL寄存器)减到0时,会自动从LOAD中加载初始值,实现“自动重装”循环。
- VAL(当前值寄存器):实时显示计数器当前数值,每过1个时钟周期减1;当减到0时,会触发 COUNTFLAG 标志(在CTRL中),若中断使能则触发SysTick中断。
- CALIB(校准寄存器):存储厂商预设的校准值,用于快速配置特定频率(如1ms)的定时,简化初始化流程(部分低端Cortex-M芯片可能省略此寄存器)。

2.2 工作流程:3步实现定时

SysTick的工作逻辑围绕“递减计数”展开,以最常用的“自动重装+中断”模式为例,完整流程如下:
 
1. 初始化配置:通过CTRL寄存器选择时钟源(如内核时钟 HCLK ),向LOAD寄存器写入预设初始值(如 HCLK/1000 ,实现1ms定时),最后开启SysTick定时器和中断。
2. 计数循环:VAL寄存器从LOAD的初始值开始,每经过1个时钟周期减1;当VAL减到0时, COUNTFLAG 标志置1,同时VAL自动重新加载LOAD的值,开始下一轮计数。
3. 中断触发:若CTRL中开启了SysTick中断,当VAL减到0时会触发中断请求;CPU响应中断后,执行SysTick中断服务函数(如实现RTOS的任务切换、延时计时等),同时 COUNTFLAG 标志自动清零。

2.3 关键特性:2个核心优势

- 内核级外设,兼容性强:SysTick属于Cortex-M内核自带外设,而非芯片厂商(如ST、NXP)自定义外设。因此,基于SysTick的代码(如延时函数、RTOS调度)可在不同品牌的Cortex-M芯片(STM32、Kinetis、GD32等)间直接复用,无需修改硬件相关配置。
- 24位计数,定时范围灵活:计数器为24位,最大计数值为 2^24 - 1 = 16777215 。若时钟源为72MHz(常见内核时钟),最大定时时长约为 16777215 / 72000000 ≈ 0.233秒 ;若使用“中断嵌套+软件计数”,可扩展到任意时长(如秒、分钟级)。

3.实战应用——如何配置SysTick

3.1 查询法

//------------------------------查询法延时函数---------------------

/**
 * @brief  初始化SysTick定时器,用于延时功能
 * @note   配置SysTick时钟源为HCLK(系统时钟)的8分频
 *         初始化后,可使用delay_us、delay_ms、delay_s函数进行延时
 */
void delay_Init(void)
{
    // 选择SysTick时钟源为HCLK/8,降低计数频率,便于实现更精确的短延时
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
}


/**
 * @brief  微秒级延时函数
 * @param  us: 延时的微秒数,最大值为1864135
 * @note   利用SysTick定时器的计数功能实现精确延时
 *         延时结束后自动关闭SysTick定时器
 */
void delay_us(uint32_t us)  //最大值 1864135
{
    uint32_t temp = 0;  // 用于存储SysTick控制寄存器的值
    
    // 加载延时计数到SysTick重装载寄存器
    // fac_us为预计算的微秒级计数因子(=系统时钟频率/8/1000000)
    SysTick->LOAD = fac_us * us;
    
    // 清除SysTick当前计数值寄存器,确保从0开始计数
    SysTick->VAL  = 0;          
    
    // 使能SysTick定时器,开始计数
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
    
    // 等待延时结束:
    // 1. temp & 0x01 确保SysTick定时器仍处于使能状态
    // 2. !(temp & (0x01 << 16)) 等待计数结束标志位(第16位)置位
    do
    {
        temp = SysTick->CTRL;  // 读取控制寄存器值
    }
    while( (temp & 0x01) && (!(temp & (0x01 << 16))) );
    
    // 清除当前计数值,准备下一次使用
    SysTick->VAL  = 0;
    
    // 关闭SysTick定时器
    SysTick->CTRL &= !SysTick_CTRL_ENABLE_Msk;
}


/**
 * @brief  毫秒级延时函数
 * @param  ms: 延时的毫秒数,最大值为1864
 * @note   原理同delay_us,只是计数因子为毫秒级
 */
void delay_ms(uint32_t ms)  //最大值 1864
{
    uint32_t temp = 0;  // 用于存储SysTick控制寄存器的值
    
    // 加载延时计数到SysTick重装载寄存器
    // fac_ms为预计算的毫秒级计数因子(=系统时钟频率/8/1000)
    SysTick->LOAD = fac_ms * ms;
    
    // 清除SysTick当前计数值寄存器,确保从0开始计数
    SysTick->VAL  = 0;          
    
    // 使能SysTick定时器,开始计数
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
    
    // 等待延时结束:
    // 1. temp & 0x01 确保SysTick定时器仍处于使能状态
    // 2. !(temp & (0x01 << 16)) 等待计数结束标志位(第16位)置位
    do
    {
        temp = SysTick->CTRL;  // 读取控制寄存器值
    }
    while( (temp & 0x01) && (!(temp & (0x01 << 16))) );
    
    // 清除当前计数值,准备下一次使用
    SysTick->VAL  = 0;
    
    // 关闭SysTick定时器
    SysTick->CTRL &= !SysTick_CTRL_ENABLE_Msk;
}


/**
 * @brief  秒级延时函数
 * @param  s: 延时的秒数
 * @note   基于毫秒延时函数实现,通过循环调用delay_ms(1000)实现秒级延时
 */
void delay_s(uint32_t s)
{
    uint32_t temp = s;  // 存储需要延时的秒数
    
    // 循环s次,每次延时1000毫秒(1秒)
    while(temp--)
    {
        delay_ms(1000);  // 每次循环延时1秒
    }
}

3.2 中断法

//---------------------------------中断法实现延时功能-------------------------------------

// 静态全局变量,用于存储毫秒级延时计数器
// 静态变量限定作用域,仅在当前文件可见
static uint32_t TimingDelay = 0;

// 静态全局变量,用于记录系统启动后的毫秒滴答数
// 可用于获取系统运行时间
static uint32_t Tick = 0; 

/**
 * @brief  初始化SysTick定时器,配置为中断方式工作
 * @note   配置SysTick每1毫秒产生一次中断
 *         系统时钟频率为72MHz时,SystemCoreClock/1000 = 72000,即每计数72000次产生一次中断
 */
void Systick_Interrupt_Init(void)
{
    // 调用库函数配置SysTick定时器
    // 参数为自动重装载值,当计数到0时产生中断并重新加载该值
    // SystemCoreClock/1000 配置为1ms产生一次中断
    SysTick_Config(SystemCoreClock/1000); // 时钟频率72MHz, 1ms中断一次
}

/**
 * @brief  SysTick定时器中断服务函数
 * @note   每1毫秒被调用一次,用于更新系统时间和延时计数器
 */
void SysTick_Handler(void)
{
    // 系统滴答数加1,记录系统运行的毫秒数
    Tick++;
    
    // 如果延时计数器不为0,则递减计数器
    // 当计数器减到0时,表示延时时间到达
    if(TimingDelay != 0)
    {
        TimingDelay--;
    }
}

/**
 * @brief  毫秒级延时函数(基于中断方式)
 * @param  ms: 需要延时的毫秒数
 * @note   非阻塞式计数,实际延时可能存在±1ms误差
 *         函数内部通过等待TimingDelay减到0实现延时
 */
void Delay_ms(uint32_t ms)
{
    // 将延时毫秒数赋值给延时计数器
    TimingDelay = ms;
    
    // 等待中断服务程序将TimingDelay减到0
    // 在此期间CPU可以执行其他任务(如果有)
    while(TimingDelay != 0) {}
}

/**
 * @brief  获取系统启动后的毫秒数
 * @retval 当前系统滴答数(Tick),单位为毫秒
 * @note   可用于计算时间间隔或记录事件发生时间
 */
uint32_t GetTick(void)
{
    return Tick;
}

查询法(轮询法)与中断法是SysTick实现定时/延时的两种核心方式,核心区别在于CPU是否需要持续等待。

举个通俗例子理解
- 查询法:像你煮开水时,一直盯着水壶(CPU持续查询),直到水开( COUNTFLAG 置1)才关火,期间什么都做不了。
- 中断法:煮开水时设置闹钟(SysTick中断),然后去看书(CPU执行其他任务),闹钟响(中断触发)再去关火。

觉得对你有帮助就点个赞再走吧!!!
 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值