TIM定时器

TIM基本

TIM简介

  • TIM(Timer)定时器
  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

定时器的组成

  1. 计数器(Counter):定时器的核心部分,用于计数。计数器可以是向上计数、向下计数或双向计数。
  2. 预分频器(Prescaler):用于将输入时钟频率分频,从而调整计数器的计数速度。
  3. 自动重装载寄存器(Auto-reload Register):用于设置计数器的最大值。当计数器达到该值时,会触发溢出事件,并重新开始计数。
  4. 捕获/比较寄存器(Capture/Compare Register):用于捕获外部事件的时间或生成PWM信号

定时器的工作模式

  • 定时器模式:用于生成精确的时间延迟。
  • 输入捕获模式:用于测量外部信号的脉冲宽度或频率。
  • 输出比较模式:用于生成PWM信号或触发其他事件。
  • PWM模式:用于生成脉宽调制信号,常用于控制电机或LED亮度

定时器类型

类型编号总线功能
高级定时器TIM1、TIM8APB2拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能
通用定时器TIM2、TIM3、TIM4、TIM5APB1拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能
基本定时器TIM6、TIM7APB1拥有定时中断、主模式触发DAC的功能

高级定时器

  • 支持多种计数模式(向上、向下、中央对齐)。
  • 支持多通道PWM输出。
  • 支持死区时间插入,适用于电机控制。
  • 支持编码器接口。
  • 支持事件触发和中断
高级定时器框图

通用定时器

通用定时器是STM32中功能最为丰富的定时器类型,它在基本定时器功能的基础上,增加了多种高级功能,适用于各种复杂的应用场景。

功能特性

  1. 基本定时功能

    • 16位向上、向下、向上/向下自动装载计数器
    • 16位可编程预分频器,可以实时修改,分频系数为1-65536之间的任意数值
    • 支持定时中断功能
  2. 输入捕获功能

    • 4个独立的通道可以用来:输入捕获、输出比较、产生PWM或单脉冲模式输出
    • 可测量脉冲长度和频率
    • 支持编码器和霍尔传感器电路直接接口
  3. 输出比较/PWM功能

    • 可产生多达4路的PWM输出
    • 支持单脉冲模式输出
    • 输出比较功能可用于精确的时间控制
  4. 时钟源选择

    • 内部时钟源(CK_INT)
    • 外部时钟模式1:外部输入引脚(TIx)
    • 外部时钟模式2:外部触发输入(ETR)
    • 内部触发输入(ITRx)
  5. 主从同步功能

    • 可作为主定时器控制其他从定时器
    • 可作为从定时器接受其他主定时器控制
    • 支持定时器链式连接

框图解析

通用定时器框图

通用定时器的框图展示了其复杂而完整的内部结构:

1. 时钟源部分(左上角)

  • 内部时钟(CK_INT):来自APB1时钟,是最常用的时钟源
  • 外部时钟模式1(TIMx_CH1/CH2):通过定时器的输入通道接收外部时钟
  • 外部时钟模式2(TIMx_ETR):通过专用的外部触发引脚接收时钟
  • 内部触发(ITR0-ITR3):来自其他定时器的触发信号
  • 时钟源选择器:可以选择上述任意一种时钟源

2. 时基单元(中心部分)

  • 预分频器(PSC):16位预分频器,实际分频系数为PSC+1
  • 计数器(CNT):16位计数器,支持向上、向下、中心对齐计数模式
  • 自动重装载寄存器(ARR):设定计数器的最大值
  • 重复计数器(RCR):仅高级定时器具有,用于控制更新事件的产生频率

3. 输入捕获部分(右侧)

  • 输入通道(TIMx_CH1-CH4):4个独立的输入通道
  • 输入滤波器:对输入信号进行滤波,去除噪声
  • 边沿检测器:检测信号的上升沿、下降沿或双边沿
  • 预分频器:可对捕获事件进行分频
  • 捕获寄存器(CCR1-CCR4):存储捕获到的计数器值

4. 输出比较部分(右侧)

  • 比较器:将计数器值与比较寄存器值进行比较
  • 输出控制逻辑:根据比较结果产生输出信号
  • 输出通道(TIMx_CH1-CH4):4路独立输出
  • PWM模式:可产生脉宽调制信号

