打印
[STM32F1]

STM32基础篇——输入捕获试验

[复制链接]
9989|26
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 aizaixiyuanqian 于 2018-1-10 12:49 编辑

前面的试验我们学习了 STM32 的通用定时器作为 PWM 输出的使用方法,这次试验我们将学习通用定时器作为输入捕获的使用。本次试验,我们将用 TIM5 的通道 1 来做输入捕获, 捕获 PA0 上高电平的脉宽,用按键来输入电平时间,通过串口打印高电平脉宽时间。试验目标:
1、了解输入捕获。
2、学会配置输入捕获。

沙发
aizaixiyuanqian|  楼主 | 2018-1-10 12:50 | 只看该作者
输入捕获简介
输入捕获模式可以用来测量脉冲宽度或者测量频率。STM32 的定时器,除了 TIM6 和TIM7,其他定时器都有输入捕获功能。 STM32 的输入捕获,简单的说就是通过检测 TIMx_CHx上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值( TIMx_CNT)存放到对应的通道的捕获/比较寄存器( TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA 等。

使用特权

评论回复
板凳
aizaixiyuanqian|  楼主 | 2018-1-10 12:50 | 只看该作者
本次实验我们用到 TIM5_CH1 来捕获高电平脉宽,也就是要先设置输入捕获为上升沿检测,记录发生上升沿的时候 TIM5_CNT 的值。然后配置捕获信号为下降沿捕获,当下降沿到来时,发生捕获,并记录此时的 TIM5_CNT 值。这样,前后两次 TIM5_CNT 之差,就是高电平的脉宽,同时 TIM5 的计数频率我们是知道的,从而可以计算出高电平脉宽的准确时间。

使用特权

评论回复
地板
aizaixiyuanqian|  楼主 | 2018-1-10 12:52 | 只看该作者
我们介绍我们本章需要用到的一些寄存器配置,需要用到的寄存器有:TIMx_ARR、TIMx_PSC、 TIMx_CCMR1、 TIMx_CCER、 TIMx_DIER、 TIMx_CR1、 TIMx_CCR1 这些寄存器在前面全部都有提到(这里的 x=5),我们这里就不再全部罗列了,我们这里针对性的介绍这几个寄存器的配置。首先 TIMx_ARR 和 TIMx_PSC,这两个寄存器用来设自动重装载值和 TIMx 的时钟分频,用法同前面介绍的,我们这里不再介绍。再来看看捕获/比较模式寄存器 1:TIMx_CCMR1,这个寄存器在输入捕获的时候,非常有用,有必要重新介绍,该寄存器的各位描述如图所示:

使用特权

评论回复
5
aizaixiyuanqian|  楼主 | 2018-1-10 12:53 | 只看该作者
当在输入捕获模式下使用的时候,对应图的第二行描述,从图中可以看出,TIMx_CCMR1明显是针对 2 个通道的配置,低八位[7: 0]用于捕获/比较通道 1 的控制,而高八位[15:8]则用于捕获/比较通道 2 的控制,因为 TIMx 还有 CCMR2 这个寄存器,所以可以知道CCMR2 是用来控制通道 3 和通道 4(详见《 STM32 参考手册》 )。这里我们用到的是 TIM5 的捕获/比较通道 1,我们重点介绍 TIMx_CMMR1 的[7:0]位(其实高 8 位配置类似), TIMx_CMMR1 的[7:0]位详细描述见图所示:



使用特权

评论回复
6
aizaixiyuanqian|  楼主 | 2018-1-10 12:54 | 只看该作者
其中 CC1S[1:0],这两个位用于 CCR1 的通道配置, 这里我们设置 IC1S[1:0]=01,也就是配置 IC1 映射在 TI1 上(关于 IC1, TI1 不明白的,可以看《 STM32 参考手册》 14.2节的图 98-通用定时器框图),即 CC1 对应 TIMx_CH1。

使用特权

评论回复
7
aizaixiyuanqian|  楼主 | 2018-1-10 12:54 | 只看该作者
输入捕获 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,该寄存器的各位描述见图。本章我们要用到这个寄存器的最低 2 位, CC1E和 CC1P 位。

使用特权

评论回复
8
aizaixiyuanqian|  楼主 | 2018-1-10 12:55 | 只看该作者
接下来我们再看看 DMA/中断使能寄存器: TIMx_DIER,该寄存器的各位描述见图,本章,我们需要用到中断来处理捕获数据,所以必须开启通道 1 的捕获比较中断,即 CC1IE 设置为 1。控制寄存器: TIMx_CR1,我们只用到了它的最低位,也就是用来使能定时器的,这里前面章节都有介绍,请大家参考前面的章节。最后再来看看捕获/比较寄存器 1:TIMx_CCR1,该寄存器用来存储捕获发生时, TIMx_CNT 的值,我们从 TIMx_CCR1 就可以读出通道 1 捕获发生时刻的 TIMx_CNT 值,通过两次捕获(一次上升沿捕获,一次下降沿捕获)的差值,就可以计算出高电平脉冲的宽度。

使用特权

评论回复
9
aizaixiyuanqian|  楼主 | 2018-1-10 12:55 | 只看该作者
1 )  开 启 TIM5  时钟和 GPIOA  时钟, 配置 PA0  为 下 拉输入
要使用 TIM5,我们必须先开启 TIM5 的时钟。 这里我们还要配置 PA0 为下拉输入,因为我们要捕获 TIM5_CH1 上面的高电平脉宽,而 TIM5_CH1 是连接在 PA0 上面的。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //使能 TIM5 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能 GPIOA 时钟

