OS能够支持多任务,能够以周期性地完成上下文的切换,以并行的架构处理任务,单一任务的崩溃并不会牵连到整个系统。上下文周期性切换需要一个定时器能够打断程序执行,SysTick定时器就可以提供必要的时钟节拍,为OS的任务调度提供一个有节奏的“心跳”。
SysTick定时器即系统滴答定时器,也称“心跳定时器”,它是一个24 位的倒计数定时器,计到0 时,将从重装载寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
SysTick定时器被捆绑在NVIC中,用于产生SysTick异常(异常号:15),SysTick中断的优先级也可以设置。
它会根据节拍来工作,把整个时间段分成很多小小的时间片,而每个任务每次只能运行一个时间片的时间长度,超时就退出给别的任务运行,这样可以确保任何一个任务都不会霸占操作系统提供的各种定时功能,都与这个滴答定时器有关。
当启用时,定时器从重载值递减计数到零,在下一个时钟周期将重装载SYST\_RVR 中的值,然后在后续时钟周期递减。将零值写入 SYST\_RVR 会在下一次回调时禁用计数器。当计数器变为零时,COUNTFLAG 状态位设置为 1。读取 SYST\_CSR 将 COUNTFLAG 位清除为 0。写入 SYST\_CVR 将清除寄存器和 COUNTFLAG 状态位为 0,写入不会触发 SysTick 异常逻辑,读取该寄存器会在访问时返回其值。
1. SysTick寄存器
SysTick定时器主要由4个寄存器组成:
在CMSIS中系统控制寄存器结构体:
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;
在Arm官方资料中4个寄存器的命名分别是SYST\_CSR、SYST\_RVR、SYST\_CVR和SYST\_CALIB,但是在CMSIS中进行了简化命名,更加清晰明了。
1.1 SysTick控制和状态寄存器(SYST\_CSR)
CSR寄存器用到的位有4个,bit0用于是否开启定时器,置1表示使能SysTick定时器;bit1用于控制是否产生中断,该位置为1为允许产生中断;bit2用于设置定时器的时钟源,设为1,定时器的时钟源为主时钟,反之设为0的话定时器的时钟源为主时钟的四分之一。
MM32F0130系列的SysTick的HCLK来源于AHB总线经过硬件4分频,FCLK直接来源于AHB时钟总线。
当 SysTick 定时器从1计到0时,它将把COUNTFLAG位置位;
而下述方法可以清零:
- 读取 SysTick 控制及状态寄存器(STCSR)
- 往 SysTick 当前值寄存器(STCVR)中写任何数据
1.2 SysTick重装载寄存器(SYST\_RVR)
RVR寄存器用到0~23位,这个值是定时器倒计时的初始值,打开定时器以后,就会从这里设置的值倒计时到0,倒计时到0以后,又会从此值开始倒计时。
1.3 SysTick当前值寄存器(SYST\_CVR)
CVR寄存器也是用到0~23位,这是一个状态寄存器,当定时器开始运作的时候,这个值在不断的变化,从RVR寄存器获取初值以后,倒计时到0。
CURRENT:读此寄存器返回系统定时器的当前值,给这个寄存器赋值,将使定时器归0,且清CTRL中的COUNTFLAG位。
1.4 SysTick当前值寄存器(SYST\_CALIB)
如果不知道校准信息,则根据处理器时钟或外部时钟的频率计算所需的校准值。
校准值寄存器提供了这样一个解决方案:它使系统即使在不同的CM0产品上运行,也能产生恒定的SysTick中断频率。最简单的作法就是:直接把TENMS的值写入重装载寄存器,这样一来,只要没突破系统极限,就能做到每10ms来一次 SysTick异常。如果需要其它的SysTick异常周期,则可以根据TENMS的值加以比例计算。
在系统定时器的四个寄存器中,SYST\_CALIB为校准寄存器,这个是在出厂之前就已经配置好了的,我们不必考虑这个寄存器。
2. SysTick编程
SysTick配置需要遵循一定的流程:
- 开始
- 禁止SysTick
- 设置重装值寄存器
- 清除当前值寄存器
- 使能SysTick
- 完成
SysTick操作相关函数有:
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks);
void SysTick_CLKSourceConfig(u32 systick_clk_source);
void RCC_SystickDisable(void);
void RCC_SystickEnable(u32 sys_tick_period);
下面的代码演示启用 SysTick 的基本程序:
; 使能SysTick定时器,并且使能SysTick异常
LDR R0, =0xE000E010 ; 加载STCSR的地址
MOV R1, #0
STR R1, [R0] ; 先停止SysTick,以防意外产生异常请求
LDR R1, =0x3FF ; 让SysTick每1024周期计完一次。因为是从1023数到
; 0,总共数了1024个周期,所以加载值为0x3FF
STR R1, [R0,#4] ; 写入重装载的值
STR R1, [R0,#8] ; 往STCVR中写任意的数,以确保清除COUNTFLAG标志
MOV R1, #0x7 ; 选择FCLK作为时钟源,并使能SysTick及其异常请求
STR R1, [R0] ; 写入数值,开启定时器
在CMSIS库中有定义对应的配置函数:
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
/* Reload value impossible */
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) {
return (1UL);
}
/* set reload register */
SysTick->LOAD = (uint32_t)(ticks - 1UL);
/* set Priority for Systick Interrupt */
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
/* Load the SysTick Counter Value */
SysTick->VAL = 0UL;
/* Enable SysTick IRQ and SysTick Timer */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
/* Function successful */
return (0UL);
}
SysTick可以通过轮询或者中断方式进行操作,使用轮询的程序可以读取SysTick控制和状态寄存器,检查COUNTFLAG,如果该位置位,则表明SysTick计数已减到0。
中断方式延时参考程序:
static __IO uint32_t TimingDelay;
void Delay(__IO uint32_t nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
void SysTick_Handler(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}
int main(void)
{
//systick时钟为HCLK,中断时间间隔1ms
if (SysTick_Config(SystemCoreClock / 1000))
{
while (1);
}
while(1)
{
Delay(200);//200ms
}
}
轮询方式延时参考程序:
void delay_init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div4); //选择外部时钟HCLK/4
//为系统时钟的1/4,实际上也就是在计算1usSysTick的VAL减的数目
fac_us=SystemCoreClock/4000000;
//代表每个ms需要的systick时钟数,即每毫秒SysTick的VAL减的数目
fac_ms=(u16)fac_us*1000;
}
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达,看CTRL的第16位(COUNTFLAG)是否为1,看STRL的第0位(ENABLE)是否为1
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如时间测量、定时或者闹铃等。