HAL库STM32常用外设教程(四)—— 定时器 基本定时
文章目录
- HAL库STM32常用外设教程(四)—— 定时器 基本定时
- 前言
- 一、定时器特性概述
- 二、基础定时器的结构和功能
- 1、基本特征
- 2、基础定时器相关寄存器
- 3、基础定时器工作流程
- 4、基础定时器时序图
- 三、基础定时器HAL驱动程序
- 1、基础定时器两种定时模式
- 2、基础定时器主要函数
- (1)定时器通用HAL库驱动函数
- (2)启动和停止定时器的三种方式
- 3、定时器其他通用操作函数
- 4、定时器有关的中断处理
- 四、 应用实例
- 1、CuebMx配置
- 2、程序功能实现
- 五、总结
前言
1、STM32F407ZGT6
2、STM32CubeMx软件
3、keil5
内容简述:
通篇文章将涉及以下内容,如有错误,欢迎指出:
1、基础定时器特性
2、基础定时器的结构和功能
3、基础定时器HAL库驱动程序
(1)CubeMx配置
(2)TIM驱动程序
一、定时器特性概述
STM32F407有2个高级控制定时器(TIM1、TIM8)、8个通用定时器和2个基本定时器。基本定时器功能比较简单,只能用于定时,通用定时器和高级定时器还具有输入捕获、输出比较、PWM输出等功能。
表1-1 STM32F407所有定时器的特性
定时器类型 | 定时器 | 计数器长度 | 计数类型 | DMA请求 | 捕获/比较通道数 | 所在总线 |
基本定时器 | TIM6、TIM7 | 16位 | 递增 | 有 | 0 | APB1 |
通用定时器 | TIM2、TIM5 | 32位 | 递增、递减、递减/递增 | 有 | 4 | APB1 |
通用定时器 | TIM3、TIM4 | 16位 | 递增、递减、递减/递增 | 有 | 4 | APB1 |
通用定时器 | TIM9 | 16位 | 递增 | 无 | 2 | APB2 |
通用定时器 | TIM12 | 16位 | 递增 | 无 | 2 | APB1 |
通用定时器 | TIM10、TIM11 | 16位 | 递增 | 无 | 1 | APB2 |
通用定时器 | TIM13、TIM14 | 16位 | 递增 | 无 | 1 | APB1 |
高级定时器 | TIM1、TIM8 | 16位 | 递增、递减、递减/递增 | 有 | 4 | APB2 |
STM32F407还有一个独立看门狗(IWDG)和1个窗口看门狗(WWDG),看门狗实际上也是一个定时器。
二、基础定时器的结构和功能
1、基本特征
TIM6和TIM7两个基础定时器,都在APB1总线上,它们的功能相同,有以下基本特征。
(1)只能使用内部时钟信号CK_INT,这个时钟信号频率为APB1总线频率的2倍,所以这两个定时器输入时钟频率最高为84Mhz。
(2)只有16位自动重装载寄存器(auto-reload register),用于设计计数周期。
(3)有16位可编程预分频器,设置范围为0-65535,分频系数范围为1-65536.
(4)可以输出触发信号(TRGO),用于触发DAC的同步电路。
(5)只有一种事件引起中断或DMA请求,即计数器上溢时产生的更新中断(Updata Event,UEV)。
2、基础定时器相关寄存器
基础定时器有3个十六位寄存器,计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR) ,这3个寄存器的值都可以读写,组成一个时基单元。基本定时器的这三个寄存器都是 16 位有效数字,即可设置值范围是 0~65535。其中,预分频寄存器和自动重装载寄存器有影子寄存器用于底层工作。
(1)预分频寄存器(TIMx_PSC),它存储的一个值用于对输入时钟信号CK_PSC进行分频,得到输出时钟信号CK_CNT。实际分频系数是预分频寄存器的值加1。
(2)计数寄存器(TIMx_CNT),计数器使用时钟信号CK_CNT进行计数,当计数器的值等于自动重装载寄存器的值时产生计数溢出(counter overflow),同时可以产生更新事件(UEV)中断或DMA请求。
(3)自动重载寄存器(TIMx_ARR),存储的值用于与计数器的值进行比较。
图2-1 基础定时器功能结构图
3、基础定时器工作流程
基础定时器只有定时功能,使用基础定时器进行定时的工作流程如下(可对照图2-1进行分析):
(1)控制器除了控制定时器复位、使能、计数等功能之外,还可以用于触发 DAC 转换。
内部时钟信号CK_INT经过控制器后成为CK_PSC进入预分频器,CK_INT和CK_PSC在频率上相等,假设CK_INT频率为50MHz。
(2)预分频器对CK_PSC时钟信号进行分频,输出 CK_CNT是分频后的时钟,它是计数器实际的计数时钟,通过设置预分频器寄存器(TIMx_PSC)的值可以得到不同频率 CK_CNT,假设预分频寄存器设置的值为49999,则实际分频系数为50000,那么分频器的输出时钟信号CK_CNT的频率就是
50MHz /(49999 + 1) = 50 000 000 / 50 000 = 1000Hz
定时器时钟频率 /(预分频寄存器的值+1)= 计数器频率
另外:预分频器寄存器(TIMx_PSC)可以在运行过程中修改它的数值,新的预分频数值将在下一个更新事件时起作用。因为更新事件发生时,会把 TIMx_PSC 寄存器值更新到其影子寄存器中,这才会起作用。
什么是影子寄存器?
①从框图上看,可以看到图2-1中的预分频器PSC后面有一个影子,自动重载寄存器也有个影子,这就表示这些寄存器有影子寄存器。 影子寄存器是一个实际起作用的寄存器, 不可直接访问。举个例子:我们可以把预分频系数写入预分频器寄存器(TIMx_PSC),但是预分频器寄存器只是起到缓存数据的作用,只有等到更新事件发生时,预分频器寄存器的值才会被自动写入其影子寄存器中,这时才真正起作用。
②自动重载寄存器及其影子寄存器的作用和上述同理。不同点在于自动重载寄存器是否具有缓冲作用还受到 ARPE 位的控制,当该位置 0 时, ARR 寄存器不进行缓冲,我们写入新的 ARR值时,该值会马上被写入 ARR 影子寄存器中,从而直接生效;当该位置 1 时, ARR 寄存器进行缓冲,我们写入新的 ARR 值时,该值不会马上被写入 ARR 影子寄存器中,而是要等到更新事件发生才会被写入 ARR 影子寄存器,这时才生效。预分频器寄存器则没有这样相关的控制位,这就是它们不同点。值得注意的是,ARPE位对应下图2-2中CubeMx里面的auto-reload preload相对应,当ARPE位被设置为1时,auto-reload preload功能使能;当ARPE位被置零时,该功能禁用。
图2-2 自动重装载值
(3)计数器对时钟信号CK_CNT从0开始计数,自动重装载寄存器存储一个值,如999。当计数器的值达到999时(此时计时时间为1000ms)就产生UEV事件,然后计数器归零又重新开始计数。
(4)开启定时器全局中断和UEV事件中断后,在发生计数溢出时产生UEV事件中断,利用中断进行定时处理。
(5)定时器的控制器产生一些控制信号,例如,控制定时器的复位、使能等。
4、基础定时器时序图
图2-3 是基础定时工作的时序图,其在TIMx_PSC红写入的预分频系数是2,所以后面基础器的时钟CK_CNT的频率是输入时钟CK_PSC的1/2,计数周期如果设置成FC,每来一个CK_CNT的脉冲,计数值加1,当计数值达到FC时,产生计数器上溢信号和UEV时间,如果开启了定时器UEV事件中断,在发生UEV事件时就会置位相应的中断挂起标志位。
再次强调,基础定时器TIM6和TIM7各有一个中断号,且只有一个中断事件源,就是定时器计数溢出的UEV事件。
图2-3 基础定时器工作时序图
三、基础定时器HAL驱动程序
1、基础定时器两种定时模式
基础定时器只有定时这一个功能,根据控制寄存器TIMx_CR1中的OPM(One-pulse mode)位设置值不同,基础定时器有两种模式:连续定时模式和单次定时模式。
(1)当 OPM位是0时,定时器是连续定时模式,也就是计数器在发生UEV事件时不停止计数。所以在连续定时模式下,可以产生连续的UEV事件,也就是产生连续、周期性的定时中断,这就是定时器默认的工作模式。
(2)当OPM位是1时,定时器是单次定时模式,也就是计数器在发生下一次UEV事件时会停止计数。所以在单词定时模式下,如果启用了UEV时间中断,在产生一次定时中断后,定时器就停止计数了。
图3-1 CubeMX中的One Pulse Mode(单次触发模式)对应于定时器控制寄存器(TIMx_CR1)中的OPM位。
图3-1 单次触发模式
2、基础定时器主要函数
(1)定时器通用HAL库驱动函数
表3-1 是基础定时器的一些主要的HAL驱动函数,所有定时器具有定时功能,所以这些函数对于通用定时器、高级定时器也是适用的。
表3-1 基础定时器的一些主要的HAL库驱动函数
分组 | 函数名 | 功能描述 |
---|---|---|
初始化 | HAL_TIM_Base_Init() | 定时器初始化,设置各种参数和连续定时模式 |
初始化 | HAL_TIM_OnePulse_Init() | 将定时器配置为单次定时模式,需要先执行HAL_TIM_Base_Init() |
初始化 | HAL_TIM_Base_MspInit() | MSP弱函数,在HAL_TIM_Base_Init() 里面被调用,重新实现的这个函数一般用于时钟器时钟使能和中断设置 |
启动 | HAL_TIM_Base_Start() | 以轮训方式启动定时器,不会产生中断 |
停止 | HAL_TIM_Base_Stop() | 停止轮询工作方式的定时器 |
启动 | HAL_TIM_Base_Start_IT() | 以中断工作方式启动定时器,发生UEV事件时产生中断 |
停止 | HAL_TIM_Base_Stop_IT() | 停止中断工作方式的定时器 |
启动 | HAL_TIM_Base_Start_DMA() | 以DMA工作方式启动定时器 |
停止 | HAL_TIM_Base_Stop_DMA() | 停止DMA工作方式的定时器 |
获取状态 | HAL_TIM_Base_GetState() | 获取基础定时器的当前状态 |
(2)启动和停止定时器的三种方式
- 轮询方式。以函数HAL_TIM_Base_Start()启动定时器后,定时器会开始计数,计数溢出会产生UEV事件标志,但是不会触发中断。用户程序需要不断的查询计数值或者UEV事件标志来判断是否发生了计数溢出。
- 中断方式。以函数HAL_TIM_Base_Start_IT()启动定时器后,定时器会开始计数,计数溢出时会产生UEV事件,并触发中断。用户在中断服务例程(Interruput Service Routine,ISR,在stm32f4xx_it.c里面)里进行处理即可,这是定时器最常用的处理方式。
- DMA方式。以函数HAL_TIM_Base_Start_DMA()启动定时器后,定时器会进行计数,计数溢出时会产生UEV事件,并产生DMA请求。DMA一般用于需要进行高速数据传输的场合,定时器一般用不着DMA功能。
3、定时器其他通用操作函数
文件stm32f4xx_hal_tim.h还定义了定时器操作的一些通用函数,这些函数都是宏定义,直接操作寄存器,所以主要用于在定时器运行时直接读取或者修改某些寄存器的值,如修改定时周期,重新设置预分频系数等,如表3-2所示。表中寄存器名称用了前缀“TIMx_”,其中“x”可以用具体的定时器编号替换,例如,TIMx_CR1表TIM6_CR1、TIM7_CR1或TIM9_CR1等。
函数名 | 功能描述 |
---|---|
HAL_TIM_ENABLE() | 启动某个定时器,就是将定时器控制寄存器TIMx_CR1的CEN位 置1 |
HAL_TIM_DISABLE() | 禁用某个定时器 |
HAL_TIM_GET_COUNTER() | 在运行时读取定时器的当前计数值,就是读取TIMx_CNT寄存器的值 |
HAL_TIM_SET_COUNTER() | 在运行时设置定时器的计数值,就是设置TIMx_CNT寄存器的值 |
HAL_TIM_GET_AUTORELOAD() | 在运行时读取自重载寄存器TIMx_ARR的值 |
HAL_TIM_SET_AUTORELOAD() | 在运行时设置自重载寄存器TIMx_ARR的值,并改变定时的周期 |
HAL_TIM_SET_PRESCALER() | 在运行时设置预分频系数,就是设置预分频寄存器TIMx_PSC的值 |
表3-2 定时器操作部分通用函数
注: HAL_TIM_GET_COUNTER()其返回值就是寄存器TIMx_CNT的值,有的定时器是32位的,有的定时器是16位的,实际使用时用uint32_t类型的变量来存储函数返回值即可。
4、定时器有关的中断处理
函数名 | 功能描述 |
---|---|
HAL_TIM_ENABLE_IT() | 启用某个定时器的中断,就是将中断使能寄存器TIMx_DIER中相应事件位置1 |
HAL_TIM_DISABLE_IT() | 禁用某个定时器的中断,就是将中断使能寄存器TIMx_DIER中相应事件位置0 |
HAL_TIM_GET_FLAG() | 判断某个中断事件源的中断挂起标志位是否被置位,就是读取状态寄存器TIMx_SR中相应的中断标志位是否置1,返回值为TURE或FALSE |
HAL_TIM_CLEAR_FLAG() | 清除某个中断事件源的中断挂起标志位,就是将状态寄存器TIMx_SR中相应的中断事件位清零 |
HAL_TIM_CLEAR_IT() | 与HAL_TIM_CLEAR_FLAG()的代码和功能完全相同 |
HAL_TIM_GET_IT_SOURCE() | 查询是否允许某个中断事件源产生中断,就是检查中断使能寄存器TIMx_DIER中相应位置位是否置1,返回值为SET或RESET |
HAL_TIM_IRQHandler() | 定时器中断的ISR里面调用的定时器中断通用处理函数 |
HAL_TIM_PeriodElapsedCallback() | 弱函数,UEV事件中断的回调函数。当定时器的计数器达到自动重装载值并重新开始计数时,会触发一个定时周期结束的事件。在HAL库中,可以通过编写HAL_TIM_PeriodElapsedCallback()函数来处理这个事件。 |
表3-3 定时器中断处理相关函数
每个定时器都只有一个中断号,也就是只有一个ISR。基础定时器只有一个中断事件源,即UEV事件,但是通用定时器和高级定时器有多个中断事件源。在定时器的HAL库驱动中,每一个中断事件对应一个回调函数,HAL驱动程序会自动判断中断事件源,清楚中断事件挂起标志,然后调用相应的回调函数。
四、 应用实例
- TIM6设置为连续定时模式,定时周期为500ms,以中断方式启动TIM6,在UEV事件中断回调函数里面使LED1输出翻转。
- TIM7设置为单次定时模式,定时周期为2000ms,按下KEY1(PE3引脚)键后使LED2点亮,并以中断方式启动TIM7,在UEV事件中断回调函数里使LED2输出翻转。
- 按键需要进行消抖,此处用定时器2定时10ms去完成消抖工作。
1、CuebMx配置
图4-1 定时器TIM6的模式和参数配置
(1)定时器TIM6的模式和参数设置结果如图4-1所示,在模式设置部分,勾选Activated复选框,已启动TIM6。启用后,会出现One Pulse Mode复选框,这是用于设置单脉冲模式的,对于基础定时器就是单次定时模式。TIM6工作于连续定时模式,不勾选此项。参数里面设置如下几个参数。
- Prescalar,预分频器值,设置范围为0 ~ 65535,对应的分频系数为1 ~ 65536。这里设置为4200-1,所以实际分频系数是4200。
- Counter Mode,计数模式,基础定时器只有递增模式(Up)。
- Counter Period,计数周期,也就是自重载寄存器(AutoReload Register,ARR)的值,这里设置为10000-1,那么产生UEV事件时,共计时10000个时钟周期。
- auto-reload preload,是否启用自动重装寄存器TIM6_ARR的预装载功能,实际上就是设置控制寄存器TIM6_CR1的ARPE位(上文有解释)。如果不是动态修改寄存器TIM6_ARR的值,这个设置对寄存器工作没什么影响。
- Trigger Event Selection,主模式下触发输出信号(TRGO)信号源选择,就是设置寄存器TIM6_CR2的MMS[2:0]位。有3个选项:Reset,使用定时器的复位信号作为TRGO输出;Enable,使用计数器的是能信号作为TRGO输出;Updata Event,使用定时器的UEV事件作为TRGO输出。
(2)定时器7采用单次定时模式,定时周期为2000ms,其他参数与TIM6一样。TIM7的模式和参数设置结果如图4-2 所示,在模式设置部分勾选现One Pulse Mode复选框,使TIM7工作于单次定时模式。
图4-2 定时器TIM7的模式和参数配置
(3)定时器TIM2的模式和参数设置结果如图4-3所示,定时周期设置为10ms。
时钟源时钟频率 = 84MHZ
PSC 预分频器值 = 840-1
ARR自动自动重装载值 = 1000-1
定时器时钟频率 = 时钟源时钟频率 / [ (PSC+1)(ARR+1) ]
注:之所以要加1是因为计数从0开始的,不是从1开始计数的。
即 84000000 / [ (840-1 +1) x (1000-1 +1) ] = 84000000/[840*1000] = 100
计时周期 = 频率的倒数 = 0.01s =10 ms,即每10ms进入一次TIM2中断
图4-3 定时器TIM2的模式和参数配置
(4)还需要开启TIM6、TIM7、TIM2的中断,在定时器配置界面的NVIC Setting页面里,可以开启定时器中断,但是不能设置中断优先级。TIM6、TIM7和TIM2的中断优先级需要在NVIC组件的配置界面设置,如图。两个定时器的抢占优先级都设置成1,其实设置成0也没有影响,因为本示例在定时器的ISR里不会直接或间接调用延时函数HAL_Delay()。
注:HAL_Delay()延时函数会用到Systick定时器中断,而这个中断的抢占优先级是0.如果某个外部中断的抢占优先级为0,执行外部中断的ISR时调用HAL_Delay(),则Systick中断无法抢占,函数HAL_Delay()的执行就会陷入死循环。如果将所有中断外部回调的抢占优先级设置为1或2,在外部中断的ISR里调用HAL_Delay()时,Systick中断就可以抢占外部中断,正常执行。
图4-4 设置TIM6、TIM7、TIM2的中断优先级
(5)按键外部触发模式设置
①如图4-5,按键KEY1时外端接地,按键按下时,输入低电平,所以需要设置成下降沿外部触发中断(External lnterrupt Mode with Falling edge trigger detection),使用输入上拉(Pull-up)。
②按照图4-6的步骤 设置CubeMx里面的按键触发模式。
图4-5 按键原理图
图4-6 按键 外部触发模式CubeMx设置
2、程序功能实现
(1)在定时器2的中断服务例程里面进行消抖,消抖成功即表明按键按下后开启定时器7,同时将定时器2关闭,为下一次按键按下做准备。
static uint8_t key_ticks = 0; /* 用于按键计数 */
if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) == GPIO_PIN_RESET){ /* 确认按键是否正常按下 */
key_ticks++; /* 消抖 */
if (key_ticks == 2){ /* 20ms */
HAL_TIM_Base_Start_IT(&htim7); /* 开启定时器7 */
HAL_TIM_Base_Stop_IT(&htim2); /* 及时关闭定时器2,已完成消抖任务 */
key_ticks = 0; /* 按键计数清零,为下一次按键触发做准备 */
}
}
(2)开启定时器6中断
HAL_TIM_Base_Start_IT(&htim6); /* 开启定时器6定时中断 */
(3)
①重新定义外部触发中断回调函数,当按键按下时,触发中断,在中断里调用对应的回调函数,在回调函数里面开启定时器2。
②在文件stm32f4xx_it.c中,CubeMx自动生成了定时器TIM6和TIM7的应交中断ISR的代码框架,代码如下(该处系统已自动生成,张贴出来只为进行说明)
/**
* @brief This function handles TIM6 global interrupt, DAC1 and DAC2 underrun error interrupts.
*/
void TIM6_DAC_IRQHandler(void)
{
/* USER CODE BEGIN TIM6_DAC_IRQn 0 */
/* USER CODE END TIM6_DAC_IRQn 0 */
HAL_TIM_IRQHandler(&htim6);
/* USER CODE BEGIN TIM6_DAC_IRQn 1 */
/* USER CODE END TIM6_DAC_IRQn 1 */
}
/**
* @brief This function handles TIM7 global interrupt.
*/
void TIM7_IRQHandler(void)
{
/* USER CODE BEGIN TIM7_IRQn 0 */
/* USER CODE END TIM7_IRQn 0 */
HAL_TIM_IRQHandler(&htim7);
/* USER CODE BEGIN TIM7_IRQn 1 */
/* USER CODE END TIM7_IRQn 1 */
}
③这两个ISR都调用用了定时器中断通用处理函数 HAL_TIM_IRQHandler(),在这个通用处理函数里,程序会判断产生定时器硬件中断的事件源然后调用对应的会调函数进行处理。
④基础定时器的中断事件源只有一个,就是计数器溢出时产生的UEV事件,对应的回调函数是HAL_TIM_PerioElapsedCallback(),用户需要重新实现这个函数进行中断处理。为此,我们将定时器回调函数与HAL_GPIO_EXTI_Callback()函数 在mian.c中重新实现,代码如下:
/**
* @brief 外部触发中断(EXTI)的回调函数
*
* @param
* @return
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
HAL_TIM_Base_Start_IT(&htim2); /* 开启定时器2 */
}
/**
* @brief 定时器(TIM)中断的回调函数
*
* @param
* @return
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6){ /* 判断是否是定时器6 */
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_10); /* 将灯的引脚电平进行翻转--PF10 */
}
if(htim->Instance == TIM7){ /* 判断是否是定时器7 */
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9); /* 将灯的引脚电平进行翻转--PF9 */
}
}
中断响应的ISR是可重入的,事件中断响应的回调函数也可是重入的,计时TIM6和TIM7的中断同时发生,它们的中断响应代码也会被执行。
五、总结
本文主要讲解基础定时器的使用,其中还涉及了按键处理和点灯的操作。其中对CubeMx自动生成的代码部分(例如 定时器初始化)没有做过多的解释,但我们不应该忽视了这部分,应该熟悉基础定时器从配置到实现功能的整个流程。
源码链接 点击百度网盘领取
提取码:n1qj
参考书籍:
《STM32Cube高效开发教程(基础篇)》王维波
《STM32F4xx中文参考手册》
《STM32F407 探索者开发指南》
只要今天比昨天好,这不就是希望吗?
-----------《士兵突击》