打印

【连载】STM32开发指南--第十五章 输入捕获实验

[复制链接]
6633|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
正点原子|  楼主 | 2013-1-19 19:01 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 正点原子 于 2013-1-19 19:04 编辑

第十五章 输入捕获实验
上一章,我们介绍了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输出的高电平脉宽。

《STM32开发指南》第十五章 输入捕获实验.rar (757.83 KB)

实验10 输入捕获实验.rar (36.3 KB)



沙发
正点原子|  楼主 | 2013-1-19 19:02 | 只看该作者
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所示:
TIM5CH1_CAPTURE_STA

bit7

bit6

bit5~0

捕获完成标志

捕获到高电平标志

捕获高电平后定时器溢出的次数
表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输出的高电平是如何变化的。

使用特权

评论回复
板凳
hnhdzzc| | 2013-3-27 20:53 | 只看该作者
mark

使用特权

评论回复
地板
zeroxxx| | 2013-5-24 00:53 | 只看该作者
请问这个实验我用模拟调试为什么无法捕获呢?而用JLINK调试就可以呢?

使用特权

评论回复
5
wyy372071187| | 2013-11-30 11:22 | 只看该作者
如果要捕获低电平 需要修改哪些东西  为什么我修改的就是不正确  急!求帮忙

使用特权

评论回复
6
zhangjin_comeon| | 2013-11-30 22:22 | 只看该作者
谢谢 好样的   继续连载

使用特权

评论回复
7
704292306| | 2013-12-18 17:26 | 只看该作者

使用特权

评论回复
8
cwii| | 2014-4-13 12:46 | 只看该作者
为什么打印不出来呢,,我用的是mini开发板 ...

使用特权

评论回复
9
wo_yjk| | 2014-4-13 17:22 | 只看该作者
:D

使用特权

评论回复
10
wakojosin| | 2014-4-13 22:33 | 只看该作者
谢谢分享,留个脚印方便访问

使用特权

评论回复
11
longfenghugui| | 2014-8-21 15:18 | 只看该作者

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:我的STM32开发板店铺:http://openedv.taobao.com 我的技术论坛论坛:www.openedv.com

91

主题

264

帖子

71

粉丝