一、高级定时器简介
高级定时器的简介在前面一章已经介绍过,可以点击下面链接了解,在这里进行一些补充。
[STM32 - 野火] - - - 固件库学习笔记 - - -十二.基本定时器
1.1 功能简介
-
1、高级定时器可以向上/向下/两边计数,还独有一个重复计数器RCR。
两边计数:例如计数器从0累加计数到ARR的值,再从ARR的值递减至0,再累加到ARR,循环这个过程。
-
2、有4个GPIO,其中通道1~3还有互补输出GPIO。
-
3、时钟来自PCLK2,为72M,可实现65536分频。
基本定时器、通用定时器的时钟来自PCK1。
1.2 GPIO说明
可在芯片对应的参考数据手册->引脚说明章节进行查询
二、高级定时器功能框图
2.1 时钟源
高级定时器有4个时钟源可选:
-
1、内部时钟CK_INT;
-
2、外部时钟模式1:外部输入引脚TIx;
-
3、外部时钟模式2:外部触发输入ETR;
-
4、内部触发输入:ITRx;
2.1.1 内部时钟CK_INT
-
内部时钟源来自RCC的TIMx_CLK。
-
内部时钟源TIMx_CLK的时钟为72M。
一般情况下,都是使用内部时钟。
2.1.2 外部时钟模式1
-
1、时钟信号输入引脚:当使用外部时钟模式 1 的时候,时钟信号来自于定时器的输入通道,总共有 4 个,分别为TIMx_CH1/2/3/4。
根据TIM_CCMRx寄存器的位 CCxS[1:0] 配置具体使用哪一路信号。
-
2、滤波器:可以使用滤波器对信号进行重新采样,以达到降频或去除高频干扰的目的。
由TIMx_CCMRx寄存器的位 ICxF[3:0]配置。
-
3、边沿检测器:决定是上升沿有效还是下降沿有效。
由 TIMx_CCER寄存器的位 CCxP 和 CCxNP 配置。
-
4、触发选择:可以选择是滤波后的定时器输入1(TI1FP1)当触发源还是滤波后的定时器输入2(TI2FP2)当触发源。
由 TIMxSMCR寄存器的位 TS[2:0] 配置。
-
5、从模式选择:选定了触发源信号后,把信号连接到 TRGI 引脚,让触发信号成为外部时钟模式 1 的输入,最终成为CK_PSC。
由 TIMx_SMCR寄存器 的位 SMS[2:0]配置。
总结一下,外部时钟模式1中时钟信号->CK_PSC的过程:
-
选择时钟信号输入引脚TIMx_CH1/2/3/4;
-
通过滤波器将高频信号降为低频信号,也可以不滤波;
-
经过边沿检测器选择上升有效还是下降有效;最终产生两路触发信号(TI1FP1、TI2FP2);
-
通过寄存器TIMx_SMCR的TS[2:0]选择哪一路连接到TRGI成为触发信号;由TIMx_SMCR的SMS[2:0]位来选择外部时钟模式1;
-
连接到CK_PSC,最终驱动预分频器经过分频后成为计数器的时钟。
2.1.3 外部时钟模式2
-
1、时钟信号输入引脚:当使用外部时钟模式 2 的时候,时钟信号来自于定时器的特定输入通道 TIMx_ETR,只有 1 个。
-
2、外部触发极性:来自 ETR 引脚输入的信号可以选择为上升沿或者下降沿有效。
由 TIMx_SMCR寄存器的位 ETP 配置。
-
3、外部触发预分频器:由于 ETRP 的信号的频率不能超过 TIMx_CLK(72M)的 1/4,当触发信号的频率很高的情况下,就必须使用分频器来降频。
- ETRP:经过外部触发预分频器处理后的信号。
由 TIMx_SMCR寄存器 的位 ETPS[1:0] 配置。
-
4、滤波器:可以使用滤波器对信号进行重新采样(通过另外一个时钟很大的信号对ETRP进行采样),以达到降频或去除高频干扰的目的。
- f~DTS~时钟频率由TIMx_CR1寄存器的CKD[1:0]控制,与f~CK_INT~相关。
由 TIMx_SMCR寄存器 的位 ETF[3:0] 配置。
-
5、从模式选择:经过滤波器后的时钟ETRF,连接到 ETRF 引脚,让ETRF信号成为外部时钟模式 2 的输入,最终成为CK_PSC。
由TIMx_SMCR寄存器 的位 ECE 配置。
总结一下,外部时钟模式2中时钟信号->CK_PSC的过程:
-
通过ETR引脚输入时钟信号;
-
选择为上升沿有效还是下降沿有效;
-
通过分频器分频,经过分频器后的信号为ETRP;
-
通过滤波器对信号进行重新采样,经过滤波器后的信号为ETRF;
-
将ETRF信号连接到ETRF引脚,由 TIMx_SMCR寄存器 的位 ETF[3:0] 来选择外部时钟模式2;
-
连接到CK_PSC,最终驱动预分频器经过分频后成为计数器的时钟。
2.1.4 内部触发输入
内部触发输入是使用一个定时器作为另一个定时器的预分频器。
硬件上高级控制定时器和通用定时器在内部连接在一起,可以实现定时器同步或级联。
高级定时器的时钟可以给通用定时器提供时钟。
2.2 控制器
-
控制器用于控制定时器的复位、使能、计数、出发DAC等功能。
-
主要使用CR1、CR2、SMCR、CCER这几个寄存器。
2.3 时基
-
1/、CK_PSC 分频后得 CK_CNT(计数器时钟) 驱动计数器计数,计数最大值存储在ARR(自动重装载寄存器)中;
-
2.1、如果没有使用重复计数器,计数器计数到ARR的值时,会产生一个更新中断,计数值会被清零;
-
2.2、如果使用重复计数器,计数器计数到ARR的值时,计数器清零,重复计数器值+1,当重复计数器的值计数到REP寄存器存放的值时再产生中断。
2.4 输入捕获(IC:Input Compare)
-
原理:当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR寄存器中,把前后两次捕获到的CCR寄存器中的值相减,就可以算出脉宽或者频率。
-
作用:用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数。
边沿信号输入引脚,一旦有边沿,例如有一个上升沿,输入滤波器和边沿检测器就会检测到这个上升沿,让输入捕获电路产生动作(检测电平跳变,执行动作,控制后续电路,让当前CNT的值锁存到CCR寄存器中)。
2.4.1 输入通道
需要被测量的信号从定时器的外部引脚 TIMx_CH1/2/3/4 进入,通常叫TI1/2/3/4。
2.4.2 输入滤波和边沿检测
-
输入滤波器:当输入的信号存在高频干扰的时候,我们需要对输入信号进行滤波,即进行重新采样。
- 采样的频率必须大于等于两倍的输入信号。
-
边沿检测器:设置信号在捕获的时候是什么边沿有效,可以是上升沿,下降沿,或者是双边沿。
2.4.3 捕获通道
捕获通道就是图中的 IC1/2/3/4,每个捕获通道都有相对应的捕获寄存器 CCR1/2/3/4,当发生捕获的时候,计数器CNT的值就会被锁存到捕获寄存器中。
-
输入通道与捕获通道的区别:
-
输入通道:用来输入信号;
-
捕获通道:用来捕获输入信号的通道;
-
一个输入通道的信号可以同时输入给两个捕获通道。
比如输入通道TI1的信号经过滤波器、边沿检测器后的TI1FP1和TI1FP2可以同时进入到捕获通道IC1与IC2。
-
2.4.4 预分频器
ICx 的输出信号会经过一个预分频器,用于决定发生多少个事件时进行一次捕获。
2.4.5 捕获寄存器
经过预分频器的信号 ICxPS 是最终被捕获的信号,当发生捕获时(第一次),计数器 CNT 的值会被锁存到捕获寄存器 CCR 中,还会产生 CCxI 中断相应的中断位 CCxIF(在 SR 寄存器中)会被置位,通过软件或者读取 CCR 中的值可以将 CCxIF 清 0。
注意:如果发生第二次捕获(即CCR寄存器中已捕获计数器值且CCxIF标志位已置1),那么捕获溢出标志位CCxOF会被置位,且CCxOF只能通过软件清零。换句话说:当CCxOF被置位时,说明有没被读取的捕获值。
2.4.6 PWMI模式
-
介绍:PWMI(Pulse Width Modulation Input Mode)模式:也就是PWM的输入模式,是专门为测量PWM频率和占空比设计的,通过硬件自动捕获信号的上升沿和下降沿,极大简化了测量逻辑。
-
原理:利用定时器的两个输入通道(只能为通道1和通道2),对同一个引脚减小捕获,可以测量频率与占空比。
-
频率测量:通过捕获两个上升沿之间的时间差,计算信号周期。
-
占空比测量:通过捕获上升沿到下降沿的时间差(高电平时间),结合周期计算占空比。
-
为什么使用PWMI模式的时候只能使用通道TIMx_CH1、TIMx_CH2?
触发信号连接到控制器里只能是TI1FP1或TI2FP2,这两个触发信号只能由TIMx_CH1、TIMx_CH2产生,因此只能是TIMx_CH1、TIMx_CH2。
-
TIMx_CH1与TIMx_CH2交叉的作用与目的:
-
一个通道灵活切换两个引脚;
-
两个通道同时捕获一个引脚。
-
2.5 输出比较(OC:Output Compare)
-
原理:通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作。
-
作用:用于输出一定频率和占空比的PWM波形。
2.5.1 输出比较寄存器
当计数器 CNT 的值跟比较寄存器 CCR 的值相等的时候,输出参考信号 OCxREF 的信号的极性就会发送改变,并且会产生比较中断 CCxI,相应的标志位 CCxIF(SR 寄存器中)会置位。
OCxREF 再经过一系列的控制之后就成为真正的输出信号 OCx/OCxN。
2.5.2 死区发生器
这是一个半桥驱动电路。
在这个半桥驱动电路中,Q1管导通,Q2管截止。现在要让Q1管截止,Q2管导通。如果在Q1管关闭之后立马打开Q2管,由于MOS管的存在,会在一段时间内相当于Q1管和Q2管都导通了,造成短路。
而死区生成电路能避免上述情况的发生:在上管关闭的时候,延迟一小段时间,再导通下管;下管关闭的时候,延迟一小段时间,再导通上管。
2.5.3 输出控制
在输出比较的输出控制中,参考信号 OCxREF 在经过死区发生器之后会产生两路带死区的互补信号 OCx_DT 和 OCxN_DT,这两路带死区的互补信号就进入输出控制电路。
通道 1~3 才有互补信号,通道 4 没有,其余跟通道 1~3 一样。
- 输出模式控制器的输入是CNT与CCR的大小关系,输出是REF的高低电平。
进入输出控制电路的信号会被分成两路,一路是原始信号OCx,一路是被反向的信号OCxN,再根据寄存器配置选择是否由OCx/OCxN引脚输出到外部引脚CHx/CHxN。
如果加入了断路(刹车)功能,则断路和死区寄存器 BDTR 的 MOE、 OSSI 和 OSSR 这三个位会共同影响输出的信号。
通用定时器的输出比较通道,与高级定时器的相比少了死区发生器部分。
2.5.4 输出引脚
输出比较的输出信号最终是通过定时器的外部 IO 来输出的,分别为 CH1/2/3/4,其中前面三个通道还有互补的输出通道 CH1/2/3N。
2.5.5 输出比较模式
模式 | 描述 |
---|---|
冻结 | CNT=CCR时,REF保持为原状态 |
匹配时置有效电平 | CNT=CCR时,REF置有效电平 |
匹配时置无效电平 | CNT=CCR时,REF置无效电平 |
匹配时电平翻转 | CNT=CCR时,REF电平翻转 |
强制为无效电平 | CNT与CCR无效,REF强制为无效电平 |
强制为有效电平 | CNT与CCR无效,REF强制为有效电平 |
PWM模式1 | 向上计数:CNT<CCR时,REF置有效电平;CNT≥CCR时,REF置无效电平 向下计数:CNT>CCR时,REF置无效电平;CNT≤CCR时,REF置有效电平 |
PWM模式2 | 向上计数:CNT<CCR时,REF置无效电平;CNT≥CCR时,REF置有效电平 向下计数:CNT>CCR时,REF置有效电平;CNT≤CCR时,REF置无效电平 |
-
冻结模式:保持REF不变,维持上一个状态。
例如:你正在输出PWM波,想暂停一会输出,可以切换为冻结模式,输出暂停,高低电平维持在暂停时的状。
-
匹配时置有效电平/匹配时置无效电平:有效电平可以理解为高电平,无效电平是低电平,与关断、刹车这些功能配合表述的。
匹配时置有效电平/匹配时置无效电平是一次性的,不适合输出连续变化的波形。
-
匹配时电平翻转:可以输出占空比为50%的PWM波形。
-
强制为无效电平/有效电平:你想暂停输出波形,并且在暂停期间保持高电平或低电平,可以使用这个模式。
-
PWM模式1/PWM模式2:可用于输出频率和占空比都可调的PWM波形。
PWM模式2实际上就是PWM模式1输出的取反。
REF输出之后还有一个极性配置,使用PWM模式1的正极性与PWM模式2的反极性最终的输出是一样的。
2.6 断路(刹车)功能
断路功能就是电机控制的刹车功能,使能断路功能时,根据相关控制位状态修改输出信号电平。
在任何情况下, OCx 和 OCxN 输出都不能同时为有效电平,这关系到电机控制常用的 H 桥电路结构原因。
断路源可以是时钟故障事件,由内部复位时钟控制器中的时钟安全系统 (CSS) 生成,也可以是外部断路输入 IO,两者是或运算关系。
这个异或门是为三相无刷电机服务的:无刷电机有3个霍尔传感器检测转子的位置,根据转子的位置进行换相。
当三个输入引脚的任何一个有电平翻转时,输出引脚就产生一次电平翻转。
经过异或门的输出信号通过数据选择器,再进入输入捕获通道1。
-
数据选择器如果选择上面,输入捕获通道1的输入就是3个引脚的异或值;
-
数据选择器如果选择下面,那么异或门就没有用,4个通道各用各的引脚。
2.7 输入捕获与输出比较
对于同一个定时器,输入捕获和输出比较只能使用其中一个,不能同时使用(输入捕获与输出比较共用CCR寄存器,通道引脚也共用)。
-
输出比较,引脚是输出端口;根据CNT和CCR的大小关系来执行输出动作。
-
输入捕获,引脚是输入端口;接收到输入信号,执行CCR锁存到CCR动作。
三、PWM
3.1 PWM简介
PWM:脉冲宽度调制。
-
作用:在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获取所需要的模拟参量,常应用于电机控速等领域。
PWM的频率越快,它等效的模拟信号就越平稳,同时性能开销就越大。
-
参数:
- 频率:F~req~ = 1/T~s~
PWM的频率越快,它等效的模拟信号就越平稳,同时性能开销就越大。
- 占空比:Duty = T~ON~/T~S~
占空比决定了PWM等效出来的模拟电压大小:占空比越大,等效的模拟电压越趋近于高电平;占空比越小,等效的模拟电压越趋近于低电平,是线性的关系。
- 分辨率:占空比变化步距
比如占空比只能是1%、2%这样以1%的步距跳变,那么它的分辨率就是1%。
3.2 PWM波形的产生
以通用定时器来说明一下PWM波形是如何产生的。
图自江协科技。
配置完时基单元后,CNT开始不断自增运行。
CCR捕获/比较寄存器中的值是我们自己设置的,不断比较CCR的值与CNT的值通过输出模式控制器输出REF信号。
上图中黄线表示ARR,红线表示CCR,蓝线表示CNT;
下图为产生的PWM波形。
-
当CNT<CCR时REF置有效电平,GPIO输出高电平;
-
当CNT≥CCR时REF置五效电平,GPIO输出低电平;
-
当CNT溢出清零后,又输出高电平;
3.3 PWM波形参数计算
-
PWM频率:F~req~ = CK_PSC/(PSC+1)/(ARR+1)
-
PWM占空比:Duty = CCR/(ARR+1)
-
PWM分辨率:R~eso~ = 1/(ARR+1)
四、实验
4.1 PWM互补输出实验
4.1.1 硬件设计
使用高级定时器 TIM1 的通道2及其互补通道作为本实验的波形输出通道,对应选择 PC7 和 PB0 引脚。
PB0引脚接到绿灯上,方便查看实验现象。
为增加断路功能,需要用到 TIM8_BKIN 引脚,这里选择 PA6引脚。程序我们设置该引脚为高电平有效,当 BKIN 引脚被置低电平的时候,两路互补的 PWM 输出就被停止,就好像是刹车一样。
4.1.2 软件设计
编程要点:
-
1、定时器用到的 GPIO 初始化
-
2、定时器时基结构体 TIM_TimeBaseInitTypeDef 初始化
-
3、定时器输出比较结构体 TIM_OCInitTypeDef 初始化
-
4、定时器刹车和死区结构体 TIM_BDTRInitTypeDef 初始化
c
// AdvancedTimer.c文件
#include "AdvancedTimer.h"
static void ADVANCED_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_IN_FLOATING;
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 ADVANCED_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_OC2Init(ADVANCE_TIM, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(ADVANCE_TIM, TIM_OCPreload_Enable); // 使能 OC1 预装载功能
/*-------------------刹车和死区结构体初始化-------------------*/
// 有关刹车和死区结构体的成员具体可参考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 ADVANCED_TIMER_Init(void)
{
ADVANCED_TIM_GPIO_Config();
ADVANCED_TIM_Mode_Config();
}
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
当使用短断路(刹车)功能时,两路PWM信号会被强制禁止,静止之后输出上面两个参数设定的状态:Set为高电平,Reset为低电平。
如果不用断路(刹车)功能,可以不配置这两个参数。
- 死区时间计算:参数设置为11(二进制:0000 1011)。
c
// AdvancedTimer.h文件
#ifndef __ADVANCEDTIMER_H
#define __ADVANCEDTIMER_H
#include "stm32f10x.h"
/************高级定时器TIM参数定义,只限TIM1和TIM8************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 这里我们使用高级控制定时器TIM8
#define ADVANCE_TIM TIM8
#define ADVANCE_TIM_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADVANCE_TIM_CLK RCC_APB2Periph_TIM8
// PWM 信号的频率 F = TIM_CLK/{(ARR+1)*(PSC+1)}
#define ADVANCE_TIM_PERIOD (8-1) // ARR的值
#define ADVANCE_TIM_PSC (9-1) // 分频因子
#define ADVANCE_TIM_PULSE 4 // 占空比 = ADVANCE_TIM_PULSE/(ADVANCE_TIM_PERIOD+1)
#define ADVANCE_TIM_IRQ TIM1_UP_IRQn
#define ADVANCE_TIM_IRQHandler TIM1_UP_IRQHandler
// TIM1 输出比较通道
#define ADVANCE_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADVANCE_TIM_CH1_PORT GPIOC
#define ADVANCE_TIM_CH1_PIN GPIO_Pin_7
// TIM1 输出比较通道的互补通道
#define ADVANCE_TIM_CH1N_GPIO_CLK RCC_APB2Periph_GPIOB
#define ADVANCE_TIM_CH1N_PORT GPIOB
#define ADVANCE_TIM_CH1N_PIN GPIO_Pin_0
// TIM1 输出比较通道的刹车通道
#define ADVANCE_TIM_BKIN_GPIO_CLK RCC_APB2Periph_GPIOA
#define ADVANCE_TIM_BKIN_PORT GPIOB
#define ADVANCE_TIM_BKIN_PIN GPIO_Pin_6
/**************************函数声明********************************/
void ADVANCED_TIMER_Init(void);
#endif /* __ADVANCEDTIMER_H */
c
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_rccclkconfig.h"
#include "bsp_systick.h"
#include "AdvancedTimer.h"
int main(void)
{
LED_G_GPIO_Config();
LED_B_GPIO_Config();
LED_R_GPIO_Config();
ADVANCED_TIMER_Init();
GPIO_SetBits(LED_PROT, GPIO_Pin_All);
while(1)
{
}
}
- PWM信号一般的频率为10~25K。
4.2 脉宽测量输入捕获实验
4.2.1 原理:
-
在上升沿捕获,进入中断服务函数中将CNT清零,并将捕获边沿改为下降沿;
-
在下降沿捕获,读取CCR寄存器中的值,并将捕获边沿改为上升沿;
4.2.2 难点
-
1、在中断服务函数中需要不断修改捕获的边沿:上升沿处理完改为下降沿;下降沿处理完改为上升沿。
-
2、在中断服务函数中记录产生了多少次更新自动。(定时器16位,只能记录65535个数,如果记一个数的时间是1us的话只能计65.536ms)
4.2.3 硬件设计
-
在开发板中 PA0 接的是一个按键,默认接 GND,当按键按下的时候 IO 口会被拉高,这个时候我们可以利用定时器的输入捕获功能来测量按键按下的这段高电平的时间。
-
根据上面表33-1可知:PA0对应的是定时器5的通道1。
4.2.4 软件设计
编程要点:
-
1、定时器用到的 GPIO 初始化
-
2、定时器时基结构体 TIM_TimeBaseInitTypeDef 初始化
-
3、定时器输入捕获结构体 TIM_ICInitTypeDef 初始化
-
4、编写中断服务函数,读取捕获值,计算出脉宽的时间
c
// GeneralTimer.c文件
#include "GeneralTimer.h"
// 定时器输入捕获用户自定义变量结构体定义
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);
}
static void GENERAL_TIM_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
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_PSC;
// 时钟分频因子 ,配置死区时间时需要用到
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);
/*--------------------输入捕获结构体初始化-------------------*/
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|GENERAL_TIM_IT_CCx);
// 开启更新和捕获中断
TIM_ITConfig (GENERAL_TIM, TIM_IT_Update | GENERAL_TIM_IT_CCx, ENABLE );
// 使能计数器
TIM_Cmd(GENERAL_TIM, ENABLE);
}
void GENERAL_TIMER_Init(void)
{
GENERAL_TIM_GPIO_Config();
GENERAL_TIM_Mode_Config();
GENERAL_TIM_NVIC_Config();
}
c
// GeneralTimer.h文件
#ifndef __GENERALTIMER_H
#define __GENERALTIMER_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)
// 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
#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_TIMER_Init(void);
#endif /* __GENERALTIMER_H */
c
// stm32f10x_it.c文件
#include "GeneralTimer.h"
void GENERAL_TIM_INT_FUN(void)
{
// 当要被捕获的信号的周期大于定时器的最长定时时,定时器就会溢出,产生更新中断
// 这个时候我们需要把这个最长的定时周期加到捕获信号的时间里面去
if ( TIM_GetITStatus ( GENERAL_TIM, TIM_IT_Update) != RESET )
{
TIM_ICUserValueStructure.Capture_Period ++;
TIM_ClearITPendingBit ( GENERAL_TIM, TIM_FLAG_Update );
}
// 上升沿捕获中断
if ( TIM_GetITStatus (GENERAL_TIM, GENERAL_TIM_IT_CCx ) != RESET)
{
// 第一次捕获
if ( TIM_ICUserValueStructure.Capture_StartFlag == 0 )
{
// 计数器清0
TIM_SetCounter ( GENERAL_TIM, 0 );
// 自动重装载寄存器更新标志清0
TIM_ICUserValueStructure.Capture_Period = 0;
// 存捕获比较寄存器的值的变量的值清0
TIM_ICUserValueStructure.Capture_CcrValue = 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);
}
}
定时器所有中断的中断源都是TIMx_IRQn,在中断服务函数中来判断相应标志位决定是哪种中断。
c
// main.c文件
#include "stm32f10x.h"
#include "bsp_rccclkconfig.h"
#include "bsp_systick.h"
#include "usart.h"
#include "GeneralTimer.h"
uint32_t time = 0;
int main(void)
{
// TIM 计数器的驱动时钟
uint32_t TIM_PscCLK = 72000000 / (GENERAL_TIM_PSC+1);
GENERAL_TIMER_Init();
USART_Config();
while(1)
{
if(TIM_ICUserValueStructure.Capture_FinishFlag == 1)
{
// 计算高电平时间的计数器值
time = TIM_ICUserValueStructure.Capture_Period * (GENERAL_TIM_PERIOD + 1) + (TIM_ICUserValueStructure.Capture_CcrValue + 1);
printf("\r\n 高电平脉宽时间: %d.%d \r\n", time/TIM_PscCLK, time%TIM_PscCLK);
TIM_ICUserValueStructure.Capture_FinishFlag = 0;
}
}
}
-
TIM_ICUserValueStructure.Capture_Period * (GENERAL_TIM_PERIOD + 1):计算的是高电平脉宽跨越的完整定时器周期的总时间。
-
TIM_ICUserValueStructure.Capture_Period:表示高电平脉宽跨越了多少个完整的定时器周期。
-
GENERAL_TIM_PERIOD + 1:表示一个完整的定时器周期的计数值。因为定时器是从0计数到GENERAL_TIM_PERIOD,所以一个完整的周期是GENERAL_TIM_PERIOD + 1。
-
-
TIM_ICUserValueStructure.Capture_CcrValue + 1:表示在最后一个定时器周期内,高电平脉宽的具体计数值,因为定时器是从0开始计数的,所以实际的计数值需要加1。
-
time/TIM_PscCLK:
-
time是以定时器的时钟周期为单位的计数值;
-
TIM_PscCLK是定时器的时钟频率,定时器的时钟周期(单位是秒)为:1 / TIM_PscCLK;
-
高电平脉宽的实际时间(单位是秒)为:time * (1 / TIM_PscCLK),即 time / TIM_PscCLK。
-