33. TIM—高级定时器

本章参考资料:《STM32F4xx参考手册》、《STM32F4xx规格书》、库帮助文档《stm32f4xx_dsp_stdperiph_lib_um.chm》。

学习本章时,配合《STM32F4xx参考手册》高级定时器章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。

特别说明,本书内容是以STM32F40xx系列控制器资源讲解。

上一章我们讲解了基本定时器功能,基本定时器功能简单,理解起来也容易。高级控制定时器包含了通用定时器的功能,再加上已经有了基本定时器基础的基础, 如果再把通用定时器单独拿出来讲那内容有很多重复,实际效果不是很好,所以通用定时器不作为独立章节讲解, 可以在理解了高级定时器后参考《STM32F4xx中文参考手册》通用定时器章节内容理解即可。

33.1. 高级控制定时器

高级控制定时器(TIM1和TIM8)和通用定时器在基本定时器的基础上引入了外部引脚,可以输入捕获和输出比较功能。高级控制定时器比通用定时器增加了可编程死区互补输出、 重复计数器、带刹车(断路)功能,这些功能都是针对工业电机控制方面。这几个功能在本书不做详细的介绍,主要介绍常用的输入捕获和输出比较功能。

高级控制定时器时基单元包含一个16位自动重载计数器ARR,一个16位的计数器CNT,可向上/下计数,一个16位可编程预分频器PSC,预分频器时钟源有多种可选, 有内部的时钟、外部时钟。还有一个8位的重复计数器RCR,这样最高可实现40位的可编程定时。

STM32F407ZGT6的高级/通用定时器的IO分配具体见表 33‑1。配套开发板因为IO资源紧缺,定时器的IO很多已经复用它途,故下表中的IO只有部分可用于定时器的实验。

高级控制和通用定时器通道引脚分布

33.2. 高级控制定时器功能框图

高级控制定时器功能框图包含了高级控制定时器最核心内容,掌握了功能框图, 对高级控制定时器就有一个整体的把握,在编程时思路就非常清晰,见图 高级控制定时器功能框图

关于图中带阴影的寄存器,即带有影子寄存器,指向左下角的事件更新图标以及指向右上角的中断和DMA输出标志在上一章已经做了解释,这里就不再介绍。

高级控制定时器功能框图

33.2.1. 时钟源

高级控制定时器有四个时钟源可选:

  • 内部时钟源CK_INT

  • 外部时钟模式1:外部输入引脚TIx(x=1,2,3,4)

  • 外部时钟模式2:外部触发输入ETR

  • 内部触发输入

内部时钟源(CK_INT)

内部时钟CK_INT即来自于芯片内部,等于168M,一般情况下,我们都是使用内部时钟。当从模式控制寄存器TIMx_SMCR的SMS位等于000时,则使用内部时钟。

外部时钟模式1

外部时钟模式1框图

1:时钟信号输入引脚

当使用外部时钟模式1的时候,时钟信号来自于定时器的输入通道,总共有4个,分别为TI1/2/3/4,即TIMx_CH1/2/3/4。具体使用哪一路信号, 由TIM_CCMx的位CCxS[1:0]配置,其中CCM1控制TI1/2,CCM2控制TI3/4。

2:滤波器

如果来自外部的时钟信号的频率过高或者混杂有高频干扰信号的话,我们就需要使用滤波器对ETRP信号重新采样,来达到降频或者去除高频干扰的目的,具体的由TIMx_CCMx的位ICxF[3:0]配置。

3:边沿检测

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

4:触发选择

当使用外部时钟模式1时,触发源有两个,一个是滤波后的定时器输入1(TI1FP1)和滤波后的定时器输入2(TI2FP2),具体的由TIMxSMCR的位TS[2:0]配置。

5:从模式选择

选定了触发源信号后,最后我们需把信号连接到TRGI引脚,让触发信号成为外部时钟模式1的输入,最终等于CK_PSC, 然后驱动计数器CNT计数。具体的配置TIMx_SMCR的位SMS[2:0]为000即可选择外部时钟模式1。

6:使能计数器

经过上面的5个步骤之后,最后我们只需使能计数器开始计数,外部时钟模式1的配置就算完成。使能计数器由TIMx_CR1的位CEN配置。

外部时钟模式2

外部时钟模式2框图

1:时钟信号输入引脚

当使用外部时钟模式2的时候,时钟信号来自于定时器的特定输入通道TIMx_ETR,只有1个。

2:外部触发极性

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

3:外部触发预分频器

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

4:滤波器

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

5:从模式选择

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

6:使能计数器

经过上面的5个步骤之后,最后我们只需使能计数器开始计数,外部时钟模式2的配置就算完成。使能计数器由TIMx_CR1的位CEN配置。

内部触发输入

内部触发输入是使用一个定时器作为另一个定时器的预分频器。硬件上高级控制定时器和通用定时器在内部连接在一起,可以实现定时器同步或级联。 主模式的定时器可以对从模式定时器执行复位、启动、停止或提供时钟。高级控制定时器和部分通用定时器(TIM2至TIM5)可以设置为主模式或从模式,TIM9和TIM10可设置为从模式。

TIM1用作TIM2的预分频器 为主模式定时器(TIM1)为从模式定时器(TIM2)提供时钟,即TIM1用作TIM2的预分频器。

TIM1用作TIM2的预分频器

33.2.2. 控制器

高级控制定时器控制器部分包括触发控制器、从模式控制器以及编码器接口。触发控制器用来针对片内外设输出触发信号,比如为其它定时器提供时钟和触发DAC/ADC转换。

编码器接口专门针对编码器计数而设计。从模式控制器可以控制计数器复位、启动、递增/递减、计数。

33.2.3. 时基单元

高级控制定时器时基单元包括四个寄存器,分别是计数器寄存器(CNT)、预分频器寄存器(PSC)、自动重载寄存器(ARR)和重复计数器寄存器(RCR)。 其中重复计数器RCR是高级定时器独有,通用和基本定时器没有。前面三个寄存器都是16位有效,TIMx_RCR寄存器是8位有效。

预分频器PSC

预分频器PSC,有一个输入时钟CK_PSC和一个输出时钟CK_CNT。输入时钟CK_PSC就是上面时钟源的输出,输出CK_CNT则用来驱动计数器CNT计数。 通过设置预分频器PSC的值可以得到不同的CK_CNT,实际计算为:fCK_CNT等于fCK_PSC/(PSC[15:0]+1),可以实现1至65536分频。

计数器CNT

高级控制定时器的计数器有三种计数模式,分别为递增计数模式、递减计数模式和递增/递减(中心对齐)计数模式。

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

(2) 递减计数模式下,计数器从自动重载寄存器ARR值开始计数,每来一个CK_CNT脉冲计数器就减1,直到计数器值为0, 然后计数器又从自动重载寄存器ARR值开始递减计数并生成计数器下溢事件,计数器总是如此循环计数。如果禁用重复计数器, 在计数器生成下溢事件就马上生成更新事件;如果使能重复计数器,每生成一次下溢事件重复计数器内容就减1,直到重复计数器内容为0时才会生成更新事件。

(3) 中心对齐模式下,计数器从0开始递增计数,直到计数值等于(ARR-1)值生成计数器上溢事件,然后从ARR值开始递减计数直到1生成计数器下溢事件。 然后又从0开始计数,如此循环。每次发生计数器上溢和下溢事件都会生成更新事件。

自动重载寄存器ARR

