STM32学习笔记(5)——系统定时器SysTick
单独拿出来讲的一个内核外设(所以不要期望在STM32中文参考手册找到它!即使找到也只会叫你看cm3内核编程手册),说明它真的很重要。
一、系统定时器Systick
1. SysTick简介
SysTick是一个24位的系统节拍定时器,具有自动重载和溢出中断功能,所有基于Cortex M3或Cortex M4处理器的微控制器都有这个定时器。
Systick定时器常用来做延时,或者用来做实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟。
它一个24位的倒计数定时器(意味着有2^24个时间间隔),计到0时,将从RELOAD寄存器中自动重装载定时初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
学过51单片机的同学应该对计数器比较熟悉了。
SysTick在NVIC的中断向量号为6(可对照STM32中文参考手册9.1.2节中断和异常向量中表格的灰色部分)。
2. SysTick相关寄存器
SysTick有4个寄存器:CTRL(控制和状态寄存器)、LOAD(自动重装载除值寄存器)、VAL(当前值寄存器)、CALIB(校准值寄存器)(如图所示)。
关于这4个寄存器的参考资料为 《 STM32F10xxx Cortex-M3编程手册》(STM32F10xxx/20xxx/21xxx/L1xxxx
Cortex ® -M3 programming manual)(没找到中文版的,自己生啃一下英文吧),4.5节SysTick timer (STK)。4.5.6节还有SysTick寄存器地图。
由于篇幅够多,下面我们来一一介绍这4个寄存器(其实引用了上述手册的介绍):
(1)SysTick control and status register (STK_CTRL)
16位(COUNTFLAG): SysTick数到0后,该位为1;其他情况为0。如果读取该位,也会置0。说白了就是用来标志是不是数完了。
2位(CLKSOURCE): 0为外部时钟源(STCLK)(HCLK的1/8),1为内部时钟源(FCLK)(HCLK)。用来选择时钟源的。顺便提一句,HCLK用的是AHB时钟。
1位(TICKINT): 1为计数器数到零时产生SysTick中断请求,0为不产生请求。
0位(ENABLE): 用来使能的。
其余位保留。
(2)SysTick reload value register (STK_LOAD)
23位-0位(RELOAD): 存储的是一个值,当计数到0时,将重新装载该值,开始新一轮倒数。
其余位保留。
(3)SysTick current value register (STK_VAL)
23位-0位(CURRENT): 跟上面的有点像,也是存储一个值。但是,当你读取它时,寄存器会返回这个值(就是读取了这个值);当你写它时会自动清零,同时CTRL寄存器的第16位(COUNTFLAG)置0。
其余位保留。
(4)SysTick calibration value register (STK_CALIB)
这个寄存器似乎比较少关注到,对于用户来讲应该不常用?
31位(NOREF): 1为没有外部参考时钟(外部时钟源STCLK不可用),0为外部参考时钟可用。
30位(SKEW): 1为校准值不是准确的10ms,0为校准值是准确的10ms。
23位-0位(TENMS): 存储的是10ms的间隔倒计时的格数。芯片设计者应该提供这数值,若该值为0,则无法使用校准功能。
其余位保留。
在头文件core_cm3.h中有结构体定义:
1 | typedef struct |
3. 详解SysTick_Config()函数
在使用SysTick之前,用户必须调用SysTick_Config()函数对其进行配置。这个函数是怎样进行配置的呢?我们可以看看头文件core_cm3.h 的1694行:
1 | static __INLINE uint32_t SysTick_Config(uint32_t ticks) |
uint32_t SysTick_Config(uint32_t ticks)
函数形参ticks为两个中断之间的脉冲数(number of ticks between two interrupts),即相隔ticks个时钟周期会引起一次中断。
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);
判断ticks是否符合规则,这个是什么规则呢?就是判断ticks是否大于SysTick_LOAD_RELOAD_Msk,如果大于,则返回1(failed),表示不符合规则。
SysTick_LOAD_RELOAD_Msk是一个宏,在文件388行可以找到:
1 |
0xFFFFFF在十进制中是2^24(=16777216),其中,ul的意思是无符号long型整数(
unsigned long int)。然后我们就知道,ticks最大值不能超过2^24(因为是24位系统定时器),否则就配置失败。
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */
这里就是将ticks写入load寄存器,ticks是计数最开始的值。减去一的原因是计数从0结束。
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
NVIC中断配置,SysTick_IRQn在头文件stm32f10x.h可以找到:
1 | SysTick_IRQn = -1, /*!< 15 Cortex-M3 System Tick Interrupt*/ |
宏定义__NVIC_PRIO_BITS可以在本文件core_cm3.h找到:
1 |
因此(1<<__NVIC_PRIO_BITS) - 1的意思是1左移4位(即16),表达式的值为15(注意二进制表示为1111),这里面的4是因为(寄存器NVIC->IPRx)使用4个位来配置中断优先级。
所以SysTick的优先级是最低的吗?由于没有指定优先级分组,所以这就要看怎么分了。
假如分组为1,则将1111分为两组,前面组为抢占优先级,只有一个1(说明抢占优先级为1),而后面组为响应优先级,为111(说明响应优先级为7),需要注意到这里1和0的个数表示了二进制的数,进而指明了优先级别的高低。
假如分组为2,则将1111分为两组,前面组为抢占优先级,有11(说明抢占优先级为3),而后面组为响应优先级,为11(说明响应优先级为3)。
假如分组为3,则将1111分为两组,前面组为抢占优先级,有111(说明抢占优先级为7),而后面组为响应优先级,为1(说明响应优先级为1)。以此类推,所以SysTick的优先级是不一定的,只要你去配置,你想怎样都行。
SysTick->VAL = 0;
将0载入到val寄存器中。
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
配置时钟源、中断请求和使能。
4. SysTick相关的函数
1 | SysTick_CLKSourceConfig() //Systick时钟源选择,在misc.c中 |
二、延时函数
使用SysTick写出来的延时函数,其精度要比软件延时要高。不过这个函数要自己写。
下面是一个写好的微妙级别的延时函数,我们来分析一下:
1 | void Systick_Delay_us(uint32_t us) |
SysTick_Config(72);
配置SysTick时钟。这里为什么是72呢?有个公式:t = reload * ( 1/clk ),clk为内核时钟频率,reload为LOAD寄存器的值。在这个程序中,Clk = 72MHz,reload = 72,因此 t = (72) *(1 / 72 MHz)= 1us。
while( !((SysTick -> CTRL) & (1 << 16)) );
这里有必要说明一下(SysTick -> CTRL) & (1 << 16)是什么意思。SysTick -> CTRL是CTRL寄存器的首地址,1<<16为左移16位,两者进行与运算,正好得到CTRL寄存器第16位地址,即COUNTFLAG,因此这条语句是用来判断计数是否到0。如果没到0,while继续循环。每跳出一次while循环,说明已经过去了1us的时间,而for循环则循环了指定次数的1us。
SysTick -> CTRL &= ~SysTick_CTRL_ENABLE_Msk;
每一次使用完SysTick后要记得关闭。
三、简单例程
1. 不使用中断服务函数
1 | //在systick.c中 |
2. 使用中断服务函数
1 | //systick.h |
上面这种写法用了extern全局变量,个人感觉很别扭,但是中断服务函数是没有形参的,所以还有一种折中的写法(自己写一个函数,然后中断服务函数去调用):
1 | //systick.h |
再次,我个人习惯是,extern只有到万不得已才使用,因为这玩意太破坏文件之间的耦合度了,而且被多个文件使用,不太好追踪源头。



