参考:https://blog.csdn.net/guosir_/article/details/78389472
https://www.cnblogs.com/dustinzhu/p/4149906.html
http://www.51hei.com/bbs/dpj-133694-1.html
SysTick滴答定时器
一、功能
SysTick定时器是一个简单的定时器,CM3\CM4内核芯片都具备此定时器。SysTick定时器常用来做延时,采用实时系统时则用来做系统时钟。
无论用作延时还是用作系统心跳时钟,不需要太复杂的功能,SysTick即可胜任。
二、实现原理
SysTick定时器是一个24位的倒计数,当倒计数为0时,将从RELOAD寄存器中取值作为定时器的初始值,同时可以选择在这个时候产生中断(异常号:15)。
例如从RELOAD的值为999,那么当倒计数为0时,就会从复位为999继续倒计数。
只要不把它在SysTick控制及状态寄存器中的使能位清楚,就永不停息,即使在睡眠模式下也能继续工作。
三、SysTick寄存器(在 core_cm3.h 有定义,凡是 M3 内核的单片机都是一样的)
#define SysTick ((SysTick_Type *) SysTick_BASE)
#define SysTick_BASE (SCS_BASE + 0x0010)
#define SCS_BASE (0xE000E000)
typedef struct
{
__IO uint32_t CTRL; // 控制及状态寄存器
__IO uint32_t LOAD; // 重装载数值寄存器
__IO uint32_t VAL; // 当前计数数值寄存器
__I uint32_t CALIB; // 校准寄存器
} SysTick_Type;
SysTick->CTRL: (可通过 SysTick_CLKSourceConfig() 函数设置)
COUNTFLAG(16)R: 计数标志位
当SysTick数到0,则该位被硬件置 1,当读取该位时,将被硬件清零
CLKSOURCE(2)R/W: 时钟源设置
1 = 外部时钟源(STCLK) (AHB总线时钟的1/8(HCLK/8))
0 = 内核时钟(FCLK) (AHB总线时钟的频率(HCLK))
TICKINT(1)R/W: 中断使能位
1 = SysTick 倒数到0时产生 SysTick 异常请求
0 = 数到 0 时无动作
ENABLE(0)R/W: SysTick 定时器使能位
(当中断被使能后,需要关注 void SysTick_Handler(void) 函数)
SysTick_Type->LOAD: (SysTick_Config() 函数会设置该寄存器)
RELOAD(23:0)R/W: 重装载数值寄存器
当SysTick数到0,将被重装载的值
SysTick_Type->VAL: (SysTick_Config() 函数会设置该寄存器)
CURRENT(23:0)R/Wc: 当前计数数值寄存器
读取时返回当前倒计数的值,写它则使之清零,同时还会清除在 SysTick 控制及状态寄存器中的 COUNTFLAG 标志。
与systick相关的寄存器的说明
SysTick寄存器说明在《Cortex-M3权威指南》(chap8.SysTick定时器章节)有说明
一共有4个寄存器,名称和地址分别是:
STK_CTRL, 0xE000E010 -- 控制寄存器
STK_LOAD, 0xE000E014 -- 重载寄存器
STK_VAL, 0xE000E018 -- 当前值寄存器
STK_CALRB, 0xE000E01C -- 校准值寄存器
位16:当前值寄存器递减到0,位16置1
位2:时钟源选择位
0:使用外部参考时钟
1:使用内核时钟
位1:使能SysTick中断,当前值寄存器递减到0时产生中断
位0:SysTick时钟使能
首先看STK_CTRL控制寄存器:寄存器内有4个位t具有意义
第0位:ENABLE,Systick 使能位 (0:关闭Systick功能;1:开启Systick功能)
第1位:TICKINT,Systick 中断使能位 (0:关闭Systick中断;1:开启Systick中断)
第2位:CLKSOURCE,Systick时钟源选择 (0:使用HCLK/8 作为Systick时钟;1:使用HCLK作为Systick时钟)
第3位:COUNTFLAG,Systick计数比较标志,如果在上次读取本寄存器后,SysTick 已经数到了0,则该位为1。如果读取该位,该位将自动清零
STK_LOAD 重载寄存器:
Systick是一个递减的定时器,当定时器递减至0时,重载寄存器中的值就会被重装载,继续开始递减。STK_LOAD 重载寄存器是个24位的寄存器最大计数0xFFFFFF。
当前值寄存器为0时,自动将重装载值重装到当前值计数器,重装载值的大小需要自己设置
STK_VAL当前值寄存器:
也是个24位的寄存器,读取时返回当前倒计数的值,写它则使之清零,同时还会清除在SysTick 控制及状态寄存器中的COUNTFLAG 标志
可读可写,当计数器使能时,这个寄存器的值开始递减,使用前后注意清零
STK_CALRB 校准值寄存器:
这个寄存器好像目前的水平我还用不到,大体意思明白点,把英文说明放这吧:
位31 NOREF :1=没有外部参考时钟(STCLK 不可用)0=外部参考时钟可用
位30 SKEW:1=校准值不是准确的1ms 0=校准值是准确的1ms
位[23:0] :校准值
Indicates the calibration value when the SysTick counter runs on HCLK max/8 as external clock. The value is product dependent, please refer to the Product Reference Manual, SysTick Calibration Value section. When HCLK is programmed at the maximum frequency, the SysTick period is 1ms. If calibration information is not known, calculate the calibration value required from the frequency of the processor clock or external clock.
SysTick逻辑图
四、库函数分析
misc.c
#define SysTick_CLKSource_HCLK_Div8 ((uint32_t)0xFFFFFFFB)
#define SysTick_CLKSource_HCLK ((uint32_t)0x00000004)
#define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
((SOURCE) == SysTick_CLKSource_HCLK_Div8))
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
/* Check the parameters */
assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
{
SysTick->CTRL |= SysTick_CLKSource_HCLK; // 设置 CLKSOURCE 为 1
}
else
{
SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; // 设置 CLKSOURCE 为 0
}
}
core_cm3.c
#define SysTick_LOAD_RELOAD_Pos 0
#define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos)
typedef enum IRQn
{
//...
SysTick_IRQn = -1,
//...
}IRQn_Type;
#define __NVIC_PRIO_BITS 4
#define SysTick_CTRL_CLKSOURCE_Pos 2
#define SysTick_CTRL_CLKSOURCE_Msk (1ul << SysTick_CTRL_CLKSOURCE_Pos)
#define SysTick_CTRL_TICKINT_Pos 1
#define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos)
#define SysTick_CTRL_ENABLE_Pos 0
#define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos)
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
// 设置计数值为 ticks - 1
// 原因1:视频说是执行这些代码需要时间,所以减少一个节拍
// 原因2:我认为是因为 SysTick 的倒计数到 0,例如设置 1000 ,那么范围就应该是 999 ~ 0。
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
// 设置中断优先级
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
SysTick->VAL = 0;
// 设置时钟源为外部时钟源,同时开启中断、并使能 SysTick 定时器
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0);
}
五、延时应用
1、中断方式
static __IO uint32_t TimingDelay;
void SysTick_Init(void)
{
// 注意,这里systick时钟为HCLK,中断时间间隔1ms
if (SysTick_Config(SystemCoreClock / 1000))
{
while (1);
}
// 先关闭滴答定时器,用的时候再打开
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}
void Delay(__IO uint32_t nTime)
{
TimingDelay = nTime;
// 使能滴答定时器
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
while(TimingDelay != 0);
}
/* 中断服务函数 */
void SysTick_Handler(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}
int main(void)
{
// ...
SysTick_Init();
while(1)
{
Delay(200);//2ms
// ...
}
}
SysTick_Config(SystemCoreClock / 1000): (原代码这里假设是采用时钟源为 HCLK)
这里设置的是 72000000Hz / 1000 = 72000 ticks,也就是说 SysTick 从 (72000-1) 开始倒数。
每倒数完 72000 个节拍就触发一次中断。
一个节拍的时间为:72000000 / 72000 = 1000us == 1ms
SysTick_Config((SystemCoreClock / 8000000) * 1000 * 1):
SysTick_Config() 会设置时钟源为 HCLK/8 所以实际应用中不能按照上述代码的参数。
SystemCoreClock / 8000000: 1us 的节拍数
1us的节拍数 * 1000: 则为 1ms 的节拍数
1ms 的节拍数 * 1: 设置 1ms 一个SysTick中断,即从 ((SystemCoreClock / 8000000) * 1000 * 1) - 1 开始倒数。
2、轮询方式
static u8 fac_us=0; //us延时倍乘数
static u16 fac_ms=0; //ms延时倍乘数
void delay_init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
fac_us = SystemCoreClock/8000000; // 为系统时钟的1/8 1us = 72000000 / 8000000 = 9 个节拍
fac_ms = (u16)fac_us*1000; // 1ms 需要 9 * 1000 = 9000 个节拍
}
//延时 nus 微秒
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; //清空计数器
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864
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;
//等待时间到达,这里使用了一个小技巧,通过(temp&0x01)检查 SysTick 的使能位, 避免 Systick 定时器被关闭而导致无限循环
}while((temp&0x01)&&!(temp&(1<<16)));
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}