自动重载寄存器ARR用来存放与计数器CNT比较的值,如果两个值相等就递减重复计数器。可以通过TIMx_CR1寄存器的ARPE位控制自动重载影子寄存器功能, 如果ARPE位置1,自动重载影子寄存器有效,只有在事件更新时才把TIMx_ARR值赋给影子寄存器。如果ARPE位为0,则修改TIMx_ARR值马上有效。

重复计数器RCR

在基本/通用定时器发生上/下溢事件时直接就生成更新事件,但对于高级控制定时器却不是这样,高级控制定时器在硬件结构上多出了重复计数器, 在定时器发生上溢或下溢事件是递减重复计数器的值,只有当重复计数器为0时才会生成更新事件。在发生N+1个上溢或下溢事件(N为RCR的值)时产生更新事件。

33.2.4. 输入捕获

输入捕获功能框图

输入捕获可以对输入的信号的上升沿、下降沿或者双边沿进行捕获,常用的有测量输入信号的脉宽和测量PWM输入信号的频率和占空比这两种。

输入捕获的大概的原理就是,当捕获到信号的跳变沿的时候,把计数器CNT的值锁存到捕获寄存器CCR中,把前后两次捕获到的CCR寄存器中的值相减, 就可以算出脉宽或者频率。如果捕获的脉宽的时间长度超过你的捕获定时器的周期,就会发生溢出,这个我们需要做额外的处理。

输入通道

需要被测量的信号从定时器的外部引脚TIMx_CH1/2/3/4进入,通常叫TI1/2/3/4,在后面的捕获讲解中对于要被测量的信号我们都以TIx为标准叫法。

输入滤波器和边沿检测器

当输入的信号存在高频干扰的时候,我们需要对输入信号进行滤波,即进行重新采样,根据采样定律,采样的频率必须大于等于两倍的输入信号。 比如输入的信号为1M,又存在高频的信号干扰,那么此时就很有必要进行滤波,我们可以设置采样频率为2M,这样可以在保证采样到有效信号的基础上把高于2M的高频干扰信号过滤掉。

滤波器的配置由CR1寄存器的位CKD[1:0]和CCMR1/2的位ICxF[3:0]控制。从ICxF位的描述可知,采样频率fSAMPLE可以由fCK_INT和fDTS分频后的时钟提供, 其中是fCK_INT内部时钟,fDTS是fCK_INT经过分频后得到的频率,分频因子由CKD[1:0]决定,可以是不分频,2分频或者是4分频。

边沿检测器用来设置信号在捕获的时候是什么边沿有效,可以是上升沿,下降沿,或者是双边沿,具体的由CCER寄存器的位CCxP和CCxNP决定。

捕获通道

捕获通道就是图中的IC1/2/3/4,每个捕获通道都有相对应的捕获寄存器CCR1/2/3/4,当发生捕获的时候,计数器CNT的值就会被锁存到捕获寄存器中。

这里我们要搞清楚输入通道和捕获通道的区别,输入通道是用来输入信号的,捕获通道是用来捕获输入信号的通道, 一个输入通道的信号可以同时输入给两个捕获通道。比如输入通道TI1的信号经过滤波边沿检测器之后的TI1FP1和TI1FP2可以进入到捕获通道IC1和IC2, 其实这就是我们后面要讲的PWM输入捕获,只有一路输入信号(TI1)却占用了两个捕获通道(IC1和IC2)。当只需要测量输入信号的脉宽时候, 用一个捕获通道即可。输入通道和捕获通道的映射关系具体由寄存器CCMRx的位CCxS[1:0]配置。

预分频器

ICx的输出信号会经过一个预分频器,用于决定发生多少个事件时进行一次捕获。具体的由寄存器CCMRx的位ICxPSC配置,如果希望捕获信号的每一个边沿,则不分频。

捕获寄存器

经过预分频器的信号ICxPS是最终被捕获的信号,当发生捕获时(第一次),计数器CNT的值会被锁存到捕获寄存器CCR中,还会产生CCxI中断, 相应的中断位CCxIF(在SR寄存器中)会被置位,通过软件或者读取CCR中的值可以将CCxIF清0。如果发生第二次捕获(即重复捕获:CCR寄存器中已捕获到计数器值且 CCxIF 标志已置 1), 则捕获溢出标志位CCxOF(在SR寄存器中)会被置位,CCxOF只能通过软件清零。

33.2.5. 输出比较

输出比较功能框图

输出比较就是通过定时器的外部引脚对外输出控制信号,有冻结、将通道X(x=1,2,3,4)设置为匹配时输出有效电平、将通道X设置为匹配时输出无效电平、 翻转、强制变为无效电平、强制变为有效电平、PWM1和PWM2这八种模式,具体使用哪种模式由寄存器CCMRx的位OCxM[2:0]配置。其中PWM模式是输出比较中的特例,使用的也最多。

比较寄存器

当计数器CNT的值跟比较寄存器CCR的值相等的时候,输出参考信号OCxREF的信号的极性就会改变,其中OCxREF=1(高电平)称之为有效电平, OCxREF=0(低电平)称之为无效电平,并且会产生比较中断CCxI,相应的标志位CCxIF(SR寄存器中)会置位。然后OCxREF再经过一系列的控制之后就 成为真正的输出信号OCx/OCxN。

死区发生器

在生成的参考波形OCxREF的基础上,可以插入死区时间,用于生成两路互补的输出信号OCx和OCxN,死区时间的大小具体由BDTR寄存器的位DTG[7:0]配置。 死区时间的大小必须根据与输出信号相连接的器件及其特性来调整。下面我们简单举例说明下带死区的PWM信号的应用,我们以一个板桥驱动电路为例。

半桥驱动电路

在这个半桥驱动电路中,Q1导通,Q2截止,此时我想让Q1截止Q2导通,肯定是要先让Q1截止一段时间之后,再等一段时间才让Q2导通, 那么这段等待的时间就称为死区时间,因为Q1关闭需要时间(由MOS管的工艺决定)。如果Q1关闭之后,马上打开Q2, 那么此时一段时间内相当于Q1和Q2都导通了,这样电路会短路。

带死区插入的互补输出 是针对上面的半桥驱动电路而画的带死区插入的PWM信号,图中的死区时间要根据MOS管的工艺来调节。

带死区插入的互补输出

输出控制

输出比较

在输出比较的输出控制中,参考信号OCxREF在经过死区发生器之后会产生两路带死区的互补信号OCx_DT和OCxN_DT(通道1~3才有互补信号, 通道4没有,其余跟通道1~3一样),这两路带死区的互补信号然后就进入输出控制电路,如果没有加入死区控制,那么进入输出控制电路的信号就直接是OCxREF。

进入输出控制电路的信号会被分成两路,一路是原始信号,一路是被反向的信号,具体的由寄存器CCER的位CCxP和CCxNP控制。 经过极性选择的信号是否由OCx引脚输出到外部引脚CHx/CHxN则由寄存器CCER的位CxE/CxNE配置。

如果加入了断路(刹车)功能,则断路和死区寄存器BDTR的MOE、OSSI和OSSR这三个位会共同影响输出的信号。

输出引脚

输出比较的输出信号最终是通过定时器的外部IO来输出的,分别为CH1/2/3/4,其中前面三个通道还有互补的输出通道CH1/2/3N。更加详细的IO说明还请查阅相关的数据手册。

33.2.6. ⑥断路功能

断路功能就是电机控制的刹车功能,使能断路功能时,根据相关控制位状态修改输出信号电平。在任何情况下, OCx和OCxN输出都不能同时为有效电平,这关系到电机控制常用的H桥电路结构原因。

