以下内容全部来自正点原子,本人只是对主要知识点进行整理,方便以后查看。
一、基本定时器
STM32F407 有两个基本定时器 TIM6 和 TIM7,完全独立的, 可以同时使用。
不太准确的描述:基本定时器工作和独立看门狗类似,他有计数器寄存器,是一个16 位寄存器,接受到一个脉冲后就递增一个数,当计数器寄存器递增到自动重载寄存器设定的值相同时就可以产生事件。
1.1、框架图
① 时钟源
基本定时器时钟挂载在 APB1 总线,所以它的时钟来自于 APB1 总线,但是基本定时器时钟不是直接由 APB1 总线直接提供,而是先经过一个倍频器。
当 APB1 的预分频器系数为 1 时,这个倍频器系数为 1, 即定时器的时钟频率等于 APB1 总线时钟频率。
当 APB1 的预分频器系数≥2 分频时,这个倍频器系数就为 2 , 即定 时 器 的 时 钟 频 率 等于 APB1 总 线 时 钟 频率 的 两 倍 。
② 控制器
控制器除了控制定时器复位、使能、计数等功能之外,还可以用于触发 DAC 转换。
③ 时基单元
时基单元包括:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器 (TIMx_ARR) 。基本定时器的这三个寄存器都是 16 位有效数字,即可设置值范围是 0~65535。
预分频器 PSC,它有一个输入和一个输出。输入CK_PSC来自 APB1 总线倍频后的时钟频率。输出CK_CNT是分频后的时钟,它是计数器实际的计数时钟,通过设置预分频器寄存器(TIMx_PSC)的值可以 得到不同频率 CK_CNT,计算公式如下:
比如我们需要一个 500ms 周期的定时器更新中断,一般只需要两部设置:①先设置预分频寄存器,②设置自动重载寄存器。
使用的stm32f407的apb1为42MHZ,但是我们将APB1倍频了,所以CK_INT 为 84MHz,即上式中第一个fck_psc=84MHz
我们把 预分频系数(即PSC[15:0]+1=8400)设置为 8400,即写入预分频寄存器(PSC[15:0])的值为 8399,那么 fCK_CNT=84MHz/8400=10KHz。 这样就得到计数器的计数频率为 10KHz,即计数器 1 秒钟可以计 10000 个数。
我们需要 500ms 的中断周期,所以就让计数器计数 5000 个数就能满足要求,即需要设置自动重载寄存器的值 为 4999。
1.2、寄存器
控制寄存器 1(TIMx_CR1)
位 0(CEN)用于使能或者禁止计数器,该位置 1 计数器 开始工作,置 0 则停止
有位 7(APRE)用于控制自动重载寄存器 ARR 是否具有缓冲作用, 如果 ARPE 位置 1,ARR 起缓冲作用,有在更新事件发生时才会把 ARR 的值写入其影子 寄存器里(也就是只有完成当前的计数后,在下一轮记数中才能生效新的值);如果 ARPE 位置 0,那么修改自动重载寄存器的值时,该值会马上被写入其影子寄 存器中(也就是不用完成当前的计数,直接将新的值立即生效)。
DMA/中断使能寄存器(TIMx_DIER)
位 0(UIE)用于使能或者禁止更新中断,用中断该位需要置 1。
位 8(UDE)用于使能或者禁止更新 DMA 请求,暂且用不到,置 0 即可。
状态寄存器(TIMx_SR)
位 0(UIF)是中断更新的标志位,当发生中断时由硬件置 1,然后就会执行中断服务函数,需要软件去清零
计数器寄存器(TIMx_CNT)
用来记数的
预分频寄存器(TIMx_PSC)
该寄存器是 TIM6/TIM7 的预分频寄存器,比如我们要 8400 分频,就往该寄存器写入 8399
自动重载寄存器(TIMx_ARR)
该寄存器可以由 APRE 位设置是否进行缓冲。计数器的值会和 ARR 寄存器影子寄存器进 行比较,当两者相等,定时器就会溢出,从而发生更新事件,如果打开更新中断,还会发生更 新中断。
二、相关函数
2.1HAL_TIM_Base_Init 函数
定时器的初始化函数,
其声明如下: HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);
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; /* 定时器状态 */
__IO HAL_TIM_ChannelStateTypeDef ChannelState; /* 定时器通道状态 */
__IO HAL_TIM_ChannelStateTypeDef ChannelNState; /* 定时器互补通道状态 */
__IO HAL_TIM_DMABurstStateTypeDef DMABurstState; /* DMA 溢出状态 */
}TIM_HandleTypeDef;
1)Instance:指向定时器寄存器基地址。比如TIM6
2)Init:定时器初始化结构体,用于配置定时器的相关参数。
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 位。
3)Channel:定时器的通道选择,基本定时器没有该功能。
4)hdma[7]:用于配置定时器的 DMA 请求。
5)Lock:ADC 锁资源。
6)State:定时器工作状态。
7)ChannelState/ChannelNState:定时器通道/互补通道工作状态。
8)DMABurstState:DMA 溢出状态
2.2 HAL_TIM_Base_Start_IT 函数
更新定时器中断和使能定时器的函数。
其声明如下: HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
该函数调用了__HAL_TIM_ENABLE_IT 和__HAL_TIM_ENABLE 两个函数宏定义,分别 是更新定时器中断和使能定时器的宏定义。
下面分别列出单独使能/关闭定时器中断和使能/关闭定时器方法:
__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 指定的定时器 */
2.3定时器中断配置步骤:
定时器中断和其他中断的配置步骤一样,就是参数不一样而已
1)开启定时器时钟
HAL 中定时器使能是通过宏定义标识符来实现对相关寄存器操作的,方法如下: __HAL_RCC_TIMx_CLK_ENABLE(); /* x=1~14 */
2)初始化定时器参数,设置自动重装值,分频系数,计数方式等
定时器的初始化参数是通过定时器初始化函数 HAL_TIM_Base_Init 实现的。 注意:该函数会调用:HAL_TIM_Base_MspInit 函数,我们可以通过后者存放定时器时钟 和中断等初始化的代码。
3)使能定时器更新中断,开启定时器计数,配置定时器中断优先级
通过 HAL_TIM_Base_Start_IT 函数使能定时器更新中断和开启定时器计数。 通过 HAL_NVIC_EnableIRQ 函数使能定时器中断,通过 HAL_NVIC_SetPriority 函数设置 中断优先级。
4)编写中断服务函数
定时器中断服务函数为:TIMx_IRQHandler 等,当发生中断的时候,程序就会执行中断服 务函数。HAL 库提供了一个定时器中断公共处理函数 HAL_TIM_IRQHandler,该函数又会调用HAL_TIM_PeriodElapsedCallback 等一些回调函数,需要用户根据中断类型选择重定义对应的中 断回调函数来处理中断程序。
三、实战
用基本定时器定时500ms,每次500ms之后将在GPIOF_11的LED灯反转。
#include "stm32f4xx.h"
#include "core_cm4.h"
#include "stm32f4xx_hal.h"
#include "stdio.h"
TIM_HandleTypeDef g_timx_handler;
/* ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓基本定时器各种使能开关↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
/* ↓↓↓↓↓↓↓↓↓↓↓此函数会被HAL_TIM_Base_Init()函数调用↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
__HAL_RCC_TIM6_CLK_ENABLE(); /* 使能TIMx时钟 */
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 1, 3); /* 抢占1,子优先级3 */
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn); /* 开启ITMx中断 */
}
/* ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 基本定时器的参数设置↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
void btim_timx_int_init(uint16_t arr, uint16_t psc)
{
g_timx_handler.Instance = TIM6; /* 定时器x */
g_timx_handler.Init.Prescaler = psc; /* 分频 */
g_timx_handler.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_handler.Init.Period = arr; /* 自动装载值 */
HAL_TIM_Base_Init(&g_timx_handler);
HAL_TIM_Base_Start_IT(&g_timx_handler); /* 使能定时器x和定时器更新中断 */
}
/* ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ LED的配置↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
void led_init(void) /* 对LED串口进行初始化*/
{
GPIO_InitTypeDef gpio_init_struct; /* 定义结构体 */
gpio_init_struct.Pin = GPIO_PIN_10; /* LED引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(GPIOF, &gpio_init_struct); /* 初始化LED引脚 */
}
/* ↓↓↓↓↓↓↓↓↓↓↓ HAL_TIM_IRQHandler函数中会引用HAL_TIM_PeriodElapsedCallback函数↓↓↓↓↓↓↓↓↓↓↓*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10);/* LED1反转 */
}
void TIM6_DAC_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_handler); /* 定时器回调函数 */
}
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
led_init(); /* 初始化LED */
btim_timx_int_init(4999, 8399); /* 84 000 000 / 84 00 = 10 000 10Khz的计数频率,计数5K
次为500ms */
__HAL_RCC_GPIOF_CLK_ENABLE(); /* F端口使能*/
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, GPIO_PIN_SET); /* 开启LED */
while(1)
{
delay_ms(200);
}
}
注:delay_ms(200);直接用的正点原子的代码,这里只是用来表示一下。