一、定时器的基本概述
通过 滴漏和 漏沙瓶这两个例子简单理解定时器的基本工作原理。滴漏是向上计数,漏沙瓶为向下计数。
STM32的常见的定时器资源: 系统嘀嗒定时器SysTick、看门狗定时器WatchDog、实时时钟RTC、基本定时器、通用定时器、高级定时器。
系统嘀嗒定时器SysTick :
这是一个集成在Cortex M3内核当中的定时器,它并不属于芯片厂商的外设,也就是说使用ARM内核的不同厂商,都拥有基本结构相同的系统定时器。主要目的是给RTOS提供时钟节拍做时间基准。
基本定时器:TIM6、TIM7。
通用定时器:TIM2、TIM3、TIM4、TIM5:
在基本定时器的基础上,实现输出比较、输入捕获、PWM生成、单脉冲模式输出等功能。这类定时器最具代表性,使用也最广泛。
高级定时器:TIM1、TIM8。
二、STM32通用定时器的重要知识点
通用定时器的基本结构组成:
STM32的通用定时器,是一个通过可编程预分频器(Prescaler)驱动的16位自动重装主计数器(Counter Period)构成。可以对内部时钟或触发源以及外部时钟或触发源进行计数。
通用定时器的基本工作原理:
首先,定时器时钟信号送入16位可编程预分配器(Prescaler),该预分配器系数为0~65535之间的任意数值。预分配器溢出后,会向16位的主计数器(Counter Period)发出一个脉冲信号。
预分频器,本质上是一个加法计数器,预分频系数实际上就是加计数的溢出值。
定时器发生中断时间的计算方法:
定时时间
三、STM32CubeMX中关于TIM的配置
例:时钟信号32MHz,每隔500ms翻转一次PB5的输出电平。
【1】设置 Clock Source时钟源。
【2】设置 Prescaler和 Counter Period参数。(如何算着方便是重点和难点)
【3】设置 NVIC嵌套向量中断控制器。
计算:32000*500/32000000=0.5s=500ms
即Prescaler参数=500-1=499
Counter Period参数=32000-1=31999
和外部中断一样,定时器中断也需要对中断服务函数进行编写,这一部分会在接下来的练习中进行详细说明。
需要注意的是,在main()函数中启动相应的定时器,注意函数是否带中断使能。
四、练习:外部中断信号控制LED灯开关
在STM32F103ZET6开发板上,利用STM32CubeMX和Keil5协同开发,完成以下的功能:
【1】利用 TIM2实现间隔定时,每隔0.2秒将 LED1的开关状态翻转。
【2】利用 TIM3实现间隔定时,每隔1秒将 LED0的开关状态翻转。
【3】修改 TIM2的初始化代码,改为每隔0.5秒将 LED1的开关状态翻转。
第一部分:配置STM32CubeMX
配置STM32CubeMX的详细步骤见基于HAL库开发的STM32学习笔记一:STM32的GPIO开发基础。这里说明配置定时器中断时需要注意的细节。
【1】由于使用了定时器中断,时钟树的设置尤为重要,时钟树的频率设置直接决定了后面定时的时长。我们在这里保证后面的时钟都为32MHz。
【2】定时器中断的配置。我们在练习中使用到了TIM2和TIM3中断,使用的是内部时钟,在左侧的Timers的选项卡中,找到TIM2和TIM3,将时钟源设置为内部时钟。
【3】两中断源参数的设置。根据第三部分关于TIM的配置这一部分的计算过程,以及TIM2和TIM3两个定时器需要设定的时间,可以得出:
定时器TIM2:32000*200/32000000=0.2s=200ms
即Prescaler参数=200-1=199
Counter Period参数=32000-1=31999
定时器TIM3:32000*1000/32000000=1s=1000ms
即Prescaler参数=1000-1=999
Counter Period参数=32000-1=31999
计数模式均设置为向上计数。
【4】使能相关的NVIC通道。与外部中断一样,定时器中断也需要使能相关NVIC通道,在左侧的Timers的选项卡中,找到TIM2和TIM3,Configuration选项卡中有NVIC Settings中的选项卡,进入后勾选对应定时器中断即可。
第二部分:Keil5中代码的编写
【1】定时器参数配置函数的剖析
可以在项目的tim.c文件中看到,该文件中对TIM2和TIM3两个定时器的参数进行了配置,这些都是我们在STM32CubeMX中进行设定的,在后续配置中也可以通过修改此处代码来改变参数。练习的第三步修改TIM2代码,修改定时时间,就在此处进行处理。
htim2.Instance = TIM2;
htim2.Init.Prescaler = 31999;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 199;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
【2】定时器中断服务函数的编写
我们在stm32f1xx_hal_tim.c文件中可以找到定时器溢出回调函数:
__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(htim);
/* NOTE : This function should not be modified, when the callback is needed,
the HAL_TIM_PeriodElapsedCallback could be implemented in the user file
*/
}
用户对定时器溢出函数进行重写即可,来实现具体的中断逻辑处理。
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2) //处理TIM2定时中断
{
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);
}
if(htim->Instance == TIM3) //处理TIM3定时中断
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
}
}
/* USER CODE END 0 */
在main函数中启用定时器:
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2); //启动定时器TIM2
HAL_TIM_Base_Start_IT(&htim3); //启动定时器TIM3
/* USER CODE END 2 */
注意事项:
(1)在stm32f1xx_hal_tim.c文件中,有两个定时器启动函数,需要注意的是,函数名称带IT的,带有中断使能;不带IT的,没有中断使能操作,在main函数中启用定时器不要出错。
HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim); //不含中断使能操作
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim); //带有中断使能操作
(2)在main函数中启用定时器时,可以发现在中断服务函数编写时,使用的是指针类型的参数,我们在调用的时候,要注意取htim变量的地址,需要加取地址符号。
以下是几篇自己学习过程中发现的比较好的文章,有助于对C语言中的指针与定时器中断的理解和使用,学习时可以参考:
C/C++中和&的用法
C语言中的和&符号
C语言指针详解(经典,非常详细)
C语言指针详解
STM32 定时器详细篇(基于HAL库)
STM32 定时器的几种基本使用
STM32基础定时器详解
STM32定时器详解(定时器中断实验)
以上内容为本人学习b站小蜜蜂老师的基于STM32CubeMX的嵌入式开发基础教程所做的笔记,其中有一些为个人的理解与感悟,如有疏漏之处,敬请大佬指正。