STM32高级定时器:配置指南

STM32高级定时器:

引言:

stm32定时器的配置非常之复杂,stm32标准库中,定时器是头文件库函数最多的一个外设但是我们实际应用做一些小项目,这些库函数很多一部分都用不到,所以可以先掌握常用的一些功能,那些不常用的功能等什么时候需要了再了解才是一个正确的学习策略,因此我封装了常用的定时器配置函数供大家使用!


stm32的高级定时器功能强大,具有定时,输入捕获,输出比较,死区互补输出、重复计数器、带刹车 (断路) 功能。本文将参考野火库开发指南来介绍一下高级定时器的基本配置以及输出比较,输入捕获,等常用功能。



一.高级定时器时钟源:

高级定时器可选四种时钟源:

  • 内部时钟源CK_INT
  • 外部时钟模式 1:外部输入引脚 TIx(x=1,2)
  • 外部时钟模式 2:外部触发输入 ETR
  • 内部触发输入 (ITRx)
  • 内部触发输入不常用,所有下文不会讲它,下面来分别介绍一下剩下的三个时钟源:

    (1)内部时钟源CK_INT:

    这个时钟源直接由定时器所在总线的时钟经过特定的分频规则来驱动,F1系列的所有定时器内部时钟源一般都为72MHz。
    当从模式控制寄存器 TIMx_SMCR 的 SMS 位等于 000 时,则使用内部时钟。而寄存器的初始值都是0,所有如果要使用内部时钟,不需要配置任何寄存器和库函数


    (2)外部时钟模式 1:TIx(x=1,2)

    每一个通用定时器和高级定时器都对应了四个GPIO引脚(TIMx_CH1,TIMx_CH2,TIMx_CH3,TIMx_CH4),它们可以完成输出比较,输入捕获等功能,也可以作为输入引脚,来输入外部时钟驱动高级定时器,这就是外部时钟模式1的功能,外部时钟模式1只能使用TIMx_CH1,TIMx_CH2两个引脚(又称TI1和TI2),具体是哪一个引脚需要查看手册,这里
    具体使用哪一路信号,由 TIM_CCMRx 的位 CCxS[1:0] 配置
    参考一下野火哥的图片:
    以TI2为例(TIMx_CH2)

    滤波器:滤波器的作用是过滤掉输入信号的高频干扰信号,可以由TIMx_CCMRx 的位 ICxF[3:0] 配置

    边沿检测:边沿检测的信号来自于滤波器的输出,在成为触发信号之前,需要进行边沿检测,决定是上升沿有效还是下降沿有效,具体由 TIMx_CCER 的位 CCxP 和 CCxNP 配置。

    触发选择:边沿检测到的信号会生成触发源,当使用外部时钟模式 1 时,触发源有两个,TI1FP1和TI2FP2,如果输入引脚选择TI1,则这里只能选择TI1FP1,如果输入脚选择TI2,则这里只能选择TI2FP2

    从模式选择:选定了触发源信号后,需把信号连接到 TRGI 引脚,让触发信号成为外部时钟模式 1 的输入,触发信号的频率经过 CK_PSC分频之后,称为时钟来驱动计数器 CNT 计数。具体的配置 TIMx_SMCR 的SMS[2:0]为 000 即可选择外部时钟模式 1。

    总结一下配置步骤:

    1. 选择输入引脚:由 TIM_CCMRx 的位 CCxS[1:0] 配置
    2. 配置滤波器:由TIMx_CCMRx 的位 ICxF[3:0] 配置
    3. 选择边沿检测:由 TIMx_CCER 的位 CCxP 和 CCxNP 配置
    4. 触发选择:由 TIMxSMCR 的位 TS[2:0] 配置
    5. 从模式选择:配置 TIMx_SMCR 的SMS[2:0]为 000 即可选择外部时钟模式 1
    6. 计数器使能:使能计数器由 TIMx_CR1 的位 CEN 配置

    (3)外部时钟模式2:外部触发输入 ETR

    通用定时器TIM2,TIM3,TIM3和高级定时器TIM1,TIM8各有一个ETR引脚专门用来作为外部时钟模式2使用,具体引脚对应关系请自行查阅手册

    引脚选择:初始化引脚位输入模式(上拉,下拉或者浮空,根据实际需要来选择)

    边沿检测:来自 ETR 引脚输入的信号可以选择为上升沿或者下降沿有效,具体的由TIMx_SMCR 的位 ETP配置

    外部触发预分频器:ETRP 的信号的频率不能超过 TIMx_CLK(72M)的 1/4,当触发信号频率很高的情况下,就必须使用分频器来降频,分频之后的信号称为ETRP,具体的由TIMx_SMCR 的位 ETPS[1:0] 配置

    滤波器:如果 ETRP 的信号的频率过高或者混杂有高频干扰信号的话,我们就需要使用滤波器对 ETRP 信号重新采样,来达到降频或者去除高频干扰的目的。具体的由 TIMx_SMCR 的位 ETF[3:0] 置,其中的 fDTS 是由内部时钟 CK_INT 分频得到,具体的由 TIMx_CR1 的位 CKD[1:0] 配置

    从模式选择:经过滤波器滤波的信号连接到 ETRF 引脚后,触发信号成为外部时钟模式 2 的输入,最终经CK_PSC分频之后,生成时钟信号,然后驱动计数器 CNT 计数。具体的配置 TIMx_SMCR 的位 ECE 为 1 即可选择外时钟模式 2

    使能计数器:使能计数器由 TIMx_CR1 的位 CEN 配置

    在这里重点说明一下滤波器这一步骤:
    滤波器采样时钟可以是内部时钟(72M),也可以是图示的fDTS时钟,fDTS时钟可以由TIMx_CR1寄存器的CKD[1:0]来决定

    下面请看TIMx_SMCR 的位 ETF[3:0]:

    简要介绍一下数字滤波器:数字滤波器有两个重要参数:采样频率和带宽,它是这样工作的,
    以采样频率来对输入信号进行检测,比如说带宽是4,那么只有连续四次检测到高电平才会输出高电平,否则输入信号会被舍弃。从而达到过滤高电平的作用,这就是数字滤波器的基本工作机制


    二.控制器:

    学习控制寄存器需要读者熟练掌握TIMx_CR1,TIMx_CR2,TIMx_SMCR,TIMx_CCER这四个寄存器


    三.时基单元:

    时基单元是定时器的核心,它主要由三部分构成:计数器,预分频器,自动重装载寄存器。而对于高级定时器而言,还会多一个重复计数寄存器,下面将一一介绍它们:

    时基单元图片

    预分频器:这个部分主要是用来分频时钟源的,时钟源的输出时钟CK_PSC经过预分频器分频之后,将生成另外一个时钟CK_CNT,由CK_CNT 来驱动计数器 CNT 计数

    自动重装载寄存器(ARR):这个可以理解为计数的周期,具体有什么用请继续往下读

    计数器CNT:计数器根据CK_CNT的时钟频率来以特定的模式计数,对于基本定时器来讲,计数模式只能选择向上计数,对于通用定时器和高级定时器来讲,计数模式可以选择向上计数,向下计数(类比51单片机)和中央对齐计数三种模式。三种模式的详细工作原理如下:

  • 向上计数模式:计数器从 0 开始计数,每来一个 CK_CNT 脉冲计数器就增加 1,直到计数器的值与自动重载寄存器 ARR 值相等,然后计数器又从 0 开始计数并生成计数器上溢事件,计数器总是如此循环计数。如果禁用重复计数器,在计数器生成上溢事件就马上生成更新事件 (UEV);如果使能重复计数器,每生成一次上溢事件重复计数器内容就减 1,直到重复计数器内容为10 时才会生成更新事件。
  • 向下计数模式:计数器从自动重载寄存器 ARR 值开始计数,每来一个 CK_CNT 脉冲计数
    器就减 1,直到计数器值为 0,然后计数器又从自动重载寄存器 ARR 值开始递减计数并生成计数器下溢事件,计数器总是如此循环计数。如果禁用重复计数器,在计数器生成下溢事件就马上生成更新事件;如果使能重复计数器,每生成一次下溢事件重复计数器内容就减 1,直到重复计数器内容为 0 时才会生成更新事件。
  • 中心对齐模式下,计数器从 0 开始递增计数,直到计数值等于 (ARR-1) 值生成计数器上溢事件,然后从 ARR 值开始递减计数直到 1 生成计数器下溢事件。然后又从 0 开始计数,如此循环。每次发生计数器上溢和下溢事件都会生成更新事件。

  • 接下来将分别介绍输入捕获,输出比较,增量式编码器接口,关于它们的源码,我专门封装了文件,可以实现仅调用某一个函数就可以实现配置的操作,可以看这里:定时器常用功能配置函数


    四.输入捕获:

    输入捕获主要有两个应用:测量脉宽和测量频率,它们的基本原理都是当检测到边沿信号,会立刻将此时CNT的值锁存在CCR寄存器中,然后用户通过读取CCR寄存器的值来计算频率或者脉宽

    高级定时器有四个输入捕获通道TI(1~4)其中TI1和TI2可以配置为PWM专用模式用于测量PWM频率和周期,PWM模式仅支持TI1和TI2两个通道

    高级定时器还有四个捕获通道,IC(1~4)每一个捕获通道都会对应一个CCR寄存器,用于锁存定时器计数器(CNT)的值。一个输入通道可以同时对应两个捕获通道(应用:测量PWM占空比或者测量脉宽),两个输入通道也可以对应一个捕获通道(不常用)

    输入捕获框图

    输入滤波器和边沿检测器:输入滤波器用于过滤掉高频干扰信号,可以通过结构体TIM_ICInitTypeDef 的TIM_ICFilter 成员来配置,边沿检测用于选择上升沿有效还是下降沿有效通过结构体TIM_ICInitTypeDef 的TIM_ICPolarity成员来配置

    IC数据选择器:每一个定时器都有四个CCR寄存器,它们都可以完成锁存CNT的任务,每一个CCR寄存器和IC通道一一对应,通过TIM_ICInitTypeDef 结构体的TIM_ICSelection 成员通过TIxFPx线来选择TI输入引脚IC通道,这里要注意,既同一个TI引脚可以对应两个IC通道,也可以多个引脚对应一个IC通道,具体对应关系如下:

    TIM_ICSelection 对应关系
    TIM_ICSelection_DirectTI TI1,2,3,4分别对应IC1,2,3,4
    TIM_ICSelection_IndirectTI TI1,2,3,4分别对应IC2,1,4,3

    预分频器:这个要区别与时基单元的预分频器,这个预分频器的功能是对捕获到的信号频率进行分频,个人觉得没什么用哈,分频系数通过TIM_ICInitTypeDef 结构体的TIM_ICPrescaler成员来配置

    配置之后总体是这样的:

    	TIM_ICInitTypeDef TIM_ICInitStructure;
    	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//选择TI1
    	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;//IC对应关系
    	TIM_ICInit(TIM2, &TIM_ICInitStructure);
    	//还有初始化引脚,开启时钟等细节就不在这里写了
    

    完成以上几个配置之后,再配置一下定时器的时基单元,开启定时器,就可以完成CCR锁存了,但是这样是没有办法测量脉宽或者PWM频率和占空比的,原因是定时器的值是混乱的,测量本次的CCR锁存值,我们无法知道上一个边沿的锁存值,因为只配置了一个CCR,所以会导致以下几个问题:

  • 不知道定时器计数器的初始值,无法计算边沿之间的计数次数
  • 无法测量占空比,因为只有一个CCR,就算捕获双边沿,emmm貌似还是没有办法测量吧
  • 这说明我们还没有完全配置完毕,解决方法是什么呢?先假想一下解决的方式:
    测量频率

    如果要测量频率,那可以选择上升沿检测,然后每次检测到升沿之后,做两件事情,第一,锁存CCR,第二,清零CNT。这样就完美了啊,因为我们只需要读取CCR的锁存值,就可以非常方便的知道两个上升沿之间的计数次数,请大家再仔细想一下这个过程

    测量脉宽和占空比,那我们可以再每一次上升沿来临,还是干两件事情,第一,锁存CCR,第二,清零CNT。在每一次下降沿干一件事,锁存CCR,但是一定要注意,这个CCR不能和上一个CCR一样,要选择另外一个CCR锁存,如果把第一个CCR叫做CCR1,第二个CCR叫做CCR2。那请大家思考,此时,在任意一个时刻,我们同时读取CCR1和CCR2的锁存值,那CCR1的值就是一个周期的计数次数吧,也就是说我们已经算出来频率了;CCR2是什么呢?因为每一次都是先清零CNT再捕获到CCR2的,很明显CCR2就是高电平期间的计数次数,所以脉宽也被我们测量出来了,脉宽和频率都有了,那占空比就很容易求了吧。

    以上两个想法第一个想法很容易实现,只需要调用以下两个函数即可:

    	TIM_SelectInputTrigger(TIMx, TIM_TS_TI1FP1);//选择触发源
    	TIM_SelectSlaveMode(TIMx, TIM_SlaveMode_Reset);//选择从模式触发事件为Reset(清零CNT)
    	//这两个函数调用之后,如果选择上升沿触发,那每次触发之后,都会先锁存再清零CNT
    

    第二个想法其实就是stm32专有的PWM输入模式,要开启此模式,只需要把初始化函数TIM_ICInit改为TIM_PWMIConfig,再加上第一个想法的两个函数即可总体配置如下:

    	TIM_ICInitTypeDef TIM_ICInitStructure;
    	TIM_ICInitStructure.TIM_Channel = TIM_Channel_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_PWMIConfig(TIMx, &TIM_ICInitStructure);
    	
    	TIM_SelectInputTrigger(TIMx, TIM_TS_TI1FP1);//选择触发源,触发源只能选择TIM_TS_TI1FP1或TIM_TS_TI2FP2
    	TIM_SelectSlaveMode(TIMx, TIM_SlaveMode_Reset);//选择从模式触发事件为Reset(清零CNT)
    

    到这里,大家可能有疑惑,为什么这样就可以选择两个CCR了?并且边沿检测只配置了一个,按照想法二,不应该是一个IC捕获上升沿一个IC捕获下降沿吗?至少要配置成双边沿才对啊?
    其实这些工作TIM_PWMIConfig这个函数都已经帮我们配置好了,我来详细的说一下这个函数的作用:

    首先要明确,PWM模式仅支持TI1和TI2两个通道,比如所你在TIM_ICInitTypeDef 结构体中配置了TI1对应IC1,上升沿触发CCR1锁存,TI1FP1作为触发源触发CNT清零,那么调用TIM_PWMIConfig之后,这个函数会自动的让TI1通过TI1FP2对应到IC2(前文已提到,同一个TI可以对应两个IC),然后下降沿触发CCR2锁存,并且下降沿不会触发CNT清零(因为触发源配置的是TI1FP1不是TI1FP2)

    也就是说,我们在结构体中配置好CCR1和CCR2的其中一个,那TIM_PWMIConfig会自动帮我们配置好另一个,并且也会配置相反的边沿触发,这里建议手动配置有触发源的那两个通道TI1的IC1或者TI2的IC2,然后调用TIM_PWMIConfig初始化函数,最后调用TIM_SelectInputTrigger和TIM_SelectSlaveMode两个函数触发从模式控制器来配置清零CNT事件

    以上就是输入捕获,输入捕获在大部分情况下是可以直接读取CCR锁存值的,但是有些特殊情况会导致CNT溢出,解决办法是写一个记录溢出次数的中断函数,可以参看本人的这篇博客:输入捕获中断函数解读


    五.输出比较:

    输出比较功能框图
    输出比较有很多中模式,其中,PWM1和PWM2模式最为常用,这里只介绍它们两个

    void PWM_Init(void)
    {
    	//第一步:开启TIM2时钟
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    	
    	//第二步:初始化PA0引脚
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	GPIO_InitTypeDef GPIO_Init_Struct;
    	GPIO_Init_Struct.GPIO_Mode = GPIO_Mode_AF_PP;//PWM需要通过定时器比较输出控制GPIO口,所以要设置GPIO口模式为复用推挽输出
    	GPIO_Init_Struct.GPIO_Pin = GPIO_Pin_0;
    	GPIO_Init_Struct.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_Init_Struct);
        
        //第二步:配置时基单元
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitstruct;
    	TIM_TimeBaseInitstruct.TIM_ClockDivision = TIM_CKD_DIV1;//滤波采样频率的分频系数确定
    	TIM_TimeBaseInitstruct.TIM_CounterMode = TIM_CounterMode_Up;//设置计数器计数模式为向上计数
    	TIM_TimeBaseInitstruct.TIM_Period = 100-1;//设定ARR,范围0~65535
    	TIM_TimeBaseInitstruct.TIM_Prescaler = 720-1;//设定PSC,范围0~65535
    	TIM_TimeBaseInitstruct.TIM_RepetitionCounter = 0;//重复计数器在高级定时器中才可以使用,所以这里直接给0
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitstruct);//调用时基初始化函数
    	
    	//第三步:配置输出比较结构体
    	TIM_OCInitTypeDef TIML_OCInitStruct;
    	TIM_OCStructInit(&TIML_OCInitStruct);//给结构体先整体赋初始值,如果不想把所有结构体成员都赋初始值,则调用此函数
    	TIML_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;//选择PWM1模式
    	TIML_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;//当CNT<CCR,REF的电平为高电平
    	TIML_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;//输出使能
    	TIML_OCInitStruct.TIM_Pulse = 0;//CCR
    	TIM_OC1Init(TIM2,&TIML_OCInitStruct);
    
    	TIM_Cmd(TIM2,ENABLE);
    }
    

    PWM1和PWM2模式本质上一样,只是极性不同而已,来看一下官方参考手册的描述:

    PWM模式1- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为
    无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否
    则为有效电平(OC1REF=1)。
    PWM模式2- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为
    有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电
    平。

    官方参考手册中的有效电平即结构体TIM_OCInitTypeDef 的TIM_OCPolarity成员,所以,PWM的极性由PWM的模式和TIM_OCPolarity来共同决定

    PWM占空比=CCR/(ARR+1)
    PWM频率=定时频率=定时器时钟频率/(PSC+1)/(ARR+1)

    这里面的CCR通过库函数TIM_SetComparex(x=1~4)来配置,PSC和ARR在时基单元的结构体中直接赋值

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32高级定时器:配置指南

    发表评论