技术交流QQ群【JAVA,C++,Python,.NET,BigData,AI】:170933152
咱们已经讲解了两个实验,一个是上面的定时器中断实验.
一个是下面的比较试验,也就是PWM输出实验.
然后今天看左边的输入捕获实验:
可以看到这里的,这个输入捕获的实验,这里
的时钟,还是由内部RC振荡器提供的,内部时钟来源,然后给右边的时基电路提供时钟来源
然后再下面的部分就是输入捕获的电路.
这里,讲解一下输入捕获的原理.
也就是他这里做了这样一个工作,
假设这里没有经过分频,也就是每次跳变都会检测上升沿,下降沿:
这个电路通过检测TIMX_CHX上的边沿信号,注意,这里的边沿信号,有上边沿信号和下边沿信号,
比如当跳变,产生有上边沿信号的时候,他会把这个上边沿信号的值,也就是TIMX_CNT这个值,
存入到TIMX_CCR寄存器中去.来完成一次捕获.
当产生下边沿的时候,也会把当前的值TIMX_CNT存入到,TIMX_CCR寄存器中去,这样
来完成一次捕获.
那么这里的,一个下降沿的捕获的值,减去,一个上升沿的捕获的值,一定程度上,就可以作为,这个高电平持续的时长,
也可以叫波的宽度.
或者,同样的也可以是低电平持续的时间.
这就是这个输入捕获的基本的工作过程.
接下来,拆解来看一下,
首先比如这里咱们用的是通道1,那么,信号经过通道1,以后通过滤波器,等下会说滤波器有什么用,然后
信号再经过边沿检测器,然后可以看到,比如这里检测到的是TI1F_Rising上升沿,经过选择器后0,TI1FP1,
然后,再经过选择器,这里标注01 10 11的地方,然后再经过分频器,最终输出一个信号,当输出信号的时候,
会触发中断,把当前捕获的值,TIMX_CNT存入到TIMX_CCR寄存器中去.
然后接下来,把这个图分成下面四个部分再进行讲解.
接下来先看滤波这里:
实际上,首先这里有个时钟,这个时钟就是FCK_INT,这个时钟就是来源于前面说的RC时钟振荡器,
这个时钟,
然后:
然后可以看到这里有个FDTS,这个和FCK_INT实际上是有关系的,他们的关系可以通过
TIMX_CR1寄存器的CKD这个位来设置,比如这个寄存器设置为00的时候,那么实际上
FDTS=FCK_INT这个时钟,注意FDTS是滤波用的时钟,而FCK_INT这个时钟是内部振荡器提供的时钟.
这个就是TIMX_CR1这个寄存器,可以看到有个CKD这个位.
注意这里:10 TDTS=4* TCK_INT 这个意思,实际上是
TDTS=1/4 * TCK_INT ,注意这里是四分之一,因为后面说了这个是分频比.
所以,这里的TIMX_CR1寄存器的的CKD位,配置的实际上就是FDTS和FCK_INT的关系,
这个FDTS的时钟就是滤波器的时钟频率,然后
可以看到TIMX_CCMR1这个寄存器,这个寄存器的IC1F位配置的是0011的时候,
并且这个时候设置IC1位,设置的是让它上升沿的时候捕获,那么比如说这里信号达到上升沿的时候,
在捕获到上升沿的时候,会以FCK_INT的频率,注意这个频率实际上经过分频的也可以说是FDTS的频率,
也就是,连续采样8次通道1上面的电平,如果8次采样都是高电平,才说明这是一个有效的
触发,这个时候就会触发输入捕获中断,这样做的目的其实就是为了防止信号抖动,来做个滤波的.
这里IC1F,输入捕获1滤波器可以通过文档查到,可以看设置是0011的时候,这里N=8
注意,可以看到这里,上升沿的时候,设置了IC1F以后,可以使用FDTS这个被FCK_INT被分频后的频率,被连续监测8次,
如果都是高电平,才确定是个有效的触发,这个实际上是一个防抖的操作.所以这里的滤波的概念,就是把电平的抖动,给
通过滤波滤掉.
这里实际上设置的就是,上升沿捕获,还是下降沿捕获,
通过设置TIMX_CCER寄存器的CC1P这个位,来设置,是上升沿的时候去捕获呢,还是下降沿的时候去捕获.
这里TIMX_CCMR1通过设置CC1S位来实现,设置为00的时候,就是把CC1通道设置为是输出的高低电平,
然后如果01的话,那么CC1这个通道就被设置为了输入,同时,IC1也就是最终输入的数据,他的来源设置在
定时器的TI1上,如果设置为10的话,CC1通道也就是最终捕获输入的数据,他的数据来源就把设置在
TI2这个定时器的通道上.
这里要注意,CC1是一个数据的出口,也就是说,一个定时器有4个通道,那么数据会通过
这4个通道,最终数据都会走到CC1这里.
这里也就是说可以通过配置实现,IC1的值是来至于TI2,还是TI1,还是TI3..等,这个是可以通过配置寄存器TIMX_CCMR
寄存器的CC1S位来进行配置的.
不过一般的情况就是TI1定时器通道就对应IC1,TI2通道就对应IC2,一般不去做特殊的配置.
这里的意思是,分频器,以通道1为例,也就是说,如果这里通过寄存器TIMX_CCMR1的ICPS位的1到0位,来配置
配置为01的时候,也就是说每2个事件触发一次捕获,意思就是说,跟上图中一样,每检测到两个上升沿的时候,再
触发一次中断.
也就是检测到第一次上升沿的时候,没有动作,再检测到第二个上升沿的时候才触发一次中断.
这里也就是一个分频的概念.
这个很好理解,就是说,可以配置TIMX_DIER寄存器,比如这里配置,CC1E,注意这里的CC1指的是哪个通道,
CC1就是通道1,E是使能的意思,也就是说配置了TIMX_DIER这个寄存器的CC1E这个位的话,就相当于
设置了设置允许捕获,还是不允许捕获.
如果设置了上升沿捕获,那么同时这里也设置了开启允许捕获,那么在不分频的情况下,每次上升沿都会
触发捕获中断.去获取电平值.
这里就是如何在文档中查看对应的引脚.
比如这里定时器5的通道2对应的PA1,定时器5的通道3对应的是PA2引脚.
这个对应的引脚要可以通过文档查出来.
接下来看看代码,对于输入捕获这个是如何配置的
首先来看看,要用到的初始化通道的函数:
这里第一个参数是选择哪个定时器,第二个参数是一个结构体,用来定义
第一个是设定定时器的通道是1,2,3,4,
第二个是设置捕获极性,高电平有效,还是低电平有效
第三个参数是设置映射关系,前面说的,IC1这个通道的数据,可以是定时器的TI1通道过来的,也可以是定时器TI2通道过来
的这个取决于设置.而这个地方就是设置这个的.下图中设置的:
第四个参数是分频系数.也就是下面的设置
第五个参数是滤波器..
然后下面,还有个极性设置,就是说设置是捕获上升沿,还是下降沿.
上面这个函数就是设置极性,就是上升沿捕获,还是下降沿捕获
他的参数有,第一个是哪个定时器,第二个是配置是上升沿捕获,还是下降沿捕获什么的
然后还可以去获取,捕获值,也就说,
也就说,捕获到的值,会存入到这个捕获比较寄存器,咱们可以读取,这个捕获比较寄存器的值.
接下来,要总结一下这个步骤
步骤是
1.初始化定时器和通道对应的IO口的时钟
2.初始化IO口,模式是输入模式.
3.然后初始化定时器的装载值ARR,和分频系数PSC这样就能确定,定时器的周期
4.然后初始化捕获通道,什么上升沿捕获,下降沿捕获什么的
5.然后开启捕获中断.
6.去使能定时器,
7.编写中断函数
说一下这个实验:
这里比如可以捕获WK_UP按键的电平,当按下电平的时候,为高电平
当松开按键的时候是低电平,然后
如果我们设置上升沿捕获,那么上升沿的时候,比如捕获值是0,那么下降沿的时候再去捕获
捕获值是A,那么用(A-0)然后再乘以一个周期时间也就是每个时钟周期的时间1/F
就是这个高电平持续的时间.
比如频率是72mhz,也就是震动72000 000次用1s,那么震动一次,也就是一个周期的时间是1/72000000
单单的通过上面说的方法去计算,这个高电平的持续时间,实际上是不对的,
注意可能会有上面的这样的情况,比如,假设如果一个高电平持续的时间非常长的话,比如长到,比
定时器从0计数到ARR的值,还要长,那么这个时候,比如第一次从0计数到ARR的值的时候,还是高电平
那么第二次从0计数到ARR值的时候,还是高电平,第三次从0计数到ARR值一半的时候,这个时候高电平结束了,
那么这个时候,因为第一次上升沿记录的值是0,然后第三次计数的值是ARR指的一半,这样计算的持续的时间
就成了ARR的一半减去0然后再乘以周期时间,那么很明显这是不对的,因为实际的持续时间应该是
(ARR的值+ARR的值+ARR的值的一半)再乘以周期,这样才能得出正确的高电平的持续时间.
所以针对这个问题,我们就做了一个更严谨的操作.
是这样实现的,用了这样一个8位的变量,
bit7是用来记录捕获完成的标志,bit6用来记录捕获到高电平的标志,
bit5到0,用来记录捕获高电平后定时器的次数.
那么这样的话,就是,每次溢出的时候,bit5到0位就会加1,捕获到高电平的时候也就是上部横着的部分的时候bit6就会是1,然后
捕获到下降沿的的时候bit7就会置1.
通过记录这几个值,就可以准确的计算出高电平持续的时间了.
上面是一个简单的解释,接下来,咱们看着代码,再来看
打开输入捕获实验的代码
首先看timer.h
这里主要看这个TIM5_Cap_Init()这个函数
定时器5通道1输入捕获的配置
和,下面这个定时器5通道,中断服务程序.
首先看这个:
TIM5_Cap_Init()这个函数,
这里,可以看到,首先
void TIM5_Cap_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//1.首先使能定时器和GPIO的时钟.
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //使能TIM5时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
//2.然后设置GPIOA的PA0为输入模式,因为
//这里WK_UP按键,松开的时候是低电平,按下的时候是高电平
//所以这里需要下拉
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 清除之前设置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //PA0 下拉
}
关于这里设置下拉输入,以前按键的实验时候也有讲过.
然后再去看一下:
初始化定时器5这个部分,之前已经说过了,这里就不多说了.
这里,设置TIM5的输入捕获参数
这个是重点说的
其实主要是设置上面的4个部分,
1.设置滤波器,是获取8次高电平才算是有效电平什么的,
2.然后设置边沿检测器,来设置上边沿触发,还是下边沿触发,
3.然后设置映射,是直接就过去,还是配置IC1的数据来源设置成TI2等
4.设置分频器,设置分频规则,是不分频还是怎么样
然后再看一下这个输入捕获初始化函数:
//初始化TIM5输入捕获参数
//1.可以看到,每个参数 TIM_Channel_1 第一个设置通道,确定用哪个通道,比如
//这里使用通道1
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01 选择输入端 IC1映射到TI1上
//2.设置是上升沿捕获,还是下降沿捕获
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
//3.这里TIM_ICSelection_DirectTI就是把通道1,CC1S直接映射到
//TI1这个定时器的通道1上._UnDirectTI,是把通道1映射到定时器的其他通道上
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
//4.这里TIM_ICPSC_DIV1,这是不分频也就是每个上升沿都会被捕获
//然后如果这地方是2,那么就是捕获到2个上升沿的时候,会触发中断.
//还有4,8什么的
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
//5.这里不滤波的意思是,不进行,对滤波进行设置,比如,连续采集8次都是高电平,才认为是有效
//的,这个操作.
TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
接下来,
//中断分组初始化
//1.开始设置中断分组初始化,设置中断定时器5的中断
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM5中断
//2.设置中断抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级2级
//3.设置响应优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
//4.设置优先级通道使能
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
//5.初始化中断优先级
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
//1.然后这里再配置,第一个是定时器,第二个是配置中断类型
//首先配置更新中断,就是说向上产生了ARR溢出会触发中断
//然后就是CC1,输入捕获中断,捕获到电平后的中断值
TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断
这样设置以后再去,使能定时器5,然后,相应的这个PA0口就开始捕获了.
TIM_Cmd(TIM5,ENABLE ); //使能定时器5
然后这里,如果设置了上升沿捕获,那么上升沿的时候就会触发捕获中断,然后
下降沿的时候,就会有下降沿中断
然后如果向上溢出了,还会有ARR溢出的,更新中断.
所以这个中断函数会复杂一些.
其实就是按照上面说的那个情况进行封装的.
u8 TIM5CH1_CAPTURE_STA=0; //输入捕获状态
u16 TIM5CH1_CAPTURE_VAL; //输入捕获值
//1.定时器5中断服务程序
void TIM5_IRQHandler(void)
{
//2.这个0x80,也就是1000 0000 也就是bit8是0 的时候
//也就是捕获完成标志位bit7还是0的时候,也就是还没有成功捕获
if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
{
//3.这里判断是不是更新溢出了,如果是溢出的话,也就是不是RESET的时候
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
{
//4.如果是更新溢出了,那么已经捕获到了高电平了
//
if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
{
//6.这里如果捕获到高电平,并且还没有成功捕获一次的
//话,也就是bit7还是0的时候,那么就一直++
//++的如果超过bit0到bit5了也就是0X 0011 1111
//那么这里就认为这个高电平实在是持续时间太长了,这个时候
//因为再+1就开始占用第6位了,bit6了,所以这个时候就认为
//他已经实现了一次完整的捕获,就把,
//bit7最高位设置为1,也就是0X 1000 0000
if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM5CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次
//7.然后当前值,设置为1111 1111
//这个初始值,也就是ARR的值.
TIM5CH1_CAPTURE_VAL=0XFFFF;
}else{
//5.如果已经捕获到高电平了的话,那么就
//记录一下捕获到高电平的次数
TIM5CH1_CAPTURE_STA++;
}
}
}
//8.如果这里发生了捕获事件的话,那么
if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)//捕获1发生捕获事件
{
//9.判断这个上次是不是0x 0100 0000
//bit6是1,也就是上次捕获到的是高电平,那么
//触发捕获中断的时候,就应该是下降沿捕获
if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
{
//10.每当捕获到一次下降沿的时候,就认为
//成功捕获了一次上升沿了
//就把bit7位置1
TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次上升沿
//11.然后读取捕获比较寄存器的值,也就是计数到哪里了
TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);
//12.然后因为这次是下降沿,所以这里再把这里设置为上升沿捕获
//
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
//13.这里如果捕获的不是下降沿就说明捕获的是上升沿
//那么,这里捕获到上升沿的时候,就要把当前值清空为0,
//然后把记录状态的变量也设置成0
//然后因为是上升沿捕获,所以下次就开启下降沿捕获就可以了
TIM5CH1_CAPTURE_STA=0; //清空
TIM5CH1_CAPTURE_VAL=0;
TIM_SetCounter(TIM5,0);
TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获
}
}
}
//14.因为每次中断会把中断位置1,所以这里使用完以后还要把中断位清除掉.
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}
然后这里,TIM5_Cap_Init(),这个第一个参数设置ARR的值,因为咱们这里
定时器是16位定时器,所以最大值就是0XFFFF
然后第二个是频率,咱们知道输入的频率是72Mhz所以,这里设置预分频系数为72M的话,
其实就是以1Mhz的频率进行计数.
extern u8 TIM5CH1_CAPTURE_STA; //输入捕获状态
extern u16 TIM5CH1_CAPTURE_VAL; //输入捕获值
int main(void)
{
u32 temp=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
TIM3_PWM_Init(899,0); //不分频。PWM频率=72000/(899+1)=80Khz
//1.上面都是以前的代码,上一讲的,不明白可以再看看上一讲的
//下面这里是
//然后这里,TIM5_Cap_Init(),这个第一个参数设置ARR的值,因为咱们这里
//定时器是16位定时器,所以最大值就是0XFFFF
//然后第二个是频率,咱们知道输入的频率是72Mhz所以,这里设置预分频系数为72M的话,
//其实就是以1Mhz的频率进行计数.
TIM5_Cap_Init(0XFFFF,72-1); //以1Mhz的频率计数
while(1)
{
delay_ms(10);
//2.下面是上一讲的内容,输出PWM波的
TIM_SetCompare2(TIM3,TIM_GetCapture2(TIM3)+1);
if(TIM_GetCapture2(TIM3)==300)TIM_SetCompare2(TIM3,0);
//3.这里捕获到一次上升沿以后,就表示完成了一次捕获,bit7
//位变成1了,完成一次捕获以后.
if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿
{
//4.这里首先要获取TIM5CH1_CAPTURE_STA,变量的
//低bit5到bit0也就是,高电平溢出了几次
temp=TIM5CH1_CAPTURE_STA&0X3F;
//5.高电平溢出的个数*,因为计数器,会从0数到0xffff,算是溢出
//而65536这个数就是0xffff的值,然后从0数到65536这一个周期是
//1ms,所以溢出次数所花的时间是temp*65536
//
temp*=65536;//溢出时间总和
//6.然后,还得加上最后一次的值
//最后一次计数到了多少的值
//这样就是总共的时间
temp+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间
printf("HIGH:%d us\r\n",temp);//打印总的高点平时间
//7.获取完高电平所持续的时间以后,把TIM5CH1_CAPTURE_STA
//这个变量清0,开启下一次捕获
TIM5CH1_CAPTURE_STA=0;//开启下一次捕获
}
}
}
然后来测试一下:
编译下载:
注意因为这里会把高电平持续的时间,打印到串口,所以
打开串口调试工具.
这个灯闪烁.是因为代码里面有PWM的输出,
然后,这个时候如果我按下KEY0的话,WK_UP的时候,这样就会产生
高电平,而产生高电平,就会有上升沿,下降沿,这样就会被,定时器捕获到,
最终,一次捕获完成以后,就会把高电平持续的时间,通过串口打印出来.
可以看到情况.按下按键的时候,就发生了捕获,连续按下3次就会有连续3次的捕获.