使用特权

评论回复
10
aizaixiyuanqian|  楼主 | 2018-1-10 12:56 | 只看该作者
2  )  初始化 TIM5, 设置 TIM5  的 ARR  和 PSC
在开启了 TIM5 的时钟之后,我们要设置 ARR 和 PSC 两个寄存器的值来设置输入捕获的自动重装载值和计数频率。 这在库函数中是通过 TIM_TimeBaseInit 函数实现的。

使用特权

评论回复
11
aizaixiyuanqian|  楼主 | 2018-1-10 12:57 | 只看该作者
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//声明一个结构体变量,用来初始化定时器
TIM_TimeBaseInitStructure.TIM_Period = 0xffff; //设定计数器自动重装值
TIM_TimeBaseInitStructure.TIM_Prescaler = 71; //以 1Mhz 的频率计数 一次即是1us
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);//根据
TIM_TimeBaseInitStruct 中指定的参数初始化 TIMx 的时间基数单位

使用特权

评论回复
12
aizaixiyuanqian|  楼主 | 2018-1-10 12:58 | 只看该作者
3 )  设置 TIM5  的输入比较参数,开启输入捕获输入比较参数的设置包括映射关系, 滤波,分频以及捕获方式等。这里我们需要设置通道 1 为输入模式,且 IC1 映射到 TI1(通道 1)上面,并且不使用滤波(提高响应速度)器,上升沿捕获。 库函数是通过 TIM_ICInit 函数来初始化输入比较参数的:void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

使用特权

评论回复
13
aizaixiyuanqian|  楼主 | 2018-1-10 12:58 | 只看该作者
同样,我们来看看参数设置结构体 TIM_ICInitTypeDef 的定义:
typedef struct
{
uint16_t TIM_Channel;
uint16_t TIM_ICPolarity;
uint16_t TIM_ICSelection;
uint16_t TIM_ICPrescaler;
uint16_t TIM_ICFilter;
} TIM_ICInitTypeDef;
参数 TIM_Channel 很好理解, 用来设置通道。 我们设置为通道 1, 为 TIM_Channel_1。
参 数 TIM_ICPolarit 是 用 来 设 置 输 入 信 号 的 有 效 捕 获 极 性 , 这 里 我们 设 置 为 TIM_ICPolarity_Rising, 上升沿捕获。 同时库函数还提供了单独设置通道 1
捕获极性的函数为:
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling),
这表示通道 1 为上升沿捕获,我们后面会用到,同时对于其他三个通道也有一个类似的函数,使用的时候一定要分清楚使用的是哪个通道该调用哪个函数,格式为
TIM_OCxPolarityConfig()。
参数 TIM_ICSelection 是用来设置映射关系,我们配置 IC1 直接映射在 TI1 上,选择
TIM_ICSelection_DirectTI。
参 数 TIM_ICPrescaler 用 来 设 置 输 入 捕 获 分 频 系 数 , 我 们 这 里 不 分频 , 所 以 选 中 TIM_ICPSC_DIV1,还有 2,4,8 分频可选。
参数 TIM_ICFilter 设置滤波器长度,这里我们不使用滤波器, 所以设置为 0。这些参数的意义,在我们讲解寄存器的时候举例说明过,这里不做详细解释。
我们的配置代码是:
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入端 IC1 映射到 TI1上
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到 TI1上
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM5_ICInitStructure.TIM_ICFilter = 0x00; //IC1F=0000 配置输入滤波器 不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure); //初始化 TIM5 输入捕获通道 1

使用特权

