STM32定时器介绍
定时器类别
STM32拥有三类定时器,以F429为例:
- 基本定时器:TIM6、TIM7
- 通用定时器:TIM2至TIM5、TIM9至TIM14
- 高级定时器:TIM1、TIM8
所有定时器之间相互独立。
定时器时钟
参考手册Block Diagram,很容易可以得到每个定时器的时钟来源。很容易可以发现F429的两个基本定时器TIM6、TIM7均挂载于APB1。APB1的时钟频率可以在CubeMX中设置。
如图,该工程中APB1定时器的频率为75MHz。
计数模式(Counter Mode)
定时器有三种计数模式:
- 向上计数
- 向下计数
- 向上/向下计数(中间对齐)
基本定时器TIM6、TIM7可以且仅能采用向上计数模式。
工作方式
TIM6、TIM7中与定时器计数密切相关的是四个寄存器,分别为:
-
状态寄存器(TIMx_SR)
-
计数器寄存器(TIMx_CNT)
-
预分频寄存器(TIMx_PSC)
-
自动重载寄存器(TIMx_ARR)
四个寄存器的作用可以简单概括为:
- 状态寄存器负责控制定时器中断
- 计数器寄存器负责计数
- 预分频寄存器包含每次发生计数器更新事件(计数次数+1)时,要装载到实际的预分频寄存器的值
- 自动重载寄存器与ARR寄存器的影子寄存器进行比较。二者相等时,该寄存器溢出,发生定时器更新事件,表明计数次数更新。如果更新中断打开,则此时还会发生更新中断。
定时器每触发一次更新事件(计数+1)的时间间隔为:
T
o
u
t
=
(
A
R
R
+
1
)
∗
(
P
S
C
+
1
)
f
c
l
k
T_{out} = \frac{(ARR+1)*(PSC+1)}{f_{clk}}
Tout=fclk(ARR+1)∗(PSC+1)
其中,
- A R R ARR ARR为自动重载寄存器的值
- P S C PSC PSC为预分频寄存器的值
- f c l k f_{clk} fclk为定时器频率
假设我们想要TIM6以1s的间隔计数, f c l k f_{clk} fclk已经得知为75MHz,那么可以令 A R R ARR ARR为10000,令 P S C PSC PSC为7500,从而令 T o u t T_{out} Tout等于1s.
注意ARR和PSC都为16位寄存器,也就是其写入范围是0到65535.
实例
预期目标
使用基本定时器TIM6在定时器中断中以1s的间隔点亮LED0,在main.c的While(1)中以200ms间隔点亮LED1,比较二者的点亮间隔。
CubeMX配置
此处只介绍TIM6的配置。如下图:
注意红框中的几个选项:
- Prescaler (PSC - 16 bits value):PSC寄存器的写入值
- Counter Mode:计数方式。由于TIM6是基本寄存器,此处应只有Up(向上计数)可选
- Counter Period (AutoReload Register - 16 bits value ):ARR寄存器的写入值
- auto-reload preload:影子寄存器使能,此处开启
不要忘记在NVIC中开启TIM6的中断。
设置完成后,生成工程。
tim.c部分
CubeMX生成的有关定时器的函数均包含于此。
MX_TIM6_Init()
TIM6初始化函数。CubeMX已经自动配置。
void MX_TIM6_Init(void)
{
/* USER CODE BEGIN TIM6_Init 0 */
/* USER CODE END TIM6_Init 0 */
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM6_Init 1 */
/* USER CODE END TIM6_Init 1 */
htim6.Instance = TIM6;
htim6.Init.Prescaler = 7500;
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 10000;
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM6_Init 2 */
/* USER CODE END TIM6_Init 2 */
}
HAL_TIM_Base_MspInit() 和 HAL_TIM_Base_MspDeInit()
MSP初始化函数。CubeMX已经自动配置。
HAL_TIM_IRQHandler()
定时器中断服务函数。由于我们之前在CubeMX中启用了TIM6的中断,因此CubeMX已经自动配置。该函数声明位置在stm32f4xx_hal_tim.c的3815行。在我们的工程中,stm32f4xx_it.c中调用了它,并重新定义了专为TIM6服务的中断服务函数:
/**
* @brief This function handles TIM6 global interrupt, DAC1 and DAC2 underrun error interrupts.
*/
void TIM6_DAC_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim6);
}
HAL_TIM_PeriodElapsedCallback()
这个函数是定时器中断的计数后回调函数,十分重要。这是一个弱定义函数,其原始声明为:
__weak void HAL_TIM_PeriodElapsedCallback (TIM_HandleTypeDef * htim)
该函数的形参为指向TIM_HandleTypeDef的句柄htim的指针,用于确认是哪个定时器申请的中断。TIM_HandleTypeDef应当已经在tim.c的头部由CubeMX声明。
我们需要在tim.c中重写这个函数,让定时器每次更新事件(计数+1)后调用该函数然后退出中断。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
UNUSED(htim);
if (htim->Instance == TIM6) //确认中断由TIM6申请
{
LED0_TOGGLE(); //反转LED0电平
}
}
回调函数不止这一种,还有这些:
void HAL_TIM_PeriodElapsedHalfCpltCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_IC_CaptureHalfCpltCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_PWM_PulseFinishedHalfCpltCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_TriggerHalfCpltCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_ErrorCallback(TIM_HandleTypeDef *htim);
main.c部分
在int main中首先要检查CubeMX是否初始化了对应的定时器。
MX_TIM6_Init();
然后打开TIM6的时基:
HAL_TIM_Base_Start_IT(&htim6);
形参为对应定时器的指针,即htim6的地址。
然后。。就设置好了,LED0已经会以1s为间隔翻转了。
最后我们在While(1)里再写一个功能,让LED1以0.2s的间隔翻转,以此对比LED0和LED1的反转速度来看看效果。
while (1)
{
LED1_TOGGLE();
delay_ms(200);
}
实物演示就不放了。以上就是一个基本定时器最简单的用法。