断路源可以是时钟故障事件,由内部复位时钟控制器中的时钟安全系统(CSS)生成,也可以是外部断路输入IO,两者是或运算关系。

系统复位启动都默认关闭断路功能,将断路和死区寄存器(TIMx_BDTR)的BKE为置1,使能断路功能。 可通过TIMx_BDTR 寄存器的BKP位设置,设置断路输入引脚的有效电平,设置为1时输入BRK为高电平有效,否则低电平有效。

发送断路时,将产生以下效果:

  • TIMx_BDTR 寄存器中主输出模式使能(MOE)位被清零,输出处于无效、空闲或复位状态;

  • 根据相关控制位状态控制输出通道引脚电平;当使能通道互补输出时,会根据情况自动控制输出通道电平;

  • 将TIMx_SR 寄存器中的 BIF位置 1,并可产生中断和DMA传输请求。

  • 如果 TIMx_BDTR 寄存器中的 自动输出使能(AOE)位置 1,则MOE位会在发生下一个UEV事件时自动再次置 1。

33.3. 输入捕获应用

输入捕获一般应用在两个方面,一个方面是脉冲跳变沿时间测量,另一方面是PWM输入测量。

33.3.1. 测量脉宽或者频率

脉宽_频率测量示意图

33.3.1.1. 测量频率

当捕获通道TIx上出现上升沿时,发生第一次捕获,计数器CNT的值会被锁存到捕获寄存器CCR中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录), 并把捕获寄存器中的值读取到value1中。当出现第二次上升沿时,发生第二次捕获,计数器CNT的值会再次被锁存到捕获寄存器CCR中,并再次进入捕获中断, 在捕获中断中,把捕获寄存器的值读取到value3中,并清除捕获记录标志。利用value3和value1的差值我们就可以算出信号的周期(频率)。

33.3.1.2. 测量脉宽

当捕获通道TIx上出现上升沿时,发生第一次捕获,计数器CNT的值会被锁存到捕获寄存器CCR中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录), 并把捕获寄存器中的值读取到value1中。然后把捕获边沿改变为下降沿捕获,目的是捕获后面的下降沿。当下降沿到来的时候,发生第二次捕获, 计数器CNT的值会再次被锁存到捕获寄存器CCR中,并再次进入捕获中断,在捕获中断中,把捕获寄存器的值读取到value3中,并清除捕获记录标志。然后把捕获边沿设置为上升沿捕获。

在测量脉宽过程中需要来回的切换捕获边沿的极性,如果测量的脉宽时间比较长,定时器就会发生溢出,溢出的时候会产生更新中断,我们可以在中断里面对溢出进行记录处理。

33.3.2. PWM输入模式

测量脉宽和频率还有一个更简便的方法就是使用PWM输入模式。与上面那种只使用一个捕获寄存器测量脉宽和频率的方法相比,PWM输入模式需要占用两个捕获寄存器。

输入通道和捕获通道的关系映射图

当使用PWM输入模式的时候,因为一个输入通道(TIx)会占用两个捕获通道(ICx),所以一个定时器在使用PWM输入的时候最多只能使用两个输入通道(TIx)。

我们以输入通道TI1工作在PWM输入模式为例来讲解下具体的工作原理,其他通道以此类推即可。

PWM信号由输入通道TI1进入,因为是PWM输入模式的缘故,信号会被分为两路,一路是TI1FP1,另外一路是TI1FP2。其中一路是周期,另一路是占空比, 具体哪一路信号对应周期还是占空比,得从程序上设置哪一路信号作为触发输入,作为触发输入的哪一路信号对应的就是周期,另一路就是对应占空比。 作为触发输入的那一路信号还需要设置极性,是上升沿还是下降沿捕获,一旦设置好触发输入的极性,另外一路硬件就会自动配置为相反的极性捕获, 无需软件配置。一句话概括就是:选定输入通道,确定触发信号,然后设置触发信号的极性即可,因为是PWM输入的缘故,另一路信号则由硬件配置,无需软件配置。

当使用PWM输入模式的时候必须将从模式控制器配置为复位模式(配置寄存器SMCR的位SMS[2:0]来实现),即当我们启动触发信号开始进行捕获的时候,同时把计数器CNT复位清零。

下面我们以一个更加具体的时序图来分析下PWM输入模式。

PWM输入模式时序

PWM信号由输入通道TI1进入,配置TI1FP1为触发信号,上升沿捕获。当上升沿的时候IC1和IC2同时捕获,计数器CNT清零,到了下降沿的时候, IC2捕获,此时计数器CNT的值被锁存到捕获寄存器CCR2中,到了下一个上升沿的时候,IC1捕获,计数器CNT的值被锁存到捕获寄存器CCR1中。 其中CCR2测量的是脉宽,CCR1测量的是周期。

从软件上来说,用PWM输入模式测量脉宽和周期更容易,付出的代价是需要占用两个捕获寄存器。

33.4. 输出比较应用

输出比较模式总共有8种,具体的由寄存器CCMRx的位OCxM[2:0]配置。我们这里只讲解最常用的PWM模式,其他几种模式具体的看数据手册即可。

33.4.1. PWM输出模式

PWM输出就是对外输出脉宽(即占空比)可调的方波信号,信号频率由自动重装寄存器ARR的值决定,占空比由比较寄存器CCR的值决定。

PWM模式分为两种,PWM1和PWM2,总得来说是差不多,就看你怎么用而已,具体的区别见表格 PWM1与PWM2模式的区别

PWM1与PWM2模式的区别

下面我们以PWM1模式来讲解,以计数器CNT计数的方向不同还分为边沿对齐模式和中心对齐模式。PWM信号主要都是用来控制电机, 一般的电机控制用的都是边沿对齐模式,FOC电机一般用中心对齐模式。我们这里只分析这两种模式在信号感官上(即信号波形)的区别, 具体在电机控制中的区别不做讨论,到了你真正需要使用的时候就会知道了。

33.4.1.1. PWM边沿对齐模式

在递增计数模式下,计数器从 0 计数到自动重载值( TIMx_ARR寄存器的内容),然后重新从 0 开始计数并生成计数器上溢事件

PWM1模式的边沿对齐波形

在边沿对齐模式下,计数器CNT只工作在一种模式,递增或者递减模式。这里我们以CNT工作在递增模式为例,在中,ARR=8,CCR=4,CNT从0开始计数, 当CNT<CCR的值时,OCxREF为有效的高电平,于此同时,比较中断寄存器CCxIF置位。当CCR=<CNT<=ARR时,OCxREF为无效的低电平。然后CNT又从0开始计数并生成计数器上溢事件,以此循环往复。

33.4.1.2. PWM中心对齐模式

PWM1模式的中心对齐波形

在中心对齐模式下,计数器CNT是工作做递增/递减模式下。开始的时候,计数器CNT从 0 开始计数到自动重载值减1(ARR-1), 生成计数器上溢事件;然后从自动重载值开始向下计数到 1 并生成计数器下溢事件。之后从0 开始重新计数。

PWM1模式的中心对齐波形 是PWM1模式的中心对齐波形,ARR=8,CCR=4。第一阶段计数器CNT工作在递增模式下,从0开始计数,当CNT<CCR的值时, OCxREF为有效的高电平,当CCR=<CNT<<ARR时,OCxREF为无效的低电平。第二阶段计数器CNT工作在递减模式,从ARR的值开始递减, 当CNT>CCR时,OCxREF为无效的低电平,当CCR=>CNT>=1时,OCxREF为有效的高电平。

