一.TIM定时器简介

1.TIM总体介绍

STM32定时器是STM32系列微控制器中的一类重要外设,用于生成精确的时间延迟、频率生成、事件计数等功能。它通常用于实时操作系统、PWM输出、输入捕捉、输出比较、脉宽调制、频率测量等应用场景。STM32的定时器具有高度的灵活性和多种工作模式,能够满足复杂的应用需求。

STM32微控制器的定时器分为多种类型,包括:

  • 基础定时器(如TIM6、TIM7)
  • 通用定时器(如TIM2、TIM3、TIM4等)
  • 高级定时器(如TIM1、TIM8)

这三种定时器的基本结构都是由时基单元组成的,时基单元包括:

  • 计数器(CNT):时基单元内含有一个计数器(Counter),它可以按照预定的时钟源进行计数。计数器的值可以递增或递减,具体由设置的计数方向(向上计数或向下计数)决定。通常以向上计数居多
  • 预分频器(PSC):为了调节定时器的计时精度,时基单元中通常包含一个预分频器(Prescaler)。可对时钟进行分频,通过调整预分频器的值,可以改变计数器的更新速率,从而实现不同的时间间隔。
  • 自动重载寄存器(ARR):时基单元中有一个自动重载寄存器,它用于设置计数器的最大值。当计数器达到预设的值时,会自动重载并重新开始计数,或者触发一个事件(如中断、PWM输出等)。

通过完成对这三个寄存器的配置,然后选择时钟源(默认系统时钟),最后使能定时器,就可以实现最基本的定时/计数操作了。为了便于理解与讲解,我们在以下都以系统时钟作为定时器的时钟源。

在功能上,这三种定时器有以下区别:

每种定时器具有不同的特点和应用场景。它们的工作频率通常由系统时钟或外部时钟源决定,并支持多种预分频设置,以实现不同的计时精度。

二.基本定时器

1.主要功能

  • 触发DAC的同步电路(可之后再细究)。
  • 在更新事件(计数器溢出)时产生中断/DMA输出。

