1.SysTick简介:
SysTick是系统滴答定时器,可以说是操作系统的的“心跳”,它被绑在NVIC中,用于产生SysTick异常(异常号:15)。一旦产生SysTick异常,就会产生滴答中断,这个滴答中断对操作系统尤其重要。例如:操作系统可以为多个任务分配不同数目的时间片,确保没有一个任务霸占系统,或者将每个定时器周期的某个时间范围赐于特定的任务等。操作系统提供的各种定时功能都与这个滴答定时器有关,因此需要一个定时器产生周期性的中断,而且最好让用户程序不能随意访问它的寄存器。以维持操作系统“心跳”的节律。
而STM32内核包含了一个简单的定时器——SysTick,所有CM3芯片都带有这个定时器,该定时器的时钟源可以是内部时钟,也可以是外部时钟,在STM32中的SysTick以HCLK(AHB时钟)或HCLK/8作为运行时钟。
SysTick定时器能产生中断,CM3为它专门开出一个异常类型,便且在向量表中有它的一席之地。 SysTick定时器除了能服务于操作系统外,还能用于其他目的,比如作为闹铃,用于测量时间。
2.SysTick工作分析
SysTick是一个24位的定时器,即一次最多可以计数2的24次方个时钟脉冲,这个脉冲计数值被保存到当前计数值寄存器STK_VAL中,只能向下计数,每收到一个时钟脉冲STK_VAL的值就向下减1,直至0。当STK_VAL的值被减为0时,由硬件自动把重载寄存器STK_LOAD中保存的数据加载到STK_VAL,重新向下计数。当STK_VAL的值被计数至0,就可以在中断服务函数中处理定时事件了。
下面写了个跑马灯的程序来体验一下SysTick定时器的作用。看代码:
主函数:main.c
#include "systick.h"
#include "led.h"
int main(void)
{
LED_Init();
SysTick_Init(); //配置SysTick为1ms中断一次
while(1)
{
LED1_OFF;
Delay_ms(500); //500 * 1ms=500ms
LED1_ON;
LED2_OFF;
Delay_ms(500); //500 * 1ms=500ms
LED2_ON;
LED3_OFF;
Delay_ms(500); //500 * 1ms=500ms
LED3_ON;
}
}
SysTick初始化函数:systick.c
#include "systick.h"
__IO uint32_t TimingDelay;
void TimingDelay_Decrement(void)
{
if(TimingDelay !=0x00)
{
TimingDelay--;
}
}
void SysTick_Init(void)
{
if(SysTick_Config(SystemCoreClock / 1000))//
{
/*Capture error */
while(1);
}
/*关闭滴答定时器*/
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}
void Delay_ms(__IO uint32_t nTime)
{
TimingDelay=nTime;
/* 使能滴答定时器*/
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
while(TimingDelay !=0);
}
*****************************************************************************************************************************************************************************************************
注意:这里最重要的就是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 */
}
(1)这个函数都有英文注释,通过看这个注释就能知道函数的功能,这个函数启动了SysTick,便把它配置为计数至0时引起中断输入的参数ticks为两个中断之间的脉冲数,即相隔ticks个时钟周期会引起一次中断;配置Systick成功时返回0,出错时返回1。
(2)从这个函数可以知道,ticks也是有上限值的,具体多大,可以追这个SysTick_LOAD_RELOAD_Msk宏,#define SysTick_LOAD_RELOAD_Msk(0xFFFFFFul << SysTick_LOAD_RELOAD_Pos),通过这个宏我们知道ticks的最大值不能超过十六进制0xffffff,也就是十进制16777215,一般用到中断,我们都要进行NVIC配置,而这个代码用到了中断,为什么外部没有进行NVIC配置呢?仔细观察,你会发现SysTick_Config()函数中已经调用了NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1)函数,这就是答案。
(3)SysTick_Config()函数中的注释没有告诉我们SysTick的时钟是AHB时钟还是AHB/8,这个很关键,注意看,这个函数向STK_CTRL寄存器写入了SysTick的控制参数,在这里我们就知道配置为了AHB时钟,当然这里还要参考STM32的datasheet,查看STK_CTRL寄存器介绍,一切就明白了。
(4)本例子采用的是AHB时钟,其频率被配置为72MHz.调用函数时,把ticks赋值为ticks=SystemCoreClock/1000=7200,表示7200个时钟周期中断一次;定时j计算公式为:T=ticks x (1/f),T为要定时的总时间,ticks为输入参数,1/f为SysTick使用的时钟源的时钟周期,f为该时钟源的时钟频率,当时钟源确定后就为一个常数。由于时钟源是AHB,AHB又默认时钟为72MHz ,所以我写的程序中1/f=1/72us,最终定时总时间T=7200 x (1/72)us=1ms,还要注意一点,由于SysTick定时器是24位的,故最大定时周期不能超过2的24次方个。
LED初始化函数:led.c#include "led.h"
void LED_Init() //LED初始化函数
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3 | GPIO_Pin_6 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_Init(GPIOD,&GPIO_InitStructure);
}
中断服务函数:stm32f10x_it.c
void SysTick_Handler(void)
{
TimingDelay_Decrement();
}
中断服务函数中的TimingDelay_Decrement()是我自己定义的,在systick.c文件中定义的。