在很多场合下,我们经常会需要一个简单的延时函数。为了不暂用片内的其他资源,往往会想到Cortex-M3自带的Systick这个定时器。但是如何实现这样的功能呢,还得慢慢道来。所谓磨刀不误砍柴工,因此我们先了解一下Systick到底有哪些寄存器,操作哪些寄存器才能达到我们的目的呢?
每个Cortex-M3内核都含有一个称之为系统定时器的定时器,即SysTick,减计数,过零重载等基本功能。它总共含有4个寄存器:分别是控制和状态寄存器,重载值寄存器,当前值寄存器和校正值寄存器。
控制和状态寄存器(Systick Control and Status Register),功能包含定时器使能位,是否使能异常( exception),以及配置Systick的时钟源和是否有计数到零的标志位。其复位之后的默认值为0x00000000;
--ENABLE, BIT0, 决定了是否使能Systick。
0 = 定时器禁止
1 = 定时器使能
-- TICKINT, BIT1, 决定了当Systick计数到零时,MCU是否会去响应该事件。是否需要响应Systick中断就是由这个位控制了。
0 = 计数器向下计到零时,MCU不会响应Systick异常。
1 = 计数器向下计到零时,MCU会响应Systick异常。
-- CLKSOURCE,BIT2,指明了Systick的时钟源。HFCORECLK或则是RTC。
0 = RTC计数器的Bit0
1 = HFCORECLK
-- COUNTFLAG, BIT16,指明了定时器是否有过零事件发生。即当定时器计数器从1变到0时,该标志位置1.
重载值寄存器(Systick Reload Value Register),定义了定时器重载时的初值。当定时器当前计数值减到0时,就会将重载值寄存器里面的值载入到定时器当前值。重载发生在当前计数器为零之后的下一个时钟信号的边沿。
-- RELOAD, BIT0~BIT23,有效位为24Bit。在Systick使能的情况下,当定时器当前值计数到零时,载入到定时器当前值。可以设置为0~0x00FFFFFF中的任意值。由于重载发生在过零时的下一个时钟边沿,因此要配置这个值需要注意:
如果是要做一个多次的时间间隔为N个时钟信号的周期性定时器,则需要将该值配置成需要N-1. 例如如果想做一个时间间隔为100个时钟的周期性定时器,则需要将这个值配置为99.因为需要额外一个时钟周期用来重载。
如果只是想做一次性的时间间隔为N个时钟周期的定时器,则可以将该值直接设置成N。
当前值寄存器(Systick Current Value Register), 是定时器计数值的当前值。
-- CURRENT,BIT0~BIT23。有效位为24bit。该寄存器也支持读写操作。读的话,则返回当前值,写如任何值,都会将该寄存器清零,并且将COUNTFLAG清零。
校正值寄存器(Systick Calibration Value Register),校正值寄存器。该寄存器是一个只读寄存器。目前对该寄存器还不是很了解。
-- TENMS,BIT0~BIT23。校正值。读回值应为0x000036B0, 即14MHz时钟下面,1ms的时钟周期数。0x000036B0即14000。
-- SKEW,BIT30。 该值是为了表明TENMS中的校正值是否精准。1为不精准,0为精准。
-- NOREF,BIT31。该值表明Systick是否是将RTC作为参考源。
通过以上寄存器的介绍,我们大致上应该知道如何用Systick做一个简答的Delay函数了吧。
1. 初始化Systick,停止Systick,配置Systick的时钟源,清除COUNTFLAG。由于不使用中断,因此清除TICKINT。
2. 将延时的值带入重载值寄存器,清除当前值寄存器,然后使能定时器,并且等待COUNTFLAG标志位置1.
3. 停止Systick,并且返回。
附上代码,请参考。由于涉及到函数调用及处理,因此如果延时很短,进度很高的情况下,需要额外考虑这些。
#include "em_device.h"
#include "em_chip.h"
#include "em_cmu.h"
__no_init unsigned long usDelay;
__no_init unsigned long msDelay;
void Systick_Delay_Init(void)
{
SystemCoreClockUpdate();
usDelay = SystemCoreClock / 1000000;
msDelay = SystemCoreClock / 1000;
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;
SysTick->CTRL &= ~(SysTick_CTRL_COUNTFLAG_Msk | SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);
}
void Systick_Delay_Cycle(unsigned long ulCycle)
{
SysTick->LOAD = ulCycle;
SysTick->VAL = 0;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
void Systick_Delay_uS(unsigned long uluS)
{
uluS = usDelay * uluS;
SysTick->LOAD = uluS;
SysTick->VAL = 0;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
void Systick_Delay_mS(unsigned long ulmS)
{
ulmS = msDelay * ulmS;
SysTick->LOAD = ulmS;
SysTick->VAL = 0;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}