5. 中断和DMA(底部)

  • 更新中断:计数器溢出时产生
  • 捕获/比较中断:输入捕获或输出比较匹配时产生
  • 触发中断:外部触发事件产生
  • DMA请求:可以触发DMA传输,实现高效的数据处理

6. 主从控制器(中下部)

  • 触发选择器:选择触发源
  • 从模式控制器:处理从模式操作(重置、门控、触发等)
  • 主模式控制器:生成触发输出信号(TRGO)供其他定时器使用

工作模式

1. 定时器模式:最基本的功能,用于产生精确的时间间隔

2. 输入捕获模式:测量输入信号的脉宽、频率等参数

  • 可以测量PWM信号的占空比
  • 可以测量外部事件的时间间隔

3. 输出比较模式:在特定时间产生输出事件

  • 可以产生精确的延时
  • 可以控制外部设备的时序

4. PWM模式:产生脉宽调制信号

  • PWM模式1:CNT < CCR时输出有效电平
  • PWM模式2:CNT < CCR时输出无效电平

5. 单脉冲模式:产生单个脉冲输出

6. 编码器模式:直接连接正交编码器,用于电机位置检测

基本定时器

基本定时器是STM32中最简单的定时器类型,主要用于提供基本的定时功能。它包含以下核心功能:

  1. 定时中断:可以产生周期性的定时中断,用于实现精确的时间控制
  2. 主模式触发DAC:可以作为主模式定时器,为DAC模块提供触发信号
  3. 向上计数:只支持向上计数模式,计数器从0开始递增到自动重装载值

框图解析

基本定时器框图

基本定时器的结构相对简单,主要包含以下几个部分:

  1. 时钟源(CK_INT):内部时钟信号,通常来自APB1时钟
  2. 预分频器(PSC):16位预分频器,用于对输入时钟进行分频
    • 实际分频系数 = PSC + 1
    • 输出时钟频率 = 输入时钟频率 / (PSC + 1)
  3. 计数器(CNT):16位向上计数器,从0开始计数
  4. 自动重装载寄存器(ARR):设置计数器的最大值
    • 当CNT达到ARR值时,产生更新事件并重新开始计数
  5. 更新事件生成:当计数器溢出时产生更新事件
  6. 中断/DMA:可以产生中断信号或DMA请求
  7. 主模式触发输出(TRGO):可以为其他外设(如DAC)提供触发信号

工作原理

基本定时器的工作流程如下:

  1. 内部时钟信号经过预分频器分频后,驱动计数器递增
  2. 计数器从0开始向上计数,每个时钟周期递增1
  3. 当计数器值达到自动重装载寄存器(ARR)的值时,产生更新事件
  4. 更新事件可以触发中断、DMA请求或为DAC提供触发信号
  5. 计数器自动重新装载,重新从0开始计数,实现周期性定时

计算公式

定时器溢出频率:f = CK_PSC / ((PSC + 1) × (ARR + 1)) 定时周期:T = (PSC + 1) × (ARR + 1) / CK_PSC

其中:

  • CK_PSC:预分频器输入时钟频率
  • PSC:预分频器寄存器值
  • ARR:自动重装载寄存器值

定时中断基本结构

定时中断基本结构

预分频器时序

Prescaler(PSC -16 bits value):预分频器

简写PSC,16位,可设置的值范围是0--65535。在STM32F103中,计数器的时钟源可以是内部时钟源或外部时钟源。

当使用内部时钟源时,PSC可以将时钟信号频率除以一个固定的分频系数,以降低计数器的时钟频率,从而扩大计数器的计时范围。

例如,如果使用内部时钟源,并将PSC设置为7199,则定时器的时钟频率将为72 MHz / (7199 + 1) = 10 kHz。

这意味着计数器每计数10次,就会过去1毫秒。如果需要更长的计时范围,可以将PSC设置为更大的值。

计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)

预分频器时序

预分频器从1变为2的时序分析

