参考资料:3-PM0214_STM32F4xx-Cortex_M4内核参考手册.pdf(-4.5 章节 SysTickTimer(STK),和 4.48 章节SHPRx)
参考源码:core_cm4.h(有关寄存器的定义和部分库函数)
什么是SysTick?
SysTick是属于CM4内核的外设 (所有基于CM4内核的单片机都有)。内嵌在NVIC中。他是一个24bit 的向下递减 的计数器,每计数一次的时间是1/SYSCLK,一般我们设置系统时钟SYSCLK=180M,当重装载数值寄存器的值递减到0的时候,系统定时器就产生一次中断,依次循环往复。应用:一般用于操作系统,产生时基,维持操作系统的心跳。
SYSTICK寄存器:有4个,但是使用SYSTICK计时的时候只需要配置前3个,即CTRL--控制及状态寄存器、LOAD--重装载数值寄存器、VAL--当前数值寄存器。第4个是CALIB--校准数值寄存器,不需要使用(不做讲解,以后大概率也用不到)。

重装载数值寄存器:

当前数值寄存器:

怎么使SysTick产生一个1s的时基?
首先分析core_cm4.h文件中的SysTick功能函数:
cpp
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
/* 不可能的重装载值 超出范围 */
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); } /* Reload value impossible */
/* 设置重装载寄存器 */
SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
/* 设置中断优先级 */
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
/* 设置当前数值寄存器 */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
/* 设置系统定时器的时钟源为AHBCLK = 168M
* 使能系统定时器中断
* 使能定时器
*/
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0UL); /* Function successful */
}
固件库函数NVIC_SetPriority()用来配置系统定时器的中断优先级,该函数也位于core_cm4.h。
cpp
__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if((int32_t)IRQn < 0) {
SCB->SHP[(((uint32_t)(int32_t)IRQn) & 0xFUL)-4UL] = (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
else {
NVIC->IP[((uint32_t)(int32_t)IRQn)] = (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
}
SysTick属于内核外设,跟普通外设的中断优先级有些区别,它是没有抢占优先级和子优先级的说法。在STM32F407中,内核外设(SysTick)的中断优先级由内核SCB的寄存器SHPRx(x = 1,2,3)来配置(可以看Cortex_M4内核参考手册 4.48章节)。
SHPR1-3是一个32位的寄存器,只能通过字节访问,每8个字段控制着一个内核外设的优先级,在STM32F407中只有7:4位这高四位有效,所以内核外设的优先级设置范围是0-15,数值越小,优先级越高 ,如果优先级相同,就根据他们在中断向量表中的位置标号来决定优先级大小(编号越小优先级越高)。

其中NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);SysTick_IRQn值为-1,带入函数中即为设置SCB的SHPx寄存器;(1UL << __NVIC_PRIO_BITS) - 1UL是15,即设置优先级为最低。
SysTick 初始化函数由用户编写:
cpp
void SysTick_Init(void)
{
if(SysTick_Config(SystemCoreClock / 100000 ))
{
while(1);
}
}
SysTick中断时间怎么计算?
已知计数器是向下计数的,计数一次的时间= 1/CLK,当重装载数值VALUE减到0的时候,产生一次中断,产生一次中断的时间 = VALUE * 1/CLK = VALUE/CLK。CLK可知是168M,如果VALUE值为180,那中断一次的时间 = 168/168M = 1us,1us太小会导致整个程序重心花在了进出中断上了,就不能处理其它任务了这不可取。所以SysTick_Config的形参我们可以设置为(SystemCoreCLK / 100000)即1680(这个值是要写到重装载寄存器里面的),则中断一次时间就是10us。
怎么实现定时呢?
SysTick中断时间已经设置好了,我们可以设置一个变量t,用来记录进入中断的次数,那么变量t*SysTick中断时间就可以实现定时了。下面实现定时函数:
cpp
void Delay_10us(__IO u32 nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
//当参数为1时,计时1*10us=10us;当参数为10时,计数10*10us=100us
中断服务函数:
cpp
void SysTick_Handler(void)
{
TimeingDelay_Decrement();
}
中断服务函数由调用了另一个函数:
cpp
void TimingDelay_Decrement(void)
{
if(TimingDelay != 0x00) {
TimingDelay--;
}
}
还有一种更简洁的定时编程:
上面的延时方式使用了中断,而且经过了多个函数的调用,还使用了全局变量,不容易理解。现在我们将学习一种新的写法,更好理解也更简洁。已知,systick的counter从reload值向下递减到0的时候,ctrl寄存器的位16countflag会置1,且该值读完后自动清零,所以我们可以使用软件查询的方式来实现延时。如下:
cpp
//微秒级延时
void SysTick_Dealy_Us(__IO uint32_t us)
{
int32_t i;
SysTick_Config(SystemCoreClock / 1000000);
for(i=0;i<us;i++)
{
while(!((SysTick->CTRL)&(1<<16)));
}
//关闭SysTick定时器
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
//毫秒级延时
void SysTick_Dealy_Ms(__IO uint32_t ms)
{
int32_t i;
SysTick_Config(SystemCoreClock / 1000);
for(i=0;i<ms;i++)
{
while(!((SysTick->CTRL)&(1<<16)));
}
//关闭SysTick定时器
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
大功告成!接下来你要实践一下,验证你的学习成果!(附件2个程序都有,仅供参考)