SysTick是存在于stm32内核的定时器,嵌套在NVIC中,24位,只能递减。在stm32中文参考手册中,对于SysTick的描述其实很少,主要如下。systick的时钟可以为AHB时钟,或者是AHB时钟8分频=9M。而校准值固定为9000,也就是说,当时钟频率为9M时,9000的固定值对应1ms时间基准,9000 000对应1s时间。
在core-CM3编程手册中,有配置systick的寄存器描述。
SysTick控制和状态寄存器STK_CTRL
位描述:
COUNTFLAG:如果上一次计数到0,则返回1,为计数标志。
CLKSOURCE:选择时钟源,为0时即为AHB时钟8分频,为1时直接就等于AHB总线时钟。
TICKINT:SysTick异常请求使能,该位为1则计数值减到0时发起系统异常请求,为0时不响应。
ENABLE:该位为1时,计数值从重装载值寄存器开始往下递减,当减到0,会设置COUNTFLAG标志位为1,并根据TICKINT的值来选择是否发起系统异常请求,然后重新装初始值,开始计数。
STK_LOAD重装载值寄存器,设置计数初值。值得注意的是,SysTick是减到0,一次完整的计数过程是0 - 99 - 98 -... -0共100个数,这是循环计数过程。因此,如果我们希望的计数值为N,实际上要写入到LOAD寄存器的值必须为N-1,这个非常重要。
另外,如果是希望计数只触发一次,则填入的值还是N,因为少了一个0-N的一次记数,这些都有寄存器描述,最常用的循环计数还是初始值为N-1
还有一个当前计数值寄存器,该寄存器一旦写入就会将当前寄存器的所有位清零,并还会把CPUNTFLAG标志位清0,所以我们不要鬼这个寄存器进行写入操作。
野火给出的框图如下,也是在说明上述意义。
SysTick定时时间的计算:
所以就有下面的配置:
t = reload * (1 / CLK)
CLK = 72M时,t = (72) * (1 / 72M) = 1us,此时reload = 72
CLK = 72M时,t = (72000) * (1 / 72M) = 1ms,此时reload = 72000
注意,这是单次计数,所以reload = N,就是从N到0,实际上计数N次。而循环计数时,reload要设置为N-1,因为从N-1到0计数N-1次,再从0到N-1计数一次,这才是一个完整的循环周期。
如果产生中断的延时是微妙级别,那么系统会频繁的进入中断,很多事情就做不了了,与系统的时间相冲突,因此意义不大,通常使用的中断都是ms级别。
标准固件库中对SysTick有非常详细的描述
这个时初始化结构体
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;
然后看系统默认对SysTick的配置,这里有几个值得注意的地方,1 输入嘀嗒数不能超过2^24;2 嘀嗒数就是要定时的数N,因为代码里自动实现了N-1;默认将时钟配置为AHB时钟72M;默认中断优先级位最低优先级15。
/**
* @brief Initialize and start the SysTick counter and its interrupt.
*
* @param ticks number of ticks between two interrupts
* @return 1 = failed, 0 = successful
*
* Initialise the system tick timer and its interrupt and start the
* system tick timer / counter in free running mode to generate
* periodical interrupts.
*/
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 */
// 设置SysTick 中断优先级,默认为最低的优先级(1 << 4) - 1 = 16 - 1 = 15
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_CLKSOURCE_Msk = 1 << 2,默认会将时钟配置为AHB时钟源72M
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0); /* Function successful */
}
固件库设置中断优先级的标准函数,首先判断中断标号<0(内核)还是>=0(外设),如果是外设的中断非常简单,直接设置IP寄存器的高4位(priority << 4),如果是内核的中断,不是设置NVIC_IP寄存器,而是设置SCB_SHPR寄存器。
/**
* @brief Set the priority for an interrupt
*
* @param IRQn The number of the interrupt for set priority
* @param priority The priority to set
*
* Set the priority for the specified interrupt. The interrupt
* number can be positive to specify an external (device specific)
* interrupt, or negative to specify an internal (core) interrupt.
*
* Note: The priority cannot be set for every core interrupt.
*/
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 */
}
当SysTick和外设同时产生中断时,根据4位数的数值大小来解析中断优先级的高低。