当前位置:网站首页>Systick滴答定时器
Systick滴答定时器
2022-07-07 06:26:00 【一只大喵咪1201】
作者:一只大喵咪1201
专栏:《STM32学习》
格言:你只管努力,剩下的交给时间!
描述
Systick定时器,是一个简单的定时器,对于CM3,CM4内核芯片,都有Systick定时器。Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。
Systic定时器也叫做滴答定时器,是一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
相关寄存器介绍
寄存器CTRL
叫做SysTick控制及状态寄存器,它有四个位起控制和监视作用,分布情况如下图
位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,并且该位只能读,不能写。
寄存器LOAD
叫做SysTick重装载数值寄存器,它是一个24位的寄存器,分布如下图
这24位是从0到23的,叫做RELOAD位。它里面放的值就是计时用的初值,VAL寄存器中的值就是从这里取出的,当VAL寄存器中的值减为0后,就会自动从LOAD寄存器中再将这个初值取出放入VAL寄存器中,然后继续做减一处理,一直循环下去,除非将使能位写0。
寄存器VAL
叫做Sysytic当前数值寄存器。它是一个24位的寄存器,分布如下图
这24位也是从0到23的,叫做CURRENT位。它里面放的值就是用来计时减一的初值,当这里的值减1到0后,就会自动从LOAD寄存器中再取过来一个初值,继续做减1操作,如此循环,除非将使能位写0。而且VAL寄存器中的值是可以修改的,在计时过程中如果想修改计时时间,则将要计时的初值先放入LOAD寄存器中,再对VAL寄存器写1,则VAL寄存器就会清0,然后从LOAD寄存器中取初值进行新的计时。
寄存器CALIB
叫做SysTick校准数值寄存器。它的分布如下图
这个寄存器很少使用到,等用到的时候本喵再作详细介绍。
与Systick有关的库函数
同样的,STM官方对Systick的使用也提供了库函数,让我们在使用的时候更加方便,不用挨个配置相关的寄存器。
时钟源选择库函数
在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;
}
}
通过宏定义以及合法性的判断,我们可以看到,该函数的形参内容只能是SysTick_CLKSource_HCLK_Div8外部时钟,或SysTick_CLKSource_HCLK
内部时钟。
当形参是内部时钟,就将CTRL寄存器中的时钟源选择位(CLKSOURE)置为1,这是通过寄存器地址名的映射实现的,可以看前面相应文章中的映射规则。
当形参是外部时钟,就将CTRL寄存器中的时钟源选择位(CLKSOURE)清0,这是通过寄存器地址名的映射实现的,可以看前面相应文章中的映射规则。
如此一来,时钟源的选择就搞定了。
计数初值配置函数
STM官方提供了计数初值配置函数,我们只需要计算出需要计数几个Systick的时钟周期即可,至于LOAD寄存器以及VAL寄存器中的值就不用我们计算好再放进去了。
代码如下:
#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 */
}
首先判断一下在这个Systick时钟周期次数下,计数的初值会不会超出VAL寄存器24位值的大小,如果超了,就返回1。
如果没有超出最大值,就给LOAD寄存器的RELOAD位赋计时初值,至于赋值的具体内容,后面讲延时函数时说明。
赋值完计时初值后,将VAL寄存器中的值清0,直接从LOAD寄存器中取计时初值,尽量避免计时误差。
接下来就是选择时钟源,设置是否中断,以及使能Systick定时器。
这里的时钟源选择和上面的时钟源选择函数所进行的功能是一样的,只需要用一个就可以,当然重复配置也每问题,但是要配置的一样才行。
延时函数
开篇就提到,Systick定时经常用来作延时使用,它本来的目的就是为了节省资源,节省定时器资源,所以我们在使用它定时的时候也要节省中断资源,采用部通过中断的方式来进行定时。
本喵就来讲解一下大家使用比较多的由正点原子写的延时函数。
代码如下:
void delay_init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
fac_us=SystemCoreClock/8000000; //为系统时钟的1/8
fac_ms=(u16)fac_us*1000; //非OS下,代表每个ms需要的systick时钟数
}
首先在程序中通过时钟源选择函数选择了外部时钟,对于STMF103ZET6芯片而言,此时的Systick时钟频率是HCLK时钟频率的八分之一,也就是72MHZ/8=9MHZ。
这里延时初始化的主要作用就是计算俩个因子fac_us和fac_ms。
1️⃣fac_us:
这因子是一个值,它的意义是时间是1微秒,会有几个Systick时钟周期。SystemCoreClock的值是系统时钟频率,也就是72MHZ。
除以8000000可以看作是俩步,先除以8进行八分之一分频,得到的是9MHZ频率,再除以1000000得到的就是1微秒内Systick时钟周期的个数。
2️⃣fac_ms:
这因子也是一个值,它的意义是时间是1毫秒,会有几个Systick时钟周期。因为us与ms的进率是1000,所以fac_ms的值就是在fac_us的值的基础上乘1000。
如此一来,我们将Systick定时器初始化好了,其实就是计算好了俩个因子。
接下来我看看如何使用它。
微秒级别的定时程序
代码如下:
oid delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
将初始化时计算出来的因子与需要延时的时间相乘,得到就是节拍数,也就是计时时间是多少个Systick时钟周期。
然后将这个值放在LOAD寄存器中的RELOAD位中当作计时初值。
再将VAL寄存器中的值清0,让它直接从LOAD寄存器中取计时初值,以减少定时误差。
然后将CRTL中的ENABLE位使能,也就是打开定时器
接下便进入了循环。不停地读取CTRL寄存器中的值,就是为了看位16也就是COUNTFLAG的状态,如果它0,说明没有计时结束,则继续循环,如果它是1,说明定时结束了。
最后关闭计数器,就是将使能位清0,并且将VAL寄存器中的值清0。
以上便是微秒级别的延时程序,接下来我们看看毫秒级别的延时程序
毫秒级别的定时程序
代码如下:
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
可以看到,它的过程是和微秒级别的延时程序一样的,只是使用的因子不同,这里使用的是fac_ms,而这个值在初始化函数中就已经计算过了。
以上便是延时函数的整个内容,但是这个延时是有时间大小限定的,不能说想延时多久就延时多久,接下来本喵带大家看一下它的延时范围是多少。
延时范围
拿本喵使用的STM32F103ZET6芯片为例,它的系统时钟是72MHZ的。Systick定时器的时钟源选择外部时钟后是9MHZ的。
9000000除以1000000得到的结果是9,所以fac_us的值就是9,也就是1微秒内有9个Systick时钟。
而fac_ms的值是9000,也就是1毫秒内有9000个Systick时钟。
而VAL寄存器已经LOAD寄存器都是24的,它的最大值是
111111111111111111111111,转化乘十进制后是16777215。
对于微秒级别的定时,最大定时时长是
16777215/9=1864135微秒,也就是1864.135毫秒。
当使用是毫秒级别的定时时,最大的定时时长只能取整数个毫秒,也就是1864毫秒。
这就是Systick定时器循环一次所能达到的最大定时时长。当然也可以通过循环嵌套来实现更长时间的定时。
总结
定时或者延时是我们在使用STM32中经常用的功能,而Systick定时器就可以解决我们大多数的定时情况,而且使用简单,占用资源也少。总的来说,Systick定时器的使用步骤就是,选择时钟源,计算计数初值,放入LOAD定时器中,然后启动定时器,再监视是否完成定时。
如果对您有帮助,请一键三连支持下本喵。
边栏推荐
- Why choose cloud native database
- Data analysis methodology and previous experience summary 2 [notes dry goods]
- MySQL主从延迟的解决方案
- OpenGL frame buffer
- oracle一次性说清楚,多种分隔符的一个字段拆分多行,再多行多列多种分隔符拆多行,最终处理超亿亿。。亿级别数据量
- Implement custom memory allocator
- cmake命令行使用
- 年薪50w阿裏P8親自下場,教你如何從測試進階
- Simulation volume leetcode [general] 1557 The minimum number of points that can reach all points
- Frequently Asked Coding Problems
猜你喜欢
Explain Huawei's application market in detail, and gradually reduce 32-bit package applications and strategies in 2022
2022-07-06 Unity核心9——3D动画
2022-06-30 unity core 8 - model import
Led analog and digital dimming
Pointer advanced, string function
Screen automatically generates database documents
硬核分享:硬件工程师常用工具包
MySQL主从延迟的解决方案
Panel display technology: LCD and OLED
[Yugong series] February 2022 U3D full stack class 005 unity engine view
随机推荐
ncs成都新電面試經驗
[Yugong series] February 2022 U3D full stack class 005 unity engine view
Shell script for changing the current folder and the file date under the folder
Test pits - what test points should be paid attention to when adding fields to existing interfaces (or database tables)?
Output all composite numbers between 6 and 1000
年薪50w阿裏P8親自下場,教你如何從測試進階
Port occupation troubleshooting
如何统计项目代码行数
Newly found yii2 excel processing plug-in
Calf problem
FPGA knowledge accumulation [6]
Analysis of abnormal channel number information before and after AGC re signature service
Common operating commands of Linux
Quick sorting (detailed illustration of single way, double way, three way)
Opencv converts 16 bit image data to 8 bits and 8 to 16
LeetCode 736. Lisp 语法解析
RuntimeError: Calculated padded input size per channel: (1 x 1). Kernel size: (5 x 5). Kernel size c
leetcode135. Distribute candy
let const
硬核分享:硬件工程师常用工具包