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执行其他任务),闹钟响(中断触发)再去关火。
觉得对你有帮助就点个赞再走吧!!!
2834

被折叠的 条评论
为什么被折叠?



