本节主要讲解的是stm32的定时器,阅读本文前最好 看一下我之前的文章,对于stm32的定时器可以分为一下几种:
系统滴答定时器Sys Tick
对于这个系统滴答定时器是M3内核的定时器,主要是给RTOS操作系统提供时钟节拍的,例如我们之前做延迟所用到的HAL库里的HAL_Delay()的延迟函数就是基于系统滴答定时器
看门狗Watchdog
看门狗是在很多单片机都有的时钟,可以防止程序跑飞
实时时钟RTC
实时时钟是用来做日历时钟的,在51单片机中也有存在
基本定时器(TIM6、TIM7)、通用定时器(TIM2、TIM3、TIM4、TIM5)、高级定时器(TIM1、TIM8)
这些stm32中比较常用的定时器,基本定时器也就是实现最为基本的功能,通用定时器可以实现更高级的功能,比如PWM波的生成等等,后面有机会的话会出相关的博客接下来是高级定时器,这些一般用在比较特殊的领域,本文不做涉及
具体可以看下官方的中文手册
在学习stm32时呢,通用定时器是最具代表性的,本文以通用定时器来做一个闪烁小灯的实例来描述定时器
通用定时器是通过预分频器(Prescaler)驱动主计数器(Counter Period)对内部时钟或者触发外部时钟来计数,由于预分频器(Prescaler)是16位,所以我们要向该预分频器系数输入0~65535之间任意的数值,定时器发生中断的时间的计算方法:定时器发生中断的时间=(Prescaler+1)×(Counter Period+1)×1/定时器时钟频率,在之前章节的学习我们一般都是将我们的定时器频率配置成32MHz,所以呢我们一般都是将Prescaler设置为31999,将Counter Period设置为自己想要发生中断的时间,比如想要设置500ms后发生中断,那就把Counter Period设置为499,以此类推。
那么我们下面在stm32cubemx中将其配置一下,首先还是和刚开始的一样,对芯片进行选型,(此处不再放图),配置好时钟,将调试模式转换为SW模式,在创建文件处将,c和.h文件勾选上
因为我用的是正点原子精英开发板,和之前一样在原理图找到LED的对应的GPIO引脚,将其设置为输出模式
然后我们这次使用TIM2和TIM3完成实验,用TIM2将LED0500ms翻转一次电平,用TIM3将LED1200ms翻转一次点平,下面对时钟进行配置,我们先将TIM2的时钟源设置为内部时钟
将Prescaler设置为31999,Counter period设置为499,将计数模式设置为向上计数,这里介绍下计数模式(摘自其他博客stm32——通用定时器)通用计时器可以向上计数、向下计数、向上向下双向计数模式。
1.向上计数模式:计数器从0计数到自动加载值(TIMx——ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
2.向下计数模式:计数器从自动装入的值(TIMx——ARR)开始向下计数到0,然后自动装入的值重新开始,并产生一个计数器向下溢出事件。
3.中央对齐模式(向上向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件,然后再从0开始重新计数。
然后将CKD(时钟分频)设置为0,最后跳转到NVIC将其使能就好了
然后对TIM3也进行同样的设置之后就可以生成代码在keil5里进行代码的编写了
注:由于目标程序中TIM3控制下的LED1实现200ms翻转一次电平,在count period中应设置为199
生成代码之后先编译一下确保生成的代码没有错误,观察生成好的代码可以看到在main函数里多了两行代码,如下
MX_TIM2_Init(); MX_TIM3_Init();
也就是TIM2和TIM3的初始化函数,我们跳转到原函数康康
void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 31999;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 499;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
可以看到有些数字和我们刚才在stm32cubemx配置一样的,如果是在库函数里这些函数都行需要自己书写。而通过stm32cubemx的图形化配置已经帮我们把这些代码写好了也大大提高了开发的效率,然后我们在main函数里将TIM2和TIM3使能好,代码如下
HAL_TIM_Base_Start_IT(&htim2); HAL_TIM_Base_Start_IT(&htim3);
我们可以跳转到源代码上看一下
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
{
uint32_t tmpsmcr;
assert_param(IS_TIM_INSTANCE(htim->Instance));
if (htim->State != HAL_TIM_STATE_READY)
{
return HAL_ERROR;
}
htim->State = HAL_TIM_STATE_BUSY;
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
if (IS_TIM_SLAVE_INSTANCE(htim->Instance))
{
tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS;
if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))
{
__HAL_TIM_ENABLE(htim);
}
}
else
{
__HAL_TIM_ENABLE(htim);
}
return HAL_OK;
}
可以在代码解释(也就是前三行)中看到这段代码就是以中断模式启动TimBase生成,也就是说利用定时器也就是在定时器到达一定的时间后产生一个中断,那么和之前的中断一样我们需要写一个回调函数来实现中断时候的程序,让我们打开tim.c文件看一下定时器的回调函数,也就是这个
__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
UNUSED(htim);
}
这个函数也就是用句柄的方式判读定时器的类别,我们用if语句就可以实现我们的目标例程
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
}
if(htim->Instance == TIM3)
{
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);
}
}
接下来下载到我们的开发板上,可以看到LED0和LED1已经按照我们所期望的时间间隔开始闪烁了,那接下来我们想改变小灯闪烁的频率,又不想重新到stm32cubemx中去改数据那我们要怎么办呢,对了,还记得我们在上面提到的TIM2和TIM3的初始化程序吗,直接到那里将count period改成自己想要翻转电平的时间就好了,例如要将LED0翻转电平的时间改成100ms我们将TIM2的count period值改成99就好了,代码如下
void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 31999;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 99;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
再将程序烧录进去我们可以看到LED0闪烁的很快
好了,本次的分享就到这里了,如果文中有什么错误的话敬请指正,我会及时指正的



