本节主要介绍以下内容:
定时器简介
高级定时器功能框图讲解
一、定时器简介
定时器功能 :定时、输出比较、输入捕获、断路输入
定时器分类 :基本定时器、通用定时器、高级定时器
定时器资源 :F103有2个高级定时器、4个通用定时器、2个基本定时器
计数器特性:
高级定时器功能简介
- -计数器16bit,上/下/两边 计数,TIM1和TIM8,还有一个重复计数器RCR,独有。
- -有4个GPIO,其中通道1~3还有互补输出GPIO
- -时钟来自PCLK2,为72M,可实现1~65536分频
高级控制定时器(TIM1 和TIM8)和通用定时器在基本定时器的基础上引入了外部引脚,可以实现输入捕获和输出比较功能。高级控制定时器比通用定时器增加了可编程死区互补输出、重复计数器、带刹车(断路)功能,这些功能都是针对工业电机控制方面 。
STM32F103ZET6 的高级/通用定时器的IO 分配具体见表
二、 高级定时器功能框图讲解
1-时钟源 2-控制器 3-时基 4-输入捕获 5-输出比较 6-断路功能
2.1 时钟源
- -内部时钟源CK_INT
- -外部时钟模式1—外部的GPIO Tix(x=1 2 3 4)
- -外部时钟模式2—外部的GPIO ETR
- -内部触发输入
2.1.1 内部时钟源
- -内部时钟源来自RCC的TIMx_CLK 72M
- -TIMx_CLK等于多少呢?如何确定? 72M
- 具体的查看:RCC时钟树部分
2.1.2 外部时钟1
①时钟信号输入引脚
- -外部的GPIO TIx,对应:TIMx_CH1/2/3/4
- -TIM_CCMRx 的位 CCxS[1:0]配置,其中 CCMR1 控制 TI1/2, CCMR2 控制 TI3/4
②滤波器
- -如果来自外部的时钟信号的频率过高或者混杂有高频干扰信号的话,我们就需要使用滤波器对 ETRP 信号重新采样,来达到降频或者去除高频干扰的目的,2-由TIMx_CCMRx 的位 ICxF[3:0]配置。
③边沿检测
- -边沿检测的信号来自于滤波器的输出,在成为触发信号之前,需要进行边沿检测,决定是上升沿有效还是下降沿有效。
- -由 TIMx_CCER 的位 CCxP 和 CCxNP 配置。
④触发选择
- -当使用外部时钟模式 1 时,触发源有两个,一个是滤波后的定时器输入 1( TI1FP1)和滤波后的定时器输入 2( TI2FP2)。
- -由 TIMx_SMCR 的位 TS[2:0]配置。
⑤从模式选择
- -选定了触发源信号后,最后我们需把信号连接到 TRGI 引脚,让触发信号成为外部时钟模式 1 的输入,最终等于 CK_PSC,然后驱动计数器 CNT 计数。
- -具体的配置TIMx_SMCR 的位 SMS[2:0]为 000 即可选择外部时钟模式 1。
⑥使能计数器
- -经过上面的 5 个步骤之后, 最后我们只需使能计数器开始计数,外部时钟模式 1 的配置就算完成。
- -使能计数器由 TIMx_CR1 的位 CEN 配置。
2.1.3 外部时钟2
①时钟信号输入引脚
当使用外部时钟模式 2 的时候,时钟信号来自于定时器的特定输入通道 TIMx_ETR,只有 1 个。
②外部触发极性
- -来自 ETR 引脚输入的信号可以选择为上升沿或者下降沿有效。
- -具体的由 TIMx_SMCR的位 ETP 配置。
③外部触发预分频器
- -由于 ETRP 的信号的频率不能超过 TIMx_CLK( 180M)的 1/4,当触发信号的频率很高的情况下,就必须使用分频器来降频。
- -具体的由 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。
⑥使能计数器
- -经过上面的 5 个步骤之后, 最后我们只需使能计数器开始计数,外部时钟模式 2 的配置就算完成。
- -使能计数器由 TIMx_CR1 的位 CEN 配置。
2.1.4 内部触发输入
- -内部触发输入是使用一个定时器作为另一个定时器的预分频器。硬件上高级控制定时器和通用定时器在内部连接在一起,可以实现定时器同步或级联。
- - 由TIMx_SMCR 的位 TS[2:0]配置。
内部触发连接
TIM1为TIM2提供时钟
2.2 控制器
- -控制器就是用来控制的,发送命令的
- -CR1、CR2、SMCR、CCER,主要学习这几个寄存器即可。
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 的值)时产生更新事件。
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 只能通过软件清零。
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 说明还请查阅相关的数据手册。
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。
2.7 输入捕获应用
输入捕获一般应用在两个方面,一个方面是脉冲跳变沿时间测量,另一方面是PWM输入测量。
2.7.1 测量脉宽或者频率
①测量频率
当捕获通道TIx 上出现上升沿时,发生第一次捕获,计数器CNT 的值会被锁存到捕获到寄存器CCR 中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并把捕获寄存器中的值读取到value1 中。当出现第二次上升沿时,发生第二次捕获,计数器CNT的值会再次被锁存到捕获寄存器CCR中,并再次进入捕获中断,在捕获中断中,把捕获寄存器的值读取到value3 中,并清除捕获记录标志。利用value3 和value1 的差值我们就可以算出信号的周期(频率)。
②.测量脉宽
当捕获通道TIx 上出现上升沿时,发生第一次捕获,计数器CNT 的值会被锁存到捕获寄存器CCR 中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并把捕获寄存器中的值读取到value1 中。然后把捕获边沿改变为下降沿捕获,目的是捕获后面的下降沿。当下降沿到来的时候,发生第二次捕获,计数器CNT 的值会再次被锁存到捕获寄存器CCR 中,并再次进入捕获中断,在捕获中断中,把捕获寄存器的值读取到value3 中,并清除捕获记录标志。然后把捕获边沿设置为上升沿捕获。
在测量脉宽过程中需要来回的切换捕获边沿的极性,如果测量的脉宽时间比较长,定时器就会发生溢出,溢出的时候会产生更新中断,我们可以在中断里面对溢出进行记录处理。
③PWM输入模式
测量脉宽和频率还有一个更简便的方法就是使用PWM 输入模式,该模式是输入捕获的特例,只能使用通道1 和通道2,通道3 和通道4 使用不了。与上面那种只使用一个捕获寄存器测量脉宽和频率的方法相比,PWM输入模式需要占用两个捕获寄存器。
当使用PWM 输入模式的时候,因为一个输入通道(TIx)会占用两个捕获通道(ICx),所以一个定时器在使用PWM输入的时候最多只能使用两个输入通道(TIx)。
我们以输入通道TI1 工作在PWM输入模式为例来讲解下具体的工作原理,其他通道以此类推即可。
PWM 信号由输入通道TI1 进入,因为是PWM输入模式的缘故,信号会被分为两路,一路是TI1FP1,另外一路是TI2FP2。其中一路是周期,另一路是占空比,具体哪一路信号对应周期还是占空比,得从程序上设置哪一路信号作为触发输入,作为触发输入的哪一路信号对应的就是周期,另一路就是对应占空比。作为触发输入的那一路信号还需要设置极性,是上升沿还是下降沿捕获,一旦设置好触发输入的极性,另外一路硬件就会自动配置为相反的极性捕获,无需软件配置。一句话概括就是:选定输入通道,确定触发信号,然后设置触发信号的极性即可,因为是PWM 输入的缘故,另一路信号则由硬件配置,无需软件配置。
当使用PWM输入模式的时候必须将从模式控制器配置为复位模式(配置寄存器SMCR的位SMS[2:0]来实现),即当我们启动触发信号开始进行捕获的时候,同时把计数器CNT复位清零。
下面我们以一个更加具体的时序图来分析下PWM输入模式。
PWM 信号由输入通道TI1 进入,配置TI1FP1 为触发信号,上升沿捕获。当上升沿的时候IC1 和IC2 同时捕获,计数器CNT 清零,到了下降沿的时候,IC2 捕获,此时计数器CNT的值被锁存到捕获寄存器CCR2中,到了下一个上升沿的时候,IC1捕获,计数器CNT的值被锁存到捕获寄存器CCR1 中。其中CCR2+1 测量的是脉宽,CCR1+1 测量的是周期。这里要注意的是CCR2 和CCR1 的值在计算占空比和频率的时候都必须加1,因为计数器是从0 开始计数的。
从软件上来说,用PWM 输入模式测量脉宽和周期更容易,付出的代价是需要占用两个捕获寄存器。
2.8 输出比较应用
输出比较模式总共有8 种,具体的由寄存器CCMRx 的位OCxM[2:0]配置。我们这里只讲解最常用的PWM模式,其他几种模式具体的看数据手册即可。
2.8.1 PWM输出模式
PWM输出就是对外输出脉宽(即占空比)可调的方波信号,信号频率由自动重装寄存器ARR 的值决定,占空比由比较寄存器CCR 的值决定。
PWM 模式分为两种,PWM1 和PWM2,总得来说是差不多,就看你怎么用而已,具体的区别见下表:
下面我们以PWM1 模式来讲解,以计数器CNT计数的方向不同还分为边沿对齐模式和中心对齐模式。PWM信号主要都是用来控制电机,一般的电机控制用的都是边沿对齐模式,FOC 电机一般用中心对齐模式。我们这里只分析这两种模式在信号感官上(即信号波形)的区别,具体在电机控制中的区别不做讨论,到了你真正需要使用的时候就会知道了。
①PWM边沿对齐模式
在递增计数模式下,计数器从 0 计数到自动重载值( TIMx_ARR 寄存器的内容),然后重新从 0 开始计数并生成计数器上溢事件
在边沿对齐模式下,计数器CNT 只工作在一种模式,递增或者递减模式。这里我们以CNT 工作在递增模式为例,在中,ARR=8,CCR=4,CNT 从0 开始计数,当CNT<CCR 的值时,OCxREF 为有效的高电平, 于此同时, 比较中断寄存器CCxIF 置位。当CCR=<CNT<=ARR 时,OCxREF为无效的低电平。然后CNT又从0 开始计数并生成计数器上溢事件,以此循环往复。
② PWM中心对齐模式
在中心对齐模式下,计数器CNT 是工作做递增/递减模式下。开始的时候,计数器CNT 从 0 开始计数到自动重载值减1(ARR-1),生成计数器上溢事件;然后从自动重载值开始向下计数到 1 并生成计数器下溢事件。之后从0 开始重新计数。图 33-14 是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。
三、定时器初始化结构体详解
在标准库函数头文件stm32f10x_tim.h 中对定时器外设建立了四个初始化结构体,分别为
时基初始化结构体TIM_TimeBaseInitTypeDef 、
输出比较初始化结构体TIM_OCInitTypeDef、
输入捕获初始化结构体TIM_ICInitTypeDef 、
断路和死区初始化结构体TIM_BDTRInitTypeDef,
高级控制定时器可以用到所有初始化结构体,通用定时器不能使用TIM_BDTRInitTypeDef 结构体,基本定时器只能使用时基结构体。接下来我们具体讲解下这四个结构体。
3.1 . TIM_TimeBaseInitTypeDef
时基结构体TIM_TimeBaseInitTypeDef 用于定时器基础参数设置,与TIM_TimeBaseInit函数配合使用完成配置。
- TIM_Prescaler:定时器预分频器设置,时钟源经该预分频器才是定时器计数时钟
CK_CNT,它设定PSC 寄存器的值。计算公式为:计数器时钟频率 (fCK_CNT) 等于fCK_PSC / (PSC[15:0] + 1),可实现1 至65536 分频。 - TIM_CounterMode:定时器计数方式,可设置为向上计数、向下计数以及中心对齐。高级控制定时器允许选择任意一种。
- TIM_Period:定时器周期,实际就是设定自动重载寄存器ARR 的值,ARR 为要装载到实际自动重载寄存器(即影子寄存器)的值,可设置范围为0 至65535。
- TIM_ClockDivision:时钟分频,设置定时器时钟CK_INT 频率与死区发生器以及数字滤波器采样时钟频率分频比。可以选择1、2、4 分频。
- TIM_RepetitionCounter:重复计数器,只有8 位,只存在于高级定时器。
3.2 TIM_OCInitTypeDef
输出比较结构体TIM_OCInitTypeDef 用于输出比较模式,与TIM_OCxInit 函数配合使用完成指定定时器输出通道初始化配置。高级控制定时器有四个定时器通道,使用时都必须单独设置。
- TIM_OCMode:比较输出模式选择,总共有八种,常用的为PWM1/PWM2。它设定CCMRx 寄存器OCxM[2:0]位的值。
- TIM_OutputState:比较输出使能,决定最终的输出比较信号OCx 是否通过外部引脚输出。它设定TIMx_CCER 寄存器CCxE/CCxNE 位的值。
- TIM_OutputNState:比较互补输出使能,决定OCx 的互补信号OCxN 是否通过外部引脚输出。它设定CCER 寄存器CCxNE 位的值。
- TIM_Pulse:比较输出脉冲宽度,实际设定比较寄存器CCR 的值,决定脉冲宽度。可设置范围为0 至65535。
- TIM_OCPolarity:比较输出极性,可选OCx 为高电平有效或低电平有效。它决定着定时器通道有效电平。它设定CCER 寄存器的CCxP 位的值。
- TIM_OCNPolarity:比较互补输出极性,可选OCxN 为高电平有效或低电平有效。它设定TIMx_CCER 寄存器的CCxNP 位的值。
- TIM_OCIdleState:空闲状态时通道输出电平设置,可选输出1 或输出0,即在空闲状态(BDTR_MOE 位为0)时,经过死区时间后定时器通道输出高电平或低电平。它设定CR2 寄存器的OISx 位的值。
- TIM_OCNIdleState:空闲状态时互补通道输出电平设置,可选输出1 或输出0,即在空闲状态(BDTR_MOE 位为0)时,经过死区时间后定时器互补通道输出高电平或低电平,设定值必须与TIM_OCIdleState 相反。它设定是CR2 寄存器的OISxN 位的值。
3.3. TIM_ICInitTypeDef
输入捕获结构体TIM_ICInitTypeDef 用于输入捕获模式,与TIM_ICInit 函数配合使用完成定时器输入通道初始化配置。如果使用PWM 输入模式需要与TIM_PWMIConfig 函数配合使用完成定时器输入通道初始化配置。
- TIM_Channel:捕获通道ICx 选择,可选TIM_Channel_1、TIM_Channel_2、TIM_Channel_3 或TIM_Channel_4 四个通道。它设定CCMRx 寄存器CCxS 位 的值。
- TIM_ICPolarity:输入捕获边沿触发选择,可选上升沿触发、下降沿触发或边沿跳变触发。它设定CCER 寄存器CCxP 位和CCxNP 位的值。
- TIM_ICSelection:输入通道选择,捕获通道ICx 的信号可来自三个输入通道,分别为TIM_ICSelection_DirectTI、TIM_ICSelection_IndirectTI 或TIM_ICSelection_TRC。如果是普通的输入捕获,4 个通道都可以使用,如果是PWM输入则只能使用通道1 和通道2。它设定CCRMx 寄存器的CCxS[1:0]位的值。
- TIM_ICPrescaler:输入捕获通道预分频器,可设置1、2、4、8 分频,它设定CCMRx寄存器的ICxPSC[1:0]位的值。如果需要捕获输入信号的每个有效边沿,则设置1 分频即可。
- TIM_ICFilter:输入捕获滤波器设置,可选设置0x0 至0x0F。它设定CCMRx 寄存器ICxF[3:0]位的值。一般我们不使用滤波器,即设置为0。
3. 4. TIM_BDTRInitTypeDef
断路和死区结构体TIM_BDTRInitTypeDef 用于断路和死区参数的设置,属于高级定时器专用,用于配置断路时通道输出状态,以及死区时间。它与TIM_BDTRConfig 函数配置使用完成参数配置。这个结构体的成员只对应BDTR 这个寄存器,有关成员的具体使用配置请参考手册BDTR 寄存器的详细描述。
- TIM_OSSRState:运行模式下的关闭状态选择,它设定BDTR 寄存器OSSR 位的值。
- TIM_OSSIState:空闲模式下的关闭状态选择,它设定BDTR 寄存器OSSI 位的值。
- TIM_LOCKLevel:锁定级别配置, BDTR 寄存器LOCK[1:0]位的值。
- TIM_DeadTime:配置死区发生器,定义死区持续时间,可选设置范围为0x0 至0xFF。它设定BDTR 寄存器DTG[7:0]位的值。
- TIM_Break:断路输入功能选择,可选使能或禁止。它设定BDTR 寄存器BKE 位的值。
- TIM_BreakPolarity:断路输入通道BRK 极性选择,可选高电平有效或低电平有效。它设定BDTR 寄存器BKP 位的值。
- TIM_AutomaticOutput:自动输出使能,可选使能或禁止,它设定BDTR 寄存器AOE位的值。
四、PWM互补输出实验
输出比较模式比较多,这里我们以PWM 输出为例讲解,并通过示波器来观察波形。实验中不仅在主输出通道输出波形,还在互补通道输出与主通道互补的的波形,并且添加了断路和死区功能。
4.1 硬件设计
根据开发板引脚使用情况,并且参考下表中定时器引脚信息 ,使用高级定时器TIM1 的通道1及其互补通道作为本实验的波形输出通道,对应选择PA8 和PB13 引脚。将示波器的两个输入通道分别与PA8 和PB13 引脚连接,用于观察波形,还有注意共地。为增加断路功能,需要用到TIM1_BKIN 引脚,这里选择PB12 引脚。程序我们设置该引脚为高电平有效,当BKIN引脚被置高低电平的时候,两路互补的PWM输出就被停止,就好像是刹车一样。
4.2 软件设计
编程要点
- 定时器用到的GP IO 初始化
- 定时器时基结构体TIM_TimeBaseInitTypeDef 初始化
- 定时器输出比较结构体TIM_OCInitTypeDef 初始化
- 定时器刹车和死区结构体TIM_BDTRInitTypeDef 初始化
4.2.1 bsp_AdvanceTim.h
#ifndef __BSP_ADVANCETIM_H
#define __BSP_ADVANCETIM_H
#include "stm32f10x.h"
/************高级定时器TIM参数定义,只限TIM1和TIM8************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 这里我们使用高级控制定时器TIM1
// TIM1 输出比较通道
#define ADVANCE_TIM TIM1
#define ADVANCE_TIM_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADVANCE_TIM_CLK RCC_APB2Periph_TIM1
// PWM 信号的频率 F = TIM_CLK/{(ARR+1)*(PSC+1)} 占空比4/(PERIOD +1) = 50%
#define ADVANCE_TIM_PERIOD (8-1)
#define ADVANCE_TIM_PSC (9-1)
#define ADVANCE_TIM_PULSE 4
#define ADVANCE_TIM_IRQ TIM1_UP_IRQn
#define ADVANCE_TIM_IRQHandler TIM1_UP_IRQHandler
#define ADVANCE_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOA
#define ADVANCE_TIM_CH1_PORT GPIOA
#define ADVANCE_TIM_CH1_PIN GPIO_Pin_8
// TIM1 输出比较通道的互补通道
#define ADVANCE_TIM_CH1N_GPIO_CLK RCC_APB2Periph_GPIOB
#define ADVANCE_TIM_CH1N_PORT GPIOB
#define ADVANCE_TIM_CH1N_PIN GPIO_Pin_13
// TIM1 输出比较通道的刹车通道
#define ADVANCE_TIM_BKIN_GPIO_CLK RCC_APB2Periph_GPIOB
#define ADVANCE_TIM_BKIN_PORT GPIOB
#define ADVANCE_TIM_BKIN_PIN GPIO_Pin_12
void ADVANCE_TIM_Init(void);
#endif /* __BSP_ADVANCETIM_H */
4.2.2 bsp_AdvanceTim.c
#include "bsp_AdvanceTim.h"
/**
******************************************************************************
* @file main.c
* @author fire
* @version V1.0
* @date 2013-xx-xx
* @brief 串口中断接收测试
******************************************************************************
* @attention
* PWM主要要配置以下几个寄存器CNT(计数器) CCR(输出比较寄存器) ARR(自动重装载寄存器)
* 当CNT从0开始向上计数的时候,我们设置初始有效电平为高电平,CNT<CCR,一直保持高电平
* 当到达CCR这个输出比较寄存器的时候,电平发生跳变,从高电平变成低电平,再往上计数
* 到达ARR的时候,计数器CNT清零,电平翻转又开始计数,往复循环这个过程
* 可以看出占空比由CRR决定 周期由ARR决定
* 计数一次的时间由PSC决定 CCR由PULSE决定 ARR PERIOD
******************************************************************************
*/
static void ADVANCE_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 输出比较通道 GPIO 初始化
RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = ADVANCE_TIM_CH1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(ADVANCE_TIM_CH1_PORT, &GPIO_InitStructure);
// 输出比较通道互补通道 GPIO 初始化
RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1N_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = ADVANCE_TIM_CH1N_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(ADVANCE_TIM_CH1N_PORT, &GPIO_InitStructure);
// 输出比较通道刹车通道 GPIO 初始化
RCC_APB2PeriphClockCmd(ADVANCE_TIM_BKIN_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = ADVANCE_TIM_BKIN_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(ADVANCE_TIM_BKIN_PORT, &GPIO_InitStructure);
// BKIN引脚默认先输出低电平
GPIO_ResetBits(ADVANCE_TIM_BKIN_PORT,ADVANCE_TIM_BKIN_PIN);
}
static void ADVANCE_TIM_mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
ADVANCE_TIM_APBxClock_FUN(ADVANCE_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period=ADVANCE_TIM_PERIOD;
// 驱动CNT计数器的时钟 = Fck_int/(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);
/*--------------------输出比较结构体初始化-------------------*/
TIM_OCInitTypeDef TIM_OCInitStructure;
// 配置为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 = ADVANCE_TIM_PULSE;
// 输出通道电平极性配置
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);
/*-------------------刹车和死区结构体初始化-------------------*/
// 有关刹车和死区结构体的成员具体可参考BDTR寄存器的描述
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
// 输出比较信号死区时间配置,具体如何计算可参考 BDTR:UTG[7:0]的描述
// 这里配置的死区时间为152ns
TIM_BDTRInitStructure.TIM_DeadTime = 11;
TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;
// 当BKIN引脚检测到高电平的时候,输出比较信号被禁止,就好像是刹车一样
TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
TIM_BDTRConfig(ADVANCE_TIM, &TIM_BDTRInitStructure);
// 使能计数器
TIM_Cmd(ADVANCE_TIM, ENABLE);
// 主输出使能,当使用的是通用定时器时,这句不需要
TIM_CtrlPWMOutputs(ADVANCE_TIM, ENABLE);
}
void ADVANCE_TIM_Init(void)
{
ADVANCE_TIM_GPIO_Config();
ADVANCE_TIM_mode_Config();
}
Main 函数很简单, 调用了ADVANCE_TIM_Init() 函数, 该函数调用了
ADVANCE_TIM_GPIO_Config()和ADVANCE_TIM_Mode_Config()这两个函数完成了定时器GPIO 引脚和工作模式的初始化,这时,相应的GPIO 引脚上就可以检测到互补输出的PWM 信号,而且带死区时间,如果程序运行的过程中,BKIN 引脚被拉高的话,PWM 输出会被禁止,就好像是断路或者刹车一样。
可以看到两个通道的波形互补,但不是完全互补,比如说,电机高电平时正转,正通道高电平时正转,低电平的时候就让电机关掉,那么反向的通道就要立马打来实现反转,但是它没有立马打开,它还是延续一段低电平时间,就是等待MOS管完全关闭掉,那么这一段时间就叫做死区时间,这个要根据电机特性来配置。
当互补通道变成低的时候,正通道也没有立马就升高,也要延续一段低电平的时间,就是为了等待互补通道驱动的MOS管完全关闭。那么这死区时间是多少呢?
可以放大来看看,死区时间差不多是150ns,这里我们算出是152ns,跟我们配置11计算出来的完全一样。上面我们设置始终分频因子的时候就已经知道tdtg=13.89ns,所以我们可以算出DT就约等与11*13.89 = 152ns。
五、输入捕获实验,用于测量脉宽
我们想测量高电平的时间,用定时器的输入捕获,就是用一个GPIO口,首先连接至这个信号,我们设计成上升沿捕获,首先从低电平跳变到高电平的第一个上升沿,我们捕获到,然后进入中断服务程序里面产生中断,然后在中断服务函数里面将CNT清零,从0开始计数,同时发生捕获的时候,CNT的值会被锁存到CCR寄存器中,因为一开始我们将CNT清0,那么锁存过来的就是0了。接下来将锁存的边沿改成下降沿,那么当第二次进入中断的时候,就是下降沿中断了,那么这个时候,计数器CNT的值就不为0了,这个时候CNT的值也被锁存在CCR里面,我们把CCR的值读出来,就可以计算出脉冲的宽度了。
难点有以下两个:第一个就是在中断服务程序里面,要不断的修改这个捕获的边沿,首先是上升沿,处理完之后,改成下降沿,下降沿触发中断触发完毕之后又要改成上升沿,为的就是捕获下一次的脉冲
第二个就是:计数器是16位的,只能计65535,如果每一个计数器的值是1ms,那么最大只能计数/测量65535ms的脉冲宽度,如果脉冲宽度超过这个时间,就会产生更新中断,就需要在中断服务函数里面计算更新中断。需要把这两部分加起来才是脉冲宽度。
5.1 硬件设计
根据开发板引脚使用情况,选用通用定时器TIM5 的CH1,就PA0 这个GPIO 来测量信号的脉宽。在开发板中PA0 接的是一个按键,默认接GND,当按键按下的时候IO口会被拉高,这个时候我们可以利用定时器的输入捕获功能来测量按键按下的这段高电平的时间,按键的具体原理图见下图:
5.2 软件设计
编程要点:
(1) 定时器用到的GP IO 初始化
(2) 定时器时基结构体TIM_TimeBaseInitTypeDef 初始化
(3) 定时器输入捕获结构体TIM_ICInitTypeDef 初始化
(4) 编写中断服务函数,读取捕获值,计算出脉宽的时间
5.2.1 bsp_GeneralTim.h
#ifndef __BSP_GENERALTIM_H
#define __BSP_GENERALTIM_H
#include "stm32f10x.h"
/************通用定时器TIM参数定义,只限TIM2、3、4、5************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 我们这里默认使用TIM5
#define GENERAL_TIM TIM5
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM5
#define GENERAL_TIM_PERIOD 0XFFFF
#define GENERAL_TIM_PSC (72-1) //72m/72 = 1M = 1us,计数器计数一次是1us,那么我们测量的脉宽最小是1us,\
如果想计数500ns,那么就需要重新配置分频因子,在不溢出的情况下,测量\
的最大宽度是65536us
// TIM 输入捕获通道GPIO相关宏定义
#define GENERAL_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOA
#define GENERAL_TIM_CH1_PORT GPIOA
#define GENERAL_TIM_CH1_PIN GPIO_Pin_0
#define GENERAL_TIM_CHANNEL_x TIM_Channel_1
// 中断相关宏定义
#define GENERAL_TIM_IT_CCx TIM_IT_CC1
#define GENERAL_TIM_IRQ TIM5_IRQn //中断源从stm32f10x.h中
#define GENERAL_TIM_INT_FUN TIM5_IRQHandler
// 获取捕获寄存器值函数宏定义
#define GENERAL_TIM_GetCapturex_FUN TIM_GetCapture1
// 捕获信号极性函数宏定义
#define GENERAL_TIM_OCxPolarityConfig_FUN TIM_OC1PolarityConfig
// 测量的起始边沿
#define GENERAL_TIM_STRAT_ICPolarity TIM_ICPolarity_Rising
// 测量的结束边沿
#define GENERAL_TIM_END_ICPolarity TIM_ICPolarity_Falling
// 定时器输入捕获用户自定义变量结构体声明
typedef struct
{
uint8_t Capture_FinishFlag; // 捕获结束标志位
uint8_t Capture_StartFlag; // 捕获开始标志位
uint16_t Capture_CcrValue; // 捕获寄存器的值 (当没有溢出的时候,把寄存器的值就锁存在这个变量里面)
uint16_t Capture_Period; // 自动重装载寄存器更新标志 (用来记录超时了多少次)
}TIM_ICUserValueTypeDef;
extern TIM_ICUserValueTypeDef TIM_ICUserValueStructure;//只要包含了头文件 就可以使用这个变量
void GENERAL_TIM_Init(void);
#endif /* __BSP_GENERALTIM_H */
5.2.2 bsp_GeneralTim.c
#include "bsp_GeneralTim.h"
/**
******************************************************************************
* @file main.c
* @author fire
* @version V1.0
* @date 2013-xx-xx
* @brief
******************************************************************************
* @attention
* PWM主要要配置以下几个寄存器CNT(计数器) CCR(输出比较寄存器) ARR(自动重装载寄存器)
* 当CNT从0开始向上计数的时候,我们设置初始有效电平为高电平,CNT<CCR,一直保持高电平
* 当到达CCR这个输出比较寄存器的时候,电平发生跳变,从高电平变成低电平,再往上计数
* 到达ARR的时候,计数器CNT清零,电平翻转又开始计数,往复循环这个过程
* 可以看出占空比由CRR决定 周期由ARR决定
* 计数一次的时间由PSC决定 CCR由PULSE决定 ARR PERIOD
******************************************************************************
*/
// 定时器输入捕获用户自定义变量结构体定义
TIM_ICUserValueTypeDef TIM_ICUserValueStructure = {0,0,0,0};
// 中断优先级配置
static void GENERAL_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIM_IRQ;
// 设置主优先级为 0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
// 设置抢占优先级为3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
static void GENERAL_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 输入捕获通道 GPIO 初始化
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//输入捕获 浮空输入
GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure);
}
///*
// * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
// * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
// * 另外三个成员是通用定时器和高级定时器才有.
// *-----------------------------------------------------------------------------
// *typedef struct
// *{ TIM_Prescaler 都有
// * TIM_CounterMode TIMx,x[6,7]没有,其他都有
// * TIM_Period 都有
// * TIM_ClockDivision TIMx,x[6,7]没有,其他都有
// * TIM_RepetitionCounter TIMx,x[1,8,15,16,17]才有
// *}TIM_TimeBaseInitTypeDef;
// *-----------------------------------------------------------------------------
// */
/* ---------------- PWM信号 周期和占空比的计算--------------- */
// ARR :自动重装载寄存器的值
// CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
// PWM 信号的周期 T = (ARR+1) * (1/CLK_cnt) = (ARR+1)*(PSC+1) / 72M
// 占空比P=CCR/(ARR+1)
static void GENERAL_TIM_MODE_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 时钟分频因子 ,配置死区时间时需要用到
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period = GENERAL_TIM_PERIOD;
// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
TIM_TimeBaseStructure.TIM_Prescaler = GENERAL_TIM_PSC;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
// 初始化定时器
TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);
/*--------------------输入捕获结构体初始化-------------------*/
TIM_ICInitTypeDef TIM_ICInitStructure;
// 配置输入捕获的通道,需要根据具体的GPIO来配置
TIM_ICInitStructure.TIM_Channel = GENERAL_TIM_CHANNEL_x;
// 输入捕获信号的极性配置
TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity;
// 输入通道和捕获通道的映射关系,有直连和非直连两种
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
// 输入的需要被捕获的信号的分频系数
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
// 输入的需要被捕获的信号的滤波系数
TIM_ICInitStructure.TIM_ICFilter = 0;
// 定时器输入捕获初始化
TIM_ICInit(GENERAL_TIM, &TIM_ICInitStructure);
// 清除更新和捕获中断标志位
TIM_ClearFlag(GENERAL_TIM,TIM_FLAG_Update|TIM_IT_CC1);
// 开启更新和捕获中断
TIM_ITConfig(GENERAL_TIM,TIM_FLAG_Update|TIM_IT_CC1,ENABLE);
// 使能计数器
TIM_Cmd(GENERAL_TIM, ENABLE);
}
void GENERAL_TIM_Init(void)
{
GENERAL_TIM_GPIO_Config();
GENERAL_TIM_NVIC_Config();
GENERAL_TIM_MODE_Config();
}
在初始化时基结构体的周期和时钟分频因子这两个成员时,我们使用了两个宏
GENERAL_TIM_PERIOD 和GENERAL_TIM_PSC。GENERAL_TIM_PERIOD 配置的是ARR 寄存器的值,决定了计数器一个周期的计数时间,默认我们配置为0XFFFF,即最大。
GENERAL_TIM_PSC 配置的是分频因子,默认配置为72-1,则可以计算出计数器的计数周期为(GENERAL_TIM_PSC+1)/72M = 1US。所以输入捕获能捕获的最小的时间为1us,最长的时间为1us*(0Xffff+1)=65536us=65.536ms,当超过这个计数周期的时候,就会产生中断,然后在中断里面做额外的处理,需要记录好产生了多少次更新中断,最后把这个更新时间加入到脉宽的时间里面。
在GENERAL_TIM_Mode_Config() 函数中, 我们配置输入捕获的起始边沿为
GENERAL_TIM_STRAT_ICPolarity,这是一个宏,默认配置为上升沿。我们的按键默认是接GND,当按键按下的时候会被拉高,这个时候这个由低到高的上升沿会被捕获到,这是第一次捕获,此时我们把计数器清0,开始计数,同时把捕获边沿改成下降沿捕获。当第二次进入中断服务函数的时候,说明捕获到下降沿,这个时候表示脉宽捕获完毕,我们读取捕获寄存器的值即可,然后我们就可以通过这个值算出脉宽的时间。最后把捕获编译配置为上升沿,为的是下一次捕获。如果脉宽的时间超过了计数器的最大计数时间,那么就会产生更新中断,我们需要做额外的处理,即产生了多少次更新中断记录下来,最后在算脉宽的时间的时候把这个更新的时间加进去即可。
5.2.3 stm32f10x_it.c
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x_it.h"
#include "bsp_usart.h"
#include "bsp_GeneralTim.h"
/** @addtogroup STM32F10x_StdPeriph_Template
* @{
*/
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/******************************************************************************/
/* Cortex-M3 Processor Exceptions Handlers */
/******************************************************************************/
/**
* @brief This function handles NMI exception.
* @param None
* @retval None
*/
void NMI_Handler(void)
{
}
/**
* @brief This function handles Hard Fault exception.
* @param None
* @retval None
*/
void HardFault_Handler(void)
{
/* Go to infinite loop when Hard Fault exception occurs */
while (1)
{
}
}
/**
* @brief This function handles Memory Manage exception.
* @param None
* @retval None
*/
void MemManage_Handler(void)
{
/* Go to infinite loop when Memory Manage exception occurs */
while (1)
{
}
}
/**
* @brief This function handles Bus Fault exception.
* @param None
* @retval None
*/
void BusFault_Handler(void)
{
/* Go to infinite loop when Bus Fault exception occurs */
while (1)
{
}
}
/**
* @brief This function handles Usage Fault exception.
* @param None
* @retval None
*/
void UsageFault_Handler(void)
{
/* Go to infinite loop when Usage Fault exception occurs */
while (1)
{
}
}
/**
* @brief This function handles SVCall exception.
* @param None
* @retval None
*/
void SVC_Handler(void)
{
}
/**
* @brief This function handles Debug Monitor exception.
* @param None
* @retval None
*/
void DebugMon_Handler(void)
{
}
/**
* @brief This function handles PendSVC exception.
* @param None
* @retval None
*/
void PendSV_Handler(void)
{
}
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
}
void GENERAL_TIM_INT_FUN(void)
{
// 当要被捕获的信号的周期大于定时器的最长定时时,定时器就会溢出,产生更新中断
// 这个时候我们需要把这个最长的定时周期加到捕获信号的时间里面去
if(TIM_GetITStatus(GENERAL_TIM,TIM_IT_Update) != RESET)
{
TIM_ICUserValueStructure.Capture_Period++;
TIM_ClearITPendingBit(GENERAL_TIM,TIM_IT_Update);
}
//上升沿捕获中断
if(TIM_GetITStatus(GENERAL_TIM,GENERAL_TIM_IT_CCx) != RESET)
{
//第一次捕获
if(0 == TIM_ICUserValueStructure.Capture_StartFlag)
{
TIM_ICUserValueStructure.Capture_CcrValue = 0;
TIM_ICUserValueStructure.Capture_FinishFlag = 0;
TIM_ICUserValueStructure.Capture_Period = 0;
//当一次捕获到上升沿之后,就把捕获边沿配置为下降沿
GENERAL_TIM_OCxPolarityConfig_FUN(GENERAL_TIM, TIM_ICPolarity_Falling);
//开始捕获标志位置1
TIM_ICUserValueStructure.Capture_StartFlag = 1;
}
//下降沿捕获中断
else
{
// 获取捕获比较寄存器的值,这个值就是捕获到的高电平的时间的值
TIM_ICUserValueStructure.Capture_CcrValue = GENERAL_TIM_GetCapturex_FUN(GENERAL_TIM);
//当第二次捕获开始之后,就把下降沿捕获配置为上升沿捕获
GENERAL_TIM_OCxPolarityConfig_FUN(GENERAL_TIM, TIM_ICPolarity_Rising);
//开始捕获标志位置0
TIM_ICUserValueStructure.Capture_StartFlag = 0;
//结束捕获标志位置1
TIM_ICUserValueStructure.Capture_FinishFlag = 1;
}
TIM_ClearITPendingBit(GENERAL_TIM,GENERAL_TIM_IT_CCx);
}
}
/**
* @brief This function handles PPP interrupt request.
* @param None
* @retval None
*/
/*void PPP_IRQHandler(void)
{
}*/
/**
* @}
*/
/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/
5.2.4 main.c
******************************************************************************
*/
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "bsp_GeneralTim.h"
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
uint32_t time;
// TIM 计数器的驱动时钟
uint32_t TIM_PscCLK = 72000000 / (GENERAL_TIM_PSC+1);
/*初始化USART 配置模式为 115200 8-N-1,中断接收*/
USART_Config();
//通用定时器初始化
GENERAL_TIM_Init();
printf ( "\r\nSTM32 通用定时器输入捕获实验\r\n" );
printf ( "\r\n按下K1,测试K1按下的时间\r\n" );
while(1)
{
if(1 == TIM_ICUserValueStructure.Capture_FinishFlag)
{
// 计算高电平时间的计数器的值
time = TIM_ICUserValueStructure.Capture_Period * (GENERAL_TIM_PERIOD+1) +
(TIM_ICUserValueStructure.Capture_CcrValue+1);
// 打印高电平脉宽时间
printf ( "\r\n测得高电平脉宽时间:%d.%d s\r\n",time/TIM_PscCLK,time%TIM_PscCLK );
TIM_ICUserValueStructure.Capture_FinishFlag = 0;
}
}
}
/*********************************************END OF FILE**********************/
主函数很简单,主要是一些初始化,然后在一个while 循环中打印测量的脉宽时间。在计算的时候,记得把周期GENERAL_TIMPERIOD 和Capture_CcrValue 的值都加1 后再运算,因为计数器是从开始计数的。
5.2.5 实验结果
六、输入捕获实验,用于测量PWM信号
前面我们讲解了用输入捕获测量了信号的脉宽,这一节我们讲输入捕获的一个特例——PWM输入。普通得输入捕获可以使用定时器的四个通道,一路捕获占用一个捕获寄存器,而PWM输入则只能使用两个通道,即通道1和通道2,且一路PWM输入要占用两个捕获寄存器,一个用于捕获周期,一个用于捕获占空比。在本实验中,我们使用通用定时器产生一路PWM信号,然后用高级定时器的通道1或2来捕获。
6.1 硬件设计
实验中用到两个引脚,一个是通用定时器TIM3的通道1,即PA6,用于输出PWM信号,另一个是高级控制定时器TIM1 的通道1,即PA8,用于PWM 输入捕获,实验中直接用一根杜邦线短接即可PA6 和PA8 即可,同时可用示波器监控PA6 的波形,看看实验捕获的数据是否正确。
6.2 软件设计
编程要点:
(1)通用定时器产生PWM配置
(2)高级定时器PWM输入配置
(3)编写中断服务程序,计算测量的频率和占空比,并打印出来比较。
编程的要点主要分成两部分,一个是通用定时器的PWM信号输出,另一个是PWM信号输入捕获。
6.2.1 bsp_GeneralTim.h
#ifndef __BSP_GENERALTIME_H
#define __BSP_GENERALTIME_H
#include "stm32f10x.h"
/************通用定时器TIM参数定义,只限TIM2、3、4、5************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 我们这里默认使用TIM3
#define GENERAL_TIM TIM3
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM3
#define GENERAL_TIM_Period 9
#define GENERAL_TIM_Prescaler 71
// TIM3 输出比较通道1
#define GENERAL_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOA
#define GENERAL_TIM_CH1_PORT GPIOA
#define GENERAL_TIM_CH1_PIN GPIO_Pin_6
// TIM3 输出比较通道2
#define GENERAL_TIM_CH2_GPIO_CLK RCC_APB2Periph_GPIOA
#define GENERAL_TIM_CH2_PORT GPIOA
#define GENERAL_TIM_CH2_PIN GPIO_Pin_7
// TIM3 输出比较通道3
#define GENERAL_TIM_CH3_GPIO_CLK RCC_APB2Periph_GPIOB
#define GENERAL_TIM_CH3_PORT GPIOB
#define GENERAL_TIM_CH3_PIN GPIO_Pin_0
// TIM3 输出比较通道4
#define GENERAL_TIM_CH4_GPIO_CLK RCC_APB2Periph_GPIOB
#define GENERAL_TIM_CH4_PORT GPIOB
#define GENERAL_TIM_CH4_PIN GPIO_Pin_1
/**************************函数声明********************************/
void GENERAL_TIM_Init(void);
#endif /* __BSP_GENERALTIME_H */
使用宏定义非常方便程序升级、移植。通过上面的宏,我们可以算出PWM 信号的频率F 为: 72M/( 10*72 )=100KHZ , 占空比为GENERAL_TIM_CCR1/(GENERAL_TIM_PERIOD+1)= 50%。
6.2.2 bsp_GeneralTim.c
#include "bsp_GeneralTim.h"
static void GENERAL_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 输出比较通道1 GPIO 初始化
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure);
// 输出比较通道2 GPIO 初始化
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH2_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH2_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GENERAL_TIM_CH2_PORT, &GPIO_InitStructure);
// 输出比较通道3 GPIO 初始化
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH3_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH3_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GENERAL_TIM_CH3_PORT, &GPIO_InitStructure);
// 输出比较通道4 GPIO 初始化
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH4_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH4_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GENERAL_TIM_CH4_PORT, &GPIO_InitStructure);
}
///*
// * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
// * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
// * 另外三个成员是通用定时器和高级定时器才有.
// *-----------------------------------------------------------------------------
// *typedef struct
// *{ TIM_Prescaler 都有
// * TIM_CounterMode TIMx,x[6,7]没有,其他都有
// * TIM_Period 都有
// * TIM_ClockDivision TIMx,x[6,7]没有,其他都有
// * TIM_RepetitionCounter TIMx,x[1,8,15,16,17]才有
// *}TIM_TimeBaseInitTypeDef;
// *-----------------------------------------------------------------------------
// */
/* ---------------- PWM信号 周期和占空比的计算--------------- */
// ARR :自动重装载寄存器的值
// CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
// PWM 信号的周期 T = (ARR+1) * (1/CLK_cnt) = (ARR+1)*(PSC+1) / 72M
// 占空比P=CCR/(ARR+1)
static void GENERAL_TIM_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
// 配置周期,这里配置为100K
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_Period;
// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM_Prescaler;
// 时钟分频因子 ,配置死区时间时需要用到
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
// 初始化定时器
TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);
/*--------------------输出比较结构体初始化-------------------*/
// 占空比配置
uint16_t CCR1_Val = 5;
uint16_t CCR2_Val = 4;
uint16_t CCR3_Val = 3;
uint16_t CCR4_Val = 2;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 配置为PWM模式1
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
// 输出使能
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
// 输出通道电平极性配置
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
// 输出比较通道 1
TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 输出比较通道 2
TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 输出比较通道 3
TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 输出比较通道 4
TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
TIM_OC4Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 使能计数器
TIM_Cmd(GENERAL_TIM, ENABLE);
}
void GENERAL_TIM_Init(void)
{
GENERAL_TIM_GPIO_Config();
GENERAL_TIM_Mode_Config();
}
/*********************************************END OF FILE**********************/
6.3.3 bsp_AdvanceTim.h
#ifndef __BSP_ADVANCETIME_H
#define __BSP_ADVANCETIME_H
#include "stm32f10x.h"
/************高级定时器TIM参数定义,只限TIM1和TIM8************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 这里我们使用高级控制定时器TIM1
#define ADVANCE_TIM TIM1
#define ADVANCE_TIM_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADVANCE_TIM_CLK RCC_APB2Periph_TIM1
// 输入捕获能捕获到的最小的频率为 72M/{ (ARR+1)*(PSC+1) }
#define ADVANCE_TIM_PERIOD (1000-1)
#define ADVANCE_TIM_PSC (72-1)
// 中断相关宏定义
#define ADVANCE_TIM_IRQ TIM1_CC_IRQn
#define ADVANCE_TIM_IRQHandler TIM1_CC_IRQHandler
// TIM1 输入捕获通道1
#define ADVANCE_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOA
#define ADVANCE_TIM_CH1_PORT GPIOA
#define ADVANCE_TIM_CH1_PIN GPIO_Pin_8
#define ADVANCE_TIM_IC1PWM_CHANNEL TIM_Channel_1
#define ADVANCE_TIM_IC2PWM_CHANNEL TIM_Channel_2
/**************************函数声明********************************/
void ADVANCE_TIM_Init(void);
#endif /* __BSP_ADVANCETIME_H */
6.4.4 bsp_AdvanceTim.c
#include "bsp_AdvanceTim.h"
/**
* @brief 高级控制定时器 TIMx,x[1,8]中断优先级配置
* @param 无
* @retval 无
*/
static void ADVANCE_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannel = ADVANCE_TIM_IRQ;
// 设置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
// 设置子优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/**
* @brief 高级定时器PWM输入用到的GPIO初始化
* @param 无
* @retval 无
*/
static void ADVANCE_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = ADVANCE_TIM_CH1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(ADVANCE_TIM_CH1_PORT, &GPIO_InitStructure);
}
///*
// * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
// * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
// * 另外三个成员是通用定时器和高级定时器才有.
// *-----------------------------------------------------------------------------
// *typedef struct
// *{ TIM_Prescaler 都有
// * TIM_CounterMode TIMx,x[6,7]没有,其他都有
// * TIM_Period 都有
// * TIM_ClockDivision TIMx,x[6,7]没有,其他都有
// * TIM_RepetitionCounter TIMx,x[1,8,15,16,17]才有
// *}TIM_TimeBaseInitTypeDef;
// *-----------------------------------------------------------------------------
// */
/* ---------------- PWM信号 周期和占空比的计算--------------- */
// ARR :自动重装载寄存器的值
// CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
// PWM 信号的周期 T = (ARR+1) * (1/CLK_cnt) = (ARR+1)*(PSC+1) / 72M
// 占空比P=CCR/(ARR+1)
/**
* @brief 高级定时器PWM输入初始化和用到的GPIO初始化
* @param 无
* @retval 无
*/
static void ADVANCE_TIM_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
ADVANCE_TIM_APBxClock_FUN(ADVANCE_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period=ADVANCE_TIM_PERIOD;
// 驱动CNT计数器的时钟 = Fck_int/(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输入模式时,需要占用两个捕获寄存器,一个测周期,另外一个测占空比
TIM_ICInitTypeDef TIM_ICInitStructure;
// 捕获通道IC1配置
// 选择捕获通道
TIM_ICInitStructure.TIM_Channel = ADVANCE_TIM_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 = 0;
// 初始化PWM输入模式
TIM_PWMIConfig(ADVANCE_TIM, &TIM_ICInitStructure);
// 当工作做PWM输入模式时,只需要设置触发信号的那一路即可(用于测量周期)
// 另外一路(用于测量占空比)会由硬件自带设置,不需要再配置
// 捕获通道IC2配置
// TIM_ICInitStructure.TIM_Channel = ADVANCE_TIM_IC1PWM_CHANNEL;
// TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
// TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;
// TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
// TIM_ICInitStructure.TIM_ICFilter = 0x0;
// TIM_PWMIConfig(ADVANCE_TIM, &TIM_ICInitStructure);
// 选择输入捕获的触发信号
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);
}
/**
* @brief 高级定时器PWM输入初始化和用到的GPIO初始化
* @param 无
* @retval 无
*/
void ADVANCE_TIM_Init(void)
{
ADVANCE_TIM_GPIO_Config();
ADVANCE_TIM_NVIC_Config();
ADVANCE_TIM_Mode_Config();
}
因为是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 的具体映射关系见下图,有直连和非直连两种。
6.5.5 stm32f10x_it.c
__IO uint16_t IC2Value = 0;
__IO uint16_t IC1Value = 0;
__IO float DutyCycle = 0;
__IO float Frequency = 0;
/*
* 如果是第一个上升沿中断,计数器会被复位,锁存到CCR1寄存器的值是0,CCR2寄存器的值也是0
* 无法计算频率和占空比。当第二次上升沿到来的时候,CCR1和CCR2捕获到的才是有效的值。其中
* CCR1对应的是周期,CCR2对应的是占空比。
*/
void ADVANCE_TIM_IRQHandler(void)
{
/* 清除中断标志位 */
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,因为计数器是从 0 开始计数的。
if (IC1Value != 0)
{
/* 占空比计算 */
DutyCycle = (float)((IC2Value+1) * 100) / (IC1Value+1);
/* 频率计算 */
Frequency = (72000000/(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输入的时序见图 33-21。中断复位函数中,我们获取输入捕获寄存器CCR1 和CCR2 寄存器中的值,当CCR1 的值不为0 时,说明有效捕获到了一个周期,然后计算出频率和占空比。在计算的时候CCR1和CCR2 的值都必须要加1,因为计数器是从0 开始计数的。
文章来源:https://www.toymoban.com/news/detail-794294.html
6.5.6 main函数
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "bsp_GeneralTim.h"
#include "bsp_AdvanceTim.h"
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
/* 串口初始化 */
USART_Config();
/* 通用定时器初始化,用于生成PWM信号 */
GENERAL_TIM_Init();
/* 高级定时器初始化 ,用户捕获PWM信号*/
ADVANCE_TIM_Init();
while(1)
{
}
}
main 函数非常简单,通用定时器初始化完之后用于输出PWM 信号,高级定时器初始化完之后用于捕获通用定时器输出的PWM信号。文章来源地址https://www.toymoban.com/news/detail-794294.html
到了这里,关于高级定时器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!