一、定时器概述
1. 定时器简介
实现定时器的方式一般有软件、硬件两种方式,本文主要针对STM32的硬件定时器。
定时器的核心是一个计数器,该计数器会按照一定的时钟频率进行加 1 或减 1 操作。当计数器的值达到预设的阈值(即自动重装载值)时,会触发特定的事件,如产生中断、更新输出信号等。同时,计数器可以根据配置自动重新开始计数,形成周期性的定时操作。
2. 定时器常见类型
3. STM32定时器特性表
F1系列
定时器类型 | 定时器编号 | 位宽 | 计数模式 | 主要功能 | 总线 |
高级控制定时器 | TIM1、TIM8 | 16 位 | 向上、向下、向上/向下(中央对齐) | PWM 输出(带死区插入)、4个输入捕获、互补输出、刹车输入、编码器接口、触发 DAC/ADC | APB2 |
通用定时器 | TIM2、TIM3、TIM4、TIM5 | 16 位(TIM2、TIM5 为 32 位) | 向上、向下、向上/向下(中央对齐) | PWM 输出、4个输入捕获、输出比较、编码器接口、触发 DAC/ADC | APB1 |
基本定时器 | TIM6、TIM7 | 16 位 | 向上 | 定时、触发 DAC/ADC | APB1 |
H7系列
定时器类型 | 定时器 | 计数器位数 | 计数模式 | 预分频系数 | 产生 DMA 请示 | 捕获/比较通道 | 互补输出 |
高级定时器 | TIM1、TIM8 | 16 位 | 向上 | 1 - 65536 | 支持 | 4 个 | 有 |
通用定时器 | TIM2、TIM5 | 32 位 | 向上、向下、中央对齐 | 1 - 65536 | 支持 | 4 个 | 无 |
通用定时器 | TIM3、TIM4 | 16 位 | 向上、向下、中央对齐 | 1 - 65536 | 支持 | 4 个 | 无 |
通用定时器 | TIM12 | 16 位 | 向上 | 1 - 65536 | 不支持 | 2 个 | 无 |
通用定时器 | TIM13、TIM14 | 16 位 | 向上 | 1 - 65536 | 不支持 | 1 个 | 无 |
通用定时器 | TIM15 | 16 位 | 向上 | 1 - 65536 | 支持 | 2 个 | 有 |
基本定时器 | TIM16、TIM17 | 16 位 | 向上 | 1 - 65536 | 支持 | 1 个 | 有 |
基本定时器 | TIM6、TIM7 | 16 位 | 向上 | 1 - 65536 | 支持 | 1 个 | 有 |
二、基本定时器
1. 基本定时器功能简介
基本定时器有TIM6/TIM7,其主要特性有:
- 基本定时器使用 16 位的计数器,计数模式为向上计数,即从 0 开始计数,逐步递增,直到达到自动重载寄存器(ARR)中设置的值,然后计数器会重新归零并开始下一轮计数。
- 可编程预分频器可以对定时器的时钟源进行分频,预分频系数范围通常为 1 - 65536。通过设置预分频寄存器(PSC)的值,可以调整定时器的计数频率,从而实现不同的定时精度。
- 自动重载寄存器(ARR)用于存储计数器的上限值。当计数器的值达到 ARR 中设置的值时,会产生更新事件,计数器会重新归零。
- 计数器溢出(达到 ARR 值)时会产生更新事件。更新事件可以用于触发一些操作,例如触发 DAC(数模转换器)或 ADC(模数转换器)的转换。
- 基本定时器可以产生 DMA(直接内存访问)请求。当定时器产生更新事件时,可以触发 DMA 传输,将数据从一个内存位置传输到另一个内存位置,而无需 CPU 的干预,提高系统效率。
2. 时钟树
在STM32F1中,TIM1和TIM8是挂载在APB2总线上,(最大稳定频率72MHz)
而TIM2~5挂载在APB1总线上(最大频率36MHz)。
并不是挂载在APB1上,定时器频率最大就是36MHz,还要取决于预分频系数,如下图所示的 TIM1 Timer x1,2 Multiplier。
3. STM32 定时器计数模式
计数器模式 | 溢出条件 |
向上 | CNT==ARR |
向下 | CNT==0 |
中心对齐 | CNT == ARR-1 、 CNT==1 |
4. STM32 定时器相关寄存器
(1)TIMx_CR1 设置ARR寄存器是否有缓冲、关闭/开启计数器
位7:ARPE: 自动重载预装载使能(Auto-reload preload enable)
- 0: TIMx_ARR寄存器没有缓冲,默认值,赋值后立即将ARR的值加载到影子寄存器
- 1: TIMx_ARR寄存器有缓冲,在更新事件时自动将ARR的值加载到影子寄存器
位0:CEN:计数器使能(Counter enable)
- 0:关闭计数器
- 1: 使能计数器
(2)TIMx_DIER
位8: UDE:更新DMA请求使能(Update DMA request enable)
- 0:关闭DMA请求
- 1: 使能DMA请求
位0:UIE:更新中断使能(Update interrupt enable)
- 0:关闭更新中断
- 1: 使能更新中断
(3)TIMx_SR 状态寄存器
位0: UIF:更新中断标志(Update interrupt flag)
- 0:无更新中断
- 1: 有更新中断
在中断服务函数中,需要手动清除中断标志位,否则会一直触发中断。
(4)TIMx_CNT 计数器寄存器
- 位宽为16位,用于存储计数器的值。
(5)TIMx_PSC 预分频器寄存器
- 位宽为16位,用于设置预分频器的值。
- 计数器的时钟频率 = 定时器时钟频率 / (预分频器值 + 1)。
(6)TIMx_ARR 自动重装载寄存器
- 位宽为16位,用于设置计数器的上限值。
- 当计数器的值达到 ARR 中设置的值时,会产生更新事件,计数器会重新归零。
- 如果自动重装载值为0,则计数器停止 。
5. 定时器溢出时间计算方法
定时器溢出时间 T o u t = (自动重装载值 A R R + 1 ) * (预分频系数 P S C + 1 ) / 定时器时钟频率 F t 定时器溢出时间T_{out} = (自动重装载值ARR + 1) * (预分频系数PSC + 1) / 定时器时钟频率F_t定时器溢出时间Tout=(自动重装载值ARR+1)*(预分频系数PSC+1)/定时器时钟频率Ft
三、定时器配置步骤
1. 配置定时器基础工作参数
HAL_TIM_Base_Init(&htim);
2. 定时器基础MSP初始化
HAL_TIM_Base_MspInit(&htim); 配置NVIC,CLOCK等, GPIO配置
3. 使能更新中断并启动计数器
HAL_TIM_Base_Start_IT(&htim);
4. 设置优先级,使能中断
HAL_NVIC_SetPriority(TIMx_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIMx_IRQn);
5. 编写中断服务函数
void TIMx_IRQHandler(void)
6. 清除中断标志位
__HAL_TIM_CLEAR_IT(&htim, TIM_IT_UPDATE);
另外有一个定时器更新中断回调函数,在中断服务函数中会自动调用,该函数用户可以自行重写:
void
HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
四、定时器配置实例
本示例使用定时器6,实现500ms定时器更新中断,控制GPIO口翻转。本项目在之前项目基础上增加配置,省略了GPIO等配置说明 。
时钟频率设置如下:
1. STM32CubeMX配置 启用TIM6
超时时间=(499+1)*(7999+1)/8MHz = 500ms。
2. 启动 TIM6全局中断
3. 生成的部分代码
main.c里TIM6初始化
/**
* @brief TIM6 Initialization Function
* @param None
* @retval None
*/
static 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 = 7999;
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 499;
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 */
}
stm32f1xx_it.c 中断
/**
* @brief This function handles TIM6 global interrupt.
*/
void TIM6_IRQHandler(void)
{
/* USER CODE BEGIN TIM6_IRQn 0 */
/* USER CODE END TIM6_IRQn 0 */
HAL_TIM_IRQHandler(&htim6);
/* USER CODE BEGIN TIM6_IRQn 1 */
/* USER CODE END TIM6_IRQn 1 */
}
4. 手工添加代码
(1)TIM6初始化的代码里,添加启动中断
/* 在TIM6初始化函数末尾添加 */
/* USER CODE BEGIN TIM6_Init 2 */
HAL_TIM_Base_Start_IT(&htim6); // 启动定时器中断
/* USER CODE END TIM6_Init 2 */
(2)实现中断回调函数
/* 定时器6中断回调 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
// 这里添加中断处理代码
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_0);
}
}