在波形图上我们把波形分为两个阶段,第一个阶段是计数器CNT工作在递增模式的波形,这个阶段我们又分为①和②两个阶段, 第二个阶段是计数器CNT工作在递减模式的波形,这个阶段我们又分为③和④两个阶段。要说中心对齐模式下的波形有什么特征的话,那就是①和③阶段的时间相等,②和④阶段的时间相等。

中心对齐模式又分为中心对齐模式1/2/3 三种,具体由寄存器CR1位CMS[1:0]配置。具体的区别就是比较中断中断标志位CCxIF在何时置1: 中心模式1在CNT递减计数的时候置1,中心对齐模式2在CNT递增计数时置1,中心模式3在CNT递增和递减计数时都置1。

33.5. 定时器初始化结构体详解

标准库函数对定时器外设建立了四个初始化结构体,分别为时基初始化结构体TIM_TimeBaseInitTypeDef、输出比较初始化结构体TIM_OCInitTypeDef、 输入捕获初始化结构体TIM_ICInitTypeDef和断路和死区初始化结构体TIM_BDTRInitTypeDef,高级控制定时器可以用到所有初始化结构体, 通用定时器不能使用TIM_BDTRInitTypeDef结构体,基本定时器只能使用时基结构体。初始化结构体成员用于设置定时器工作环境参数, 并由定时器相应初始化配置函数调用,最终这些参数将会写入到定时器相应的寄存器中。

初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如。 初始化结构体定义在stm32f4xx_tim.h文件中,初始化库函数定义在stm32f4xx_tim.c文件中,编程时我们可以结合这两个文件内注释使用。

33.5.1. TIM_TimeBaseInitTypeDef

时基结构体TIM_TimeBaseInitTypeDef用于定时器基础参数设置,与TIM_TimeBaseInit函数配合使用完成配置。

代码清单:高级定时器-1 定时器基本初始化结构体
1
2
3
4
5
6
7
typedef struct {
    uint16_t TIM_Prescaler;          // 预分频器
    uint16_t TIM_CounterMode;        // 计数模式
    uint32_t TIM_Period;             // 定时器周期
    uint16_t TIM_ClockDivision;      // 时钟分频
    uint8_t TIM_RepetitionCounter;   // 重复计算器
} TIM_TimeBaseInitTypeDef;

(1) TIM_Prescaler:定时器预分频器设置, 时钟源经该预分频器才是定时器计数时钟CK_CNT,它设定PSC寄存器的值。计算公式为: 计数器时钟频率 (fCK_CNT) 等于 fCK_PSC / (PSC[15:0] + 1),可实现1至65536分频。

(2) TIM_CounterMode:定时器计数方式, 可设置为向上计数、向下计数以及中心对齐。高级控制定时器允许选择任意一种。

(3) TIM_Period:定时器周期,实际就是设定自动重载寄存器ARR的值, ARR 为要装载到实际自动重载寄存器(即影子寄存器)的值,可设置范围为0至65535。

(4) TIM_ClockDivision:时钟分频, 设置定时器时钟CK_INT频率与死区发生器以及数字滤波器采样时钟频率分频比。可以选择1、2、4分频。

(5) TIM_RepetitionCounter:重复计数器, 只有8位,只存在于高级定时器。

33.5.2. TIM_OCInitTypeDef

输出比较结构体TIM_OCInitTypeDef用于输出比较模式,与TIM_OCxInit函数配合使用完成指定定时器输出通道初始化配置。高级控制定时器有四个定时器通道,使用时都必须单独设置。

代码清单:高级定时器-2 定时器比较输出初始化结构体
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
typedef struct {
    uint16_t TIM_OCMode;        // 比较输出模式
    uint16_t TIM_OutputState;   // 比较输出使能
    uint16_t TIM_OutputNState;  // 比较互补输出使能
    uint32_t TIM_Pulse;         // 脉冲宽度
    uint16_t TIM_OCPolarity;    // 输出极性
    uint16_t TIM_OCNPolarity;   // 互补输出极性
    uint16_t TIM_OCIdleState;   // 空闲状态下比较输出状态
    uint16_t TIM_OCNIdleState;  // 空闲状态下比较互补输出状态
} TIM_OCInitTypeDef;

(1) TIM_OCMode:比较输出模式选择,总共有八种, 常用的为PWM1/PWM2。它设定CCMRx寄存器OCxM[2:0]位的值。

(2) TIM_OutputState:比较输出使能, 决定最终的输出比较信号OCx是否通过外部引脚输出。它设定TIMx_CCER寄存器CCxE/CCxNE位的值。

(3) TIM_OutputNState:比较互补输出使能, 决定OCx的互补信号OCxN是否通过外部引脚输出。它设定CCER寄存器CCxNE位的值。

(4) TIM_Pulse:比较输出脉冲宽度, 实际设定比较寄存器CCR的值,决定脉冲宽度。可设置范围为0至65535。

(5) TIM_OCPolarity:比较输出极性, 可选OCx为高电平有效或低电平有效。它决定着定时器通道有效电平。它设定CCER寄存器的CCxP位的值。

(6) TIM_OCNPolarity:比较互补输出极性, 可选OCxN为高电平有效或低电平有效。它设定TIMx_CCER寄存器的CCxNP位的值。

(7) TIM_OCIdleState:空闲状态时通道输出电平设置, 可选输出1或输出0,即在空闲状态(BDTR_MOE位为0)时,经过死区时间后定时器通道输出高电平或低电平。它设定CR2寄存器的OISx位的值。

(8) TIM_OCNIdleState:空闲状态时互补通道输出电平设置, 可选输出1或输出0,即在空闲状态(BDTR_MOE位为0)时,经过死区时间后定时器互补通道输出高电平或低电平,设定值必须与TIM_OCIdleState相反。它设定是CR2寄存器的OISxN位的值。

33.5.3. TIM_ICInitTypeDef

输入捕获结构体TIM_ICInitTypeDef用于输入捕获模式,与TIM_ICInit函数配合使用完成定时器输入通道初始化配置。 如果使用PWM输入模式需要与TIM_PWMIConfig函数配合使用完成定时器输入通道初始化配置。

代码清单:高级定时器-3 定时器输入捕获初始化结构体
1
2
3
4
5
6
7
typedef struct {
    uint16_t TIM_Channel;      // 输入通道选择
    uint16_t TIM_ICPolarity;   // 输入捕获触发选择
    uint16_t TIM_ICSelection;  // 输入捕获选择
    uint16_t TIM_ICPrescaler;  // 输入捕获预分频器
    uint16_t TIM_ICFilter;     // 输入捕获滤波器
} TIM_ICInitTypeDef;

(1) TIM_Channel:捕获通道ICx选择, 可选TIM_Channel_1、TIM_Channel_2、TIM_Channel_3或TIM_Channel_4四个通道。它设定CCMRx寄存器CCxS位 的值。

(2) TIM_ICPolarity:输入捕获边沿触发选择, 可选上升沿触发、下降沿触发或边沿跳变触发。它设定CCER寄存器CCxP位和CCxNP位的值。

(3) TIM_ICSelection:输入通道选择, 捕获通道ICx的信号可来自三个输入通道,分别为TIM_ICSelection_DirectTI、TIM_ICSelection_IndirectTI或TIM_ICSelection_TRC, 具体的区别见图 输入通道与捕获通道IC的映射 。它设定CCRMx寄存器的CCxS[1:0]位的值。

输入通道与捕获通道IC的映射

