STM32的定时器详解
- 0. 前言
- 1. Systick定时器
- 概念
- 工作原理
- 时钟基准
- Systick练习
- 2. HAL_Delay函数分析
- 3. 定时器
- 基本概念
- 定时器分类
- 定时器组成
- 计数器
- 自动重装寄存器
- 预分频器
- 定时器计数原理
- 定时器练习
- 4. 软件定时器
- 概念
- 设计思想
- 代码
0. 前言
想要了解STM32的时钟系统
点击这里跳转——》STM32的时钟系统(嵌入式学习)
建议先看看时钟树如何配置
点击这里跳转——》STM32的时钟树配置(嵌入式学习)
跳转——》STM32的时钟基础详解(嵌入式学习)
跳跳转——》STM32的时钟源详解(嵌入式学习)
1. Systick定时器
概念
SysTick又称滴答定时器。是一个定时设备,位于Cortex-M0内核中,和NVIC捆绑,产生SysTick异常(IRQ异常号15)可以对输入的时钟进行计数,系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。
Systick定时器是一种内置的定时器,通常用于实时操作系统(RTOS)或其他需要精确定时的应用中。它在大多数STM32微控制器中都有提供。
Systick定时器的主要特点如下:
-
基于计数器:Systick定时器基于一个32位的自动递减计数器。计数器从一个初始值开始递减,当计数器的值减至零时,会产生一个中断或触发一个事件。
-
系统时钟驱动:Systick定时器的时钟源通常是系统时钟(SYSCLK)。这使得它能够提供与系统时钟相关的精确定时功能。
-
配置灵活:Systick定时器可以通过寄存器进行灵活的配置。你可以设置计数器的初始值、使能或禁用定时器、选择中断触发或事件触发等。
-
中断或事件触发:当计数器减至零时,可以选择触发一个中断或一个事件。中断触发可以用于在特定时间间隔内执行一些任务,而事件触发可以用于与其他外设或模块进行同步。
-
时间精度:Systick定时器的时间精度取决于它的时钟源和计数器的位数。通常情况下,它可以提供微秒级别的定时精度。
Systick定时器常用于实现延时函数、系统时钟节拍、定时任务调度等功能。在实时操作系统中,它通常用作系统的节拍定时器,用于调度任务的执行。
要使用Systick定时器,你需要在代码中进行相应的配置和中断处理程序编写。具体的配置和使用方法可以参考相应的微控制器参考手册和编程手册,以及相关的开发工具(如STM32Cube库和HAL库)的文档和示例代码。
工作原理
滴答定时器是一个24位递减定时器,也就是最多能计数2^24(0xFFFFFF)。
SysTick设定初值并使能后,每来一个时钟信号,计数值就减1。
计数到0时,触发中断,SysTick计数器自动重装初值并继续减一,循环不断。
系统默认开启滴答定时器。
Systick定时器是一种基于计数器的定时器,在STM32微控制器中,它的工作原理如下:
-
时钟源选择:Systick定时器的时钟源通常是系统时钟(SYSCLK)。系统时钟通常由外部晶体振荡器或内部RC振荡器提供,并通过时钟树的配置确定其频率。
-
计数器配置:Systick定时器使用一个32位的自动递减计数器进行计时。在初始化时,需要设置计数器的初始值,这决定了定时器开始计数的起点。
-
计数器递减:Systick定时器的计数器从初始值开始递减,每个时钟周期减少一个计数值,直到计数器减至零。
-
中断或事件触发:当计数器减至零时,可以选择触发一个中断或一个事件。中断触发会生成一个中断请求,CPU会相应地跳转到Systick中断处理程序执行特定的操作。事件触发则可以与其他外设或模块进行同步操作。
-
中断处理程序:Systick定时器的中断处理程序是由开发者编写的一段代码,用于处理定时器中断触发时需要执行的任务。在中断处理程序中,可以执行特定的操作,例如更新系统节拍计数、调度任务、处理延时函数等。
-
重载计数器:当计数器减至零后,可以选择自动重载计数器的值。这允许定时器自动重新开始计数,以实现周期性的定时功能。
通过适当配置和使用Systick定时器,可以实现延时函数、系统时钟节拍、定时任务调度等应用。在实时操作系统中,Systick定时器通常用作系统的节拍定时器,用于精确调度任务的执行。
在编程时,你需要配置Systick定时器的相关寄存器,包括计数器的初始值、使能/禁用定时器、中断使能等。同时,需要编写中断处理程序,以响应Systick定时器中断触发时的操作。
请注意,Systick定时器的具体配置和使用方法可能因不同的STM32微控制器型号而有所差异。因此,在进行Systick定时器的配置和使用之前,建议参考所选微控制器的参考手册和编程手册,以获得准确的配置细节和操作说明。
时钟基准
首先看Systick每次触发异常在异常处理程序中做了哪些工作
每次systick触发中断后会让uwTick自加1
这个uwTick的值就作为系统的时钟基准。
然后再来研究一下,uwTick多久会被加1.
在main.c中main函数首个执行的函数HAL_Init(),会对systick进行初始化。
在HAL_Init中通过调用HAL_InitTick函数,对Tick进行初始化
传递参数为:
TICK_INT_PRIORITY = 0 作为systick的中断优先级
调用HAL_SYSTICK_Config函数用于配置systick时基
SystemCoreClock / (1000U /(uint32_t)uwTickFreq)
uint32_t SystemCoreClock = 16000000UL;
16000000 / 1000 / 1 = 16000
将16000作为参数传递给了HAL_SYSTICK_Config函数
Systic_Config函数对Systick的重装值、初值、优先级和校准值都进行了配置。
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) //如果systick初值大于最大值0xFFFFFF
{
return (1UL); //返回1 说明配置失败
}
SysTick->LOAD = (uint32_t)(ticks - 1UL); /* 重载的计数值 */ 16000 - 1
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0UL);
SysTick->LOAD = (uint32_t)(ticks - 1UL); //即16000 - 1
从16000 - 1开始递减到0,总共需要计数16000次
当前时钟频率16Mhz,说明计一个数用时1/16M s
计16000个数需要用时 16000 * 1/16000000 s = 1/1000 s = 1ms
因此Systick每隔1ms会触发一次异常。
Systick练习
利用Systick异常,实现1秒打印“helloworld”
每次Systick触发异常时让flag自加1
在主函数中判断,当flag加到1000时,说明systick异常触发了1000次,此时刚好用时1s。打印“helloworld”
2. HAL_Delay函数分析
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick(); //获取当前时间
uint32_t wait = Delay; //获取用户要延时的时间
/* Add a freq to guarantee minimum wait */
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
while ((HAL_GetTick() - tickstart) < wait)
{ //如果当前系统时间 - 初始时间 < 延时时间 则继续循环
//当达到延时条件时,循环结束。
}
}
GetTick函数返回当前系统uwTick的值
uwTick每个1ms会自加1
因此HAL_Delay的实现依靠Systick,以达到毫秒为单位的延时效果。
3. 定时器
基本概念
定时器本质上是一个计数器,可对输入的时钟进行计数,并在计数值达到设定值时触发中断,当这个计数器的输入是一个准确可靠的基准时钟时,对基准时钟计数的过程就是计时的过程。
定时器分类
定时器的基本结构是通用的,很多模块电路都能用到,所以STM32定时上扩展了非常多的功能,根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型。
F103
F407
定时器组成
计数器
向上计数模式: 计数器从0开始计数,当达到自动装载寄存器(TIMx_ARR)里的值时,自动清零且产生一个溢出事件,然后再从0开始向上计数。
向下计数模式: 计数器从自动装载寄存器(TIMx_ARR)里的值开始递减计数,当计数值达到0时产生一个定时器溢出事件,并重装初值,继续向下计数。
中央对齐模式: 又称为向上/向下计数,计数器从0开始递增达到ARR的值,产生一个定时器溢出事件,再从ARR的值递减到0,产生一个定时器溢出事件。
自动重装寄存器
预分频器
定时器计数原理
时钟频率配置成了48Mhz,如何让定时器产生1s中断?
分频值写0相当于不分频 48/1 => 48
先对主频进行48分频得到1Mhz的频率,则分频值为48-1
48Mhz / 48 => 1Mhz
1Mhz的时钟频率,相当于计一个数需要1/1000000秒,
所以如果想得到1s中断,则需要从0开始计数到1000000-1,即计1000000个数需要1秒。
定时器练习
利用定时器中断实现1s打印一个“hellowolrd”
在main.c中重写定时器溢出中断回调函数
启动定时器以中断模式计数
4. 软件定时器
概念
在STM32微控制器中,通常使用软件定时器来执行特定的定时任务。软件定时器是通过编程实现的,而不是通过硬件计时器。以下是一种实现软件定时器的方法:
-
初始化计时器参数:选择一个适当的定时器变量,例如一个32位的计数器变量,以便能够表示较长的时间。另外,设置一个定时器间隔,即希望定时器每隔多久触发一次。
-
计时器启动:在合适的时机,例如在系统初始化时或任务启动时,将计时器变量设置为0,并启动计时器。
-
定时器中断处理:在每次计时器溢出时,触发一个中断。在中断服务程序(ISR)中,将计时器变量递增,并检查是否已经达到预定的定时器间隔。
-
定时任务执行:如果达到了定时器间隔,执行所需的定时任务,然后重置计时器变量为0,以便下一次定时器中断时再次触发任务。
这是一个基本的软件定时器框架,可以根据具体的应用场景进行调整和扩展。需要注意的是,软件定时器是基于系统时钟的,因此在设计定时器间隔时,需要考虑系统时钟频率和定时器的精度。
设计思想
1)设计一个定时器(软件层次,结构体内含有开始的时间和想要延时的时间)
2)设置定时器时间 和 比较定时器是否到达 两个函数
代码
1)设计定时器(记录起始时间,记录用户延时时间)
typedef struct
{
uint32_t start; //保存起始时间
uint32_t delay; //保存延时时间
}MyTim;
2)提供一个用于传递定时时间的软件定时器配置函数
//定时器初始化函数
void setTim(MyTim *timer,uint32_t delayms)
{
timer->start = HAL_GetTick();
timer->delay = delayms;
}
3)提供一个用于比对是否达到定时要求的函数
uint32_t compareTim(MyTim *timer)
{
if(HAL_GetTick() - timer->start >= timer->delay)
{
return 1; //返回1说明达到了定时要求
}else
{
return 0;
}
}