STM32最基础定时器SysTick(系统滴答)不难理解
参考资料:《Cortex-m权威指南第三版》- 9.5 The SysTick timer
0-关于数字系统的时间概念
在了解 SysTick前,我们先搞清楚数字系统的时间问题。在数字系统运作中,主要通过识别脉冲来处理时间问题,,实际上并没有直接的时间概念。
脉冲的主要参数:频率、周期
、占空比D
数字系统衡量时间的办法:对已知频率为的脉冲信号进行计数:
时间 其中 n–脉冲计数值
— 已知脉冲周期
脉冲数 其中
— 预设时间长度
— 已知脉冲频率
因此,1)对已知频率的脉冲进行计数,可以实现了时间测量;
2)通过控制计数脉冲数量,可以实现定时 。
所以,数字系统中的时间问题主要是脉冲计数问题,定时器本质上仍然是计数器!所以在单片机的资料中,定时器部分常常看到“定时器/计数器”这一术语。
1-SysTick(滴答时钟)简介
SysTick是一个24位减计数定时器,属于Cortex-M处理器的内核资源,所有的Cortex-M内核处理器都具有相同的SysTick(方便了程序的移植),它集成于嵌套向量中断控制器(NVIC)中,可用于周期性中断产生、延时函数实现、定时时间测量等,SysTick常用于产生周期为1ms的基准时钟,当用于基于OS任务轮询和时间片管理时,用户程序无法停止SysTick,即使是系统在睡眠模式下也能工作。
2-SysTick工作原理
2.1 功能框图
SysTick是一个24位的减法计数器,它有两个时钟源可选:1-内核时钟(STM32F10x系列常规设置为72MHz),0-内部参考时钟(一般是内核时钟/8=9MHz),通过设置控制/状态寄存器的bit2进行时钟源选择。设置控制/状态寄存器的bit0=1控制其开始工作。
当计数器计数到0时:
1)触发Reload Value重载寄存将起始值(初值)赋给计数器;
2)将控制/状态寄存器的bit16-countflag置位=1。
3)如果控制/状态寄存器的bit1-中断使能=1,开通中断屏蔽寄存器(与门),将允许向NVIC发送中断挂起请求,产生15号‘异常’。


