学习板:STM32F103ZET6
参考:
STM32F103五分钟入门系列(十二)定时器中断
STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试
STM32F103五分钟入门系列(五)按键实验(库函数+寄存器)
一、输入捕获简介
接触过Arduino的朋友应该知道,在Arduino中pulseIn()可以返回高电平、低电平持续时间,如以下代码:
int pin = 7;
unsigned long duration;
void setup()
{
pinMode(pin, INPUT);
}
void loop()
{
duration = pulseIn(pin, HIGH);;
}
本博通过输入捕获同样可以获取到输入脉冲的高低电平持续时间。那怎么来实现呢?
在STM32F1中,除了基本定时器TIM6和TIM7,其它定时器都具有输入捕获功能。通过检测对应通道TIMx_CHx 上的边沿信号,来获取电平跳变。如获取到上升沿后,记录下此时定时器的值(TIMx_CNT),再一次捕获到下降沿(TIM1~5、TIM8需要算法,不支持双边沿检测,本博(六)5中有说明)后记录下此时定时器的值,两者之间的差为定时器在一个高电平持续时间内计数的次数,这个次数除以定时器的时钟频率就得到了高电平持续时间。
检测到本次下降沿后记录定时器的值,到下次上升沿到来之时再次记录定时器的值,两者之差除以定时器时钟频率得到输入脉冲的低电平持续时间。
需要注意的是:当低电平、高电平持续时间很长,定时器多次重装载后才得到下一次电平跳变,此时就需要记录定时器重装载次数,否则会到导致计算结果出错。
基于上述思想,接下来从寄存器、库函数及举例测试来深入了解输入捕获的相关内容
二、输入捕获相关寄存器
1、自动重装载寄存器(TIMx_ARR)
该寄存器是16位寄存器,用来重装载计数器的初值,最大为0xffff。
2、 预分频器(TIMx_PSC)
该寄存器为16位寄存器,用来设置定时器时钟的。我们知道定时器2~7搭载在APB1外设上,初始状态下(系统时钟72MHZ、AHB预分频系数1、APB1预分频系数2、APB2预分频系数1),APB1时钟为36MHZ、APB2时钟为72MHZ。通过该寄存器设置预分频系数后得到定时器的时钟频率(默认状态TIM1 ~TIM8输入时钟都为72MHZ)。
该寄存器16位有效,即预分频系数为0~0xffff,即预分频系数可以为0 ~65535。如将该寄存器赋值为7199,则定时器的时钟频率为72MHZ/7200=10KHZ。
需要注意的是,当该寄存器为0时表示不分频,即分频因子为1,所以真正的分频因子是:该寄存器的值+1
3、捕获/比较模式寄存器 1、2(TIMx_CCMR1、TIMx_CCMR2)(滤波重点说明)
以捕获/比较模式寄存器 1(TIMx_CCMR1)为例说明,TIMx_CCMR2类似。
这个寄存器上一博客STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试中涉及到了,所以输入捕获的内容也比较好理解。
位1:0
①位1:0=01,CC1通道被配置为输入,IC1映射在TI1上,如下图所示:
②位1:0=10,CC1通道被配置为输入,IC1映射在TI2上,如下图所示:
③位1:0=11,CC1通道被配置为输入,IC1映射在TRC上,如下图所示:
可见该寄存器用来设置IC1通道来源的,同理CCMR1高8位的位9:8设置IC2的通道来源、CCMR2的位1:0设置IC3的通道来源、CCMR2的位9:8设置IC4的通道来源。
位3:2
这两位是设置检测频率的。当CC1E=’0’(TIMx_CCER寄存器中),即关闭了OC1输入,这两位就会复位。
位3:2=00:无预分频器,捕获输入口上检测到的每一个边沿都触发一次捕获; 位3:2=01:每2个事件触发一次捕获; 位3:2=10:每4个事件触发一次捕获; 位3:2=11:每8个事件触发一次捕获。
我们知道,当在对应通道识别到上升沿或下降沿时就会发生一次更新事件。如果这两位被设置为00,则每一次更新事件都捕获一次,记录定时器此时的值,同01、10、11。
位7:4
理解IC1F[3:0]之前,先明白各时钟关系:
默认状态下TIM1~ 8输入时钟为72MHZ。再经过预分频寄存器PSC设置分频作为定时器时钟:72MHZ/(1 ~65536)
这个时钟是用来计数,也只用来计数,与滤波、采样没有直接关系。
接下来就是滤波器(采样)频率=fDTS和采样频率fSAMPLING。
滤波(采样)频率=fDTS在CR1寄存器位9:8定义
可以看到: ①当CKD[1:0]=00时,滤波器(采样)频率=定时器时钟=72MHZ/(1 ~65536)
②当CKD[1:0]=01时,滤波器(采样)频率=定时器时钟×2=72MHZ/(1 ~65536)×2
③当CKD[1:0]=10时,滤波器(采样)频率=定时器时钟×4=72MHZ/(1 ~65536)×4
再回到CCMR1寄存器:
再强调一下,采样跟计数是两个线程,没有关联!
可以看到当IC1F[3:0]取不同值时,对输入信号的采样频率为滤波器(采样)频率/n。(注意是n,不是N)。而这个N也非常关键,表示采样几个周期。
为了方便理解,我们仔细的再写一遍:
1)位7:4=0000,无滤波器,以fDTS采样。 ①若CR1寄存器位9:8—>CKD[1:0]=00,即fDTS = fCK_INT,则通道1以定时器时钟×1的频率捕获输入。
②若CR1寄存器位9:8—>CKD[1:0]=01,即fDTS = fCK_INT×2,则通道1以定时器时钟×2的频率捕获输入。
③若CR1寄存器位9:8—>CKD[1:0]=10,即fDTS = fCK_INT×4,则通道1以定时器时钟×4的频率捕获输入。
2)位7:4=0001,采样频率fSAMPLING=fCK_INT,N=2
①若CR1寄存器位9:8—>CKD[1:0]=00,即fDTS = fCK_INT×1,则通道1以定时器时钟fCK_INT的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fCK_INT)捕获2次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采样频率(fCK_INT)捕获2次,若都为低电平,才表示下降沿捕获。
②若CR1寄存器位9:8—>CKD[1:0]=01,即fDTS = fCK_INT×2,则通道1以定时器时钟fCK_INT的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fCK_INT)捕获2次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采样频率(fCK_INT)捕获2次,若都为低电平,才表示下降沿捕获。
③若CR1寄存器位9:8—>CKD[1:0]=10,即fDTS = fCK_INT×4,则通道1以定时器时钟fCK_INT的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fCK_INT)捕获2次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采样频率(fCK_INT)捕获2次,若都为低电平,才表示下降沿捕获。
… … … 3)位7:4=0111,采样频率fSAMPLING=fDTS/4,N=8
①若CR1寄存器位9:8—>CKD[1:0]=00,即fDTS = fCK_INT×1,则通道1以定时器时钟/4的频率捕获输入。但是当捕获到上升沿后,还要以这个采样频率(fDTS/4=tCK_INT/4)捕获8次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采用频率(fDTS/4=fCK_INT/4)捕获8次,若都为低电平,才表示下降沿捕获。
②若CR1寄存器位9:8—>CKD[1:0]=01,即fDTS = fCK_INT×2,则通道1以定时器时钟/2的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fDTS/4=tCK_INT/2)捕获8次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采用频率(fDTS/4=tCK_INT/2)捕获8次,若都为低电平,才表示下降沿捕获。
③若CR1寄存器位9:8—>CKD[1:0]=10,即fDTS = fCK_INT×4,则通道1以定时器时钟×1的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fDTS/4=fCK_INT)捕获8次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采用频率(fDTS/4=fCK_INT)捕获8次,若都为低电平,才表示下降沿捕获。
… … … 4)位7:4=1110,采样频率fSAMPLING=fDTS/32,N=6
①若CR1寄存器位9:8—>CKD[1:0]=00,即fDTS = fCK_INT×1,则通道1以定时器时钟/32的频率捕获输入。但是当捕获到上升沿后,还要以这个采样频率(fDTS/32=tCK_INT/32)捕获6次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采用频率(fDTS/32=fCK_INT/32)捕获6次,若都为低电平,才表示下降沿捕获。
②若CR1寄存器位9:8—>CKD[1:0]=01,即fDTS = fCK_INT×2,则通道1以定时器时钟/16的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fDTS/32=tCK_INT/16)捕获6次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采用频率(fDTS/32=tCK_INT/16)捕获6次,若都为低电平,才表示下降沿捕获。
③若CR1寄存器位9:8—>CKD[1:0]=10,即fDTS = fCK_INT×4,则通道1以定时器时钟/8的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fDTS/32=fCK_INT/8)捕获6次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采用频率(fDTS/32=fCK_INT/8)捕获6次,若都为低电平,才表示下降沿捕获。 … … … 滤波的目的:在接收到外部脉冲输入后,可能会因为噪声使得某一时刻有一个冲激,如果不多次检测,这个冲激就会被检测为上升沿,造成干扰。
剩下的位15:8位设置通道2,CCMR2寄存器位7:0设置通道3、位15:8设置通道4,与通道1设置一样,不在赘述。
4、捕获/比较使能寄存器(TIMx_CCER)
位0
使能通道1,该位置1后(文档错误)使能通道1捕获。
位1
该位很重要,设置捕获事件发生在上升沿还是下降沿
当位1=0,捕获发生在上升沿 当位1=1,捕获发生在下降沿
剩下的位5:4、位9:8、位13:12分别设置通道2、通道3、通道4,与通道1设置一样,不在赘述。
5、DMA/中断使能寄存器(TIMx_DIER)
该寄存器使能各类请求
上图位4:0从低位到高位分别:使能定时器更新中断、使能CH1中断、使能CH2中断、使能CH3中断、使能CH4中断。
位6使能定时器触发中断(更新中断、触发中断区分),剩下的就是对DMA请求的使能,之后博客会专门总结DMA,这里先不总结了。
6、控制寄存器 1(TIMx_CR1)
该寄存器上一博客STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试,主要是对定时器的配置,这里也不在赘述。
7、 捕获/比较寄存器 1~4(TIMx_CCR1 ~4)
这个寄存器上一博客也总结过,不过只总结了输出比较的部分,现总结一下输入捕获。
若将通道设置为输入模式,则该寄存器存储上一次事件(上升沿或下降沿)时计数器的值。
CCR2、CCR3、CCR4分别对应CH2、CH3、CH4,不再赘述。
三、输入捕获相关库函数
1、输入捕获设置函数TIM_ICInit()
参数:
第一个参数:TIM_TypeDef* TIMx,选择哪个定时器
第二个参数:TIM_ICInitTypeDef* TIM_ICInitStruct
①TIM_Channel
选择通道,四个通道: ②TIM_ICPolarity
选择是上升沿捕获、下降沿捕获还是上升沿、下降沿都捕获(TIM1~5、TIM8不支持双边沿检测)
③TIM_ICSelection 选择通道来源,第一个参数已经选定了通道,TIM_ICSelection选择通道的来源,如下图通道1的三种来源: 若选择参数:TIM_ICSelection_DirectTI,则由TI1作为IC1来源。 若选择参数:TIM_ICSelection_IndirectTI,则由TI2作为IC1来源。 若选择参数:TIM_ICSelection_TRC,则由TRC作为IC1来源。
当然若第一个参数选择通道2,则: 若选择参数:TIM_ICSelection_DirectTI,则由TI2作为IC1来源。 若选择参数:TIM_ICSelection_IndirectTI,则由TI1作为IC1来源。 若选择参数:TIM_ICSelection_TRC,则由TRC作为IC1来源。
④TIM_ICPrescaler
设置捕获次数,是对CCMR寄存器位3:2的操作。 参数对于关系一目了然,表示每X个事件触发一次捕获。
⑤TIM_ICFilter
选择滤波器,是对CCMR寄存器位7:4的设置
这个参数自己写就行,0~0xf之间的数。需要注意的是这个值不能随便写,比如捕获上升沿时,高电平持续时间很短,大概2个计数器时钟周期,此时就不能N>1了,毕竟还没采样完,高电平持续时间就结束了。
2、设置捕获通道极性函数TIM_OC1PolarityConfig()
参数: 第一个参数:TIM_TypeDef* TIMx,选择哪个定时器
第二个参数 uint16_t TIM_OCPolarity
设置极性,即设置捕获发生在上升沿还是下降沿,是对CCER寄存器位1的操作。 位1为0时捕获发生在上升沿 位1位1时捕获发生在下降沿
3、使能捕获和更新中断函数TIM_ITConfig()
参数:
第一个参数: TIM_TypeDef* TIMx,选择哪个定时器
第二个参数: uint16_t TIM_IT,选择通道x中断、更新中断,是对DIER寄存器的位0和为4:1、位6(触发中断这里不用)操作。
第三个参数:FunctionalState NewState,使能ENABLE
4、获取中断和捕获状态标志函数TIM_GetITStatus()、TIM_GetFlagStatus()
这个函数在STM32F103五分钟入门系列(十二)定时器中断 中总结过,而且在那篇博客中还强调了相比于TIM_GetITStatus(),TIM_GetFlagStatus()函数多了是否重复捕获。不过TIM_GetITStatus()更加严谨。
函数体这里就不再总结了。主要总结一下捕获相关的用法:
判断是否为更新中断:
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET){}
判断是否发生捕获事件:
if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET){}
5、清除中断和捕获标志位函数TIM_ClearITPendingBit()、TIM_ClearFlag()
这个函数在STM32F103五分钟入门系列(十二)定时器中断 中也总结过,且强调过,重复捕获标记只有TIM_ClearFlag()函数拥有这个功能。其它东西两个函数都一样。
主要看一下用法:
清除中断标志位:
TIM_ClearITPendingBit(TIM5, TIM_IT_Update);
清除捕获标志位:
IM_ClearITPendingBit(TIM5, TIM_IT_CC1);
四、输入捕获编程顺序
1、使能定时器时钟RCC_APB1PeriphClockCmd()、RCC_APB2PeriphClockCmd()
定时器2~7挂载在APB1上,定时器1和8挂载在APB2上。如使能定时器5:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
2、使能GPIO时钟RCC_APB2PeriphClockCmd()
如使能GPIOA:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
3、使能复用时钟
4、重映射设置(如需要)
5、初始化定时器TIM_TimeBaseInit()
用TIM_TimeBaseInit()设置重装载、预分频系数、向上、相信计数模式
6、输入捕获参数设置 TIM_ICInit()
使用 TIM_ICInit()设置输入捕获通道、输入捕获极性、通道来源、分频系数、滤波器
7、使能捕获中断、更新中断TIM_ITConfig()
通过TIM_ITConfig()来使能定时器更新中断、通道捕获中断
8、设置中断分组NVIC_Init()
通过NVIC_Init()函数设置中断优先级、初始化中断。
9、使能定时器TIM_Cmd()
10、编写中断服务函数TIMx_IRQHandler()
五、例1+测试
1、题
使用定时器5通道2(PA1复用)检测PA1引脚输入PWM在1s内上升沿次数。其中输入的PWM为定时器4通道2输出500HZ、占空比为70%的PWM信号。(PB7复用)
硬件连接:将PA1与PB7引脚用飞线连接起来。
2、分析
定时器4通道2输出500HZ、占空比为50%的PWM信号参考上一博客,这里不再分析。
检测1s内上升沿次数,则可把定时器设置为1s。定时器5通道1为PA1的复用功能引脚。定时器5输入时钟为72MHZ,若将预分频系数设置为7199,则定时器5时钟为10kHZ,计数一次的时间为0.1ms,则可将重装载值设置为1s/0.1ms-1=9999。
设置定时器5通道1为捕获通道,且每个上升沿都触发。输入信号500HZ,则一个周期2ms,占空比为70%的话,每个高电平持续时间为1.4ms,为定时器时钟的14倍,即在一个高电平持续时间定时器计数14、一个低电平持续时间定时器计数6,次。所以可以用定时器时钟的大小作为采样频率,且采样2次作为滤波。
定时器每1s进入一次中断服务函数,则可在中断服务函数中输出1s内上升沿次数,采用串口输出,利用串口调试助手检测串口输出。
3、输出比较代码
输出比较代码上一篇博客都总结过,现直接附代码:
pwm.h代码:
#ifndef _PWM_
#define _PWM_
void PWM_Init(void);
#endif
pwm.c代码:
#include "pwm.h"
#include "stm32f10x.h"
void PWM_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_TimeBaseInitStructure.TIM_Period=19;
TIM_TimeBaseInitStructure.TIM_Prescaler=7199;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
TIM_OC2Init(TIM4,&TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_Cmd(TIM4, ENABLE);
}
4、输入捕获代码
(1)cap.h代码编写
cap.h固定格式:
#ifndef _CAP_
#define _CAP_
void CAP_Iint(void);
#endif
(2)cap.c代码编写
经查资料,发现定时器5通道2为PA1复用引脚,所以需设置PA1时钟和复用。
使能定时器5时钟、PA1时钟和复用时钟:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
设置PA1,为浮空输入:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
初始化定时器5:
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=9999;
TIM_TimeBaseInitStructure.TIM_Prescaler=7199;
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);
输入捕获参数设置:
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel=TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter=0x0001;
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_BothEdge;
TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
TIM_ICInit(TIM5,&TIM_ICInitStructure);
使能捕获输入中断、更新中断:
TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC2, ENABLE);
设置中断分组:
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
使能定时器:
TIM_Cmd(TIM5,ENABLE);
(3)中断服务函数
void TIM5_IRQHandler(void)
{
if(TIM_GetITStatus(TIM5, TIM_IT_CC2)!=RESET)
{
Rise_count++;
}
TIM5->SR&=~TIM_IT_CC2;
if(TIM_GetITStatus(TIM5, TIM_IT_Update)!=RESET)
{
printf("Rise_count:%d \r\n",Rise_count);
Rise_count=0;
}
TIM_ClearITPendingBit(TIM5, TIM_IT_CC2|TIM_IT_Update);
}
}
解释一下: 定时器5进行输入捕获,因为使能了通道2的中断,所以每次捕获后后发生中断,进入中断服务函数,并且对Rise_count++,需要注意的是:由于没有对CCR1寄存器进行读操作,所以捕获中断标志不能自动清零,需要手动去清零,所以才有以下代码:
TIM5->SR&=~TIM_IT_CC2;
同时由于使能了更新中断,所以每计数1s需要自动重装载初值,发生更新事件,进入中断服务函数,所以当发生更新事件后,说明1s计数结束,输出上升沿次数Rise_count并重新归零。
无论是捕获事件中断还是更新事件中断,进入中断服务函数后都需要清除中断标志。
(4)完整代码
#include "cap.h"
#include "stm32f10x.h"
#include "usart.h"
u16 Rise_count=0;
void CAP_Iint()
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=9999;
TIM_TimeBaseInitStructure.TIM_Prescaler=7199;
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);
TIM_ICInitStructure.TIM_Channel=TIM_Channel_2;
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter=0x0001;
TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
TIM_ICInit(TIM5,&TIM_ICInitStructure);
NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC2, ENABLE);
TIM_Cmd(TIM5,ENABLE);
}
void TIM5_IRQHandler(void)
{
if(TIM_GetITStatus(TIM5, TIM_IT_CC2)!=RESET)
{
Rise_count++;
}
TIM5->SR&=~TIM_IT_CC2;
if(TIM_GetITStatus(TIM5, TIM_IT_Update)!=RESET)
{
printf("Rise_count:%d \r\n",Rise_count);
Rise_count=0;
}
TIM_ClearITPendingBit(TIM5, TIM_IT_CC2|TIM_IT_Update);
}
5、主函数代码
主函数中直接初始化各类函数即可
#include "stm32f10x.h"
#include "pwm.h"
#include "cap.h"
#include "usart.h"
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
PWM_Init();
TIM_SetCompare2(TIM4, 6);
CAP_Iint();
uart_init(115200);
while(1)
{
}
}
6、测试
注意板子PA1与PB7的飞线连接!
可以看到,对应500HZ的脉冲,其1s内上升沿为500次。
六、例2+测试
1、题
通过定时器5检测KEY_U按键按下的时间,并通过串口打印出来。
2、分析
通过该查找资料可知,KEY_UP连接在PA0引脚上,而定时器5通道1刚好是PA0引脚复用。按下、松开KEY_UP后PA0引脚都有一个电平跳变,定时器5通道1可以捕获到这个电平跳变。并且按下后PA0引脚变为高电平,所以配置PA0为下拉输入,在未按下时,下拉到低电平;且按下后为PA0检测到高电平,所以捕获上升沿。
按下后,下次松开的时间无法控制,如果这个时间比较长的话,定时器5会经过多次重装载初值,所以要记录好更新中断次数。
定时器5挂载在APB1外设上,由下图可知,定时器5的时钟频率为72MHZ。
所以不妨将定时器5的预分频系数设置为7199,则定时器5的时钟频率为10KHZ,即0.1ms计数一次。若将重装载值设置为9999,则每1s发生一次更新事件。
3、KEY_UP代码
之前博客总结过,直接附代码:
key.h:
#ifndef KEY_H
#define KEY_H
void KEY_Init(void);
#endif
key.c:
#include "stm32f10x.h"
#include "sys.h"
#include "key.h"
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct_A;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
GPIO_InitStruct_A.GPIO_Mode=GPIO_Mode_IPD;
GPIO_InitStruct_A.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStruct_A.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct_A);
}
4、CAP代码编写
cap.h固定格式,代码:
#ifndef _CAP_
#define _CAP_
void CAP_Iint(void);
#endif
cap.c中需使能PA0复用功能,配置定时器5通道1为输入捕获通道,可以滤波,为了防抖,可以去滤波。采用频率就采用定时器时钟频率,N=8,所以TIM_ICFilter=0x0011。
#include "cap.h"
#include "stm32f10x.h"
#include "usart.h"
u16 Rise_count=0;
void CAP_Iint()
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=9999;
TIM_TimeBaseInitStructure.TIM_Prescaler=7199;
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);
TIM_ICInitStructure.TIM_Channel=TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter=0x0011;
TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
TIM_ICInit(TIM5,&TIM_ICInitStructure);
NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1, ENABLE);
TIM_Cmd(TIM5,ENABLE);
}
为了记录更新事件次数(重装载初值次数),需要使能定时器5的更新中断,当然因为要捕获,所以也得使能定时器5通道1的捕获中断。这样,捕获到上升沿、更新事件后都会发生中断进入中断服务函数。
5、中断服务函数(核心思想)
首先要明白中断服务函数里面应该干啥。定时器计数到重装载值后归零产生中断,进入中断服务函数;定时器捕获到上升沿后会产生对应通道的中断,并进入中断服务函数。两种途径进入中断服务函数,所以都得考虑到。
再者,按下KEY_UP时间可能会很长,如10s…则定时器会多次重装载初值,这样计算时间的话,就不能单纯的下降沿计数器的-上升沿计数器值,这个多次重装载也得考虑到。
然后,还得判断下降沿,因为TIM1、TIM2、TIM3、TIM4、TIM5、TIM8不支持双边沿检测,可以看一下TIM_ICInit()函数:
所以要考虑下降沿怎么检测
最后还要考虑什么时候串口输出,要把握输出时机。
现附我自己写的一个代码(不是最优解 😐),然后再分析。
u16 updata_count=0;
u16 TIM5CH1_RISE_VAL=0;
u16 TIM5CH1_FALLVAL=0;
u16 flag=0;
void TIM5_IRQHandler(void)
{
if((TIM_GetITStatus(TIM5, TIM_IT_Update)!=RESET)&&( GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)))
{
updata_count++;
}
if(TIM_GetITStatus(TIM5, TIM_IT_CC1)!=RESET)
{
TIM5CH1_RISE_VAL=TIM_GetCapture1(TIM5);
flag=1;
}
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update);
}
while(1)
{
if((flag==1)&&(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==Bit_RESET))
{
time=(int)(0.1*((abs(TIM5CH1_FALLVAL-TIM5CH1_RISE_VAL))+10000*updata_count));
printf("Time=%d ms\r\n",time);
flag=0;
TIM5CH1_FALLVAL=0;
TIM5CH1_RISE_VAL=0;
updata_count=0;
}
}
上面程序中先定义了4个变量:
u16 updata_count=0;
u16 TIM5CH1_RISE_VAL=0;
u16 TIM5CH1_FALLVAL=0;
u16 flag=0;
第一个变量用来计算在高电平期间计数器重装载了几次 第二个变量用来储存上升沿时计数器的值 第三个变量用来储存下降沿时计数器的值 第四个变量用来判断下降沿
不管是计数器更新中断还是通道上升沿捕获中断,进入中断服务函数后,首先:
if((TIM_GetITStatus(TIM5, TIM_IT_Update)!=RESET)&&( GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)))
{
updata_count++;
}
即当有更新中断产生(重装载计数器),并且此时PA0输入是高电平,说明在高电平持续时间计数器重装载了初值,此时updata_count++。
接下来处理上升沿,当捕获到上升沿后,计数器的当前值自动保存在CCR1中,对CCR1读操作得到当前计数器的值,同时可以让SR寄存器的捕获状态标志清零,以便下次捕获中断。通过对CRR1寄存器读操作,SR寄存器捕获状态标志位自动清零,就不用专门去持续中清零了
因为没有下降沿捕获,所以无法直接判断产生下降沿的准确时间。所以定义变量flag,初始flag=0,当有捕获中断产生时,令flag=1,表示产生了捕获中断,此时是高电平。
if(TIM_GetITStatus(TIM5, TIM_IT_CC1)!=RESET)
{
TIM5CH1_RISE_VAL=TIM_GetCapture1(TIM5);
flag=1;
}
那怎么就是下降沿了呢?要实时检测PA0的电平,所以把判断函数放在主函数的while()循环中。当检测到flag=1(即发生过上升沿捕获)且PA0为低电平(原来高电平,下降沿后变为低电平),就说明一个完整的高电平持续时间结束。
再次获取计数器的值,根据前后计数器的值、重装载次数可以算出上升沿->下降沿总共计数多少个。而定时器时钟前面设置的10KHZ,即0.1ms计数一次,则可以求出上升沿->下降沿持续时间。
while(1)
{
if((flag==1)&&(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==Bit_RESET))
{
time=(int)(0.1*((abs(TIM5CH1_FALLVAL-TIM5CH1_RISE_VAL))+10000*updata_count));
printf("Time=%d ms\r\n",time);
flag=0;
TIM5CH1_FALLVAL=0;
TIM5CH1_RISE_VAL=0;
updata_count=0;
}
6、完整代码
cap.c代码+中的服务函数:
#include "cap.h"
#include "stm32f10x.h"
#include "usart.h"
u16 updata_count=0;
u16 TIM5CH1_RISE_VAL=0;
u16 TIM5CH1_FALLVAL=0;
u16 flag=0;
void CAP_Iint()
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=9999;
TIM_TimeBaseInitStructure.TIM_Prescaler=7199;
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);
TIM_ICInitStructure.TIM_Channel=TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter=0x0011;
TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
TIM_ICInit(TIM5,&TIM_ICInitStructure);
NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1, ENABLE);
TIM_Cmd(TIM5,ENABLE);
}
void TIM5_IRQHandler(void)
{
if((TIM_GetITStatus(TIM5, TIM_IT_Update)!=RESET)&&( GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)))
{
updata_count++;
}
if(TIM_GetITStatus(TIM5, TIM_IT_CC1)!=RESET)
{
TIM5CH1_RISE_VAL=TIM_GetCapture1(TIM5);
flag=1;
}
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update);
}
main.c代码:
#include "stm32f10x.h"
#include "cap.h"
#include "usart.h"
#include "key.h"
#include "math.h"
extern u16 flag;
extern u16 TIM5CH1_FALLVAL;
extern u16 TIM5CH1_RISE_VAL;
extern u16 updata_count;
u16 time=0;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
CAP_Iint();
KEY_Init();
uart_init(115200);
while(1)
{
if((flag==1)&&(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==Bit_RESET))
{
time=(int)(0.1*((abs(TIM5CH1_FALLVAL-TIM5CH1_RISE_VAL))+10000*updata_count));
printf("Time=%d ms\r\n",time);
flag=0;
TIM5CH1_FALLVAL=0;
TIM5CH1_RISE_VAL=0;
updata_count=0;
}
}
}
7、测试
按下KEY_UP键后一段时间松开,察看在串口检测器中的显示:
|