SysTick定时器是被捆绑在NVIC中的,用于产生SysTick异常(异常号是15)。(同样,玩过51单片机的都知道定时器的作用了)
在STM32在内核部分是包含了一个简单的定时器–SysTick timer。因为在所有的Cortex-M3芯片上都有这个定时器,所以软件在不同芯片生产厂商的Cortex-M3器件间的一只工作就得以化简。
该定时器的时钟源可以是内部时钟( FCLK, CM3 上的自由运行时钟),或者是外部时钟(CM3 处理器上的 STCLK 信号)。不过, STCLK 的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可
能会大不相同。因此,需要阅读芯片的使用手册来确定选择什么作为时钟源。
*(在 STM32 中 SysTick 以 HCLK(AHB 时钟)或 HCLK/8 作为运行时钟。)
SysTick定时器能产生中断,Cortex-M3专门为它开出一个异常类型,并且在向量表中存在。所以他能使操作系统和其他系统软件在Cortex-M3器件间的移植变的更简单了,因为在所有Cortex-M3产品之间,SysTick的处理方式都是相同的。
SysTick timer工作分析:
SysTick 是一个 24 位的定时器,即一次最多可以计数 224 个时钟脉冲,这个脉冲计数值被保存到 当前计数值寄存器 STK_VAL (SysTick current valueregister) 中,只能向下计数,每接收到一个时钟脉冲 STK_VAL 的值就向下减1,直至 0,当 STK_VAL 的值被减至 0 时,由硬件自动把重载寄存器
STK_LOAD( SysTick reload value register) 中保存的数据加载到 STK_VAL,重新向下计数。当 STK_VAL 的值被计数至 0 时,触发异常,就可以在中断服务函数中处理定时事件了。
(要使 SysTick 进行工作必须要进行SysTick配置。它的控制配置很简单,只有三个控制位和一个标志位,都位于寄存器 STK_CTRL)
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。
(STK_CALIB 寄存器是用于校准的,不常用。可以在STM32数据手册中找到所有SysTick相关寄存器)
实例分析SysTick(延时流水灯)
主函数功能:
int main()
{
LED_GPIO_Config(); //LED GPIO初始化
SysTick_Init(); //配置SysTick
while(1) //一个死循环的流水灯
{
LED1(0);
Delay_us(50000);
LED1(1);
LED2(0);
Delay_us(50000);
LED2(1);
LED3(0);
Delay_us(50000);
LED3(1);
}
return 0;
}
SysTick_Init()分析
void SysTick_Init(void)
{
/* SystemFrequency / 1000 1ms 中断一次
SystemFrequency / 100000 10us 中断一次
SystemFrequency / 1000000 1us 中断一次 */
// if (SysTick_Config(SystemFrequency / 100000)) // ST3.0.0 库版本
if (SysTick_Config(SystemCoreClock / 100000)) // ST3.5.0 库版本
{
while (1);
}
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;//关闭定时器
}
其实函数里面就调用了一个函数SysTick_Config(),这个函数是属于内核层的Cortex-M3的通用函数,位于Core_cm3.h中。函数工作很简单,就是若调用SysTick_Config()不成功,则进入死循环,当初始化SysTick成功后,先关闭定时器,需要用的时候再开启。(我们可以在keil环境下跟踪这个函数查看函数的定义)
/*
* @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)
{
/* Reload value impossible */
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); //
/* set reload register */
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
/* set Priority for Cortex-M0 System Interrupts */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
/* Load the SysTick Counter Value */
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enab
le SysTick IRQ and SysTick Timer */
return (0); /* Func
tion successful */
}
这个函数的主要功能:启动了SysTimer,并且把它配置为计数至0引起中断,输入的参数ticks为两次中断之间的脉冲数(既相隔ticks个时钟周期会引起一次中断),配置SysTick成功时返回0,出错返回1
以下为详细分析:
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);
因为ticks这个参数是脉冲计数值,要被保存到重载寄存器 STK_LOAD 寄存器中,再由硬件把STK_LOAD值加载到当前计数值寄存器STK_VAL使用的,因为STK_LOAD和STK_VAL都是24位,所以在输入ticks大于可存储的最大值时,由这行代码检查出错误返回。
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
当ticks的值没有错误后,就把ticks-1赋值到STK_LOAD寄存器中(注意是减1),当STK_VAL从ticks-1向下计数到0,则就经过了ticks个脉冲。
(这里面用到的SysTick_LOAD_RELOAD_Msk宏(在keil环境下跟踪查看定义),这个宏是用来指示寄存器的“特定位置”或者是用来“位屏蔽”,所以叫位指示宏或位屏蔽宏,这个在操作寄存器代码中经常用到,通常都是用作控制寄存器某些位(“或”和“与”操作))
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
调用了NVIC_SetPriority()函数配置了SysTick中断,所以我们就不需要再去手动配置。配置完后把STK_VAL寄存器重新赋值为0。
SysTick->CTRL配置
向STK_CTRL写入SysTick timer的控制参数,配置为时钟,使能为计数至0引起中断,使能SysTick,接着SysTick就开始运行,做脉冲计数。
(如果想使用AHB/8作为时钟,可以调用库函数SysTick_CLKSourceConfig()或者修改SysTick->CTRL的使能配置)
最后在调用完SysTick_Config()后,SysTick定时器被打开了,但是我们并不是需要这样,所以在调用完之后关闭定时器
// 使能定时器
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
// 关闭定时器
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
(这里又用到位屏蔽宏,可见用的是十分广泛)
定时时间的计算:
再调用完SysTick_Config()时,输入ticks为SystemCoreClock / 100000 SystemCoreClock 为定义了系统时钟(SYSCLK)频率的宏,即等于AHB的时钟频率,历程用到AHB都是72MHz,所以SystemCoreClock这个宏展开就是7200 0000.
根据前面对 SysTick_Config()函数的介绍,它的输入参数为SysTick将要计的脉冲数,经过 ticks 个脉冲(经过 ticks 个时钟周期)后将触发中断,触发中断后又重新开始计数。
由此我们可以算出定时的时间,下面为计算公式:
T=ticks*(1/f)
T 为要定时的总时间。
ticks 为 SysTick_Config()的输入参数。
1/ f 即为 SysTick timer 使用的时钟源的时钟周期,f为该时钟源的时钟频率,当时钟源确定后为常数。
例如本实例使用时钟源为 AHB 时钟,其频率被配置为 72MHz。调用函数时,把ticks赋值为 ticks=SystemFrequency / 10000 =720,表示720个时钟周期中断一次;( 1/f)是时钟周期的时间,此时(1/f =1/72us ),所以最终定时总时间 T=720*(1/72),为 720 个时钟周期,正好是 10us。
SysTick 定时器的定时时间(配置为触发中断,即为中断周期),由ticks参数决定,最大定时周期不能超过 224个。以下是几种常用的中断周期配置,就是根据上面的公式计算出来的。
SystemFrequency / 1000 1ms 中断一次
SystemFrequency / 100000 10us 中断一次
SystemFrequency / 1000000 1us 中断一次
中断服务函数:
在main中,我们让LED工作在无限循环中,在开关之间调用了Delay_us(),一旦调用了之后,SysTick就会被开启,按照设定好的定时周期递减计数,当SysTick的计数值减到0时,就进入到中断,中断执行完后又重新计数
void Delay_us(__IO u32 nTime)
{
TimingDelay = nTime;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //使能定时器
while(TimingDelay != 0);
}
使能了SysTick之后,就使用 while(TimingDelay != 0)语句等待TimingDelay变量变为 0,这个变量是在中断服务函数中被修改的。
/*
* @brief This function handles SysTick Handler.
* @param None
* @retval : None
*/
void SysTick_Handler(void)
{
if (TimingDelay != 0x00)
{
TimingDelay–;
}
}
SysTick中断属于系统异常向量,在stm32f10x_it.c文件中已经默认有了它的中断服务函数SysTick_Handler(),但内容为空。所以我们要自己编写。
每次进入 SysTick 中断就把全局变量TimingDelay自减一次。用户函数 Delay_us ()在TimingDelay被减至等于 0 时,才退出延时循环,即我们对 TimingDelay 赋的值为要中断的次数。
所以总的延时时间 T延时= T 中断周期 * TimingDelay 。
ps:
定时器我们还可以用作计算程序执行时间:
当我们开启 SysTick 定时器后,定时器开始工作,我们可以定义一个变量a来对中断次数进行记录,在定时器进入中断时,这个变量就a++,当我们关闭定时器后,将变量的数值乘与定时器的中断周期 就等于测量时间。特别是涉及到算法的程序,这对于优化算法是有非常大的帮助。假如你的算法的是 us 级别的,那么 SysTick 就应该设定为us 级中断,如果是 ms 级别的,就将 SysTick 设定为 ms 级中断。