2.2 SysTick寄存器
SysTick共有四个寄存器,
1)Calibration校准寄存器。此寄存器可用于时钟的校准(校准寄存器用于提供系统时钟的校准信息,以确保 SysTick 定时器的准确性。它包含有关时钟周期的信息,帮助系统在不同的工作条件下保持稳定的时间基准)。在使用标准库进行时钟初始化时,库函数会自动处理该寄存器的相关设置,用户无需干预。
2)Current Value寄存器。读–可以随时读取计数器当前值。写-程序中对其进行写操作时会有两个结果:将计数器清零=0 同时会将控制/状态寄存器的bit16=0清零(有效避免了产生错误的“减计数至0”的countflag信号)。
3)Relaod Value寄存器。24位寄存器,程序中通过对其赋值设定SysTick脉冲计数值。当Current Value寄存器减计数至0时,会自动将计数值赋值给Current Value寄存器。
4)控制/状态寄存器。可读/写,编程时主要对其进行设置。bit0~2为控制位,分别控制减计数器、中断功能、时钟源选择。bit16在减计数器减至0是会将该位置位=1,当程序中对其进行读操作后或者对CurrentVlaue进行写操作时,会将该位清零=0。
3-SysTick的应用
3.1 SysTick时间长度控制基础
SysTick
为
24 位计数器,其最大计数值。
由计数值 n 可确定对应的时间
由预定的时间 t 可确定相应的计数值
3.1.1当时钟源为内核时钟:72 MHz时
1) 最大计时时长:
适合微秒级
us级定时至200ms间时间长度定时,
甚至是ns级延时。但
无法满足1~1000
ms范围
的ms级定时/延时需求。
2)定时/延时时长控制(计数值控制)。
根据预设的时长t,确定脉冲数
毫秒级定时:
(t<233ms)
微秒级定时:
(
t<233000us
)
3.1.2当时钟源为参考时钟:9MHz时
1) 最大计时时长:
满足1~1000
ms范围
的
ms级定时需求,由于也可用于us级延时
。
2)定时/延时时长控制(计数值控制)。
根据预设的时长t,确定脉冲数
毫秒级定时:
(t<1864ms)
微秒级定时:
(
t<1864 000us
)
由以上分析可知,两种时钟源下,均可实现us级定时,由于内核时钟源72MHz的脉冲周期短,更是能实现ns级延时精度控制。当时钟源为
参考时钟9MHz时,相对脉冲周期长,能实现更长的定时/延时周期。
3.2 利用SysTick实现延时函数
实现原理:对Relaod Value重载寄存器设置脉冲计数值,通过查询控制/状态寄存器SysTick->CTRL的bit16=1,判断延时是否时间到 。 本例中采用内核时钟实现us延时,参考时钟源实现ms级延时。函数实现C文件名称:SysTick.c
延时函数实现的基本步骤:
1)停止定时器 SysTick->CTRL=0;
2)赋值计数初值 SysTick->LOAD (目标值-1)
3)写 当前值寄存器SysTick->VAL,清空该寄存器,同时将countflag清零
4)赋值SysTick->CTRL设置定时器工作参数:时钟源、中断屏蔽、启用定时器
5)用while函数等待直至技术到0(直到SysTick->CTRL的bit16=1)
6)停止定时器SysTick->CTRL=0;
3.2.1 毫秒级延时实现
void SysTickDelay_Nms(u32 Nms)
{
if(Nms>1863)Nms=1863; //load值超界处理,最长1864ms
SysTick->CTRL=0; //关闭减计数器
SysTick->LOAD=Nms*(9000-8); //设置计数器自动加载值,8为校准值
SysTick->VAL = 0; //清除当前计数值并自动加载LOAD值,同时复位COUNTFLAG位
SysTick->CTRL=1; //设置时钟源0-为参考时钟9MHz、启动计数器(该命令后计数器立刻开始工作)
while(!(SysTick->CTRL&(1<<16))); //等待,直到计数直到 ‘0’ 使bit16:countflag=1
SysTick->CTRL=0X00000000; //关闭减计数器
}
3.2.2 微秒延时实现
void SysTickDelay_Nus(u32 Nus)
{
if(Nus>233000)Nus=233000; //load值超界处理,72m时钟时最长时间233毫秒
SysTick->LOAD=Nus*72-85; //计数初值计数,加载,85为逻辑分析仪调试获得的校准值,供参考
SysTick->VAL = 0; //清除当前计数值并自动加载LOAD值,同时复位COUNTFLAG位
SysTick->CTRL =0x05; //bit0=1启动减计数器,开始倒数、bit2=1选择内核时钟
while(!(SysTick->CTRL&(1<<16)));//等待,直到计数直到 ‘0’ 使bit16:countflag=1
SysTick->CTRL=0X00000000; //关闭计数器
SysTick->VAL=0; //清空计数器
}
3.3 利用SysTick实现程序运行时间测量
SysTick 定时器可用于时间测量。例如,可以使用以下代码测量 函数的持续时间:
/***
@brief:Systick实现 程序运行时间测量,72时钟源下可测量运行在233毫秒以内的程序
***/
uint32_t SysTick_TimeMeasure(void)
{
uint32_t start_time, stop_time, cycle_count;
SysTick->CTRL = 0; // 关闭 SysTick
SysTick->LOAD = 0xFFFFFFFF; // 将加载值设为最大(测量时间最长)
SysTick->VAL = 0; // 清除current value to 0
SysTick->CTRL = 0x5; // 开启SysTick, 选择内核时钟72M时钟(测量基础时钟频率越高,测量误差越小)
while(SysTick->VAL != 0); // 等待计数器初值加载完成(减少由加载初值产生的时间延时)
start_time = SysTick->VAL; // 记录起始计数值
function(); //执行被测函数
stop_time = SysTick->VAL; // 记录结束时计数值
cycle_count = start_time-stop_time;//计算计数值差值,function运行所耗时间=计数差值*时钟源脉冲周期
return cycle_count ;
}
3.4 SysTick定时中断实现
在STM32的参考手册中,在时钟源为参考时钟9MHz时,对1ms延时进行了校准,所以本例实现1ms定时中断。
初始化函数:
/**
NVIC中进行中断优先级设置
**/
void NVIC_SetPriority_SysTick(IRQn_Type IRQn, uint32_t priority)
{
if(IRQn < 0) {
SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for Cortex-M3 System Interrupts */
else {
NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for device specific Interrupts */
}
void SysTickINT_1ms_Init(void)
{
// 设置中断优先级
NVIC_SetPriority_SysTick(SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
SysTick->CTRL=0; //关闭减计数器
SysTick->LOAD=9000-1; //设置计数器自动加载值
SysTick->VAL = 0; //清除当前计数值并自动加载LOAD值,同时复位COUNTFLAG位
SysTick->CTRL=0x07; //设置时钟源0-为参考时钟9MHz、开启中断、启动计数器(该命令后计数器立刻开始工作)
}
中断服务程序:
在进入中断服务函数后由硬件自动清除SysTick相关的中断标识
/**-------------------------------------------------------
* @函数名 SysTick_Handler
* @功能 系统节拍定时器服务请求处理函数
* @参数 无
* @返回值 无
* @说明 :中断挂起/标识在进入中断服务函数后由硬件自动清除
***------------------------------------------------------*/
extern uint8_t Time1sFlag; //1秒标识,在main函数中定义和处理
void SysTick_Handler(void)
{
static uint16_t TimeMsCount;
TimeMsCount++;
if(TimeMsCount>1000) //1秒到
{
Time1sFlag=1; //时间到设置标识,在main函数中处理后复位
TimeMsCount=0;//复位计数值
}
}
总结:
SysTick为ARM微处理器中最基本的定时器,其功能简单易于使用,STM32中其他基本定时器和更复杂的高级定时器均是在其基础上进行功能扩充而设计,或者是基于定时器集成某些专用功能电路,比如定时器中实现PWM输出电路、脉冲测量等等。所以掌握好SysTick的基本工作原理、深刻理解其定时和延时的本质对于学习好单片机具有非常重要的意义。
作者:Spark星源科创