(4) TIM_ICPrescaler:输入捕获通道预分频器, 可设置1、2、4、8分频,它设定CCMRx寄存器的ICxPSC[1:0]位的值。如果需要捕获输入信号的每个有效边沿,则设置1分频即可。

(5) TIM_ICFilter:输入捕获滤波器设置, 可选设置0x0至0x0F。它设定CCMRx寄存器ICxF[3:0]位的值。一般我们不使用滤波器,即设置为0。

33.5.4. TIM_BDTRInitTypeDef

断路和死区结构体TIM_BDTRInitTypeDef用于断路和死区参数的设置,属于高级定时器专用,用于配置断路时通道输出状态, 以及死区时间。它与TIM_BDTRConfig函数配置使用完成参数配置。这个结构体的成员只对应BDTR这个寄存器,有关成员的具体使用配置请参考手册BDTR寄存器的详细描述。

代码清单:高级定时器-4 断路和死区初始化结构体
1
2
3
4
5
6
7
8
9
typedef struct {
    uint16_t TIM_OSSRState;        // 运行模式下的关闭状态选择
    uint16_t TIM_OSSIState;        // 空闲模式下的关闭状态选择
    uint16_t TIM_LOCKLevel;        // 锁定配置
    uint16_t TIM_DeadTime;         // 死区时间
    uint16_t TIM_Break;            // 断路输入使能控制
    uint16_t TIM_BreakPolarity;    // 断路输入极性
    uint16_t TIM_AutomaticOutput;  // 自动输出使能
} TIM_BDTRInitTypeDef;

(1) TIM_OSSRState: 运行模式下的关闭状态选择,它设定BDTR寄存器OSSR位的值。

(2) TIM_OSSIState: 空闲模式下的关闭状态选择,它设定BDTR寄存器OSSI位的值。

(3) TIM_LOCKLevel: 锁定级别配置, BDTR寄存器LOCK[1:0]位的值。

(4) TIM_DeadTime: 配置死区发生器,定义死区持续时间,可选设置范围为0x0至0xFF。它设定BDTR寄存器DTG[7:0]位的值。

(5) TIM_Break: 断路输入功能选择,可选使能或禁止。它设定BDTR寄存器BKE位的值。

(6) TIM_BreakPolarity: 断路输入通道BRK极性选择,可选高电平有效或低电平有效。它设定BDTR寄存器BKP位的值。

(7) TIM_AutomaticOutput: 自动输出使能,可选使能或禁止,它设定BDTR寄存器AOE位的值。

33.6. PWM互补输出实验

输出比较模式比较多,这里我们以PWM输出为例讲解,并通过示波器来观察波形。实验中不仅在主输出通道输出波形, 还在互补通道输出与主通道互补的的波形,并且添加了断路和死区功能。

33.6.1. 硬件设计

根据开发板引脚使用情况,并且参考表 33‑1中定时器引脚信息 ,使用TIM8的通道1及其互补通道作为本实验的波形输出通道, 对应选择PC6和PA5引脚。将示波器的两个输入通道分别与PC6和PA5引脚短接,用于观察波形,还有注意共地。

为增加断路功能,需要用到TIM8_BKIN引脚,这里选择PA6引脚。程序我们设置该引脚为低电平有效,所以先使用杜邦线将该引脚与开发板上3.3V短接。

另外,实验用到两个按键用于调节PWM的占空比大小,直接使用开发板上独立按键即可,电路参考独立按键相关章节。

33.6.2. 软件设计

这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考本章配套的工程。 我们创建了两个文件:bsp_advance_tim.c和bsp_advance_tim.h文件用来存定时器驱动程序及相关宏定义。

33.6.2.1. 编程要点

(1) 定时器 IO 配置

(2) 定时器时基结构体TIM_TimeBaseInitTypeDef配置

(3) 定时器输出比较结构体TIM_OCInitTypeDef配置

(4) 定时器断路和死区结构体TIM_BDTRInitTypeDef配置

33.6.2.2. 软件分析

宏定义

代码清单:高级定时器-5 宏定义
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 定时器 */
#define ADVANCE_TIM                   TIM8
#define ADVANCE_TIM_CLK               RCC_APB2Periph_TIM8

/* TIM8通道1输出引脚 */
#define ADVANCE_OCPWM_PIN             GPIO_Pin_6
#define ADVANCE_OCPWM_GPIO_PORT       GPIOC
#define ADVANCE_OCPWM_GPIO_CLK        RCC_AHB1Periph_GPIOC
#define ADVANCE_OCPWM_PINSOURCE       GPIO_PinSource6
#define ADVANCE_OCPWM_AF              GPIO_AF_TIM8

/* TIM8通道1互补输出引脚 */
#define ADVANCE_OCNPWM_PIN            GPIO_Pin_5
#define ADVANCE_OCNPWM_GPIO_PORT      GPIOA
#define ADVANCE_OCNPWM_GPIO_CLK       RCC_AHB1Periph_GPIOA
#define ADVANCE_OCNPWM_PINSOURCE      GPIO_PinSource5
#define ADVANCE_OCNPWM_AF             GPIO_AF_TIM8

/* TIM8断路输入引脚 */
#define ADVANCE_BKIN_PIN              GPIO_Pin_6
#define ADVANCE_BKIN_GPIO_PORT        GPIOA
#define ADVANCE_BKIN_GPIO_CLK         RCC_AHB1Periph_GPIOA
#define ADVANCE_BKIN_PINSOURCE        GPIO_PinSource6
#define ADVANCE_BKIN_AF               GPIO_AF_TIM8

使用宏定义非常方便程序升级、移植。如果使用不同的定时器IO,修改这些宏即可。

定时器复用功能引脚初始化

