1.1通用定时器介绍
通用定时器具有基本定时器的所有特征,基本定时器只能递增计数,而通用定时器可以递减计数,可以中心对齐计数;也可以触发ADC和DAC,同时在更新事件,触发事件,输入捕获,输出比较的时候可以产生中断和DMA请求,而基本定时器只有更新事件的时候可以触发中断或DMA请求,通用定时器还有独立通道用来和外部进行信息交互,比如输入捕获,输出比较,输出PWM和单脉冲模式,这就有个疑问了,之前基本定时器就看到配置寄存器里有单脉冲模式,但是原子的教程说这个是通用定时器特有,有空可以做个实验验证一下基本定时器有没有单脉冲模式。通用定时器可以使用外部信号控制定时器,可以实现多个定时器互连的同步电路,应该就是主从模式了。也支持编码器和霍尔传感器电路等,这些东西是用在电机控制中的。现在很多概念都不懂,随着学习的深入慢慢理解吧。
1.2 通用定时器框图
通用定时器的框图比基本定时器多了很多东西,因为它包含了基本定时器所有的功能,所以可以先从框图里找到基本定时器的结构,剩下的就是通用定时器对它的扩展,下面对框图大致讲解一下,后面会详细说明。
①时钟源
基本定时器的时钟只能来自内部时钟,也就是APB总线上时钟经过倍频器之后得到的时钟,而通用定时器的时钟可以有4个来源,1:来自APB总线的时钟;2:内部触发输入的时钟,就是①中的ITR0到ITR3的部分,这个内部信号就是其他的定时器;3:外部时钟模式1,右下角的TI1F_ED,TI1FP1和TI2FP2,这个来自下面第④部分的输入滤波器和边沿检测器,最初是来自外部IO口的输入,只有外部通道TIMx_CH1和TIMx_CH2的输入信号可以作为时钟源;4:图中TIMx_ETR的那里,来自外部IO口,称为外部时钟模式2。
②控制器
TRGO触发信号可以连接到其余定时器的ITR0到ITR3,作为别的定时器的时钟源,也就是完成定时器的级联;从模式和基本定时器没什么区别。
控制器包括:从模式控制器、编码器接口和触发控制器(TRGO)。从模式控制器可以控制计数器复位、启动、递增/递减、计数。编码器接口针对编码器计数。触发控制器用来提供触发信号给别的外设,可以连接到其余定时器的ITR0到ITR3,作为别的定时器的时钟源,也就是完成定时器的级联,也可以为 DAC/ADC 的触发转换提供信号。
③时基单元
这个就和基本定时器一样,参考STM32学习之基本定时器。
④输入捕获
以TIMx_CH1为例,信号从外部输入后先经过一个异或门(常用于电机控制领域,这里先忽略),然后进入输入滤波器和边沿检测器,滤波器可以过滤掉一些高频噪声,然后得到两个信号,TI1FP1和TI2FP2,这两个信号可以由我们用户自己选择映射到IC1还是IC2,不能同时使用,一般就是CH1映射到IC1,然后经过一个预分频器之后产生一个捕获事件U,捕获事件就是捕获的上升沿还是下降沿,产生捕获事件之后就会把计数器的值转移到捕获比较1寄存器里面,来一次事件记一个数,这样就可以根据配置去计算外部信号的时间,比如周期和脉宽之类的。除了捕获事件U以外还可以通过配置来产生一个CC1I中断。
⑤输入比较
这部分是连接④和⑤的桥梁,结合另外两部分看就可以了。
⑥输出比较
将计数器的值与预先存入捕获/比较1寄存器的影子寄存器进行比较,如果二者相等,则会使输出参考信号进行改变,这个信号为固定高电平有效,同时还会产生一个比较事件CC1I, 如果同时开启了中断的话还会开启比较中断,输出参考信号会进入输出控制来控制产生一些信号,比如PWM,然后通过TIMx_CH1输出。图片的最左边和最右边属于分时复用,同一时间根据IO扣得配置模式只能有一个起作用。
现在对上面的框图详细解释一下。
1.2.1. 通用定时器时钟源
时钟模式 | 来源 | 设置方法 |
---|---|---|
内部时钟(CK_INT) | APB总线 | 设置 TIMx_SMCR 的 SMS=0000 |
外部时钟模式1 | 外部输入引脚TIx | 设置 TIMx_SMCR 的 SMS=1111 |
外部时钟模式 2 | 外部触发输入ETR | 设置 TIMx_SMCR 的 ECE=1 |
内部触发输入模式 | 内部触发输入ITRx | 参考《STM32F10xxx参考手册_V10(中文版).pdf》 14.3.15 小节 |
- 时钟源选择内部时钟CK_INT的时候和基本定时器一样,都是由APB总线经过倍频器之后得到的。
- 外部时钟模式1的时钟源是来自定时器通道1或者通道2的引脚信号输入,外部时钟源信号→IO→TIMx_CH1(或者 TIMx_CH2),只有CH1和CH2可以,CH3和CH4是不行的,这时候就需要配置IO的复用模式为定时器,这时候信号从IO口进来才能和定时器通道相连。
如上图所示,时钟源信号经过CH2以后会先经过一个滤波器,滤波器这里把这个信号称为TI2,由TIMx_CCMR1的寄存器 ICF[3:0]位来设置滤波方式,然后经过边沿检测器,由 TIMx_CCER寄存器的CC2P 位来设置信号检测是上升沿还是下降沿。然后经过触发输入选择器,由TIMx_SMCR寄存器的TS[2:0]位来选择 TRGI(触发输入信号)的来源。可以看到图中框出了 TI1F_ED(图片中漏了字母F)、 TI1FP1 和 TI2FP2 三个触发输入信号(TRGI)。 TI1F_ED 表示来自于 CH1,并且没有经过边沿检测器过滤的信号,所以它是 CH1 的双边沿信号,即上升沿或者
下降沿都是有效的。 TI1FP1 表示来自 CH1 并经过边沿检测器后的信号,可以是上升沿或者下降沿。 TI2FP2 表示来自 CH2 并经过边沿检测器后的信号,可以是上升沿或者下降沿。这里以CH2 为例,那只能选择 TI2FP2。如果是 CH1 为例,那就可以选择 TI1F_ED 或者TI1FP1。最后经过从模式选择器,从模式是上升沿触发的,所以TIMx_CCER的CC2P 位如果配置成采集下降沿,那么检测器就会对信号进行反相操作,以保证传到从模式选择器的信号是上升沿,由TIMx_SMCR寄存器的ECE位和SMS[2:0]位来选择定时器的时钟源。这里介绍的是外部时钟模式 1,所以 ECE 位置 0, SMS[2:0] = 111 即可。 CK_PSC 经过定时器的预分频器分频后就能到达计数器进行计数了。 - 外部时钟模式 2来自于外部ETR引脚,ETR引脚指的是可以复用为TIMx_ETR的IO引脚,把IO口重定义到TIMx_ETR。
信号从外部IO口进入以后会经过外部触发极性选择器,由 TIMx_SMCR寄存器的ETP 位来设置上升沿有效还是下降沿有效,选择下降沿有效的话,信号会经过反相器,作用和外部时钟模式1的CC2P一样。然后会经过外部触发分频器,分频系数由TIMx_SMCR寄存器的ETP[1:0]来设置,然后经过滤波器,由TIMx_SMCR寄存器的 ETF[3:0]位来设置滤波方式。其中滤波器的采样频率 f D T S f_{DTS} fDTS由TIMx_CR1寄存器的CKD[1:0]位来决定。滤波原理后面再说。最后经过从模式选择器,这里和外部时钟模式1一样,由 TIMx_SMCR寄存器的ECE 位和 SMS[2:0]位来选择定时器的时钟源。这里采用的是外部时钟模式 2,直接把 ECE 位置 1 即可。 CK_PSC 经过定时器的预分频器分频后就能到达计数器进行计数了。 - 内部触发输入(ITRx)用于和内部的其他通用/高级定时器进行级联,级联的本质是定时器1作为定时器2的预分频器。
定时器1的主模式需要把TIMx_CR2寄存器的MMS[2:0]位设置为010,作用是更新事件被选为触发输出(中文手册的这里写的是错误的),定时器2TIMx_SMCR寄存器的TS[2:0]位用来选择 TRGI(触发输入信号)的来源,设置为000,这样就将定时器1和2连接起来了。然后定时器2的TIMx_SMCR寄存器的SMS[2:0]位来进行从模式选择,设置为111,选择外部时钟模式1。最后启动两个定时器就可以了。
1.2.2. 通用定时器输入捕获
输入捕获是用来采集外部信号的上升沿和下降沿的,一个常见的应用就是电机转动的时候采集霍尔数,是一系列的脉冲信号,本质上就是一个pwm波,霍尔数就是指其中的上升沿和下降沿的数量,一种方法就是采用输入捕获,然后在上升沿和下降沿的时候触发中断,在中断函数内对计数变量加1就行了。STM32的输入捕获主要用到上面通用定时器框图的④和⑤部分,TIMx_CH1~TIMx_CH4 表示定时器的 4 个通道,这 4 个通道都是独立工作的。通过IO端口复用功能将IO口与这些计数器通道相连。配置好 IO 端口的复用功能后,将需要测量的信号输入到相应的IO 端口,输入捕获部分就可以对输入的信号的上升沿,下降沿或者双边沿进行捕获了,常见的测量有:测量输入信号的脉冲宽度、测量 PWM 输入信号的频率和占空比等。本质上是通过计数器采集时间的,所以喊空比和频率都是后期算出来的,原始的读出来的数据一般都是脉宽和周期,然后根据这俩去计算其他的信息。
在测量脉宽的时候一般要先设置输入捕获的边沿检测极性,如:我们设置上升沿检测,那么当检测到上升沿时,定时器会把计数器 CNT的值锁存到相应的捕获/比较寄存器 TIMx_CCRy 里, y=1~4。然后我们再设置边沿检测为下降沿检测,当检测到下降沿时,定时器会把计数器 CNT 的值再次锁存到相应的捕获/比较寄存器TIMx_CCRy 里。最后,我们将前后两次锁存的 CNT 的值相减,就可以算出高电平脉冲期间内
计数器的计数个数,再根据定时器的计数频率就可以计算出这个高电平脉冲的持续时间。如果要测量的高电平脉宽时间长度超过定时器的溢出时间周期,就会发生溢出,这时候还需要做定时器溢出的额外处理。以通道1来简单介绍一下:
外部信号TI1通过IO口进入TIMx_CH1后,先经过一个滤波器,滤波器的配置由TIMx_CCMR1决定,接着经过边沿检测器,由 CC1P 位来设置检测的边沿,可以上升沿或者下降沿检测。 CC1NP是配置互补通道的边沿检测的,只有在高级定时器才有,通用定时器没有。然后经过输入捕获映射选择器,由 CC1S[1:0]位来选择把 IC1 映射到TI1FP1、TI2FP1还是 TRC。这里我们的待测量信号从通道1进来,所以选择IC1 映射到TI1FP1。紧接着经过输入捕获 1 预分频器,由 ICPS[1:0]位来设置预分频系数,范围: 1、 2、 4、 8。最后把CC1E位置1,使能输入捕获, IC1PS 就是分频后的捕获信号。这个信号将会到达通用定时器框图的第⑤部分,下图是第⑤部分的放大版。
上图的灰色阴影部分是输出比较功能部分,和输入捕获无关。左边没有阴影部分是输入捕获功能部分。首先看到捕获/比较预装载寄存器,还是以通道1为例,它就是CCR1寄存器,通道 2、通道 3、通道 4 就是CCR2、CCR3、CCR4。在最开始通用定时器框图中可以看到 CCR1~4 是有影子寄存器的,所以这里就可以看到上图中有捕获/比较影子寄存器,影子寄存器都是不可直接访问的。上图左下角TIMx_EGR寄存器的CC1G位可以产生软件捕获事件,和基本定时器的更新事件一样,就是软件触发一个更新事件来将预装载寄存器的值传递到对应的影子寄存器中,那么硬件捕获事件如何产生的?这里我们还是以通道 1 输入为例,CC1S[1:0] = 01,即IC1映射到TI1上;CC1E位置1,使能输入捕获;比如不滤波、不分频, ICF[3:0] = 00, ICPS[1:0] = 00;比如检测上升沿, CC1P 位置 0;接着就是等待测量信号的上升沿到来。当上升沿到来时, IC1PS 信号就会触发输入捕获事件发生,计数器的值就会被锁存到捕获/比较影子寄存器里。当 CCR1 寄存器没有被进行读操作的时候,捕获/比较影子寄存器里的值就会锁存到 CCR1 寄存器中,那么程序员就可以读取 CCR1 寄存器,得到计数器的计数值。检测下降沿同理。
1.2.3. 通用定时器输出比较
输出比较是用来向外部发出信号的,一般是周期性的发出高低电平,一个常见的应用就是发出PWM波来控制MOS管来进一步控制电机的转速和车灯的亮度等。STM32的输出比较主要用到通用定时器框图的⑤和⑥部分,和输入捕获一样,TIMx_CH1~TIMx_CH4 表示定时器的4个通道,这 4 个通道都是独立工作的。通过IO端口复用功能将IO口与这些计数器通道相连。下图是通用定时器框图的第⑤部分的放大版:
灰色阴影部分是输入捕获功能部分,前面已经说过了。右 边没有阴影部分就是输出比较功能部分。下面以通道 1 输出比较功能为例来看定时器如何实现输出功能的。首先向CCR1 寄存器内写入比较值。这个比较值需要转移到对应的捕获/比较影子寄存器后才会真正生效。当compare_transfer 旁边与门的三个条件都满足的时候才会进行数据转移:1. CCR1 不在写入操作期间、 2. CC1S[1:0] = 0 配置为输出、 3. OC1PE位置0(或者OC1PE 位置1,并且需要发生更新事件,这个更新事件可以软件产生或者硬件产生)。当 CCR1 寄存器的值转移到其影子寄存器后,新的值就会和计数器的值进行比较,它们的比较结果将会通过第⑥部分影响定时器的输出。下面看第⑥部分:
可以看到输出模式控制器,由OC1M[2:0]位配置输出比较模式,该位的描述需要看参考手册。oc1ref 是输出参考信号,高电平有效,为高电平时称之为有效电平,为低电平时称之为无效电平。它的高低电平受到三个方面的影响:1. OC1M[3:0]位配置的输出比较模式,2. 第⑤部分比较器的比较结果,3. OC1CE位配置的ETRF信号,ETRF信号就是通用定时器框图第⑤部分最下面和第①部分上面的那个信号。 ETRF 信号可以将 Oc1ref 电平强制清零,该信号来自 IO 外部。
一般来说,当计数器的值和捕获/比较寄存器的值相等时,输出参考信号 oc1ref 的极性就会根据我们选择的输出比较模式而改变。如果开启了比较中断,还会触发比较中断。CC1P 位用于选择通道输出极性,配置高电平就输出高电平,配置低电平就取反后输出。CC1E 位置 1 使能通道输出。OC1 信号就会从 TIMx_CH1 输出到 IO 端口,再到 IO 外部。
1.3. 通用定时器寄存器学习
1.3.1. 控制寄存器 1(TIMx_CR1)
寄存器低十位有效。
- 位9:8,CKD[1:0]:设置滤波器的采样频率与定时器时钟之间的关系;
- 位7,ARPE:设置自动预装载寄存器是否具有缓存功能;
- 位6:5,CMS[1:0]:设置计数器的计数的对齐模式,边沿对齐还是中央对齐,以及中央对齐的时候触发中断的方式;
- 位4,DIR:计数器的计数方向,递增计数还是递减计数;
- 位3,OPM:单脉冲模式,和基础计数器一样,计数器溢出一次之后就不计数了;
- 位2,URS:更新事件的触发源选择位,通过配置该位来选择计数器更新事件的触发源;
- 位1,UDIS:与基本定时器一样,更新事件的使能位,如果置1,那么影子寄存器的值就不会改变,如果TIMx_EGR寄存器的UG位被置1或者从模式控制器产生了硬件复位,那么整个定时器都会被初始化,包括影子寄存器的内容都会被清除。
- 位0,CEN:计数器的使能位,置1则使能计数器。
1.3.2. 控制寄存器2(TIMx_CR2)
寄存器7:3位有效。
- 位7,TI1S:选择TI1信号的输入通道;
- 位6:4,MMS[2:0]:配置当前计数器在主模式下送到从定时器的同步信息(TRGO);
- 位3,CCDS:置0时DMA请求源是预装载寄存器(TIMx_CCRx)比如发生计数器溢出的时候,置1时计数器的DMA请求源是实际的捕获/比较寄存器,比如计数器的值与捕获/比较寄存器相等的时候。
1.3.3. 从模式控制寄存器(TIMx_SMCR)
这个寄存器除了第3位保留,其余所有位都有意义。
- 位15, ETP:用来选择在外部时钟模式 2的时候外部输入的时钟源的触发极性,置1反相,低电平有效,置0不反相,高电平有效;
- 位14,ECE:外部时钟模式2的使能位,置0禁止外部时钟模式2,置1使能外部时钟模式2;
- 位13:12,ETPS[1:0]:外部触发信号ETRP的预分频配置寄存器,ETRP的频率最多是内部时钟CK_INT的1/4,如果频率过高的话可以配置这个寄存器来降低ETRP的频率;
- 位11:8,ETF[3:0]:这个寄存器对外部触发信号ETRP进行滤波,就像手册里说的那样,它的本质上是一个事件计数器,比如配置成:“0001:采样频率 f S A M P L I N G = f C K _ I N T f_{SAMPLING}=f_{CK\_INT} fSAMPLING=fCK_INT, N=2”,它的意思是说这个寄存器以 f S A M P L I N G f_{SAMPLING} fSAMPLING的频率对外部信号进行采样,采样频率和内部时钟频率相同,如果是别的情况,由TIMx_CR1寄存器的CKD位决定,然后后面的数字N表示只有采满N个数字才认为是有效的电平变化,比如采集的第一个数据是低电平,第二个是高电平,那就不认为这个高电平是有效的,还是记录这个数据为低电平,只有第二个数据也是低电平,第三个数据变成高电平的时候就认为它是有效的高电平,这时候会记录为高电平,如果N等于其他值时候也是同理,比如N=6,那就是必须采够6个数据才会认为是有效的电平变化,所以高频变化的信号就会被过滤掉;
- 位7,MSM:主模式选择位,置1,当前计数器工作在主模式,定时器可以为其他定时器或外设提供同步信号;置0,当前计数器工作在从模式,定时器的启动,停止复位等由其他的定时器控制。
- 位6:4,TS[2:0]:选择定时器触发输入TRGI的触发源;
- 位2:0,SMS[2:0]:从模式选择位,对从模式进行选择。
1.3.4. DMA/中断使能寄存器(TIMx_DIER)
这个寄存器的各个位的作用是用来使能和禁止一些中断和DMA请求的,没什么好说的。
1.3.5. 状态寄存器(TIMx_SR)
这个寄存器工是个位有效,记录捕获比较的一些标志位。
- 位12:CC4OF:捕获/比较4重复捕获标记,意思就是说输入捕获通道 4 中是已经存储了捕获的值,但是又发生了一次捕获事件,而新的捕获值无法及时存储,导致了数据丢失,这时候会被硬件置1,可以通过软件写入0来清除该位;
- 位11:CC3OF:捕获/比较3重复捕获标记;
- 位10:CC2OF:捕获/比较2重复捕获标记;
- 位9:CC1OF:捕获/比较1重复捕获标记;
- 位6:TIF:触发器中断标记,当从模式控制器处于除门控模式外的其它模式时,在TRGI输入端检测到有效边沿,或门控模式下的任一边沿时由硬件置1,通过软件置1清0;
- 位4:CC4IF:捕获/比较4 中断标记,配置为输入模式的时候,捕获到配置的触发源的时候已经将计数器的值拷贝到捕获/比较寄存器(TIMx_CCR1),这时候会对这位置1;配置为输出模式的时候,TIMx_CNT的值与TIMx_CCR1的值匹配的时候该位置1;
- 位3:CC3IF:捕获/比较4 中断标记;
- 位2:CC2IF:捕获/比较4 中断标记;
- 位1:CC1IF:捕获/比较4 中断标记;
- 位0:UIF:更新中断标记,当产生更新事件时(计数器溢出或软件触发更新事件)硬件置1,由软件清0。
1.3.6. 事件产生寄存器(TIMx_EGR)
这个寄存器6个位有效。
- 位6,TG:该位置1之后产生一个触发事件,同时会将TIMx_SR寄存器的TIF位置1,硬件自动清零;
- 位4,CC4G:产生捕获/比较4事件,在通道CC4上产生一个捕获/比较事件:若通道CC4配置为输出,则设置CC4IF=1,若开启了对应的中断和DMA,则产生相应的中断和DMA,若通道CC1配置为输入,则将当前的计数器值捕获至TIMx_CCR4寄存器并设置CC4IF=1,若开启对应的中断和DMA,则产生相应的中断和DMA。若CC4IF已经为1,则设置CC4OF=1;
- 位3,CC3G:产生捕获/比较3事件;
- 位2,CC2G:产生捕获/比较2事件;
- 位1,CC1G:产生捕获/比较1事件;
- 位0,UG:产生更新事件,软件产生一个更新事件。
1.3.7. 捕获/比较模式寄存器1(TIMx_CCMR1)
这个寄存器的16位均有效,高八位和低八位功能相同,只是针对不同的器通道:
输出比较模式:
- 位15,OC2CE:输出比较2清0使能,如果该位置1,那么当检测到ETRF输入高电平时就将OC2REF置0;
- 位14:12,OC2M[2:0]:选择输出比较2的模式,选择输出比较的模式,具体的可以看参考手册;
- 位11,OC2PE:输出比较2预装载使能,可以设置TIMx_CCR1寄存器是否具有缓存功能;
- 位10,OC2FE:输出比较2快速使能位,该位用于加快CC输出对触发器输入事件的响应,正常情况下,当定时器的计数器值与输出比较通道的比较值相等时,会触发一个输出比较事件。这个事件会更新相应的输出引脚状态(切换高低电平状态),在常规的操作中,这个更新过程会受到定时器内部的同步机制的影响,所谓同步机制是为了确保输出信号的完整性和稳定性,需要在合适的时刻更新输出引脚的状态,比如修改了预分频值或者别的东西的时候所做的修改不会立即反映到引脚上。而当这一位置1的话,定时器只做一件事情,那就是比较计数器值与输出比较通道的比较值是否相等,以此来加快OC2的输出速度。只有在通道配置成PWM模式的时候才会生效;
- 位9:8,CC2S[1:0]:这一位定义输入输出的通道方向以及输入引脚的位置
输入捕获模式: - 位15:12,IC2F[3:0]:输入捕获2滤波器,滤波原理和TIMx_SMCR的ETF位相同,配置参数查询参考手册;
- 位11:10,IC2PSC[1:0]:输入/捕获2预分频器,这两位定义外部信号捕获的预分频系数;
- 位9:8,CC2S[1:0]:这一位定义输入输出的通道方向以及输入引脚的位置
低八位和高八位含义相同,针对通道1。
1.3.8. 捕获/比较模式寄存器2(TIMx_CCMR2)
含义相同,针对通道3和4。
1.3.9. 捕获/比较使能寄存器(TIMx_CCER)
这个寄存器配置四个定时器通道的输出极性和使能:
- 位13,CC4P:输入/捕获4输出极性,当CC4通道配置为输出时,置1高电平位为OC4的有效电平,置0低电平为有效电平;当CC4通道配置为输入时,置0,捕获上升沿,置1,捕获下降沿;
- 位12,CC4E:输入/捕获4输出使能,当CC4通道配置为输出时,置1使能输出,置0禁止输出;当CC4通道配置为输入时,置0,捕获禁止,置1,捕获使能;
- 位9,CC3P:输入/捕获3输出极性;
- 位8,CC3E:输入/捕获3输出使能;
- 位5,CC2P:输入/捕获2输出极性;
- 位4,CC2E:输入/捕获2输出使能;
- 位1,CC1P:输入/捕获1输出极性;
- 位0,CC1E:输入/捕获1输出使能。
1.3.10. 计数器(TIMx_CNT)
16位寄存器,CNT[15:0]:储存计数器的值。
1.3.11. 预分频器(TIMx_PSC)
16位寄存器,PSC[15:0]:储存预分频器的值。
1.3.12. 自动重装载寄存器(TIMx_ARR)
16位寄存器,ARR[15:0]:包含了将要传送至实际工作的自动重装载寄存器的数值。
1.3.13. 捕获/比较寄存器 1(TIMx_CCR1)
16位寄存器,CCR1[15:0]: 捕获/比较1的值,当CC1通道是输出时,CCR1包含了装入当前捕获/比较1寄存器的值(预装载值);当CC1通道是输入时,CCR1包含了由上一次输入捕获1事件(IC1)传输的计数器值。
1.3.14. 捕获/比较寄存器 2(TIMx_CCR2)
16位寄存器,CCR2[15:0]: 捕获/比较2的值,当CC2通道是输出时,CCR2包含了装入当前捕获/比较1寄存器的值(预装载值);当CC2通道是输入时,CCR2包含了由上一次输入捕获1事件(IC2)传输的计数器值。
1.3.15. 捕获/比较寄存器 3(TIMx_CCR3)
16位寄存器,CCR3[15:0]: 捕获/比较3的值,当CC3通道是输出时,CCR3包含了装入当前捕获/比较1寄存器的值(预装载值);当CC3通道是输入时,CCR3包含了由上一次输入捕获1事件(IC3)传输的计数器值。
1.3.16. 捕获/比较寄存器 4(TIMx_CCR4)
16位寄存器,CCR4[15:0]: 捕获/比较4的值,当CC4通道是输出时,CCR4包含了装入当前捕获/比较1寄存器的值(预装载值);当CC4通道是输入时,CCR4包含了由上一次输入捕获1事件(IC4)传输的计数器值。
1.3.17. DMA控制寄存器(TIMx_DCR)
位12:8,DBL[4:0]: DMA连续传送长度 (DMA burst length);
位4:0,DBA[4:0]: DMA基地址 (DMA base address)。
目前还用不到。
1.3.18. 连续模式的DMA地址(TIMx_DMAR)
位15:0,DMAB[15:0]: DMA连续传送寄存器 (DMA register for burst accesses),目前还用不到。
1.4. PWM输出实验
pwm输出实验的目标是通过使用定时器来出书pwm波控制LED0来实现一个呼吸灯。通过上面知识的学习可以知道定时器输出pwm波的原理很简单,因为定时器计数的频率是确定的,所以通过预先设置好捕获比较寄存器的值和自动重装载寄存器的值就可以调节pwm的高脉宽和周期了,这俩参数确定了以后pwm波的所有的信息就都确定了。
1.4.1. 原理图阅读
这里只需用用到LED0,LED0的一端接入VCC,另一端接入MCU的PB5,所以需要查看PB5可以映射哪些功能,打开数据手册找到PB5的引脚定义如下:
可以看到PB5可以重映射到TIM3_CH2,所以需要将PB5映射到TIM3_CH2。
1.4.2. 定时器的初始化
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
uint8_t chy = GTIM_TIMX_PWM_CHY;
GTIM_TIMX_PWM_CHY_GPIO_CLK_ENABLE(); /* TIMX 通道 IO口时钟使能 */
GTIM_TIMX_PWM_CHY_CLK_ENABLE(); /* TIMX 时钟使能 */
sys_gpio_set(GTIM_TIMX_PWM_CHY_GPIO_PORT, GTIM_TIMX_PWM_CHY_GPIO_PIN,
SYS_GPIO_MODE_AF, SYS_GPIO_OTYPE_PP, SYS_GPIO_SPEED_HIGH, SYS_GPIO_PUPD_PU); /* TIMX PWM CHY 引脚模式设置 */
sys_gpio_remap_set(10, 2, 2); /* IO口重映射 */
GTIM_TIMX_PWM->ARR = arr; /* 设定计数器自动重装值 */
GTIM_TIMX_PWM->PSC = psc; /* 设置预分频器 */
GTIM_TIMX_PWM->BDTR |= 1 << 15; /* 使能MOE位(仅TIM1/8 有此寄存器,必须设置MOE才能输出PWM), 其他通用定时器, 这个
* 寄存器是无效的, 所以设置/不设置并不影响结果, 为了兼容这里统一改成设置MOE位
*/
if (chy <= 2)
{
GTIM_TIMX_PWM->CCMR1 |= 6 << (4 + 8 * (chy - 1)); /* CH1/2 PWM模式1 */
GTIM_TIMX_PWM->CCMR1 |= 1 << (3 + 8 * (chy - 1)); /* CH1/2 预装载使能 */
}
else if (chy <= 4)
{
GTIM_TIMX_PWM->CCMR2 |= 6 << (4 + 8 * (chy - 3)); /* CH3/4 PWM模式1 */
GTIM_TIMX_PWM->CCMR2 |= 1 << (3 + 8 * (chy - 3)); /* CH3/4 预装载使能 */
}
GTIM_TIMX_PWM->CCER |= 1 << (4 * (chy - 1)); /* OCy 输出使能 */
GTIM_TIMX_PWM->CCER |= 1 << (1 + 4 * (chy - 1)); /* OCy 低电平有效 */
GTIM_TIMX_PWM->CR1 |= 1 << 7; /* ARPE使能 */
GTIM_TIMX_PWM->CR1 |= 1 << 0; /* 使能定时器TIMX */
}
- 第3行,变量
chy
初始化为GTIM_TIMX_PWM_CHY,表示当前选择的通道是通道2,就是TIM3_CH2中的CH2; - 第4行和第5行分别使能定时器通道IO口的时钟和定时器外设的时钟;
- 第7行,设置IO的初始化模式和状态,第3个参数SYS_GPIO_MODE_AF表示当前是复用功能,而不是普通的GPIO,第4个参数SYS_GPIO_OTYPE_PP说明当前是复用推挽输出,在输出模式下最后一个参数无效,因为输出模式下内部上下拉电阻无效;
- 第10行,将IO口PB5重映射至TIM3_CH2,因为PB5的默认复用功能并没有定时器,所以需要这行代码进行重映射,官方例程中说这里是非必需的,实际上就是取决于是不是默认复用,如果不是默认复用,那就需要重映射,那么这里就是必需的;
- 第12行和第13行设置计数器的自动重装载值和预分频系数,例程中使用1Mhz的计数频率且产生2Khz的PWM波,TIM3定时器位于APB1总线,和基本定时器一样,所以当采用内部时钟的时候TIM3的时钟频率也是72Mhz,要得到1Mhz频率的计数器频率,需要配置预分频系数为72,配置的PSC寄存器的值就是72-1,产生的PWM周期是2Khz,说明一个周期计数器计数的个数为(1/2000)/(1000000)=500,所以自动装载值为500-1;
- 第14行,这个寄存器只有高级定时器有,这里官方例程写出来只是为了兼容统一,没啥用;
- 第18-27行,分别选择通道12和通道34的输出模式以及使能对应通道的捕获比较寄存器TIMx_CCRy的预装载功能,这里chy的y等于2,移位运算前面的6和1表示将要写入寄存器的值,即0b110和0b1;
- 第29行,使能捕获比较寄存器的输出;
- 第30行,设置输出信号的有效电平;
- 第31行,使能自动重装载寄存器的缓存功能;
- 第32行,使能对应的定时器。
1.4.3. 主程序解读
while (1)
{
delay_ms(10);
if (dir) {
ledrpwmval++;
} else {
ledrpwmval--;
}
if (ledrpwmval > 300)dir = 0;
if (ledrpwmval == 0)dir = 1;
GTIM_TIMX_PWM_CHY_CCRX = ledrpwmval;
}
定时器初始化结束以后就可以编写主程序了,在主程序里面先延时10ms,延时的意义是防止闪烁频率过快,肉眼观察不到,然后通过变量dir
来调整占空比是增加还是减少,对应灯亮度的增加和减少,通过变量ledrpwmval
来调整具体占空比的值,计数值300对应占空比300/500=60%,这时候对占空比的变化方向取反,也就是说占空比从0变化到60%以后紧接着从60%再变化到0。
可以通过把300改成499来调节灯的最大亮度,同时把第5行到第8行改成下面这样,让灯的呼吸灯效果更加明显。
if (dir) {
ledrpwmval += 5;
} else {
ledrpwmval -= 5;
}
1.5. 输入捕获实验
输入捕获实验是对一个管脚进行高电平持续时间进行计算
1.5.1. 原理图阅读
这个实验用到的是开关KEY_UP这一路,开关未打开时,PA0采集到低电平,开关打开时,PA0采集到持续的高电平,然后将这个高电平的持续时间采集后通过串口发出来。
PA0可以映射的定时器通道是TIM5_CH1,而TIM2_CH1_ETR和TIM8_ETR是外部时钟模式2的时钟输入信号,不要搞混。
1.5.2. 定时器的初始化
void gtim_timx_cap_chy_init(uint16_t arr, uint16_t psc)
{
uint8_t chy = GTIM_TIMX_CAP_CHY;
GTIM_TIMX_CAP_CHY_GPIO_CLK_ENABLE(); /* TIMX 通道IO口时钟使能 */
GTIM_TIMX_CAP_CHY_CLK_ENABLE(); /* TIMX 时钟使能 */
sys_gpio_set(GTIM_TIMX_CAP_CHY_GPIO_PORT, GTIM_TIMX_CAP_CHY_GPIO_PIN,
SYS_GPIO_MODE_AF, SYS_GPIO_OTYPE_PP, SYS_GPIO_SPEED_HIGH, SYS_GPIO_PUPD_PU); /* TIMX PWM CHY 复用功能 下拉 */
GTIM_TIMX_CAP->ARR = arr; /* 设定计数器自动重装值 */
GTIM_TIMX_CAP->PSC = psc; /* 设置预分频器 */
if (chy <= 2)
{
GTIM_TIMX_CAP->CCMR1 |= 1 << 8 * (chy - 1); /* CCyS[1:0] = 01 选择输入端 IC1/2映射到TI1/2上 */
GTIM_TIMX_CAP->CCMR1 |= 0 << (2 + 8 * (chy - 1)); /* ICyPSC[1:0] = 00 输入捕获不分频,全捕获 */
GTIM_TIMX_CAP->CCMR1 |= 0 << (4 + 8 * (chy - 1)); /* ICyF[3:0] = 00 输入端滤波 不滤波 */
}
else if (chy <= 4)
{
GTIM_TIMX_CAP->CCMR2 |= 1 << 8 * (chy - 3); /* CCyS[1:0] = 01 选择输入端 IC3/4映射到TI3/4上 */
GTIM_TIMX_CAP->CCMR2 |= 0 << (2 + 8 * (chy - 3)); /* ICyPSC[1:0] = 00 输入捕获不分频,全捕获 */
GTIM_TIMX_CAP->CCMR2 |= 0 << (4 + 8 * (chy - 3)); /* ICyF[3:0] = 00 输入端滤波 不滤波 */
}
GTIM_TIMX_CAP->CCER |= 1 << (4 * (chy - 1)); /* CCyE = 1 输入捕获使能 */
GTIM_TIMX_CAP->CCER |= 0 << (1 + 4 * (chy - 1)); /* CCyP = 0 捕获上升沿 ,注意:CCyNP使用默认值0 */
GTIM_TIMX_CAP->EGR |= 1 << 0; /* 软件控制产生更新事件,使写入PSC的值立即生效,否则将会要等到定时器溢出才会生效 */
GTIM_TIMX_CAP->DIER |= 1 << 1; /* 允许捕获中断 */
GTIM_TIMX_CAP->DIER |= 1 << 0; /* 允许更新中断 */
GTIM_TIMX_CAP->CR1 |= 1 << 0; /* 使能定时器TIMX */
sys_nvic_init(1, 3, GTIM_TIMX_CAP_IRQn, 2);/* 抢占1,子优先级3,组2 */
}
- 第3行,变量
chy
初始化为GTIM_TIMX_CAP_CHY,这个宏的值为1,表示当前选择的通道是通道1,就是TIM5_CH1中的CH2; - 第4行和第5行分别使能定时器通道IO口的时钟和定时器5外设的时钟;
- 第7行,设置IO的初始化模式和状态,第3个参数SYS_GPIO_MODE_AF表示当前是复用功能,而不是普通的GPIO,第4个参数SYS_GPIO_OTYPE_PP说明当前是复用推挽输出,在输出模式下最后一个参数无效,因为输出模式下内部上下拉电阻无效;
- 第10行和第11行设置计数器的自动重装载值和预分频系数,设置方法和之前一样,重装载值指的是采集高电平的时候溢出的时间,设置的小多溢出几次,设置的大少溢出几次;
- 第13到24行设置通道的映射关系,信号滤波情况以及信号的预分频情况等,按自己的需求配置就行了;
- 第26和27行使能输入捕获通道(就是设置输入捕获寄存器连接到哪里)和设置有效电平的极性;
- 第29行,软件控制产生一个更新事件,使写入PSC的值立即生效,否则将会要等到定时器溢出才会生效;
- 第30行和第31行,使能更新中断和捕获中断,触发中断是为了采集上升沿和下降沿,更新中断是为了记录在高电平持续时间内计时器溢出了多少次;
- 第32行,使能定时器TIM5;
- 第34行,设置定时器的中断优先级。
1.5.3. 中断函数学习
void GTIM_TIMX_CAP_IRQHandler(void)
{
uint16_t tsr = GTIM_TIMX_CAP->SR; /* 获取中断状态 */
uint8_t chy = GTIM_TIMX_CAP_CHY; /* 需要捕获的通道 */
if ((g_timxchy_cap_sta & 0X80) == 0) /* 还未成功捕获 */
{
if (tsr & (1 << 0)) /* 溢出中断 */
{
if (g_timxchy_cap_sta & 0X40) /* 已经捕获到高电平了 */
{
if ((g_timxchy_cap_sta & 0X3F) == 0X3F) /* 高电平太长了 */
{
GTIM_TIMX_CAP->CCER &= ~(1 << (1 + 4 * (chy - 1)));/* CCyP = 0 设置为上升沿捕获 */
g_timxchy_cap_sta |= 0X80; /* 标记成功捕获了一次 */
g_timxchy_cap_val = 0XFFFF;
}
else /* 还可以累加高电平长度 */
{
g_timxchy_cap_sta++;
}
}
}
if (tsr & (1 << chy)) /* 通道y 发生了捕获事件 */
{
if (g_timxchy_cap_sta & 0X40) /* 捕获到一个下降沿 */
{
g_timxchy_cap_sta |= 0X80; /* 标记成功捕获到一次高电平脉宽 */
g_timxchy_cap_val = GTIM_TIMX_CAP_CHY_CCRX; /* 获取当前的捕获值. */
GTIM_TIMX_CAP->CCER &= ~(1 << (1 + 4 * (chy - 1)));/* CCyP = 0 设置为上升沿捕获 */
}
else /* 还未开始,第一次捕获上升沿 */
{
g_timxchy_cap_val = 0;
g_timxchy_cap_sta = 0X40; /* 标记捕获到了上升沿 */
GTIM_TIMX_CAP->CNT = 0; /* 计数器清空 */
GTIM_TIMX_CAP->CCER |= 1 << (1 + 4 * (chy - 1)); /* CCyP = 1 设置为下降沿捕获 */
}
}
}
GTIM_TIMX_CAP->SR = 0; /* 清除所有中断标志位 */
}
中断函数干的事情总结起来就是捕获上升沿和下降沿,并记录时间,同时记录计数器溢出次数,最后计算总的高电平持续时间。
- 第1行,获取中断状态就是获取当前的触发源,这里获取了所有的通道是因为原本也需要用到更新中断标志和捕获中断标志,所以就所有的都获取了,用到的时候再解析;
- 第2行,设置捕获的通道;
- 第6行,这个if判断中的全局变量表示的是当前捕获中断的电平变化类型,在后面的代码中进行赋值操作,当它与
0x80
进行与运算以后得到的值如果是0,说明当前还没有捕获到一个完整的上升沿加下降沿,要么还没有捕获到上升沿,要么是刚捕获到了上升沿,当前正在高电平持续时间阶段,如果是0,说明当前已经捕获到了一个完整的高脉宽,接下来就会通过main
函数将高脉宽的持续时间通过串口发到上位机; - 第8行,这个if判断当前的中断的触发源是不是计数器溢出;
- 第10行,判断当前是否已经捕获到高电平 ,如果已经捕获到了高电平,且当前的触发源是计数器溢出,说明当前正处在高脉宽的持续时间内,需要记录计数器的溢出次数;
- 第12行,对计数器最大溢出次数做判断,如果溢出次数达到
0x3F
次,则强制返回一次能记录到的最大脉宽时间; - 第14行,修改中断的触发为上升沿触发,因为已经采集到了最大的脉宽时间,需要重新采集,所以已经是完成了一次完整的采集,需要重新捕获上升沿;
- 第15行,标记捕获完成,在主函数内进行判断
g_timxchy_cap_sta
是否等于0x80
,然后将采集时间通过串口发送至上位机; - 第16行,记录采集的最大脉宽时间为
0xFFFF
; - 第20行,当前还没有达到设置的最大计数器溢出次数,对溢出次数进行累加;
- 第25行,当前的中断触发源不是计数器溢出,而是外部触发;