一、什么是 SysTick
SysTick---系统定时器是属于CM3内核中的一个外设,内嵌在NVIC中。系统定时器是一个24bit的向下递减的计数器, 计数器每计数一次的时间为1/SYSCLK,一般我们设置系统时钟SYSCLK等于72M。当重装载数值寄存器的值递减到0的时候,系统定时器就产生一次中断,以此循环往复。
因为SysTick是属于CM3内核的外设,所以所有基于CM3内核的单片机都具有这个系统定时器,使得软件在CM3单片机中可以很容易的移植。系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。
当然更常用的还是在裸机环境下作为延迟函数使用,本篇不介绍rtos相关的 SysTick 使用。
二、SysTick 寄存器描述
SysTick->CTRL, --控制和状态寄存器
SysTick->LOAD, --重装载寄存器
SysTick->VAL, --当前值寄存器
SysTick->CALIB, --校准值寄存器 (不常用 不做介绍)
2.1 SysTick->CTRL
2.2 SysTick->LOAD
2.3 SysTick->VAL
2.4 SysTick->CALIB
三、如何使用滴答定时器?
3.1 初始化滴答定时器
通过设置 SysTick->CTRL 的第二位寄存器,我们可以配置滴答定时器使用外部时钟 (STCLK) 或者是 Cortex 内核时钟 (FCLK)。
所谓 Cortex 内核时钟就是如下图所示,AHB 八分频后的时钟。
在此我们配置为使用 Cortex 内核时钟,对 CTRL 第二位置 1 即可。
cpp
SysTick->CTRL&=~(1<<2); //使用 Cortex 内核时钟
SystemCoreClock 是定义了系统时钟 (SYSCLK)频率的宏,即等于AHB 的时钟频率。笔者使用的是STM32F103x系列mcu,SystemCoreClock 被系统库配置成 72Mhz。
因为我们使了 Cortex 内核时钟。所以同样需要除 8 来当作八分频使用。
下面的代码则代表每1秒钟,SysTick会有 72000000/8= 9000000 次的计数周期。
cpp
fac_s = SystemCoreClock/8; //为系统时钟的1/8 代表每个s完成的systick时钟数
之后我们依次配置每秒,每毫秒,每微秒的滴答定时器周期。
cpp
//1s=1000ms=1000000=us
static u32 fac_s=0; //一秒的滴答定时器计数周期数量
static u32 fac_ms=0; //一毫秒的滴答定时器计数周期数量
static u32 fac_us=0; //一微秒的滴答定时器计数周期数量
void delay_init(){
SysTick->CTRL&=~(1<<2); //使用 Cortex 内核时钟
fac_s = SystemCoreClock/8; //为系统时钟的1/8 代表每个s完成的systick时钟数
fac_ms = fac_s/1000; //代表每个ms完成的systick时钟数
fac_us = fac_ms/1000; //代表每个us完成的systick时钟数
}
3.2 实现 delay_us() 函数
细看注释配合之前的寄存器描述即可看懂。
cpp
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=1 ; //使能计数器 ENABLE位置1
do
{
temp=SysTick->CTRL;
//检查CTRL的ENABLE位和COUNTFLAG位
//如果ENABLE位为0或者COUNTFLAG位为1则判断为计数结束 等待时间到达
}while((temp&0x01)&&!(temp&(1<<16)));
SysTick->CTRL&=~1; //关闭计数器 ENABLE位置0
SysTick->VAL =0X00; //清空计数器
}
三、使用 SysTick 的中断
根据 SysTick->CTRL 寄存器表的提示,我们只需要把第一位置高即可开启 SysTick 异常中断。
3.1 使用库函数
在这里我们不再从头写寄存器,而是使用core_cm3.h 的库函数,并且解析其中的原理。
cpp
#include "stm32f10x.h"
int main(void)
{
if (SysTick_Config(SystemCoreClock / 1000)) // ST3.5.0库版本
{
while (1);
}
for(;;)
{
__NOP();
}
}
void SysTick_Handler(void)
{
//每1ms中断一次
}
在下列代码中,我们的程序可以每隔 1ms 调用一次滴答定时器回调函数。
3.2 源码解析
cpp
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* 值超过0xFFFFFF直接退出 */
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* 设置重载寄存器 与运算防止越界 */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* 设置Cortex-M0系统中断的优先级 */
SysTick->VAL = 0; /* 加载SysTick计数器值 */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* 使能 SysTick IRQ 和 SysTick Timer */
return (0); /* 函数成功 */
}
方便阅读代码,我将下边的几个宏定义也粘贴过来:
cpp
#define SysTick_CTRL_CLKSOURCE_Pos 2 /*!< SysTick CTRL: CLKSOURCE Position */
#define SysTick_CTRL_CLKSOURCE_Msk (1ul << SysTick_CTRL_CLKSOURCE_Pos) /*!< SysTick CTRL: CLKSOURCE Mask */
#define SysTick_CTRL_TICKINT_Pos 1 /*!< SysTick CTRL: TICKINT Position */
#define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos) /*!< SysTick CTRL: TICKINT Mask */
#define SysTick_CTRL_ENABLE_Pos 0 /*!< SysTick CTRL: ENABLE Position */
#define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos) /*!< SysTick CTRL: ENABLE Mask */
/* SysTick Reload Register Definitions */
#define SysTick_LOAD_RELOAD_Pos 0 /*!< SysTick LOAD: RELOAD Position */
#define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos) /*!< SysTick LOAD: RELOAD Mask */