一、SysTick是什么?
Systick定时器是一个24bit的倒计时(向下计数)定时器,功能就是实现简单的延时。
SysTick 是一种系统定时器,通常在嵌入式系统中使用。它是 ARM Cortex-M 处理器的一个特殊定时器,用于提供系统级的定时服务。SysTick 可以用于生成定时中断,以便执行特定的任务或进行系统级的时间跟踪。
例如:计数初值为100,经过一个时钟周期后,计数值减一,即99,98,97……1,0;计数至0后,又重新开始从100开始倒计数至0。 可以借此做精准延时。
二、SysTick框架图
因为SysTick是属于内核的一部分,其被捆绑在NVIC中,用于产生SYSTICK异常。
三、SysTick组成
SysTick包含四个寄存器,都是24位的寄存器,分别是:
(1) SysTick->CTRL
SysTick控制及状态寄存器 (-- 0xE000 E010
(2) SysTick->LOAD
SysTick重装载寄存器 – 0xE000 E014
(3) SysTick->VAL
SysTick当前值寄存器 – 0xE000 E018
(4) SysTick->CALIB
SysTick校准值寄存器 – 0xE000 E01C
四、SysTick时钟知识点
(1)首先明白频率(Hz)与时间(S)的转换。
●1Hz代表每秒周期震动1次, 60Hz代表每秒周期震动60次。假如滴答时钟的频率是72MHZ,72MHz表示每秒钟有72,000,000个时钟周期。那让滴答时钟计1次,时间过去了1/72μs,也就是一个时钟周期为1/72000000 s =1/72 us。
●定时1us,就需要72个时钟周期。
●定时1s,就需要72000个时钟周期。
(2)为什么需要装载预期值-1?
答:装载值就是装载的时钟周期个数。SysTick 定时器的计数是从 LOAD 装载值寄存器的值递减到零的,所以如果你希望实现 n 个时钟周期的延时,你需要将 LOAD 寄存器设置为 n - 1。如系统时钟频率为72MHz,经过8分频后,频率为9MHz。即1s震动9000 000个周期。所以装载值为8999 000,计数器从8999000减到0,总共经过 9000000 个时钟周期,则正好为1s的时间,即实现定时1s。
(3)
(3)时钟源选择—库函数( SysTick_CLKSourceConfig(时钟源)):
#define SysTick_CLKSource_HCLK_Div8 ((uint32_t)0xFFFFFFFB) //经过8分频的外部时钟
#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;
}
else
{
SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
}
}
时钟源选择—寄存器
SysTick->CTRL &=~(1<<2); //选择外部时钟,必须清零默认是1内核时钟
SysTick->CTRL |=(1<<2); //选择内核时钟。
(4)重装载值—库函数( SysTick_Config(装载值))
SysTick_Config(72000) ;滴答定时器的参数是72000即计数72000。使用这个函数不需要预期值-1。直接设为预期值即可。(因为我们使用72M的时钟频率,即1s计数72M=72000000次,那1ms计数72000次,所以计数值为72000)
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */
SysTick->VAL = 0; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0); /* Function successful */
}
(4)延时范围
●如系统时钟频率为72MHz,经过8分频后为9MHz。1s的时钟周期个数为9000 000,1ms的时钟周期个数为9000,1us的时钟周期个数为9。
●VAL寄存器以及LOAD寄存器都是24位的,它的最大值是1111 1111 1111 1111 1111 1111,转化乘十进制后是16777215。即装载的最大十周周期个数为16777215。
●秒级别的定时器,一次最大定时时长为:16777215 / 9000000 s。
●毫秒级别的定时器,一次最大的定时时长16777215/9000 ms,也就是1864.135毫秒,由于对于毫秒只能取整,也就是1864毫秒。
●微秒级别的定时器,一次最大定时时长是16777215/9=1864135 us。
这就是Systick定时器循环一次所能达到的最大定时时长。也就是装载值的最大范围。当然也可以通过循环嵌套来实现更长时间的定时。
五、SysTick两种功能
(1)延时功能:
只需要定时器工作一个周期,也就是从重装载值减到0的一个过程,执行一次后需要关闭定时器,不然它还会不停的从重装载值减到0然后又从重装载值减到0无限循环。
伪代码:
实现系统的us延时(参数)
{
1.选择时钟 建议选择经过8分频后的外部时钟
2.写入重装载值,设为预期值-1。
3.清空计数器
4。打开计数器
5.等待时间到达,等待标志位置1
6.关闭计数器
7.清空计数器
}
具体代码:
// uint32_t SystemCoreClock = SYSCLK_FREQ_72MHz; /*!< System Clock Frequency (Core Clock) */
void delay_init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); // 1.选择外部时钟 HCLK/8
s_fac_num=SystemCoreClock/8; //选择的经过8分频的外部时钟,所以要将系统时钟72Mhz/8。此时频率为9MHz。1s震动9 000 000 次。
us_fac_num=Clock_Div8_after/1000000; //1us 震动9次。1s=1000 000 us.
ms_fac_num=(u16)fac_us*1000; //1个ms需要的systick时钟数 。
}
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*us_fac_num; //2.写入装载值
SysTick->VAL=0x00; //3.清空计数器(当前值)这里大家一定要注意,必须使得当前寄存器的值VAL等于0! SysTick->VAL = (0x00);只有当VAL值为0时,计数器自动重载RELOAD。下面同理。
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //4.打开计数器,开始倒数,使能
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //5.等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //6.关闭计数器
SysTick->VAL =0X00; //7.清空计数器(当前值)
}
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*ms_fac_num; //时间加载(SysTick->LOAD为24bit)
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; //清空计数器
}
(2)中断功能:
利用中断,一定时间进一次中断,以此来实现一个时间片轮询的操作方式。这时候,就需要定时器一直计数了,所以不能计数完成后就关闭计数器了。
实现功能:每过1ms发送一次123456。
伪代码:
系统滴答的初始化代码
{
1.选择外部滴答的时钟源
2.配置系统滴答的重装载值,设为预期值-1。
3.当前值清零--清空计数器
4.打开中断使能
5.配置NVIC控制器
6.开启定时器
}
中断服务函数
{
1.检测标志与清除标志;
2.执行操作。
}
具体代码:
#include "SysTick.h"
u16 SysTick_us;
u16 SysTick_ms;
/*******************************
函数名:SysTick_Init
函数功能:初始化系统滴答,选择外部时钟
函数形参:u32 sysclk 系统时钟72(MHZ)
函数返回值:void
备注:开启1ms中断
********************************/
void SysTick_Init(u32 sysclk) //72HZ
{
SysTick->CTRL &=~(1<<2); //1.选择外部时钟,必须清零。默认是1,为内核时钟。
SysTick_s=SystemCoreClock/8; //9000 000 1s //外部时钟8分频
SysTick_us=SysTick_s/1000 000; //9 1us
SysTick_ms=SysTick_s/1000; //9 000 1ms
SysTick->LOAD = SysTick_ms;//2.重装载值9000
SysTick->VAL=0; //3.清空计数器,清标志位
SysTick->CTRL |=1<<1; //4.使能中断 SysTick倒数计数到0时产生SysTick异常(中断)请求 */
/*-----------------------配置NVIC---------------------------------------------*/
NVIC_InitTypeDef NVIC_InitStructure; //结构体重命名
NVIC_InitStructure.NVIC_IRQChannel = SysTick_IRQn; //选择通道(要中断的对象)
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //设置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //设置响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //通道使能
NVIC_Init(&NVIC_InitStructure); //根据以上参数初始化NVIC寄存器
//NVIC_SetPriority(SysTick_IRQn,NVIC_EncodePriority(7-2,1,2)); // 5.配置NVIC控制器
//NVIC_EnableIRQ(SysTick_IRQn);
SysTick->CTRL |=1<<0; //6.使能定时器
}
void SysTick_Handler(void)
{
if((SysTick->CTRL & 0x1 << 16))//检测标志位,也是清除标志位
{
printf("123456\r\n");
}
}
六、附录:
上述函数中,为什么系统时钟要经过8分频?
答:因为在时钟树框图中,Cortex系统时钟需要系统时钟经过8分频。