往期文章推荐:
STM32CUBEMX 使用教程5 — DMA配置 & 串口结合DMA实现数据搬运
STM32CUBEMX 使用教程4 — 串口 (USART) 配置、重定向 printf 输出
STM32CUBEMX 使用教程3 — 外部中断(EXTI)的使用
STM32CUBEMX 使用教程2 — GPIO的使用、输入/输出
STM32CUBEMX 使用教程 1 — 配置环境、新建工程
一、了解什么是定时器
什么是定时器?定时器实现的基本原理是什么?定时器可以做什么?
为了能搞懂上面的问题,文章直接用STM32F103ZET6作讲解说明定时器的原理和基本用途。
STM32F103的定时器属于MCU内部的硬件资源,数量有很多,分类上大致分为:
1)基本定时器
2)通用定时器
3)高级定时器
这里用基本定时器说明工作原理,如下的基本定时器的框图:
首先需要明确的事情:定时器的本质就是一个计数器,通过在一定的计数频率下计量一定的装载值实现计数定时的功能。
如上图中:基本定时器中有一个 CNT计数器(1)用来计数,这个计数器的计数值来自于重装载寄存器。基本定时的时钟(2)来源于RCC的时钟(实际为APB2),通过控制器(4)复位、使能定时器功能,并通过PSC预分频器(3)设置时钟的分频提供给CNT计数器。从而实现计数器在某个频率下计数,达到装载值寄存器中的设定数时完成计数,实现时间定时。
二、STM32F103 的定时器其他特性
(1)计数模式
STM32F103中的定时器除了基本定时器(仅有向上计数),其他的定时器都有向上计数、向下计数、中央对齐模式(向上/向下计数)。
向上计数模式:计数器从0计数到自动加载值(TIMx_ARR计数器的内容),然后重新从0开始计数并且产生一个计数器溢出事件。
向下模式:计数器从自动装入的值(TIMx_ARR计数器的值)开始向下计数到0,然后从自动装入的值重新开始并且产生一个计数器向下溢出事件。
中央对齐模式:计数器从0开始计数到自动加载的值(TIMx_ARR寄存器)−1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器下溢事件;然后再从0开始重新计数。
(2)更新中断
所有的定时器都可以产生更新中断事件,在定时器计数到装载值时溢出,会产生更新事件 UEV ,如果设置了中断允许标志位,还会生成更新中断 UIF并跳转至中断入口地址处(中断函数)。
如下是向上模式下溢出后生成更新事件和更新中断的时序图:
(3)输入捕获模式
在输入捕获模式下,当检测到ICx信号上相应的边沿后,计数器的当前值被锁存到捕获/比较寄存器(TIMx_CCRx)中。当发生捕获事件时,相应的CCxIF标志(TIMx_SR寄存器)被置1,如果开放了中断或者DMA操作,则将产生中断或者DMA请求。
(4)PWM 模式
脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。在TIMx_CCMRx寄存器中的OCxM位写入‘110’(PWM模式1) 或‘111’(PWM模式2),能够独立地设置每个OCx输出通道产生一路PWM。
基本定时器、通用定时器、高级定时器的比较:
三、STM32CUBEMX 配置定时器
配置定时器 TIM2 定时100ms,超时溢出后产生更新中断。
需要先介绍一下定时器怎么实现固定时间的定时:
1)在STM32F103 中 TIM2 的时钟来源于 APB1 的 2 倍,即 72 MHz;
2)TIM2的预分频系数 1~65535 可选;
3)定时器计数器 CNT 为 16bit,最大值 65535;
所以定时器的超时时间计算公式如下:
Tout = ((Psc + 1) x (arr + 1))/ 72000000 s
其中:
Tout:定时器超时溢出时间
Psc:预分频系数
arr:装载值。也就是计数器的计数值
上面的方式计算出来的是单位是秒!!!
要定时100ms的时间,按照上面的公式,Psc = 7199,arr = 999,得到 Tout = 0.1s = 1000ms。
相关配置如下:
(1)因为只需要作为定时使用,所以比较简单,开始定时器,并配置相关的定时参数即可。如下:
(2)开启定时器中断功能,只有开启了才能触发溢出中断,如下:
(3)配置中断的优先级,包括抢占优先级,子优先级。如下:
生成代码后,在文件 tim.c 中 TIM2 的配置代码如下:
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM2_Init 1 */
/* USER CODE END TIM2_Init 1 */
htim2.Instance = TIM2;
htim2.Init.Prescaler = 7199;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 999;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
定时器使能和中断配置如下:
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspInit 0 */
/* USER CODE END TIM2_MspInit 0 */
/* TIM2 clock enable */
__HAL_RCC_TIM2_CLK_ENABLE();
/* TIM2 interrupt Init */
HAL_NVIC_SetPriority(TIM2_IRQn, 3, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
/* USER CODE BEGIN TIM2_MspInit 1 */
/* USER CODE END TIM2_MspInit 1 */
}
}
相应的中断入口函数在 stm32f1xx_it.c 中,如下:
void TIM2_IRQHandler(void)
{
/* USER CODE BEGIN TIM2_IRQn 0 */
/* USER CODE END TIM2_IRQn 0 */
HAL_TIM_IRQHandler(&htim2);
/* USER CODE BEGIN TIM2_IRQn 1 */
/* USER CODE END TIM2_IRQn 1 */
}