STM32-笔记10-手写延时函数(SysTick)
1、什么是SysTick
Systick,即滴答定时器,是内核中的一个特殊定时器,用于提供系统级的定时服务。该定时器是一个24位的倒计数定时器。它从设定的初值(即重载值)开始计数,每经过一个系统时钟周期,计数值就减1,直到计数到0时,SysTick计数器会自动从RELOAD寄存器中重装初值并继续计数 1 。如果中断使能,当计数到0时,还会触发中断 1 。
Systick定时器的主要功能包括实现简单的延时、生成定时中断以及进行精确定时和周期定时操作。此外,Systick定时器还可以被用于其他目的,例如作为操作系统的时基(如FreeRTOS),或者用于软件看门狗等系统调度操作。在STM32中,Systick通常以HCLK(AHB时钟)或HCLK/8作为运行时钟。
2、SysTick工作原理
在使用Systick定时器进行延时操作时,可以设定初值并使能后,每经过一个系统时钟周期,计数值就减1。 当计数到0时,Systick计数器自动重装初值并继续计数,同时内部的COUNTFLAG标志会置位,触发中断 (如果中断使能)。这样,可以在中断处理函数中实现特定的延时逻辑。
3、SysTick寄存器介绍
SysTick控制及状态寄存器(CTRL)
SysTick重装载数值寄存器(LOAD)
SysTick当前数值寄存器(VAL)
4、手写代码
#include "delay.h"
/**
* @brief 微秒级延时
* @param nus 延时时长,范围:0~233015
* @retval 无
*/
void delay_us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD = 72 * nus; /* 设置定时器重装值 */
SysTick->VAL = 0x00; /* 清空当前计数值 */
SysTick->CTRL |= 1 << 2; /* 设置分频系数为1分频 */
SysTick->CTRL |= 1 << 0; /* 启动定时器 */
do
{
temp = SysTick->CTRL;
} while ((temp & 0x01) && !(temp & (1 << 16))); /* 等待计数到0 */
SysTick->CTRL &= ~(1 << 0); /* 关闭定时器 */
}
/**
* @brief 毫秒级延时
* @param nms 延时时长,范围:0~4294967295
* @retval 无
*/
void delay_ms(uint32_t nms)
{
while(nms--)
delay_us(1000);
}
/**
* @brief 秒级延时
* @param ns 延时时长,范围:0~4294967295
* @retval 无
*/
void delay_s(uint32_t ns)
{
while(ns--)
delay_ms(1000);
}
/**
* @brief 重写HAL_Delay函数
* @param nms 延时时长,范围:0~4294967295
* @retval 无
*/
void HAL_Delay(uint32_t nms)
{
delay_ms(nms);
}
5、手写代码分析
关于函数delay_s(); = 1000*delay_ms(); = 1000*delay_us();之间的换算
等价于=>1s = 1000ms; 1ms = 1000us;
秒,毫秒之间的延迟函数只需要相互调用就好,重点是关于微秒的实现
下面这段代码是微妙的实现方法:
void delay_us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD = 72 * nus; /* 设置定时器重装值 */
SysTick->VAL = 0x00; /* 清空当前计数值 */
SysTick->CTRL |= 1 << 2; /* 设置分频系数为1分频 */
SysTick->CTRL |= 1 << 0; /* 启动定时器 */
do
{
temp = SysTick->CTRL;
} while ((temp & 0x01) && !(temp & (1 << 16))); /* 等待计数到0 */
SysTick->CTRL &= ~(1 << 0); /* 关闭定时器 */
}
我们知道SysTick有三个寄存器,分别是CTRL、LOAD、VAL
SysTick启动对应的寄存器使用方法为:SysTick->LOAD
给相应的寄存器存它的意义,作用,使用=、|=、&=
= :直接赋值
|= :把某一位给它置1
&= :把某一位给它置0
- 为什么给定时器重装值是 72*nus ?
因为这里选用设置的是72MHZ;分频系数是1分频的SysTick,所以是72*nus(nus是传递进来多少微妙的数值)
如果选用设置的是72MHZ;分频系数是8分频的SysTick,就应该是72/8 = 9,是9*nus.
- 那么为什么是72乘以微妙数呢?
72MHZ = 72 000 000 HZ
1S = 1000 MS = 1000 000 US
72MHZ是指STM32微控制器的系统时钟频率。在STM32微控制器中,72MHz通常是系统时钟(SYSCLK)的频率,72MHZ表示每秒钟有72,000,000个时钟周期。
所以1000 000 us= 71 000 000 HZ
1us有72个时钟周期
所以,重装值被赋值为72*nus
- 为什么设置分频系数为1分频时是SysTick->CTRL |= 1 << 2;这种表现形式?
CTRL是控制及状态寄存器,对应配置分频系数是位段2,要把位段2赋值为1,
把CTRL赋值为1,向左移2位
- 思考:启动定时器,怎么设置?
读上述信息可以知道,定时器的启动和关闭是CTRL寄存器的ENABLE来管理,当ENABLE为1的时候启动定时器,复位值为0的时候关闭计时器。所以CTRL赋值为1,向左移0位,表示为:1<<0;
所以设置为:SysTick->CTRL |= 1 << 0; // |= :把某一位给它置1
关闭定时器就是:SysTick->CTRL &= ~(1 << 0); // &= :把某一位给它置0
因为在关闭定时器的时候CTRL的倒数第三位,也就是开启定时器时向左移两位的位置,已经赋值为1了,现在要把这个位置赋值为0,可以使用与运算,1&0 = 0;原理,所以,先把CTRL赋值为1,向左移0位,然后取反,就搞出了一个倒数第三位是0的一个位,使用与运算,与原来CTRL的值进行与,就会把1置为0.
- 为什么要使用do-while来进行循环?
因为从启动滴答定时器开始,滴答定时器就在一直倒数,我们要等滴答定时器,数完,然后将定时器关闭,这样就完成了1us的延时,所以do-while语句就是一个等的操作。
- do-while中具体怎么实现?
首先,我们明白了do-while存在的意义,那么我们知道在SysTick中CTRL寄存器的位段16 COUNTFLAG的作用是 当SysTick数到0时,该位为1,所以我们只需要在while中判断COUNTFLAG什么时候为1就知道SysTick什么时候数完了。
while(!(SysTick->CTRL & (1 << 16))); //可以这么写,但是有点小错误还需要完善
- 会出现什么错误呢?
如果你只是单一的使用delay,那么不会出错,但是如果在很多地方使用了delay,可能会在其他地方被关掉了,所以还需要一个判断条件。需要判断你此时的定时器是正常的(还在开启的模式下)。
do{
temp = SysTick->CTRL;
} while ((temp & 0x01) && !(temp & (1 << 16)));
使用do-while多做一步,定义一个临时变量temp来承接SysTick->CTRL;的值,方便用来进行判断。
6、系统HAL_Delay()函数代码流程
在上述代码中可以看出来HAL库自带的delay函数最小只支持到ms级别(HAL_Delay();)
可以在这里看到: __weak void HAL_Delay(uint32_t Delay)这条语句。
双击点开,看到该函数由于_weak void...是弱函数,可以重写的函数所以可以在delay.c文件中重写void HAL_Delay(uint32_t Delay);函数
在上面的图片中我们可以看到,Delay形参是有外界传过来的时间,也就是想要延迟多久的时间,最小单位是ms,uint32表示32位无符号整数,不能表示小数,所以Delay的值为整数。
在HAL_Delay函数的上下文中,HAL_GetTick()和tickstart都是调用HAL_GetTick()函数时获取的系统时钟滴答数(通常是以毫秒为单位的)。HAL_GetTick()函数返回一个无符号的32位整数,表示自系统启动以来的滴答数。用tickstart变量来承接HAL_GetTick();函数是当前系统时钟滴答数也就是刚进到这个函数时的时间。
后面在while子句(HAL_GetTick() - tickstart) < wait中,while函数不断循环,HAL_GetTick()函数不断被调用,此时这里的HAL_GetTick()函数就是每一次读完一轮数以后执行中断服务函数时的时间。
是SysTick的中断服务函数,思考:那么在什么时候会触发这个中断服务函数呢?
答案:SysTick是递减计数器,当计数器数到0的时候,就会触发一次中断(前提中断使能)
故上述的while子句中HAL_GetTick()函数就是每一次读完一轮数以后执行中断服务函数时的时间。
SysTick是递减计数器,HAL_GetTick() - tickstart) < wait也就是 (新获取的时间-最开始的时间)<设置想要的时间 ,在实际应用中,HAL_GetTick()的值在每次调用时都会增加(通常是因为SysTick定时器的中断服务例程会在每经历过1ms时增加它),所以HAL_GetTick() - tickstart的值最终会超过wait,从而退出while循环。