一、介绍
Systick中,Sys指系统,tick是滴答声,所以Systick就是一个系统滴答定时器。它被捆绑在 NVIC 中,用于产生 SysTick 异常(异常号:15)。SysTick的最大使命,就是定期地产生异常请求,作为系统的时基。操作系统都需要这种“滴答”来推动任务和时间的管理。
Cortex-M3 在内核部分 包含了一个简单的定时器——SysTick timer。在 STM32 中 SysTick 以 HCLK(AHB 时钟)或 HCLK/8 作为运行时钟。另外,SysTick 定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等。
二、SysTick timer工作分析
Systick的介绍在STM32参考手册中没有找到,费了些时间才在权威指南和固件库手册中找到。Systick有四个寄存器:
CTRL Systick控制和状态寄存器
LOAD Systick重装载值寄存器
VAL Systick当前值寄存器
CALIB Systick标准值寄存器
在权威指南中有图说明:
图1
SysTick 是一个 24 位的定时器,即一次最多可以计数 2^24 个时钟脉冲,这个脉冲计数值被保存到 当前计数值寄存器 STK_VAL (SysTick current value register) 中,只能向下计数,每接收到一个时钟脉冲 STK_VAL 的值就向下减1,直至 0,当 STK_VAL 的值被减至 0 时,由硬件自动把重载寄存器STK_LOAD(SysTick reload value register)中保存的数据加载到 STK_VAL,重新向下计数。当 STK_VAL 的值被计数至 0 时,触发异常,就可以在中断服务函数中处理定时事件了。
SysTick只有三个控制位和一个标志位,都位于寄存器 STK_CTRL(SysTick control and status register )中,如下图:
图2
Bit0: ENABLE 为 SysTick timer 的使能位,此位为 1 的时候使能 SysTick timer,此位为 0的时候关闭 SysTick timer。
Bit1:TICKINT 为异常触发使能位,此位为 1 的时候并且 STK_VAL 计数至 0 时会触发SysTick 异常,此位被配置为 0 的时候不触发异常。
Bit2:CLKSOURCE 为 SysTick 的时钟选择位,此位为 1 的时候 SysTick 的时钟为 AHB 时钟,此位为 0 的时候 SysTick 时钟为 AHB/8(AHB 的八分频)。
Bit16:COUNTFLAG 为计数为 0 标志位,若 STK_VAL 计数至 0,此标志位会被置 1。
三、Systick编程
首先看了一下固件库函数,发现有六个,但我只找到了SysTick_CLKSourceConfig这个时钟源配置函数,在misc.c中,其他的库函数没有找到(库版本问题?),之后查资料发现大家都不怎么用这些函数,都是使用SysTick_Config函数来配置,这个函数位于core_cm3.h中,其具体代码如下:
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 */
}
首先,检查输入参数ticks,这里用了一个宏SysTick_LOAD_RELOAD_Msk,在同一文件中可以找到其定义和代码下面所用到宏的定义,代码如下:
#define SysTick_CTRL_CLKSOURCE_Pos 2 /*!< SysTick CTRL: CLKSOURCE Position */
#define SysTick_CTRL_CLKSOURCE_Msk (1ul << SysTick_CTRL_CLKSOURCE_Pos) /*!< SysTick CTRL: CLKSOURCE Mask */
#define SysTick_CTRL_TICKINT_Pos 1 /*!< SysTick CTRL: TICKINT Position */
#define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos) /*!< SysTick CTRL: TICKINT Mask */
#define SysTick_CTRL_ENABLE_Pos 0 /*!< SysTick CTRL: ENABLE Position */
#define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos) /*!< SysTick CTRL: ENABLE Mask */
/* SysTick Reload Register Definitions */
#define SysTick_LOAD_RELOAD_Pos 0 /*!< SysTick LOAD: RELOAD Position */
#define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos) /*!< SysTick LOAD: RELOAD Mask */
由图1 可知,STK_LOAD 和 STK_VAL 都是 24 位的,ticks 是脉冲计数值,要被保存到重载寄存器 STK_LOAD 寄存器中,再由硬件把 STK_LOAD 值加载到当前计数值寄存器 STK_VAL 使用,所以ticks不能超过其最大存储值。另外这里的SysTick_xxx_Pos就是相应寄存器中的位置,SysTick_xxx_Msk是相应寄存器的位全部置1后,左移SysTick_xxx_Pos 位,利用&、|运算可以方便地修改寄存器的某些位。
接下来将值赋给STK_LOAD寄存器,这里用到了一个指针SysTick ,找到其定义为:
#define SysTick ((SysTick_Type *) SysTick_BASE) /*!< SysTick configuration struct */
说明SysTick是一个SysTick_Type类型的指针,继续跳转,可以看到SysTick_Type的定义:
typedef struct
{
__IO uint32_t CTRL; /*!< Offset: 0x00 SysTick Control and Status Register */
__IO uint32_t LOAD; /*!< Offset: 0x04 SysTick Reload Value Register */
__IO uint32_t VAL; /*!< Offset: 0x08 SysTick Current Value Register */
__I uint32_t CALIB; /*!< Offset: 0x0C SysTick Calibration Register */
} SysTick_Type;
原来寄存器的定义就包含在这个结构体中。
然后调用了NVIC_SetPriority函数来配置SysTick中断,同样可以在同一文件中找到它的具体代码:
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 */
}
这个是用来配置中断的优先级的,第一个参数是中断类型,第二个是优先级,比较复杂。
配置好中断后,将 STK_VAL 寄存器重新赋值为 0,当使能SysTick时,硬件会把存储在 STK_LOAD 寄存器中的 ticks 值加载给它。
在这段代码的最后,向 STK_CTRL 寄存器写入了 SysTick timer 的控制参数,由以上宏分析知是将CTRL寄存器的[2:0]都设为了1。又由图1及图2可知,设为1时,分别是使用内核时钟(FCLK)、计数至 0 时引起中断和使能 SysTick。当然你也可以自己修改成自己需要的参数。
在执行完这段代码后,SysTick 就开始运行,进行脉冲计数了。不过运行完这个函数后,SysTick就开启了,但我们一般是在需要的时候才用它,所以在运行完这个函数后最好先将它关闭。可以使用如下方法:
// 使能滴答定时器
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
// 失能滴答定时器
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
例如:
void SysTick_Init()
{
if (SysTick_Config(SystemCoreClock / 100000)) //SystemCoreClock 为定义了系统时钟(SYSCLK)频率的宏,即等于 AHB 的时钟频率,被设置成72MHz
{
while (1);
}
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}
至于时间的计算,公式为:
T=ticks*(1/f) , T为总时间,1/ f 即为 SysTick timer 使用的时钟源的时钟周期,f 为该时钟源的时钟频率,当时钟源确定后为常数。
通过计算可得出此函数延时为10us。只要在主函数中将SysTick使能开启,就能使用10us的精确延时了。
int main(void)
{
SystemInit();
SysTick_Init();
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
......
}