代码清单:高级定时器-6 定时器复用功能引脚初始化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static void TIMx_GPIO_Config(void)
{
    /*定义一个GPIO_InitTypeDef类型的结构体*/
    GPIO_InitTypeDef GPIO_InitStructure;

    /*开启定时器相关的GPIO外设时钟*/
    RCC_AHB1PeriphClockCmd (ADVANCE_OCPWM_GPIO_CLK  |
                            ADVANCE_OCNPWM_GPIO_CLK |
                            ADVANCE_BKIN_GPIO_CLK,
                            ENABLE);
    /* 指定引脚复用功能 */
    GPIO_PinAFConfig(ADVANCE_OCPWM_GPIO_PORT,
                    ADVANCE_OCPWM_PINSOURCE,
                    ADVANCE_OCPWM_AF);
    GPIO_PinAFConfig(ADVANCE_OCNPWM_GPIO_PORT,
                    ADVANCE_OCNPWM_PINSOURCE,
                    ADVANCE_OCNPWM_AF);
    GPIO_PinAFConfig(ADVANCE_BKIN_GPIO_PORT,
                    ADVANCE_BKIN_PINSOURCE,
                    ADVANCE_BKIN_AF);

    /* 定时器功能引脚初始化 */
    GPIO_InitStructure.GPIO_Pin = ADVANCE_OCPWM_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(ADVANCE_OCPWM_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = ADVANCE_OCNPWM_PIN;
    GPIO_Init(ADVANCE_OCNPWM_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = ADVANCE_BKIN_PIN;
    GPIO_Init(ADVANCE_BKIN_GPIO_PORT, &GPIO_InitStructure);
}

定时器通道引脚使用之前必须设定相关参数,这选择复用功能,并指定到对应的定时器。使用GPIO之前都必须开启相应端口时钟。

定时器模式配置

代码清单:高级定时器-7 定时器模式配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
static void TIM_Mode_Config(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;
    TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
    /* ================== 时基结构体初始化=================== */
    // 开启TIMx_CLK,x[1,8]
    RCC_APB2PeriphClockCmd(ADVANCE_TIM_CLK, ENABLE);

    // 累计 TIM_Period+1个计数后产生一个更新或者中断
    // 当定时器从0计数到1023,即为1024次,为一个定时周期
    TIM_TimeBaseStructure.TIM_Period = 1024-1;
    // 高级控制定时器时钟源TIMxCLK = HCLK=168MHz
    // 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=100KHz
    TIM_TimeBaseStructure.TIM_Prescaler = 1680-1;
    // 时钟分频,在计算死区时间的时候会用到
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    // 计数方式
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
    // 重复计数器,这里没使用
    TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
    // 初始化定时器TIMx, x[1,8]
    TIM_TimeBaseInit(ADVANCE_TIM, &TIM_TimeBaseStructure);

    /* ================== 输出结构体初始化=================== */
    // 配置为PWM模式1,先输出高电平,达到比较值的时候再改变电平
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    // 主输出使能
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    // 互补输出使能
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
    // 配置比较值
    TIM_OCInitStructure.TIM_Pulse = ChannelPulse;
    // 主输出高电平有效
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    // 互补输出高电平有效
    TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
    // 主输出在被禁止时为高电平
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
    // 互补输出在被禁止时为低电平
    TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
    // 通道初始化
    TIM_OC1Init(ADVANCE_TIM, &TIM_OCInitStructure);

    // 使能通道重装载
    TIM_OC1PreloadConfig(ADVANCE_TIM, TIM_OCPreload_Enable);

    /* ================ 断路和死区结构体初始化================ */
    // 自动输出使能,断路、死区时间和锁定配置
    TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
    TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
    TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
    // 配置死区时间
    TIM_BDTRInitStructure.TIM_DeadTime = 11;
    TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;
    // 配置刹车引脚电平,当引脚为配置的电平时,主和互补输出都被禁止
    TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_Low;
    TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
    TIM_BDTRConfig(ADVANCE_TIM, &TIM_BDTRInitStructure);

    // 使能定时器
    TIM_Cmd(ADVANCE_TIM, ENABLE);

    // 主动输出使能
    TIM_CtrlPWMOutputs(ADVANCE_TIM, ENABLE);
}

首先定义三个定时器初始化结构体,定时器模式配置函数主要就是对这三个结构体的成员进行初始化,然后通过相应的初始化函数把这些参数写入定时器的寄存器中。 有关结构体的成员介绍请参考定时器初始化结构体详解小节。

不同的定时器可能对应不同的APB总线,在使能定时器时钟是必须特别注意。高级控制定时器属于APB2,定时器内部时钟是168MHz。

在时基结构体中我们设置定时器周期参数为1024-1,频率为100KHz,使用向上计数方式。因为我们使用的是内部时钟, 所以外部时钟采样分频成员不需要设置,重复计数器我们没用到,也不需要设置。

在输出比较结构体中,设置输出模式为PWM1模式,主通道和互补通道输出均使能,且高电平有效, 设置脉宽为ChannelPulse,ChannelPulse是我们定义的一个无符号16位整形的全局变量,用来指定占空比大小,实际上脉宽就是设定比较寄存器CCR的值,用于跟计数器CNT的值比较。

断路和死区结构体中,使能断路功能,设定断路信号的有效极性,设定死区时间。有关死区时间的配置具体要参考断路和死区控制寄存器TIMx_BDTR的位DTG[7:0]的说明, 具体见图 BDTR寄存器DTG说明。在 代码清单:高级定时器-7 中我们配置死区时间为11,根据图 BDTR寄存器DTG说明 , 可知死区时间tdtg=DTG[7:0]*tDTS,其中tDTS的值与CR1寄存器的CKD[1:0]的取值有关, 该寄存器的位说明具体见图 CR1寄存器的时钟分频位说明 。在 代码清单:高级定时器-7 中的时基结构体中我们设置时钟分频因子为1分频, 即可得到tDTS = tCK_INT =1/168M,从而算的死区时间tdtg=DTG[7:0]*tDTS=11*(1/168M)=65ns。

BDTR寄存器DTG说明 CR1寄存器的时钟分频位说明

最后使能定时器让计数器开始计数和通道主输出。

主函数

代码清单:高级定时器-8 main函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include "stm32f4xx.h"
#include "./tim/bsp_advance_tim.h"
#include "./key/bsp_key.h"

extern __IO uint16_t ChannelPulse;
/**
* @brief  主函数
* @param  无
* @retval 无
*/
int main(void)
{
    /* 初始化按键GPIO */
    Key_GPIO_Config();

    /* 初始化高级控制定时器,设置PWM模式,使能通道1互补输出 */
    TIMx_Configuration();

    while (1) {
        /* 扫描KEY1 */
        if ( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON  ) {
            /* 增大占空比 */
            if (ChannelPulse<960)
                ChannelPulse+=64;
            else
                ChannelPulse=1024;
            TIM_SetCompare1(ADVANCE_TIM,ChannelPulse);
        }
        /* 扫描KEY2 */
        if ( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON  ) {
            /* 减小占空比 */
            if (ChannelPulse>=64)
                ChannelPulse-=64;
            else
                ChannelPulse=0;
            TIM_SetCompare1(ADVANCE_TIM,ChannelPulse);
        }
    }
}

首先,调用Key_GPIO_Config函数完成按键引脚初始化配置,该函数定义在bsp_key.c文件中。

接下来,调用TIMx_Configuration函数完成定时器参数配置,包括定时器复用引脚配置和定时器模式配置, 该函数定义在bsp_advance_tim.c文件中它实际上只是简单的调用TIMx_GPIO_Config函数和TIM_Mode_Config函数。 运行完该函数后通道引脚就已经有PWM波形输出,通过示波器可直观观察到。

最后,在无限循环函数中检测按键状态,如果是KEY1被按下,就增加ChannelPulse变量值,并调用TIM_SetCompare1函数完成增加占空比设置; 如果是KEY2被按下,就减小ChannelPulse变量值,并调用TIM_SetCompare1函数完成减少占空比设置。TIM_SetCompare1函数实际是设定TIMx_CCR1寄存器值。

33.6.3. 下载验证

根据实验的硬件设计内容接好示波器输入通道和开发板引脚连接,并把断路输入引脚拉高。编译实验程序并下载到开发板上,调整示波器到合适参数, 在示波器显示屏和看到一路互补的PWM波形,参考图 PWM互补波形输出示波器图 。此时,按下开发板上KEY1或KEY2可改变波形的占空比。

PWM互补波形输出示波器图

33.7. PWM输入捕获实验

实验中,我们用通用定时器产生已知频率和占空比的PWM信号,然后用高级定时器的PWM输入模式来测量这个已知的PWM信号的频率和占空比,通过两者的对比即可知道测量是否准确。

33.7.1. 硬件设计

实验中用到两个引脚,一个是通用定时器通道用于波形输出,另一个是高级控制定时器通道用于输入捕获,实验中直接使用一根杜邦线短接即可。

33.7.2. 软件设计

这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考本章配套的工程。 我们创建了两个文件:bsp_advance_tim.c和bsp_advance_tim.h文件用来存定时器驱动程序及相关宏定义。

33.7.2.1. 编程要点

(1) 通用定时器产生PWM配置

(2) 高级定时器PWM输入配置

(3) 计算测量的频率和占空比,并打印出来比较

33.7.2.2. 通用定时器PWM信号输出软件分析

通用定时器宏定义

代码清单:高级定时器-9 通用定时器宏定义
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/* *********************通用定时器PWM输出**************************** */
/* PWM输出引脚 */
#define GENERAL_OCPWM_PIN             GPIO_Pin_5
#define GENERAL_OCPWM_GPIO_PORT       GPIOA
#define GENERAL_OCPWM_GPIO_CLK        RCC_AHB1Periph_GPIOA
#define GENERAL_OCPWM_PINSOURCE       GPIO_PinSource5
#define GENERAL_OCPWM_AF              GPIO_AF_TIM2

/* 通用定时器 */
#define GENERAL_TIM                   TIM2
#define GENERAL_TIM_CLK               RCC_APB1Periph_TIM2

// 输出PWM的频率为 84M/{ (ARR+1)*(PSC+1) }
// 这里配置为100KHZ
#define            GENERAL_TIM_PERIOD            (10-1)
#define            GENERAL_TIM_PSC               (84-1)
#define            GENERAL_TIM_CCR1              5

使用宏定义非常方便程序升级、移植。通过上面的宏,我们可以算出PWM信号的频率F为:84M/( 10*84 )=100KHZ, 占空比为GENERAL_TIM_CCR1/ (GENERAL_TIM_PERIOD+1)= 50%。要特别注意的是,我们这里使用PA5作为PWM信号的输出引脚, 而PA5默认又通过一个调帽连接到了电容按键上面,具体见图 PA5与电容按键连接电路 。所以做这个实验的时候,要把PA5与电容按键连接的调帽先拔掉。

PA5与电容按键连接电路

通用定时器引脚和PWM输出初始化

代码清单:高级定时器-10 通用定时器引脚和PWM输出初始化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
void TIM_PWMOUTPUT_Config(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    /*--------------------GPIO结构体初始化-------------------------*/
    // 开始GPIO端口时钟
    RCC_AHB1PeriphClockCmd (GENERAL_OCPWM_GPIO_CLK, ENABLE);

    // 通用定时器复用引脚

    GPIO_PinAFConfig(GENERAL_OCPWM_GPIO_PORT,GENERAL_OCPWM_PINSOURCE,GENERAL_OCPWM_AF);

    // 通用定时器PWM输出引脚
    GPIO_InitStructure.GPIO_Pin = GENERAL_OCPWM_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GENERAL_OCPWM_GPIO_PORT, &GPIO_InitStructure);

    /*--------------------时基结构体初始化-------------------------*/
    // 配置周期,这里配置为100K

    // 开启TIMx_CLK
    // 通用定时器时钟源TIMxCLK = 2*PCLK1=84MHz
    RCC_APB1PeriphClockCmd(GENERAL_TIM_CLK, ENABLE);

    // 累计 TIM_Period个后产生一个更新或者中断*/
    TIM_TimeBaseStructure.TIM_Period = GENERAL_TIM_PERIOD;
    // 驱动CNT计数器的时钟 = TIMxCLK/(psc+1)
    TIM_TimeBaseStructure.TIM_Prescaler = GENERAL_TIM_PSC;
    // 采样时钟分频
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    // 计数方式
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
    // 初始化定时器TIMx
    TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);

    /*--------------------输出比较结构体初始化-------------------*/
    // 配置为PWM模式1
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    // 输出使能
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    // 输出通道电平极性配置
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    // 输出比较值配置
    TIM_OCInitStructure.TIM_Pulse = GENERAL_TIM_CCR1;
    // 使能通道
    TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
    // 使能通道重载
    TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);

    // 使能定时器
    TIM_Cmd(GENERAL_TIM, ENABLE);
}

