一.Systick系统定时器简介:
SysTick—系统定时器是属于 CM3 内核中的一个外设,内嵌在 NVIC 中。系统定时器是一个 24bit 的向下递减的计数器,计数器每计数一次的时间为 1/SYSCLK,一般我们设置系统时钟 SYSCLK 等于 72M。当重装载数值寄存器的值递减到 0 的时候,系统定时器就产生一次中断,以此循环往复。因为 SysTick 是属于 CM3 内核的外设,所以所有基于 CM3 内核的单片机都具有这个系统定时器,使得软件在 CM3 单片机中可以很容易的移植。系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。
二.Systick系统定时器的功能框图:
三.Systick寄存器介绍:
SysTick—系统定时器有 4 个寄存器,简要介绍如下。在使用 SysTick 产生定时的时候,只需要配置前三个寄存器,最后一个校准寄存器不需要使用。(详见《Cortex-M3 内核编程手册》 -4.5 章节 SysTick Timer(STK))
表 1 SysTick 寄存器汇总
寄存器名称 | 寄存器描述 |
---|---|
CTRL | SysTick 控制及状态寄存器 |
LOAD | SysTick 重装载数值寄存器 |
VAL | SysTick 当前数值寄存器 |
CALIB | SysTick 校准数值寄存器 |
注释:(个人理解,有错误望指正)
1.VAL寄存器是保存计数器计数的当前值,也就是计数器计数到什么值,VAL寄存器里面的值就是多少(计数器是计数器,只是计数,VAL寄存器只是保持每次计数的数值,两者不是一个概念!)。
2.LOAD寄存器是保存重装载数值的寄存器,用户配置重装载寄存器的值,实际就是写入到该寄存器。
3.当系统定时器的计数器在时钟(STK_CLK 72M或9M)的驱动下向下计数时,重装载寄存器里面的数值,会被加载到计数器里面,也就是计数器初始值就是重装载值。然后计数器向下计数,并且从重装载寄存器的值计数到0时,产生一次中断。同样的VAL寄存器里面的数值也是相同变化。(系统定时器的时钟源是系统时钟SYSCLK = 72MHZ经过8分频或者不分频后产生时钟,所以可以为9M或者72M)。
现根据《Cortex-M3 内核编程》手册总结下寄存器的定义和说明:
表 2 SysTick 控制及状态寄存器
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
16 | COUNTFLAG | R/W | 0 | 如果在上次读取本寄存器后, SysTick 已经计到了 0,则该位为 1。 |
2 | CLKSOURCE | R/W | 0 | 时钟源选择位, 0=AHB/8, 1=处理器时钟 AHB |
1 | TICKINT | R/W | 0 | 1=SysTick 倒数计数到 0 时产生 SysTick 异常请求, 0=数到 0 时无动作。也可以通过读取COUNTFLAG 标志位来确定计数器是否递减到0 |
0 | ENABLE | R/W | 0 | SysTick 定时器的使能位 |
表 3 SysTick 重装载数值寄存器
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
23:0 | RELOAD | R/W | 0 | 当倒数计数至零时,将被重装载的值 |
表 4 SysTick 当前数值寄存器
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
23:0 | CURRENT | R/W | 0 | 读取时返回当前倒计数的值,写它则使之清零,同时还会清除在 SysTick 控制及状态寄存器中的 COUNTFLAG 标志 |
四.Systick中断优先级设置和判断:
SysTick 属于内核的外设,有关的寄存器定义和库函数都在内核相关的库文件
core_cm3.h 中 。
介绍一下重要的知识 :
(1)SysTick 配置库函数
// SysTick 配置库函数
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
// 不可能的重装载值,超出范围
if (ticks > SysTick_LOAD_RELOAD_Msk)
return (1);
// 设置重装载寄存器
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
// 设置中断优先级
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
// 设置当前数值寄存器
SysTick->VAL = 0;
// 设置系统定时器的时钟源为 AHBCLK=72M
// 使能系统定时器中断
// 使能定时器
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0);
}
用固件库编程的时候我们只需要调用库函数 SysTick_Config()即可,
形参 ticks 用来设置重装载寄存器的值,最大不能超过重装载寄存器的值 2^24,当重装载寄存器的值递减到 0的时候产生中断,
然后重装载寄存器的值又重新装载往下递减计数,以此循环往复。紧随其后设置好中断优先级,
最后配置系统定时器的时钟等于 AHBCLK=72M,使能定时器和定时器中断,这样系统定时器就配置好了,
一个库函数搞定。
SysTick_Config()库函数主要配置了 SysTick 中的三个寄存器: LOAD、 VAL 和 CTRL,
有关具体的部分看代码注释即可。
(2)配置 SysTick 中断优先级
在 SysTick_Config()库函数还调用了固件库函数 NVIC_SetPriority()来配置系统定时器的中断优先级,该库函数也在 core_m3.h 中定义,原型如下:
static __INLINE void NVIC_SetPriority(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 */
}
函数首先先判断形参 IRQn 的大小,如果是小于 0,则表示这个是系统异常,系统异常
的优先级由内核外设 SCB 的寄存器 SHPRx 控制,如果大于 0 则是外部中断,外部中断的
优先级由内核外设 NVIC 中的 IPx 寄存器控制。
因为 SysTick 属于内核外设,跟普通外设的中断优先级有些区别,并没有抢占优先级
和子优先级的说法。
在 STM32F103 中,内核外设的中断优先级由内核 SCB 这个外设的寄
存器: SHPRx(x=1.2.3)来配置。有关 SHPRx 寄存器的详细描述可参考《Cortex-M3 内核
编程手册》 4.4.8 章节。下面我们简单介绍下这个寄存器。
SPRH1-SPRH3 是一个 32 位的寄存器,但是只能通过字节访问,每 8 个字段控制着一
个内核外设的中断优先级的配置。在 STM32F103 中,只有位 7:4 这高四位有效,低四位没
有用到,所以内核外设的中断优先级可编程为: 0~15,只有 16 个可编程优先级,数值越小,
优先级越高。
如果软件优先级配置相同,那就根据他们在中断向量表里面的位置编号来决
定优先级大小,编号越小,优先级越高。
表 5 系统异常优先级字段
异常 | 字段 | 寄存器描述 |
---|---|---|
Memory management fault | PRI_4 | SHPR1 |
Bus fault | PRI_5 | SHPR1 |
Usage fault | PRI_6 | SHPR1 |
SVCall | PRI_11 | SHPR2 |
PendSV | PRI_14 | SHPR3 |
SysTick | PRI_15 | SHPR3 |
如果要修改内核外设的优先级,只需要修改下面三个寄存器对应的某个字段即可。
表 6 SHPR1 寄存器
表 7 SHPR2 寄存器
表 8 SHPR3 寄存器
从表8知:PRI_15[7:0]控制着SysTick的优先级,但是STM32F103只有高4bit控制着优先级,所以实际PRI_15[7:4]控制着优先级,所以优先级范围是0~15(2^4 即2的4次方)。
在系统定时器中,配置优先级为(1UL << __NVIC_PRIO_BITS) - 1UL),其中宏
__NVIC_PRIO_BITS 为 4,那计算结果就等于 15,可以看出系统定时器此时设置的优先级
在内核外设中是最低的,如果要修改优先级则修改这个值即可,范围为: 0~15。
1. // 设置系统定时器中断优先级
2. NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
2点注意点:
(1)片上外设和systick(内核外设)中断优先级怎么比较?:
如果我同时使用了systick 和片上外设呢? 而且片上外设也刚好需要使用中断,
那 systick 的中断优先级跟外设的中断优先级怎么设置? 会不会因为 systick 是内核里面的外设,
所以它的中断优先级就一定比内核之外的外设的优先级高?
外设在设置中断优先级的时候,首先要分组,然后设置抢占优先级和子优先级。
而 systick 这类内核的外设在配置的时候,只需要配置一个寄存器即可,取值范围为 0~15。
既然配置方法不同,那如何区分两者的优先级?
下面举例说明:
比如配置一个外设的中断优先级分组为 2,抢占优先级为 1,子优先级也为 1,
systick的优先级为固件库默认配置的 15。 当我们比较内核外设和片上外设的中断优先级的时候,
我们只需要抓住 NVIC 的中断优先级分组不仅对片上外设有效,同样对内核的外设也有效。
我们把 systick 的优先级 15 转换成二进制值就是 1111(0b),又因为 NVIC 的优先级分组 2,
那么前两位的 11(0b)就是 3,后两位的 11(0b)也是 3。无论从抢占还是子优先级都比我们设
定的外设的优先级低。 如果当两个的软件优先级都配置成一样,那么就比较他们在中断向
量表中的硬件编号,编号越小,优先级越高。
(2)系统定时器中断优先级怎么修改?:
设定模块中断优先级的函数为:
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
将后面那个 粗字体的“1”改为其他值即可改变优先级,然后根据上述的**注意点(1)**进行计算就能知道优先级怎么计算和比较了!
五.SysTick 初始化函数:
/**
* @brief 启动系统滴答定时器 SysTick
* @param 无
* @retval 无
*/
void SysTick_Init(void)
{
/* SystemFrequency / 1000 1ms中断一次
* SystemFrequency / 100000 10us中断一次
* SystemFrequency / 1000000 1us中断一次
*/
if (SysTick_Config(SystemCoreClock / 100000)) // ST3.5.0库版本
{
/* Capture error */
while (1);
}
}
SysTick 初始化函数由用户编写,里面调用了 SysTick_Config()这个固件库函数,通过设置该固件库函数的形参,就决定了系统定时器经过多少时间就产生一次中断
六.SysTick定时时间计算:
(1) SysTick 中断时间的计算
SysTick 定时器的计数器是向下递减计数的,计数一次的时间 TDEC=1/CLKAHB,
当重装载 寄 存 器 中 的 值 VALUELOAD 减 到 0 的 时 候 , 产 生 中 断 ,
可 知 中 断 一 次 的 时 间Time_INT=VALUELOAD * TDEC= VALUELOAD/CLK_AHB ,
其 中 CLK_AHB =72MHZ 。 如 果 设 置VALUELOAD为 72,那中断一次的时间 Time_INT=72/72M=1us。
不过 1us 的中断没啥意义,整个程序的重心都花在进出中断上了,根本没有时间处理其他的任务。
SysTick_Config(SystemCoreClock / 100000))
SysTick_Config()的形我们配置为 SystemCoreClock / 100000=72M/100000=720,从
刚刚分析我们知道这个形参的值最终是写到重装载寄存器 LOAD 中的,从而可知我们现在
把 SysTick 定时器中断一次的时间 Time_INT=720/72M=10us。
(2) SysTick 定时时间的计算
当设置好中断时间 Time_INT后,我们可以设置一个变量 t,用来记录进入中断的次数,那
么变量 t 乘以中断的时间 Time_INT就可以计算出需要定时的时间。
(3) SysTick 定时函数
现在我们定义一个微秒级别的延时函数,形参为 nTime,当用这个形参乘以中断时间
Time_INT就得出我们需要的延时时间,其中 Time_INT我们已经设置好为 10us。关于这个函数的具体
调用看注释即可。
/**
* @brief us延时程序,10us为一个单位
* @param
* @arg nTime: Delay_us( 1 ) 则实现的延时为 1 * 10us = 10us
* @retval 无
*/
void Delay_us(__IO u32 nTime)
{
TimingDelay = nTime;
// 使能滴答定时器
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
while(TimingDelay != 0);
}
函数 Delay_us()中我们等待 TimingDelay 为 0,当 TimingDelay 为 0 的时候表示延时时
间到。变量 TimingDelay 在中断函数中递减,即 SysTick 每进一次中断即 10us 的时间
TimingDelay 递减一次。
(4) SysTick 中断服务函数
void SysTick_Handler(void)
{
TimingDelay_Decrement();
}
中断复位函数调用了另外一个函数 TimingDelay_Decrement(),原型如下:
/**
* @brief 获取节拍程序
* @param 无
* @retval 无
* @attention 在 SysTick 中断函数 SysTick_Handler()调用
*/
void TimingDelay_Decrement(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}
TimingDelay 的值等于延时函数中传进去的 nTime 的值,比如 nTime=100000,则延时
的时间等于 100000*10us=1s。