时序图信号解析

  1. CK_PSC(预分频器输入时钟)

    • 这是输入到预分频器的原始时钟信号
    • 在STM32F103中,通常来自72MHz的系统时钟(经过APB预分频后)
    • 保持恒定的高频率脉冲
  2. CNT_EN(计数器使能信号)

    • 控制计数器是否开始计数的使能信号
    • 高电平时计数器工作,低电平时计数器停止
  3. 计数器写入操作

    • 显示了在运行过程中写入新的预分频器值的时刻
    • 图中显示从PSC=1写入PSC=2的时刻
  4. 更新事件(UEV)

    • Update Event,更新事件信号
    • 当计数器溢出或手动触发更新时产生
    • 新的预分频器值在更新事件时才真正生效
  5. 预分频器控制存器

    • 存储当前生效的预分频器值
    • 在更新事件发生前保持原值(1),更新事件后变为新值(2)
  6. CK_CNT(计数器时钟)

    • 经过预分频器处理后的计数器时钟信号
    • PSC=1时:CK_CNT频率 = CK_PSC/(1+1) = CK_PSC/2
    • PSC=2时:CK_CNT频率 = CK_PSC/(2+1) = CK_PSC/3
  7. 预分频器计数器

    • 内部预分频计数器的值
    • PSC=1时计数范围0-1,PSC=2时计数范围0-2

关键观察点

从时序图可以看出:

  • 预装载特性:新写入的PSC值(2)不会立即生效,而是等待下一个更新事件
  • 平滑过渡:这种设计避免了计数器时钟突然改变导致的时序问题
  • 同步更新:所有相关寄存器在同一个更新事件时刻同时更新

STM32标准库代码示例

计数器时序

Counter Period(Auto Reload Register - 16 bit value):计数周期

Auto Reload寄存器用于设置定时器的计数周期。

当定时器的计数器达到ARR的值时,定时器会自动重新加载ARR的值并继续计数。

这样可以实现定时器的周期性计数,从而实现定时和计时的功能。

计数器时序

计数器溢出频率:K_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)
取倒数就是计数一个周期所用时间:T_CNT_OV = 1 / K_CNT_OV = (PSC + 1) * (ARR + 1) / CK_PSC

计数器无预装时序

计数器无预装时序

计数器有预装时序

计数器有预装时序

RCC时钟树

RCC时钟树

标准库函数

1.TIM_DeInit

/**
 * @brief  将定时器外设寄存器重设为默认值
 * @param  TIMx: 选择要重置的定时器外设
 * @retval
 * @example
 *     TIM_DeInit(TIM2);
 */
void TIM_DeInit(TIM_TypeDef* TIMx);

2.TIM_TimeBaseInit

/**
 * @brief  初始化定时器的时基单元
 * @param  TIMx: 选择要初始化的定时器外设
 * @param  TIM_TimeBaseInitStruct: 指向时基单元配置结构体的指针
 * @retval
 * @example
 *     TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
 */
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

3.TIM_ITConfig


/**
 * @brief  使能或失能指定的定时器中断
 * @param  TIMx: 选择要配置的定时器外设
 * @param  TIM_IT: 指定要使能或失能的中断源
 * @param  NewState: 新的使能或失能状态(ENABLE 或 DISABLE)
 * @retval
 * @example
 *     TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
 */
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

4.TIM_Cmd

/**
 * @brief  使能或失能定时器
 * @param  TIMx: 选择要使能或失能的定时器外设
 * @param  NewState: 新的使能或失能状态(ENABLE 或 DISABLE)
 * @retval
 * @example
 *     TIM_Cmd(TIM2, ENABLE);
 */
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

例子

定时器定时中断

Timer.h
#ifndef __TIMER_H
#define __TIMER_H

void Timer_Init(void);

#endif
Timer.c
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;				//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
*/

定时器外部时钟

Timer.h
#ifndef __TIMER_H
#define __TIMER_H

void Timer_Init(void);
uint16_t Timer_GetCounter(void);

#endif
Timer.c
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数配置为外部时钟,定时器相当于计数器
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA0引脚初始化为上拉输入
	
	/*外部时钟配置*/
	TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
																//选择外部时钟模式2,时钟从TIM_ETR引脚输入
																//注意TIM2的ETR引脚固定为PA0,无法随意更改
																//最后一个滤波器参数加到最大0x0F,可滤除时钟信号抖动
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;					//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
																
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:返回定时器CNT的值
  * 参    数:无
  * 返 回 值:定时器CNT的值,范围:0~65535
  */
uint16_t Timer_GetCounter(void)
{
	return TIM_GetCounter(TIM2);	//返回定时器TIM2的CNT
}

/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
*/