TIM_PWMOUTPUT_Config函数中初始化了PWM输出需要用到的GPIO,还初始化了两个跟PWM输出相关的结构体, 有关这两个结构体成员的具体含义可参考“定时器初始化结构体详解”小节,剩下的程序参考注释阅读即可。

如果需要修改PWM的周期和占空比,修改头文件里面的GENERAL_TIM_PERIOD、GENERAL_TIM_PSC和GENERAL_TIM_CCR1这三个宏即可。 PWM信号的频率的计算公司为:F = TIM_CLK/{(ARR+1)*(PSC+1)},其中TIM_CLK等于84MHZ,ARR即自动重装载寄存器的值, 对应GENERAL_TIM_PERIOD这个宏,PSC即计数器时钟的分频因子,对应GENERAL_TIM_PSC这个宏。

33.7.2.3. 高级定时器PWM输入捕获软件分析

高级定时器宏定义

代码清单:高级定时器-11高级定时器宏定义
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* *******************高级控制定时器PWM输入捕获*********************** */
/* PWM输入捕获引脚 */
#define ADVANCE_ICPWM_PIN             GPIO_Pin_6
#define ADVANCE_ICPWM_GPIO_PORT       GPIOC
#define ADVANCE_ICPWM_GPIO_CLK        RCC_AHB1Periph_GPIOC
#define ADVANCE_ICPWM_PINSOURCE       GPIO_PinSource6
#define ADVANCE_ICPWM_AF              GPIO_AF_TIM8
#define ADVANCE_IC1PWM_CHANNEL        TIM_Channel_1


/* 高级控制定时器 */
#define ADVANCE_TIM                   TIM8
#define ADVANCE_TIM_CLK               RCC_APB2Periph_TIM8

/* 捕获/比较中断 */
#define ADVANCE_TIM_IRQn              TIM8_CC_IRQn
#define ADVANCE_TIM_IRQHandler        TIM8_CC_IRQHandler


// 输入捕获能捕获到的最小的频率为 168M/{ (ARR+1)*(PSC+1) }
// 这里配置为1KHZ
#define            ADVANCE_TIM_PERIOD            (1000-1)
#define            ADVANCE_TIM_PSC               (168-1)

在上面的宏定义里面,我们可以算出计数器的计数周期为T=(1000*168)/168M=1MS,这个是定时器在不溢出的情况下的最大计数周期, 也就是说周期小于1ms的PWM信号都可以被捕获到,转换成频率就是能捕获到的最小的频率为1KHZ。所以我们要根据捕获的PWM信号来调节ADVANCE_TIM_PER IOD和ADVANCE_TIM_PSC这两个宏。

高级定时器引脚和PWM输入初始化

