通用定时器包含一个 16 位或 32 位自动重载计数器(CNT),该计数器由可编程预分频器(PSC) 驱动。
定 时 器 相 关 的 库 函 数 主 要 集 中 在 HAL 库 文 件 stm32f4xx_hal_tim.h 和stm32f4xx_hal_tim.c 文件中。
+++++++++++++++++++++++++++++++++++++++++++++++
TIM3 时钟使能。
HAL 中定时器使能是通过宏拟函数来实现对相关寄存器操作的,方法如下:
__HAL_RCC_TIM3_CLK_ENABLE(); //使能 TIM3 时钟
初始化定时器参数,设置自动重装值, 分频系数,计数方式等。
在 HAL 库中,定时器的初始化参数是通过定时器初始化函数 HAL_TIM_Base_Init 实现的:
HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);
入口参数,就是 TIM_HandleTypeDef 类型结构体指针,这是TIM的句柄。
我们看看这个结构体的定义:
typedef struct
{
TIM_TypeDef *Instance;
TIM_Base_InitTypeDef Init;
HAL_TIM_ActiveChannel Channel;
DMA_HandleTypeDef *hdma[7];
HAL_LockTypeDef Lock;
__IO HAL_TIM_StateTypeDef State;
}TIM_HandleTypeDef;
Instance 是寄存器基地址。在HAL中都定义好了,比如要初始化定时器1,那么 Instance 的值设置为 TIM1 即可。
Init 为真正的初始化结构体 TIM_Base_InitTypeDef 类型。该结构体定义如下:
typedef struct
{
uint32_t Prescaler; //预分频系数
uint32_t CounterMode; //计数方式
uint32_t Period; //自动装载值 ARR
uint32_t ClockDivision; //时钟分频因子
uint32_t RepetitionCounter;
} TIM_Base_InitTypeDef;
Prescaler 是用来设置分频系数的,
CounterMode 是用来设置计数方式,比 较 常 用 的 是
向 上 计 数 模 式 TIM_CounterMode_Up 和 向 下 计 数 模 式TIM_CounterMode_Down。
Period 是设置自动重载计数周期值。
ClockDivision 是用来设置时钟分频因子,也就是定时器时钟频率 CK_INT 与数字滤波器所使用的采样时钟之间的分频比。
RepetitionCounter 用来设置重复计数器寄存器的值,用在高级定时器中。
回到TIM的结构体。
Channel 用来设置活跃通道。每个定时器最多有四个通道可以用来做输出比较,输入捕获等功能之用。取值范围为: HAL_TIM_ACTIVE_CHANNEL_1 到 HAL_TIM_ACTIVE_CHANNEL_4。
Lock 和 State,是状态过程标识符,是 HAL 库用来记录和标志定时器处理过程。
+++++++++++++++++++++++++++++++++++++
定时器初始化范例如下:
TIM_HandleTypeDef TIM3_Handler; //定时器句柄
TIM3_Handler.Instance=TIM3; //通用定时器 3
TIM3_Handler.Init.Prescaler=8999; //分频系数
TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上计数器
TIM3_Handler.Init.Period=4999; //自动装载值
TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
HAL_TIM_Base_Init(&TIM3_Handler);
++++++++++++++++++++++++++++++++++++++++
HAL 库 中 , 使 能 定 时 器 更 新 中 断 和 使 能 定 时 器 两 个 操 作 可 以 在 函 数
HAL_TIM_Base_Start_IT()中一次完成的,该函数声明如下:
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
在定时器中断使能之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器,设置中
断优先级。
HAL 库为定时器初始化定义了回调函数 HAL_TIM_Base_MspInit。
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim);
++++++++++++++++++++++++++++++++++++++
编写定时器中断服务函数,通过该函数来处理定时器产生的相关中断。
在中断产生后,通过状态寄存器的值来判断此次产生的中断属于什么类型。
对于定时器中断, HAL 库同样为我们封装了处理过程。
首先,中断服务函数是不变的,定时器 3 的中断服务函数为:
TIM3_IRQHandler();
HAL 库为我们定义了 新的定时器中断共用处理函数 HAL_TIM_IRQHandler,在每个定时器的中断服务函数内部,我们会调用该函数。该函数声明如下:
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim);
函数 HAL_TIM_IRQHandler 内部,会对相应的中断标志位进行详细判断,判断确定中断来源后,会自动清掉该中断标志位,同时调用不同类型中断的回调函数。
所以我们的中断控制逻辑只用编写在中断回调函数中,并且中断回调函数中不需要清中断标志位。
比如定时器更新中断回调函数为:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
对于其他类型中断, HAL 库同样提供了几个不同的回调函数,这里我们列出常用的几个回调函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);//更新中断
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim);//输出比较
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);//输入捕获
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim);//触发中断
++++++++++++++++++++++++++++++++++++++++++
实际应用中,TIM主要是用来作为事件发生器。
所以,TIM产生中断事件,触发ISR的执行,与TIM相关的处理工作,放在ISR中执行,更确切的,放在callback中执行。
如果实时性要求高,那么功能处理,可以直接放在calllback中执行,callback运行在ISR的运行上下文中,通常要求是尽量少占用CPU。
如果实时性要求不是特别高,可以在前后台架构中,分开处理。
在callback中打标,在main中检查标志,做出处理,然后清标。
++++++++++++++++++++++++++++++++++++++++++
实例。
预分频寄存器(TIMx_PSC)。该寄存器用设置对时钟进行分频,然后提供给计数器,作为计数器的时钟。
CK_CNT = fck_int /(PSC[15:0] + 1) = fapb1 *2 /(PSC[15:0] + 1)
CK_INT时钟是从 APB1 倍频的来的,
TIMx_CNT 寄存器,该寄存器是定时器的计数器,该寄存器存储了当前定时器的计数值。
TIMx_ARR,该寄存器在物理上实际对应着 2 个寄存器。
一个是程序员可以直接操作的,另外一个是程序员看不到的,这个看不到的寄存器被叫做影子寄存器。事实上真正起作用的是影子寄存器。
APRE=1 时,在每一次更新事件(UEV)时,才把预装载寄存器(ARR) 的内容传送到影子寄存器。
状态寄存器( TIMx_SR)。该寄存器用来标记当前与定时器相关的各种事件/中断是否发生。
在cubemx中配置TIM,
clock source选择 internal clock,
prescale设置为8399,预分频倍数为8400,所以,最后将84MHZ时钟分频到10KHZ,
auto reload value 设置为4999,计数周期为5000,所以,每次中断,周期为500ms。
CKD设置为disable,所以fclk_int并不会分频,就是84MHZ。
auto_reload preload 设置为enable。
trigger event 设置为update event。
配置nvic,使能TIM3的interrupt。
覆盖定义HAL_TIM_PeriodElapsedCallback
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim3){
time_out = 1;
}
}
由于callback会被所有的TIMER的ISR所调用,所以,为了区分当前是哪个TIMER的ISR调用了callback,所以传递了TIMER的句柄作为参数。
在callback中,要对TIMER的句柄进行检查判断,并针对不同的TIMER做出不同的处理。
这里,针对TIM3,callback打标。
在main中,
检查TIM3的标志,并做出后续处理,然后清标。
if(time_out){
pin_value = HAL_GPIO_ReadPin(led1_GPIO_Port, led1_Pin);
HAL_GPIO_WritePin(led1_GPIO_Port ,led1_Pin ,!pin_value);
pin_value = HAL_GPIO_ReadPin(led2_GPIO_Port, led2_Pin);
HAL_GPIO_WritePin(led2_GPIO_Port ,led2_Pin ,!pin_value);
time_out = 0;
}
注意,
在main中,初始化定时器,只是配置好各个参数,
启动定时器,需要手工使用start函数来启动。
HAL_TIM_Base_Start_IT(&htim3);