第十五章 输入捕获实验
上一章,我们介绍了STM32的通用定时器作为PWM输出的使用方法,这一章,我们将向大家介绍通用定时器作为输入捕获的使用。在本章中,我们将用TIM5的通道1(PA0)来做输入捕获,捕获PA0上高电平的脉宽(用WK_UP按键输入高电平),通过串口打印高电平脉宽时间,从本章分为如下几个部分:
15.1 输入捕获简介
15.2 硬件设计
15.3 软件设计
15.4 下载验证
15.1 输入捕获简介 输入捕获模式可以用来测量脉冲宽度或者测量频率。STM32的定时器,除了TIM6和TIM7,其他定时器都有输入捕获功能。STM32的输入捕获,简单的说就是通过检测TIMx_CHx上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA等。
本章我们用到TIM5_CH1来捕获高电平脉宽,也就是要先设置输入捕获为上升沿检测,记录发生上升沿的时候TIM5_CNT的值。然后配置捕获信号为下降沿捕获,当下降沿到来时,发生捕获,并记录此时的TIM5_CNT值。这样,前后两次TIM5_CNT之差,就是高电平的脉宽,同时TIM5的计数频率我们是知道的,从而可以计算出高电平脉宽的准确时间。
接下来,我们介绍我们本章需要用到的一些寄存器配置,需要用到的寄存器有:TIMx_ARR、TIMx_PSC、TIMx_CCMR1、TIMx_CCER、TIMx_DIER、TIMx_CR1、TIMx_CCR1这些寄存器在前面2章全部都有提到(这里的x=5),我们这里就不再全部罗列了,我们这里针对性的介绍这几个寄存器的配置。
首先TIMx_ARR和TIMx_PSC,这两个寄存器用来设自动重装载值和TIMx的时钟分频,用法同前面介绍的,我们这里不再介绍。
再来看看捕获/比较模式寄存器1:TIMx_CCMR1,这个寄存器在输入捕获的时候,非常有用,有必要重新介绍,该寄存器的各位描述如图15.1.1所示:
图15.1.1 TIMx_CCMR1寄存器各位描述
当在输入捕获模式下使用的时候,对应图15.1.1的第二行描述,从图中可以看出,TIMx_CCMR1明显是针对2个通道的配置,低八位[7:0]用于捕获/比较通道1的控制,而高八位[15:8]则用于捕获/比较通道2的控制,因为TIMx还有CCMR2这个寄存器,所以可以知道CCMR2是用来控制通道3和通道4(详见《STM32参考手册》290页,14.4.8节)。
这里我们用到的是TIM5的捕获/比较通道1,我们重点介绍TIMx_CMMR1的[7:0]位(其实高8位配置类似),TIMx_CMMR1的[7:0]位详细描述见图15.1.2所示:
图15.1.2 TIMx_CMMR1 [7:0]位详细描述
其中CC1S[1:0],这两个位用于CCR1的通道配置,这里我们设置IC1S[1:0]=01,也就是配置IC1映射在TI1上(关于IC1,TI1不明白的,可以看《STM32参考手册》14.2节的图98-通用定时器框图),即CC1对应TIMx_CH1。
输入捕获1预分频器IC1PSC[1:0],这个比较好理解。我们是1次边沿就触发1次捕获,所以选择00就是了。
输入捕获1滤波器IC1F[3:0],这个用来设置输入采样频率和数字滤波器长度。其中,
是定时器的输入频率(TIMxCLK),一般为72Mhz,而
则是根据TIMx_CR1的CKD[1:0]的设置来确定的,如果CKD[1:0]设置为00,那么
=
。N值就是滤波长度,举个简单的例子:假设IC1F[3:0]=0011,并设置IC1映射到通道1上,且为上升沿触发,那么在捕获到上升沿的时候,再以 的频率,连续采样到8次通道1的电平,如果都是高电平,则说明却是一个有效的触发,就会触发输入捕获中断(如果开启了的话)。这样可以滤除那些高电平脉宽低于8个采样周期的脉冲信号,从而达到滤波的效果。这里,我们不做滤波处理,所以设置IC1F[3:0]=0000,只要采集到上升沿,就触发捕获。
再来看看捕获/比较使能寄存器:TIMx_CCER,该寄存器的各位描述见图14.1.2(在第14章)。本章我们要用到这个寄存器的最低2位,CC1E和CC1P位。这两个位的描述如图15.1.3所示:
图15.1.3 TIMx_CCER最低2位描述
所以,要使能输入捕获,必须设置CC1E=1,而CC1P则根据自己的需要来配置。
接下来我们再看看DMA/中断使能寄存器:TIMx_DIER,该寄存器的各位描述见图13.1.2(在第13章),本章,我们需要用到中断来处理捕获数据,所以必须开启通道1的捕获比较中断,即CC1IE设置为1。
控制寄存器:TIMx_CR1,我们只用到了它的最低位,也就是用来使能定时器的,这里前面两章都有介绍,请大家参考前面的章节。
最后再来看看捕获/比较寄存器1:TIMx_CCR1,该寄存器用来存储捕获发生时,TIMx_CNT的值,我们从TIMx_CCR1就可以读出通道1捕获发生时刻的TIMx_CNT值,通过两次捕获(一次上升沿捕获,一次下降沿捕获)的差值,就可以计算出高电平脉冲的宽度。
至此,我们把本章要用的几个相关寄存器都介绍完了,本章要实现通过输入捕获,来获取TIM5_CH1(PA0)上面的高电平脉冲宽度,并从串口打印捕获结果。下面我们介绍输入捕获的配置步骤:
1)开启TIM5时钟,配置PA0为下拉输入。
要使用TIM5,我们必须先开启TIM5的时钟(通过APB1ENR设置)。这里我们还要配置PA0为下拉输入,因为我们要捕获TIM5_CH1上面的高电平脉宽,而TIM5_CH1是连接在PA0上面的。
2)设置TIM5的ARR和PSC。
在开启了TIM5的时钟之后,我们要设置ARR和PSC两个寄存器的值来设置输入捕获的自动重装载值和计数频率。
3)设置TIM5的CCMR1
TIM5_CCMR1寄存器控制着输入捕获1和2的模式,包括映射关系,滤波和分频等。这里我们需要设置通道1为输入模式,且IC1映射到TI1(通道1)上面,并且不使用滤波(提高响应速度)器。
4)设置TIM5的CCER,开启输入捕获,并设置为上升沿捕获。
TIM5_CCER寄存器是定时器的开关,并且可以设置输入捕获的边沿。只有TIM5_CCER寄存器使能了输入捕获,我们的外部信号,才能被TIM5捕获到,否则一切白搭。同时要设置好捕获边沿,才能得到正确的结果。
5)设置TIM5的DIER,使能捕获和更新中断,并编写中断服务函数
因为我们要捕获的是高电平信号的脉宽,所以,第一次捕获是上升沿,第二次捕获时下降沿,必须在捕获上升沿之后,设置捕获边沿为下降沿,同时,如果脉宽比较长,那么定时器就会溢出,对溢出必须做处理,否则结果就不准了。这两件事,我们都在中断里面做,所以必须开启捕获中断和更新中断。
设置了中断必须编写中断函数,否则可能导致死机。我们需要在中断函数里面完成数据处理和捕获设置等关键操作,从而实现高电平脉宽统计。
6)设置TIM5的CR1,使能定时器
最后,必须打开定时器的计数器开关,通过设置TIM5_CR1的最低位为1,启动TIM5的计数器,开始输入捕获。
通过以上6步设置,定时器5的通道1就可以开始输入捕获了,同时因为还用到了串口输出结果,所以还需要配置一下串口。
15.2 硬件设计 本实验用到的硬件资源有:
1) 指示灯DS0
2) WK_UP按键
3) 串口
4) 定时器TIM3
5) 定时器TIM5
前面4个,在之前的章节均有介绍。本节,我们将捕获TIM5_CH1(PA0)上的高电平脉宽,通过WK_UP按键输入高电平,并从串口打印高电平脉宽。同时我们保留上节的PWM输出,大家也可以通过用杜邦线连接PB5和PA0,来测量PWM输出的高电平脉宽。
15.3 软件设计 本章,我们依旧是在前一章的基础上修改代码,先打开之前的工程,然后我们在上一章的基础上,在timer.c里面加入如下代码:
//定时器5通道1输入捕获配置
//arr:自动重装值
//psc:时钟预分频数
void TIM5_Cap_Init(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<3; //TIM5 时钟使能
RCC->APB2ENR|=1<<2; //使能PORTA时钟
GPIOA->CRL&=0XFFFFFFF0; //PA0 清除之前设置
GPIOA->CRL|=0X00000008; //PA0 输入
GPIOA->ODR|=0<<0; //PA0 下拉
TIM5->ARR=arr; //设定计数器自动重装值
TIM5->PSC=psc; //预分频器
TIM5->CCMR1|=1<<0; //CC1S=01 选择输入端 IC1映射到TI1上
TIM5->CCMR1|=0<<4; //IC1F=0000 配置输入滤波器 不滤波
TIM5->CCMR1|=0<<10; //IC2PS=00 配置输入分频,不分频
TIM5->CCER|=0<<1; //CC1P=0 上升沿捕获
TIM5->CCER|=1<<0; //CC1E=1 允许捕获计数器的值到捕获寄存器中
TIM5->DIER|=1<<1; //允许捕获中断
TIM5->DIER|=1<<0; //允许更新中断
TIM5->CR1|=0x01; //使能定时器2
MY_NVIC_Init(2,0,TIM5_IRQChannel,2);//抢占2,子优先级0,组2
}
//捕获状态
//[7]:0,没有成功的捕获;1,成功捕获到一次.
//[6]:0,还没捕获到高电平;1,已经捕获到高电平了.
//[5:0]:捕获高电平后溢出的次数
u8 TIM5CH1_CAPTURE_STA=0; //输入捕获状态
u16 TIM5CH1_CAPTURE_VAL ; //输入捕获值
//定时器5中断服务程序
void TIM5_IRQHandler(void)
{
u16 tsr;
tsr=TIM5->SR;
if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if(tsr&0X01)//溢出
{
if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM5CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次
TIM5CH1_CAPTURE_VAL=0XFFFF;
}else TIM5CH1_CAPTURE_STA++;
}
}
if(tsr&0x02)//捕获1发生捕获事件
{
if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽
TIM5CH1_CAPTURE_VAL=TIM5->CCR1;//获取当前的捕获值.
TIM5->CCER&=~(1<<1); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM5CH1_CAPTURE_STA=0; //清空
TIM5CH1_CAPTURE_VAL=0;
TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM5->CNT=0; //计数器清空
TIM5->CCER|=1<<1; //CC1P=1 设置为下降沿捕获
}
}
}
TIM5->SR=0;//清除中断标志位
}
此部分代码包含2个函数,其中TIM5_Cap_Init函数用于TIM5通道1的输入捕获设置,其设置和我们上面讲的步骤是一样的,这里就不多说,重点来看看第二个函数。
TIM5_IRQHandler是TIM5的中断服务函数,该函数用到了两个全局变量,用于辅助实现高电平捕获。其中TIM5CH1_CAPTURE_STA,是用来记录捕获状态,该变量类似我们在usart.c里面自行定义的USART_RX_STA寄存器。TIM5CH1_CAPTURE_STA各位描述如表15.3.1所示:
表15.3.1 TIM5CH1_CAPTURE_STA各位描述
另外一个变量TIM5CH1_CAPTURE_VAL,则用来记录捕获到下降沿的时候,TIM5_CNT的值。
现在我们来介绍一下,捕获高电平脉宽的思路:首先,设置TIM5_CH1捕获上升沿,这在TIM5_Cap_Init函数执行的时候就设置好了,然后等待上升沿中断到来,当捕获到上升沿中断,此时如果TIM5CH1_CAPTURE_STA的第6位为0,则表示还没有捕获到新的上升沿,就先把
TIM5CH1_CAPTURE_STA、TIM5CH1_CAPTURE_VAL和TIM5->CNT等清零,然后再设置TIM5CH1_CAPTURE_STA的第6位为1,标记捕获到高电平,最后设置为下降沿捕获,等待下降沿到来。如果等待下降沿到来期间,定时器发生了溢出,就在TIM5CH1_CAPTURE_STA里面对溢出次数进行计数,当最大溢出次数来到的时候,就强制标记捕获完成(虽然此时还没有捕获到下降沿)。当下降沿到来的时候,先设置TIM5CH1_CAPTURE_STA的第7位为1,标记成功捕获一次高电平,然后读取此时的定时器值到TIM5CH1_CAPTURE_VAL里面,最后设置为上升沿捕获,回到初始状态。
这样,我们就完成一次高电平捕获了,只要TIM5CH1_CAPTURE_STA的第7位一直为1,那么就不会进行第二次捕获,我们在main函数处理完捕获数据后,将TIM5CH1_CAPTURE_STA置零,就可以开启第二次捕获。
接着我们修改timer.h如下:
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
//通过改变TIM3->CCR2的值来改变占空比,从而控制LED0的亮度
#define LED0_PWM_VAL TIM3->CCR2
void TIM3_Int_Init(u16 arr,u16 psc);
void TIM3_PWM_Init(u16 arr,u16 psc);
void TIM5_Cap_Init(u16 arr,u16 psc);
#endif
这里比较简单,就不多说了。
接下来,我们修改主程序里面的main函数如下:
extern u8 TIM5CH1_CAPTURE_STA; //输入捕获状态
extern u16 TIM5CH1_CAPTURE_VAL; //输入捕获值
int main(void)
{
u32 temp=0;
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,9600); //串口初始化为9600
delay_init(72); //延时初始化
LED_Init(); //初始化与LED连接的硬件接口
TIM3_PWM_Init(899,72-1); //不分频。PWM频率=72000/(899+1)=80Khz
TIM5_Cap_Init(0XFFFF,72-1); //以1Mhz的频率计数
while(1)
{
delay_ms(10);
LED0_PWM_VAL++;
if(LED0_PWM_VAL==300)LED0_PWM_VAL=0;
if(TIM5CH1_CAPTURE_STA&0X80) //成功捕获到了一次高电平
{
temp=TIM5CH1_CAPTURE_STA&0X3F;
temp*=65536; //溢出时间总和
temp+=TIM5CH1_CAPTURE_VAL; //得到总的高电平时间
printf("HIGH:%d us\r\n",temp); //打印总的高点平时间
TIM5CH1_CAPTURE_STA=0; //开启下一次捕获
}
}
}
该main函数是在PWM实验的基础上修改来的,我们保留了PWM输出,同时通过设置TIM5_Cap_Init(0XFFFF,72-1),将TIM5_CH1的捕获计数器设计为1us计数一次,并设置重装载值为最大,所以我们的捕获时间精度为1us。
主函数通过TIM5CH1_CAPTURE_STA的第7位,来判断有没有成功捕获到一次高电平,如果成功捕获,则将高电平时间通过串口输出到电脑。
至此,我们的软件设计就完成了。
15.4 下载验证 在完成软件设计之后,将我们将编译好的文件下载到战舰STM32开发板上,可以看到DS0的状态和上一章差不多,由暗à亮的循环。说明程序已经正常在跑了,我们再打开串口调试助手,选择对应的串口,然后按WK_UP按键,可以看到串口打印的高电平持续时间,如图15.4.1所示:
图15.4.1 PWM控制DS0亮度
从上图可以看出,其中有2次高电平在50us以内的,这种就是按键按下时发生的抖动。这就是为什么我们按键输入的时候,一般都需要做防抖处理,防止类似的情况干扰正常输入。大家还可以用杜邦线连接PA0和PB5,看看上一节中我们设置的PWM输出的高电平是如何变化的。