代码清单:高级定时器-12级定时器引脚和PWM输入初始化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
void TIM_PWMINPUT_Config(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_ICInitTypeDef  TIM_ICInitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    /*--------------------GPIO结构体初始化-------------------------*/
    // 开始GPIO端口时钟
    RCC_AHB1PeriphClockCmd (ADVANCE_ICPWM_GPIO_CLK, ENABLE);

    // 定时器复用引脚

    GPIO_PinAFConfig(ADVANCE_ICPWM_GPIO_PORT,ADVANCE_ICPWM_PINSOURCE,ADVANCE_ICPWM_AF);

    // 高级控制定时器PWM输入捕获引脚
    GPIO_InitStructure.GPIO_Pin = ADVANCE_ICPWM_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(ADVANCE_ICPWM_GPIO_PORT, &GPIO_InitStructure);

    // 开启TIMx_CLK
    // 高级定时器时钟源TIMxCLK = 2*PCLK2=168MHz
    RCC_APB2PeriphClockCmd(ADVANCE_TIM_CLK, ENABLE);

    /*--------------------时基结构体初始化----------------------*/
    // 累计 TIM_Period个后产生一个更新或者中断*/
    TIM_TimeBaseStructure.TIM_Period = ADVANCE_TIM_PERIOD;
    // 驱动CNT计数器的时钟 = TIMxCLK/(psc+1)
    TIM_TimeBaseStructure.TIM_Prescaler = ADVANCE_TIM_PSC;
    // 时钟分频因子 ,配置死区时间时需要用到
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    // 计数方式
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
    // 重复计数器的值,没用到不用管
    TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
    // 初始化定时器
    TIM_TimeBaseInit(ADVANCE_TIM, &TIM_TimeBaseStructure);

    /*--------------------输入捕获结构体初始化-------------------*/
    // 使用PWM输入模式时,需要占用两个捕获寄存器,一个测周期,另外一个测占空比
    // 捕获通道IC1配置
    // 选择捕获通道
    TIM_ICInitStructure.TIM_Channel = ADVANCE_IC1PWM_CHANNEL;
    // 设置捕获的边沿
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    // 设置捕获通道的信号来自于哪个输入通道,有直连和非直连两种
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    // 1分频,即捕获信号的每个有效边沿都捕获
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    // 不滤波
    TIM_ICInitStructure.TIM_ICFilter = 0x0;
    // 初始化PWM输入模式
    TIM_PWMIConfig(ADVANCE_TIM, &TIM_ICInitStructure);

    // 当工作做PWM输入模式时,只需要设置触发信号的那一路即可(用于测量周期)
    // 另外一路(用于测量占空比)会由硬件自带设置,不需要再配置

    // 选择输入捕获的触发信号
    TIM_SelectInputTrigger(ADVANCE_TIM, TIM_TS_TI1FP1);

    // 选择从模式: 复位模式
    // PWM输入模式时,从模式必须工作在复位模式,当捕获开始时,计数器CNT会被复位
    TIM_SelectSlaveMode(ADVANCE_TIM, TIM_SlaveMode_Reset);
    TIM_SelectMasterSlaveMode(ADVANCE_TIM,TIM_MasterSlaveMode_Enable);

    // 使能捕获中断,这个中断针对的是主捕获通道(测量周期那个)
    TIM_ITConfig(ADVANCE_TIM, TIM_IT_CC1, ENABLE);
    TIM_ClearITPendingBit(ADVANCE_TIM, TIM_IT_CC1);

    // 使能高级控制定时器,计数器开始计数
    TIM_Cmd(ADVANCE_TIM, ENABLE);
}

TIM_PWMINPUT_Config ()函数中初始化了PWM输入捕获需要用到的GPIO,还初始化了两个跟PWM输入捕获相关的结构体两个结构体, 有关这两个结构体成员的具体含义可参考“定时器初始化结构体详解”小节,剩下的程序参考注释阅读即可。

因为是PWM输入模式,只能使用通道1和通道2 ,假如我们使用的是通道1,即TI1,输入的PWM信号会被分成两路,分别是TI1FP1和TI1FP2, 两路都可以是触发信号。如果选择TI1FP1为触发信号,那么IC1捕获到的是PWM信号的周期, IC2捕获到的是占空比,这种输入通道TI和捕获通道IC的映射关系叫直连, 输入捕获结构体的TIM_ICSelection要配置为TIM_ICSelection_DirectTI。如果选择TI1FP2为触发信号,则IC2捕获到的是周期,IC1捕获到的是占空比, 这种输入通道TI和捕获通道IC的映射关系叫非直连,输入捕获结构体的TIM_ICSelection要配置为TIM_ICSelection_IndirectTI。 有关输入通道TI和捕获通道IC的具体映射关系见图 输入通道与捕获通道IC的映射图 ,有直连和非直连两种。

输入通道与捕获通道IC的映射图

高级定时器中断服务函数

代码清单:高级定时器-13 NVIC配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
static void TIMx_NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    // 设置中断组为0
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
    // 设置中断来源
    NVIC_InitStructure.NVIC_IRQChannel = ADVANCE_TIM_IRQn;
    // 设置抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    // 设置子优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

实验用到高级控制定时器捕获/比较中断,需要配置中断优先级,因为实验只用到一个中断,所以这里对优先级配置没具体要求,只要符合中断组参数要求即可。

高级控制定时器中断服务函数

代码清单:高级定时器-14 高级控制定时器中断服务函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void  ADVANCE_TIM_IRQHandler (void)
{
    /* 清除定时器捕获/比较1中断 */
    TIM_ClearITPendingBit(ADVANCE_TIM, TIM_IT_CC1);

    /* 获取输入捕获值 */
    IC1Value = TIM_GetCapture1(ADVANCE_TIM);
    IC2Value = TIM_GetCapture2(ADVANCE_TIM);
    //printf("IC1Value = %d  IC2Value = %d ",IC1Value,IC2Value);

    // 注意:捕获寄存器CCR1和CCR2的值在计算占空比和频率的时候必须加1
    if (IC1Value != 0) {
        /* 占空比计算 */
        DutyCycle = (float)((IC2Value+1) * 100) / (IC1Value+1);

        /* 频率计算 */
        Frequency = 168000000/(ADVANCE_TIM_PSC+1)/(float)(IC1Value+1);
        printf("占空比:%0.2f%%   频率:%0.2fHz\n",DutyCycle,Frequency);
    } else {
        DutyCycle = 0;
        Frequency = 0;
    }
}

当捕获到PWM信号的第一个上升沿时,产生中断,计数器被复位,锁存到捕获寄存器IC1和IC2的值都为0。当下降沿到来时,IC2会捕获,对应的是占空比, 但是会产生中断。当捕获到第二个上升沿时,IC1会捕获,对应的是周期,而且会再次进入中断,这个时间就可以根据IC1和IC2的值计算出频率和占空比。 有关PWM输入的时序见图 PWM输入模式时序图

中断复位函数中,我们获取输入捕获寄存器CCR1和CCR2寄存器中的值,当CCR1的值不为0时,说明有效捕获到了一个周期,然后计算出频率和占空比。 在计算的时候CCR1和CCR2的值都必须要加1,因为计数器是从0开始计数的。

PWM输入模式时序图

主函数

代码清单:高级定时器-15 main函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "stm32f4xx.h"
#include "./tim/bsp_advance_tim.h"
#include "./usart/bsp_debug_usart.h"

/**
* @brief  主函数
* @param  无
* @retval 无
*/
int main(void)
{
    Debug_USART_Config();
    printf("\r\n这是一个定时器输入捕获例程\r\n");

    /* 通用定时器初始化,用于生成PWM信号 */
    TIM_PWMINPUT_Config();

    /* 高级定时器中断优先级配置 */
    TIMx_NVIC_Config();
    /* 高级定时器初始化 ,用户捕获PWM信号*/
    TIM_PWMOUTPUT_Config();


    while (1) {
    }
}

main函数非常简单,通用定时器初始化完之后用于输出PWM信号,高级定时器初始化完之后用于捕获通用定时器输出的PWM信号。

33.7.3. 下载验证

把编译好的程序烧写到开发板,用杜邦线把通用定时器的PWM输出引脚(PA5)连接到高级定时器的PWM输入引脚(PC6)。然后用USB线连接电脑与开发板的USB转串口, 打开串口调试助手,即可看到捕获到的PWM信号的频率和占空比,具体见图 串口调试助手打印的捕获信息 。 于此同时,可用示波器监控通用定时器输出的PWM信号,看下捕获到的信号是否正确,具体见图 示波器监控的波形

串口调试助手打印的捕获信息 示波器监控的波形

从图 示波器监控的波形 和图 示波器监控的波形 我们可以看到,程序捕获计算出的频率和占空比和示波器监控到的波形的频率和占空比跟一致,所以我们的程序是正确的。