本文目录
一、SysTick是什么?
Systick定时器是一个24bit的倒计时(向下计数)定时器,功能就是实现简单的延时。注意:系统滴答定时器主要用于简单的定时!如果需要复杂的中断处理,请使用其他定时器。
SysTick 是一种系统定时器,通常在嵌入式系统中使用。它是 ARM Cortex-M 处理器的一个特殊定时器,用于提供系统级的定时服务。SysTick 可以用于生成定时中断,以便执行特定的任务或进行系统级的时间跟踪。
例如:计数初值为100,经过一个时钟周期后,计数值减一,即99,98,97……1,0;计数至0后,又重新开始从100开始倒计数至0。 可以借此做精准延时。
二、SysTick框架图
因为SysTick是属于内核的一部分,其被捆绑在NVIC中,用于产生SYSTICK异常。
三、SysTick组成
SysTick包含四个寄存器,都是32位的寄存器(下图未标注的位端为未使用或不常用字段),分别是:
1. SysTick->CTRL
用于控制 SysTick 计时器的启停、时钟源选择、定时溢出标志等。
2. SysTick->LOAD
SysTick->LOAD 是 SysTick 计时器的重载值寄存器,用于设置计数器的初始值。当 SysTick 计数到 0 时,它会自动重新加载 SysTick->LOAD 中的值,并开始新的倒计时。
3. SysTick->VAL
SysTick->VAL 是 SysTick(系统滴答定时器)的当前计数值寄存器,用于存储当前倒计时的值。它的值会在 SysTick 运行时不断递减,每个时钟周期减 1,直到 0 时触发溢出,并重新加载 SysTick->LOAD 的值继续计时。
4. SysTick->CALIB
SysTick->CALIB 是 SysTick 参考时钟校准值寄存器,用于提供 系统时钟的校准值,帮助软件程序计算精确的时间间隔。该寄存器主要包含出厂校准值,用于指示 SysTick 计时器每 10ms 需要多少个时钟周期。
四、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 999,计数器从8999 999减到0,它会溢出并重新从8999 999开始计数,从而达到周期为9000000 个时钟周期的目标。则正好为1s的时间,即实现定时1s。
3. 为什么选择经过8分频的外部时钟,而不选择内部时钟?
答:选择使用外部时钟而不是内部时钟,是为了保证定时器的精度和稳定性。
内部时钟是由微控制器内部提供的时钟源,通常频率相对较低。在某些情况下,使用内部时钟作为SysTick的时钟源可能会导致定时器的溢出时间过长,无法满足精确的延时需求。
外部时钟,例如外部晶体振荡器或主芯片提供的外部时钟信号,具有较高的频率和稳定性。使用外部时钟作为SysTick的时钟源可以提供更高的精度和可靠性。对于需要较准确的延时操作或时间计量的应用,选择外部时钟是更好的选择。
因此,在该代码中选择使用外部时钟来配置SysTick定时器,以确保精确和稳定的延时功能。
4. 时钟源选择
(1)库函数— SysTick_CLKSourceConfig(时钟源)
●函数代码如下:
//#define SysTick_CLKSource_HCLK_Div8 ((uint32_t)0xFFFFFFFB) //经过8分频的外部时钟
//#define SysTick_CLKSource_HCLK ((uint32_t)0x00000004) //内部时钟
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource) //时钟源选择库函数
(2)寄存器
SysTick->CTRL &=~(1<<2); //选择外部时钟,必须清零默认是1内核时钟
SysTick->CTRL |=(1<<2); //选择内核时钟。
5. 延时范围
●如系统时钟频率为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、ms级别的延时函数。(成功)
伪代码:
实现系统的us延时(参数)
{
1.选择时钟 建议选择经过8分频后的外部时钟。
2.写入重装载值,设为预期值-1。
3.禁止中断。
4.清空计数器。
5.使能计数器。
6.等待时间到达,等待标志位置1。
7.关闭计数器。
8.清空计数器。
}
(1)寄存器版
#include "stm32f10x.h" // 适用于 STM32F1,其他芯片请更改头文件
static u32 s_fac_num = 0; // SysTick 计数因子
static u32 us_fac_num = 0; // 微秒级延时因子
static u32 ms_fac_num = 0; // 毫秒级延时因子
void delay_init()
{
SysTick->CTRL &= ~(1 << 2); // 1. 选择外部时钟源 (HCLK/8)
s_fac_num = SystemCoreClock / 8; // 计算 SysTick 计数频率 SystemCoreClock= SYSCLK_FREQ_72MHz;
us_fac_num = s_fac_num / 1000000; // 计算 1us 需要的计数值
ms_fac_num = s_fac_num / 1000; // 计算 1ms 需要的计数值
}
void delay_us(u32 nus)
{
if (nus == 0) return; // 避免无效调用
SysTick->LOAD = nus * us_fac_num - 1; // 1. 设定倒计时初值
SysTick->CTRL &= ~(1 << 1); // 2. 关闭中断模式
SysTick->VAL = 0x00; // 3. 计数器清零
SysTick->CTRL |= (1 << 0); // 4. 启动计数
while ((SysTick->CTRL & (1 << 16)) == 0); // 5. 等待倒计时结束
SysTick->CTRL &= ~0x07; // 6. 彻底关闭 SysTick
SysTick->VAL = 0x00; // 7. 清空计数器
}
void delay_ms(u32 nms)
{
while (nms--) delay_us(1000); // 逐个调用 delay_us(1000) 避免 SysTick 溢出
}
(2)库函数版
#include "stm32f10x.h"
static uint32_t fac_us = 0; // 微秒延时因子
static uint32_t fac_ms = 0; // 毫秒延时因子
void my_delay_init(void)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); // 选择时钟源 HCLK/8
fac_us = SystemCoreClock / 8000000; // 计算微秒因子 (72MHz / 8 = 9MHz)
fac_ms = fac_us * 1000; // 计算毫秒因子
}
void my_delay_us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD = nus * fac_us - 1; // 计算重装值
SysTick->VAL = 0; // 清空当前值
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 开启 SysTick 计数
do
{
temp = SysTick->CTRL;
} while ((temp & SysTick_CTRL_COUNTFLAG_Msk) == 0); // 等待计数到 0
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 关闭 SysTick 计数器
SysTick->VAL = 0; // 清零计数器
}
void my_delay_ms(uint32_t nms)
{
while (nms--) my_delay_us(1000); // 1 ms = 1000 us
}
2. 中断功能延时
利用中断,一定时间进一次中断,以此来实现一个时间片轮询的操作方式。这时候,就需要计数器一直计数了,所以不能计数完成后就关闭计数器了。
实现功能:每过1ms就发送一次’systick’。
(1)库函数版本
#include "systick.h"
#include "stm32f10x.h"
#include <stdio.h>
#include "usart1.h"
#include "led.h"
/*******************************
功能:初始化SysTick_ms
参数:u32 ms - 参数单位为ms
返回值:void
注意:1ms间隔
********************************/
void SysTick_Init(void)
{
if(SysTick_Config(SystemCoreClock / 1000)){ //每过1ms触发一次中断!
while(1);
}
}
void SysTick_Handler(void)
{
printf("systick\r\n");
}
#include "stm32f10x.h"
#include "usart1.h"
#include "delay.h"
#include "string.h"
#include "systick.h"
#include "led.h"
int main(void)
{
// 设置NVIC中断优先级分组为2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 初始化延时和LED
LED_init();
delay_init();
USART_Config(115200);
printf("start\r\n");
SysTick_Init();
while (1)
{
LED1=!LED1;
delay_ms(1000);
}
}
六、附录
上述函数中,为什么SysTick的时钟频率需要经过8分频系统时钟?
答:因为在时钟树框图中,Cortex系统时钟需要系统时钟经过8分频。