评论回复
14
aizaixiyuanqian|  楼主 | 2018-1-10 13:14 | 只看该作者
4)  使能捕获和更新中断(设置 TIM5  的 DIER  寄存器)因为我们要捕获的是高电平信号的脉宽,所以,第一次捕获是上升沿,第二次捕获时下降沿,必须在捕获上升沿之后,设置捕获边沿为下降沿,同时,如果脉宽比较长,那么定时器就会溢出,对溢出必须做处理,否则结果就不准了。这两件事,我们都在中断里面做,所以必须开启捕获中断和更新中断。
这里我们使用定时器的开中断函数 TIM_ITConfig 即可使能捕获和更新中断:
TIM_ITConfig( TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断和捕获中断

使用特权

评论回复
15
aizaixiyuanqian|  楼主 | 2018-1-10 13:15 | 只看该作者
5)  设置中断分组,编写中断服务函数设置中断分组的方法前面多次提到这里我们不做讲解,主要是通过函数 NVIC_Init()来完成。 分组完成后, 我们还需要在中断函数里面完成数据处理和捕获设置等关键操作,从而实现高电平脉宽统计。 在中断服务函数里面,跟以前的外部中断和定时器中断实验中一样,我们在中断开始的时候要进行中断类型判断,在中断结束的时候要清除中断标志位。 使用到的函数在上面的实验已经讲解过,分别为 TIM_GetITStatus()函数和TIM_ClearITPendingBit()函数。
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET){}//判断是否为更新中断
If (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET){}//判断是否发生捕获事件
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update);//清除中断和捕获标志位

使用特权

评论回复
16
aizaixiyuanqian|  楼主 | 2018-1-10 13:15 | 只看该作者
6 )使能定时器(设置 TIM5  的 CR1  寄存器)
最后,必须打开定时器的计数器开关, 启动 TIM5 的计数器,开始输入捕获。
TIM_Cmd(TIM5,ENABLE ); //使能定时器 5
通过以上 6 步设置,定时器 5 的通道 1 就可以开始输入捕获了

使用特权

评论回复
17
aizaixiyuanqian|  楼主 | 2018-1-10 13:16 | 只看该作者
输入捕获程序
void input_init()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//声明一个结构体变量,用来初始化定时器
TIM_ICInitTypeDef TIM5_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* 开启定时器 5 时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);  //使能 TIM5 时钟
TIM_ClearITPendingBit(TIM5,TIM_IT_Update|TIM_IT_CC1); //清除中断和捕获标志位
TIM_TimeBaseInitStructure.TIM_Period = 0xffff; //设定计数器自动重装值
TIM_TimeBaseInitStructure.TIM_Prescaler = 71; //以 1Mhz 的频率计数 一次即是 1us
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;  //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);//根据 TIM_TimeBaseInitStruct 中指定的参数初始化 TIMx 的时间基数单位
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入端 IC1 映射到 TI1 上
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到 TI1 上
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM5_ICInitStructure.TIM_ICFilter = 0x00; //IC1F=0000 配置输入滤波器 不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure); //初始化 TIM5 输入捕获通道 1
//中断分组初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;  //打开 TIM5 的全局中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;  //抢占优先级为 0
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; //响应优先级为 1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM5,ENABLE); //使能或者失能 TIMx 外设
TIM_ITConfig(TIM5, TIM_IT_Update|TIM_IT_CC1, ENABLE );  //使能或者失能指定的TIM中断
}

使用特权

评论回复
18
aizaixiyuanqian|  楼主 | 2018-1-10 13:17 | 只看该作者
输入捕获 中断程序
void TIM5_IRQHandler() //定时器 5 输入捕获中断函数
{
if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
{
if(TIM5CH1_CAPTURE_STA&0X40) //已经捕获到高电平了
{
if((TIM5CH1_CAPTURE_STA&0x3f)==0x3f)//高电平太长了
{
TIM5CH1_CAPTURE_STA|=0x80; //标记成功捕获了一次
TIM5CH1_CAPTURE_VAL=0xffff;
}
else
{
TIM5CH1_CAPTURE_STA++;
}
}
}
}
if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET) //捕获 1 发生捕获事件
{
if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次上升沿
TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5); //获得 TIMx 输入捕获 1 的值
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //设置为上升沿捕获
}
else
{
TIM5CH1_CAPTURE_STA=0; //清空
TIM5CH1_CAPTURE_VAL=0;
TIM_SetCounter(TIM5,0);
TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //设置为下降沿捕获
}
}TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}

使用特权

评论回复
19
aizaixiyuanqian|  楼主 | 2018-1-10 13:19 | 只看该作者
TIM5_IRQHandler 是 TIM5 的中断服务函数,该函数用到了两个全局变量,用于辅助实现高电平捕获。其中 TIM5CH1_CAPTURE_STA,是用来记录捕获状态。 TIM5CH1_CAPTURE_STA
各位描述如表所示:



另外一个变量 TIM5CH1_CAPTURE_VAL,则用来记录捕获到下降沿的时候, TIM5_CNT的值。

使用特权

评论回复
20
aizaixiyuanqian|  楼主 | 2018-1-10 13:20 | 只看该作者
现在我们来介绍一下,捕获高电平脉宽的思路:首先,设置 TIM5_CH1 捕获上升沿,这在 input_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 里面,最后设置为上升沿捕获,回到初始状态。

使用特权

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

本版积分规则

62

主题

1353

帖子

6

粉丝