1. 定时器概述
1.1 软件定时原理
使用纯软件(CPU死等)的方式实现(延时)功能
缺点:1,延时不精准 2,CPU死等
1.2 定时器定时原理
使用精准的时基,通过硬件的方式,实现定时功能。
定时器的核心就是计数器
1.3 STM32定时器分类
1.4 STM32定时器特性表
1.5 STM32基本,通用,高级定时器功能整体的区别
2. 基本定时器简介
STM32F103 有两个基本定时器 TIM6 和 TIM7,它们的功能完全相同,资源是完全独立的, 可以同时使用。其主要特性如下:16 位自动重载递增计数器,16 位可编程预分频器,预分频系 数 1~65536,用于对计数器时钟频率进行分频,还可以触发 DAC 的同步电路,以及生成中断 /DMA 请求。
3. 基本定时器框图
① 时钟源
定时器的核心就是计数器,要实现计数功能,首先要给它一个时钟源。基本定时器时钟挂 载在 APB1 总线,所以它的时钟来自于 APB1 总线,但是基本定时器时钟不是直接由 APB1 总 线直接提供,而是先经过一个倍频器。当 APB1 的预分频器系数为 1 时,这个倍频器系数为 1, 即定时器的时钟频率等于 APB1 总线时钟频率;当 APB1 的预分频器系数≥2 分频时,这个倍 频器系数就为 2 , 即定 时 器 的 时 钟 频 率 等 于 APB1 总 线 时 钟 频率 的 两 倍 。 我 们 在sys_stm32_clock_init 时钟设置函数已经设置 APB1 总线时钟频率为 36M,APB1 总线的预分频 器分频系数是 2,所以挂载在 APB1 总线的定时器时钟频率为 72Mhz。
② 控制器
控制器除了控制定时器复位、使能、计数等功能之外,还可以用于触发 DAC 转换。
③ 时基单元
时基单元包括:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器 (TIMx_ARR) 。基本定时器的这三个寄存器都是 16 位有效数字,即可设置值范围是 0~65535。
时基单元中的预分频器 PSC,它有一个输入和一个输出。输入 CK_PSC 来源于控制器部分, 实际上就是来自于内部时钟(CK_INT),即 2 倍的 APB1 总线时钟频率(72MHz)。输出 CK_CNT 是分频后的时钟,它是计数器实际的计数时钟,通过设置预分频器寄存器(TIMx_PSC)的值可以 得到不同频率 CK_CNT,计算公式如下:fCK_CNT= fCK_PSC / (PSC[15:0]+1)
上式中,PSC[15:0]是写入预分频器寄存器(TIMx_PSC)的值。
另外:预分频器寄存器(TIMx_PSC)可以在运行过程中修改它的数值,新的预分频数值将在 下一个更新事件时起作用。因为更新事件发生时,会把 TIMx_PSC 寄存器值更新到其影子寄存 器中,这才会起作用。
什么是影子寄存器?从框图上看,可以看到图20.1.1.1中的预分频器PSC后面有一个影子, 自动重载寄存器也有个影子,这就表示这些寄存器有影子寄存器。影子寄存器是一个实际起作 用的寄存器,不可直接访问。举个例子:我们可以把预分频系数写入预分频器寄存器(TIMx_PSC), 但是预分频器寄存器只是起到缓存数据的作用,只有等到更新事件发生时,预分频器寄存器的 值才会被自动写入其影子寄存器中,这时才真正起作用。
自动重载寄存器及其影子寄存器的作用和上述同理。不同点在于自动重载寄存器是否具有 缓冲作用还受到 ARPE 位的控制,当该位置 0 时,ARR 寄存器不进行缓冲,我们写入新的 ARR 值时,该值会马上被写入 ARR 影子寄存器中,从而直接生效;当该位置 1 时,ARR 寄存器进 行缓冲,我们写入新的 ARR 值时,该值不会马上被写入 ARR 影子寄存器中,而是要等到更新 事件发生才会被写入 ARR 影子寄存器,这时才生效。预分频器寄存器则没有这样相关的控制 位,这就是它们不同点。
值得注意的是,更新事件的产生有两种情况,一是由软件产生,将 TIMx_EGR 寄存器的位 UG 置 1,产生更新事件后,硬件会自动将 UG 位清零。二是由硬件产生,满足以下条件即可: 计数器的值等于自动重装载寄存器影子寄存器的值。下面来讨论一下硬件更新事件。
基本定时器的计数器(CNT)是一个递增的计数器,当寄存器(TIMx_CR1)的 CEN 位置 1,即使能定时器,每来一个 CK_CNT 脉冲,TIMx_CNT 的值就会递增加 1。当 TIMx_CNT 值 与 TIMx_ARR 的设定值相等时,TIMx_CNT 的值就会被自动清零并且会生成更新事件(如果 开启相应的功能,就会产生 DMA 请求、产生中断信号或者触发 DAC 同步电路),然后下一个 CK_CNT 脉冲到来,TIMx_CNT 的值就会递增加 1,如此循环。在此过程中,TIMx_CNT 等于 TIMx_ARR 时,我们称之为定时器溢出,因为是递增计数,故而又称为定时器上溢。定时器溢 出就伴随着更新事件的发生。
由上述可知,我们只要设置预分频寄存器和自动重载寄存器的值就可以控制定时器更新事 件发生的时间。自动重载寄存器(TIMx_ARR)是用于存放一个与计数器作比较的值,当计数器的 值等于自动重载寄存器的值时就会生成更新事件,硬件自动置位相关更新事件的标志位,如: 更新中断标志位。
下面举个例子来学习如何设置预分频寄存器和自动重载寄存器的值来得到我们想要的定时器上 溢事件发生的时间周期。比如我们需要一个 500ms 周期的定时器更新中断,一般思路是先设置 预分频寄存器,然后才是自动重载寄存器。考虑到我们设置的 CK_INT 为 72MHz,我们把预分 频系数设置为 7200,即写入预分频寄存器的值为 7199,那么 fCK_CNT=72MHz/7200=10KHz。这 样就得到计数器的计数频率为 10KHz,即计数器 1 秒钟可以计 10000 个数。我们需要 500ms 的 中断周期,所以我们让计数器计数 5000 个数就能满足要求,即需要设置自动重载寄存器的值为 4999,另外还要把定时器更新中断使能位 UIE 置 1,CEN 位也要置 1。
4. 定时器计数模式及溢出条件
递增计数模式实例说明
递减计数模式实例说明
中心对齐模式实例说明
5. TIM6/TIM7 寄存器
⚫ 控制寄存器 1(TIMx_CR1)
⚫ DMA/中断使能寄存器(TIMx_DIER)
⚫ 状态寄存器(TIMx_SR)
该寄存器位 0(UIF)是中断更新的标志位,当发生中断时由硬件置 1,然后就会执行中断服务函数,需要软件去清零,所以我们必须在中断服务函数里把该位清零。如果中断到来后, 不把该位清零,那么系统就会一直进入中断服务函数,这显然不是我们想要的。
⚫ 计数器寄存器(TIMx_CNT)
⚫ 预分频寄存器(TIMx_PSC)
⚫ 自动重载寄存器(TIMx_ARR)
6. 定时器溢出时间计算方法
7. 定时器中断实验配置步骤
8. 相关HAL库函数介绍
1. HAL_TIM_Base_Init 函数
定时器的初始化函数,其声明如下:
HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);
⚫ 函数描述: 用于初始化定时器。
⚫ 函数形参:
⚫ 形参 1 是 TIM_HandleTypeDef 结构体类型指针变量(亦称定时器句柄),结构体定义如下:
typedef struct
{
TIM_TypeDef *Instance; /* 外设寄存器基地址 */
TIM_Base_InitTypeDef Init; /* 定时器初始化结构体*/
HAL_TIM_ActiveChannel Channel; /* 定时器通道 */
DMA_HandleTypeDef *hdma[7]; /* DMA 管理结构体 */
HAL_LockTypeDef Lock; /* 锁定资源 */
__IO HAL_TIM_StateTypeDef State; /* 定时器状态 */
}TIM_HandleTypeDef;
1)Instance:指向定时器寄存器基地址。
2)Init:定时器初始化结构体,用于配置定时器的相关参数。
我们主要看 TIM_Base_InitTypeDef 这个结构体类型定义:
typedef struct
{
uint32_t Prescaler; /* 预分频系数 */
uint32_t CounterMode; /* 计数模式 */
uint32_t Period; /* 自动重载值 ARR */
uint32_t ClockDivision; /* 时钟分频因子 */
uint32_t RepetitionCounter; /* 重复计数器 */
uint32_t AutoReloadPreload; /* 自动重载预装载使能 */
} TIM_Base_InitTypeDef;
1)Prescaler:预分频系数,即写入预分频寄存器的值,范围 0 到 65535。
2)CounterMode:计数器计数模式,这里基本定时器只能向上计数。
3)Period:自动重载值,即写入自动重载寄存器的值,范围 0 到 65535。
4)ClockDivision:时钟分频因子,也就是定时器时钟频率 CK_INT 与数字滤波器所使用的 采样时钟之间的分频比,基本定时器没有此功能。
5)RepetitionCounter:设置重复计数器寄存器的值,用在高级定时器中。
6)AutoReloadPreload:自动重载预装载使能,即控制寄存器 1 (TIMx_CR1)的 ARPE 位。
⚫ 函数返回值: HAL_StatusTypeDef 枚举类型的值。
2. HAL_TIM_Base_Start_IT 函数
HAL_TIM_Base_Start_IT 函数是更新定时器中断和使能定时器的函数。其声明如下:
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
⚫ 函数描述: 该函数调用了__HAL_TIM_ENABLE_IT 和__HAL_TIM_ENABLE 两个函数宏定义,分别 是更新定时器中断和使能定时器的宏定义。
⚫ 函数形参: 形参 1 是 TIM_HandleTypeDef 结构体指针类型的定时器句柄。
⚫ 函数返回值: HAL_StatusTypeDef 枚举类型的值。
⚫ 注意事项: 下面分别列出单独使能/关闭定时器中断和使能/关闭定时器方法:
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE); /* 使能句柄指定的定时器更新中断 */
__HAL_TIM_DISABLE_IT (htim, TIM_IT_UPDATE); /* 关闭句柄指定的定时器更新中断 */
__HAL_TIM_ENABLE(htim); /* 使能句柄 htim 指定的定时器 */
__HAL_TIM_DISABLE(htim); /* 关闭句柄 htim 指定的定时器 */
9. 代码
1. btim.c
#include "./BSP/BTIM/btim.h"
#include "./BSP/LED/led.h"
TIM_HandleTypeDef g_timx_handle;
/* 定时器中断初始化函数 */
void btim_timx_int_init(uint16_t psc, uint16_t arr)
{
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);
}
/* 定时器基础MSP初始化函数 */
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)
{
LED1_TOGGLE();
}
}
2. main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/BTIM/btim.h"
#include "./BSP/LED/led.h"
int main(void)
{
HAL_Init(); /* 初始化 HAL 库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
btim_timx_int_init(7200 - 1, 5000 - 1);/* psc=7200,arr=5000,7200*5000/72000000=0.5 */
while(1)
{
}
}