正点原子精英板STM32基本定时器实现LED灯模拟

什么是定时器

​ 定时器其实就是一个计数器,当时间递增或者递减到我们设置的一个数值后就会实现一些功能,这就是定时器。那么定时器与我们平常用的延时有什么区别呢?我们平常用的延时函数是属于一种软件延时,而接下来要学的定时器是一种硬件延时,这二者的区别其实很好区分和理解,我们只需要知道软件延时,也就是我们平常用的延时函数他会阻塞我们的程序,例如在主函数里我们使用了延时函数,我们的程序是不是得等这个延时过去了,我们的程序才会继续向下进行,这其实就是一种阻塞。但是硬件延时,也就是定时器并不会存在这种阻塞,我们可以理解STM32额外划分出了一部分用来专门定时,让它不会阻塞我们程序的运行。

基本定时器

定时器的分类

​ 根据此图我们可以清楚地看到定时器的分类,这里像独立看门狗和窗口看门狗我都已经做出过讲解,仔细回想他们我们其实就可以发现,他们就是一种硬件延时。这篇文章我只对常规定时器里的基本定时器做出讲解,后续我会对常规定时器做出全部的讲解。

上图就是STM32中关于常规定时器的具体分类和相关的特性,我们可以看到基本定时器有TIM6和TIM7。且计数模式只有递增,预分频系数从1~65536。

定时器计数模式及溢出条件

上图就是STM32定时器计数模式及溢出条件,因为基本定时器只有递增计数模式,所以这里我只对递增计数模式做出讲解,递增计数顾名思义,我们可以很简单的理解,那它的溢出条件图中也给出了(CNT==ARR)。其实就是从零计数,当数值增长到我们设置的阈值后定时器计数复位,接着从零开始计数。这就是递增计数模式。

基本定时器中断实验配置步骤

上图就是定时器配置的步骤,接下来我将通过代码逐步进行讲解。

步骤一

TIM_HandleTypeDef g_timx_handle;

/* 定时器中断初始化函数 */
void btim_timx_int_init(uint32_t arr, uint32_t psc)
{	
	g_timx_handle.Instance = TIM6;
	g_timx_handle.Init.Prescaler = psc;
	g_timx_handle.Init.Period = arr;
	HAL_TIM_Base_Init(&g_timx_handle);
	
	HAL_TIM_Base_Start_IT(&g_timx_handle);
}

这个就是我定时器中断初始化函数我们可以看到它其实和看门狗的初始化类似。我们可以通过查看**HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)**的句柄来找到我们需要配置的相关参数。

那么我们转到定义后可以看到其内部相关参数是很多的,但是我们只需要配置

  TIM_TypeDef                        *Instance;         /*!< Register base address                             */
  TIM_Base_InitTypeDef               Init;              /*!< TIM Time Base required parameters                 */

这两个就可以了其他的我们不用管,学习过看门狗我们可以知道第一个其实就是定时器的基地址,在HAL库中对基地址都进行了相关的宏定义,我们这里使用基本定时器中的TIM6所以直接让基地址等于TIM6即可。而第二个也和看门狗类似,是有关于定时器初始化的结构体,我们可以转到定义查看

根据上图我们可以看到在这个结构体里有这么多的参数,这里因为我们使用的是基本定时器,所以我们只会用到两个结构体成员,分别是第一个结构体成员:uint32_t Prescaler;和第三个结构体成员: uint32_t Period;他们二者一个是预分频系数,另一个是自动重装载的值。我们可以通过设置他们两个的数值来设置我们想要定时的时间。所以到此关于基本定时器初始化函数的相关配置已经全部完成了,但是在我上面的代码中我们可以看到我将第三步的定时器使能更新中断并启动计数器函数也放在了定时器初始化函数里,这里我们也可以很好理解,当你配置完定时器的相关参数后,我们得让定时器启动啊,而且这个函数的功能是定时器使能更新中断并启动计数器。所以据此我们也应该将它放在初始化函数里,因为这个函数一定是在定时器中断之前的,而在这之前只有定时器的初始化。到此我们已经完成了配置步骤的第一步和第三步。

步骤二

我们通过转到定时器基础MSP初始化函数的定义(上图)可以看到他是一个由用户自行编写的函数,那么我们在这个函数里应该实现怎样的功能呢?在配置步骤的图片中我们可以知道我们需要在这个函数里配置NVIC、CLOCK等。那么下面就是其相关代码

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM6)
    {
        __HAL_RCC_TIM6_CLK_ENABLE();
        HAL_NVIC_SetPriority(TIM6_IRQn, 1, 3);
        HAL_NVIC_EnableIRQ(TIM6_IRQn);
    }
}

首先我们需要判断一下是那个定时器,之后使能这个定时器的时钟,以及设置它的优先级并使能中断。这其实与中断是一样的所以我就不做解释了。我们只需要判断一下是哪个定时器即可。那么到此我们就剩下步骤五步骤六。而他们的相关配置其实和看门狗的中断是一样的,也是先调用中断服务函数,编写中断回调函数,并在中断回调函数里实现我们想要的功能。所以我直接对步骤五和步骤六一起进行讲解。

步骤五和六

/* 定时器6中断服务函数 */
void TIM6_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_timx_handle);
}

/* 定时器溢出中断中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM6)
    {
        LED0_TOGGLE();
    }
}

在学习过看门狗之后我们就知道了,这和看门狗的相关配置简直一摸一样啊!也是在中断服务函数里调用中断公共处理函数。之后编写回调函数。在我这个代码里我在中断回调函数里实现的功能是LED0状态翻转。并且先判断一下是那个定时器。到此有关于基本定时器所有的配置我们就全部完成了。接下来我将通过主函数来讲解这个溢出时间怎么计算(也就是我们要设置的阈值),和验证定时器是不会阻塞我们的程序的。

基本定时器功能的实现

主函数代码:

int main(void)
{
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    led_init();                                 /* 初始化LED */
    btim_timx_int_init(4999, 7199);     /* 10Khz的计数频率,计数5K次为500ms */
    while(1)
    {
		delay_ms(5000);
		LED1_TOGGLE();
    }
}

在这里我们可以看到定时器初始化函数的参数我设置为了(4999,7199)那么这是怎么来的呢,通过下图的计算公式可以得出。

假如我们想要将定时器设置为500ms(我代码里设置的就是500ms)那么我们就可以这样计算:
首先用定时器溢出时间乘以定时器时钟源频率,那么基本定时器时钟源频率是多少呢,我们记一下就行,在STM32F1系列里它是72MHz,所以用500*72000得出36000000,之后我们都是先设置预分频系数的值后求重装载的值所以这里我们将预分频系数设置为7199,加1后得7200,这样方便我们计算,最后得出ARR+1得5000,所以ARR就等于4999。这样我们就设置好了关于溢出时间为500ms的定时器的相关参数的设置。最后在while函数里我编写了每延时5秒翻转一次LED1的状态。最后的实验现象就是LED1每5秒翻转一次,LED0每0.5秒翻转一次。据此我们就可以知道,定时器并没有阻塞我们的程序,而我们的程序也没有影响我们的定时器,所以也就验证了,定时器的功能和特性。

作者:昭阳_0324

物联沃分享整理
物联沃-IOTWORD物联网 » 正点原子精英板STM32基本定时器实现LED灯模拟

发表回复