2.基本结构

    如图,来自RCC总线上的内部时钟经过控制器,再经过预分频器PSC 分频(例如 12MHz 经过 2分频 变成 6MHz,72MHz 经过 4分频 变成 18MHz),但需要注意的是,PSC预分频值和实际的分频系数相差1,即对PSC写入1,实际分频系数是2,最后作为驱动定时器的时钟频率,CNT再按照时钟频率进行 向上加 ,直到溢出后,自动重装载寄存器ARR 对 计数器CNT 重新装填,并同时触发中断,或DMA输出,或更新事件触发DAC输出。

    在图片的右上角为定时器的主模式触发DAC,当把更新事件映射到触发输出TRGO时,每当产生更新事件(计数值重装载)时,便会触发特定引脚执行DAC操作,这样做的好处是不用频繁进入中断完成DAC的操作,而是由硬件全自动完成。

    3.主要特性

    • 只支持由内部时钟源驱动。
    • 计数器CNT只支持向上计数

    三.通用定时器

    相较于基本定时器,通用定时器的功能更加丰富,但同时结构也更加复杂。

    1.主要功能

    主要功能包括但不限于:

    • 定时功能:通用定时器可以配置为普通的定时器模式,通过设置自动重载寄存器(ARR)和预分频器(PSC)来控制计数器的溢出时间。当计数器溢出时,定时器会产生更新中断或者触发其他操作(例如生成PWM信号)。
    • 可调节的PWM输出:通用定时器能够生成PWM信号,通过配置定时器的捕捉/比较功能,可以输出具有可调占空比和频率的PWM波。
    • 输入捕获:在输入捕捉模式下,定时器可以捕捉外部信号的脉冲宽度或频率。当外部信号(如脉冲)到达指定引脚时,定时器捕捉该信号的时刻,并保存其计数值。这样,可以用定时器测量信号的频率或周期。
    • 输出比较:在输出比较模式下,定时器会根据设定的比较值(CCR)与计数器的当前值进行比较。当计数器的值等于或超出设定的比较值时,定时器会触发相应的操作(如产生中断、改变输出引脚状态等)。

    2.基本结构

    可以看到,通用定时器的结构比基本定时器复杂的多。

    其中,时基单元与基本定时器的基本相同,但通用定时器的 计数器CNT 除了支持 向上计数 外,还支持:

    • 向下计数:减到0之后,回到重装值,同时申请中断
    • 中央对齐:从0开始,先向上自增,回到重装值,申请中断,再向下自减,减到0,再申请中断,依次不断循环。

    对于黑框部分结构:

    • 输入时钟源:通用定时器不仅可以选择内部时钟,还可以选择:
      • 外部的TIMx_ETR时钟:从支持该功能的引脚上可输入一定频率的方波时钟,再对极性选择,边沿检测和预分频器进行配置,最后进行滤波,即可作为驱动该定时器的时钟。
      • 来自输入捕获的CH1引脚:通过这一路输入的时钟,边沿(上升沿,下降沿)均有效。
      • TF1FP1,TF2FP2:分别来自CH1引脚,CH2引脚的时钟。
    • ITR0,ITR1,ITR2,ITR3:这些是中断触发源,分别对应不同的定时器事件(如溢出、中断请求等)。定时器的某些事件(如计数完成或匹配)会触发中断信号,通过中断处理程序进行处理。                                                                                                                                      
    • TRC:通过输入时钟信号,定时器可被驱动进行计数操作。
    • TRGO:在某些特定事件发生时(如计数器溢出或匹配时),定时器可以产生触发输出(TRGO)信号,或者通过触发输入(TRGI)响应外部事件,与基本定时器不同的是,其TRGO信号还可用于触发别的定时器,即可以进行多个计时器的级联。
    • 编码器接口:可以读取正交编码器的输出波形。

    3.定时器定时中断

    3.1 定义

    定时器定时中断是定时器计数器达到设定值(如ARR寄存器中的值)时,自动产生的中断信号,触发中断服务程序来执行预定的操作。它通常用于实现周期性或定时的任务。

    这个功能相信大家都会使用。要注意的是定时器的频率计算公式为:

    f = 原始时钟频率  /(PSC + 1)/ (ARR + 1)

    3.2 代码案例

    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);
    	}
    }

    4.输入捕获

    4.1 什么是输入捕获?

    输入捕获(IC)

    输入捕获是定时器的一种功能,用于捕捉外部信号的到达时刻,并将其对应的 定时器计数值CNT 锁存到 捕获寄存器CCR 中,以便后续处理或测量信号的频率、脉冲宽度等特性。

    每个高级定时器和通用定时器都拥有四个输入捕获通道,可配置为PWMI模式,同时测量频率和占空比;可配合主从触发模式,实现硬件全自动测量

    我们以输入捕获端口CH1为例,进行讲解:

    左上角的异或门是专门为电机准备的,我们在此先将它忽略。

    通过配置边沿检测器,设置成捕获 上升沿 或 下降沿,信号通过 TIMx_CH1 通道,经输入滤波器滤除毛刺信号后,再由边沿检测器检测特定的边沿,当检测到了特定的边沿后,会经过TI2FP1通道进入IC1,再经过预分频器后,可以选择触发捕获中断,最后计数器CNT的值会锁存进捕获寄存器1中,通过标准库函数,就可以读取到此时的CNT值,此时就可以根据CNT,测量信号的频率,脉冲的宽度等信息。

    同时,通过CH1的信号,检测到了特定的边沿后也可以向下经过TI1FP2进入IC2,再次将CNT的值锁存到捕获寄存器2中,这样就可以实现TI1FP1捕获上升沿,测量周期;TI1FP2捕获下降沿,测量占空比,把一个引脚的输入,同时映射到两个捕获单元。也就是PWMI的模式。

    每次捕获之后,最好都对CNT的值进行手动清零,方便与下一次捕获计算时间差。可以用主从触发模式自动完成。

    TI1FP1同时还可以触发从模式的Reset操作,硬件全自动对CNT进行清零。

    所以CH1,CH2既可以独立工作,也可以联合工作,一个通道灵活切换两个引脚,或两个通道同时捕获一个引脚。

    独立工作时,总体结构如下:

    联合工作时,总体结构如下:

    CH3,CH4同理,但CH3,CH4通道的TIxFPy没有从模式,如果想要对CNT的值清零的话,需要利用中断,不过这样的话就会频繁的进入中断,而消耗大量的软件资源了。

    4.2 输入捕获的相关函数配置案例

    	/*开启时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟
    	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_6;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6引脚初始化为上拉输入
    
        /*配置时钟源*/
    	TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
    	
    	/*时基单元初始化*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;               //预分频器,即PSC的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
    	
    	/*输入捕获初始化*/
    	TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量
    	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1
    	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动
    	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//极性,选择为上升沿触发捕获
    	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;			//捕获预分频,选择不分频,每次信号都触发捕获
    	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;	//输入信号交叉,选择直通,不交叉
    	TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
    	
    	/*选择触发源及从模式*/
    	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);					//触发源选择TI1FP1
    	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);					//从模式选择复位
    																	//即TI1产生上升沿时,会触发CNT归零
    	
    	/*TIM使能*/
    	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行

    5.输出比较

    5.1 什么是输出比较?

    输出比较(Output Compare):通过将定时器的 计数器CNT值 与一个预设的比较值(存储在CCR寄存器中)进行比较。当定时器的计数器值与CCR值匹配时,定时器会触发预定的动作,如输出信号的变化(对输出电平置0,置1,翻转等操作)、生成中断、或者更新PWM信号的占空比等。每个高级定时器和通用定时器都拥有4个输出比较通道。高级定时器的前3个通道额外拥有死区生成和互补输出的功能。

    简单来说,就是CNT的值会实时的与比较寄存器CCR中的值进行比较,当

    • CNT < CCR
    • CNT == CCR
    • CNT > CCR

    时,可自行配置不同情况,OC对外输出电平的变化。

    以下是输出比较的结构图

    要注意的是,所有通道的输出比较都用的同一个计数器CNT值。

    5.2 输出比较的几种类型

    5.2 PWM知识点补充

    5.3 输出比较模式结构图

    这里我们以常用的PWM1模式为例

    5.4 案例代码

    void PWM_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_AF_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		//GPIO_Pin_15;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA0引脚初始化为复用推挽输出	
    																	//受外设控制的引脚,均需要配置为复用模式		
    	
    	/*配置时钟源*/
    	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 = 100 - 1;					//计数周期,即ARR的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;				//预分频器,即PSC的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
    	
    	/*输出比较初始化*/
    	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
    	TIM_OCStructInit(&TIM_OCInitStructure);							//结构体初始化,若结构体没有完整赋值
    																	//则最好执行此函数,给结构体所有成员都赋一个默认值
    																	//避免结构体初值不确定的问题
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式,选择PWM模式1
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//输出极性,选择为高,若选择极性为低,则输出高低电平取反
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//输出使能
    	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
    	TIM_OC1Init(TIM2, &TIM_OCInitStructure);						//将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
    	
    	/*TIM使能*/
    	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
    }
    
    /**
      * 函    数:PWM设置CCR
      * 参    数:Compare 要写入的CCR的值,范围:0~100
      * 返 回 值:无
      * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
      *           占空比Duty = CCR / (ARR + 1)
      */
    void PWM_SetCompare1(uint16_t Compare)
    {
    	TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值
    }
    

    四.高级定时器

    作为入门篇,我们不在这里对高级定时器做过多详细的介绍。

    1.主要功能

    2.基本结构

    重复次数计数器可设置当触发一定次数的更新后,再触发中断或事件,相当于再次对时钟分频。

    五.总结语

    有关于定时器这一部分的内容特别多,特别杂,还有很多的内容由于篇幅问题,在这里不方便展开,读者可以先从学会基本功能开始,慢慢往后探究,没必要特意全部学一遍,可以等需要用到的时候,再继续深入了解。

    Logo

    智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

    更多推荐