蓝桥嵌入式之 输入捕获

工程代码可见Github<传送门>


一、输入捕获测量原理

之前我们做的是PWM波的输出,现在再来看PWM的输入,即测量PWM波频率和占空比的测量。

理解一副重要的示意图:
在这里插入图片描述
图中紫色部分为我们输入的方波信号,首先我们刚开始时要把捕获通道设置为上升沿触发,假如我们捕获到了一个上升沿,也就是到达了A点,那么我们的定时器就开始计数,随后我们还要吧捕获通道设置成为下降沿触发。当我们捕获了一个下降沿时,也就是到达了B点,那么我们获取一个计数值,定时器还得继续计数,同时重新把捕获通道设置为上升沿触发。直到再次捕获到了一个上升沿,那么就代表已经到达C点了,我们再获取一次计数值。

那么我们通过获取的这两个计数值,就很容易的算出频率和占空比。<参考eavane大神博客>

频率 = 1000000 / 第二次捕获值(72分频的前提)

占空比 = 第一次捕获值 / 第二次捕获值 * 100

知道了原理,再来看看具体实现吧。

二、实现代码

main.c

/*******************************************************************************
* 文件名:main.c
* 描  述:
* 作  者:CLAY
* 版本号:v1.0.0
* 日  期: 2019年1月26日
* 备  注:TIM3_CH1-PA6, TIM3_CH2-PA7配置为输入捕获,
*         测量TIM4的CH1-PB6和PB7的频率以及占空比
*******************************************************************************
*/

#include "config.h"
#include "led.h"
#include "key.h"
#include "timer.h"
#include "beep.h"
#include "lcd.h"
#include "stdio.h"
#include "usart.h"
#include "i2c.h"
#include "eeprom.h"
#include "PWMMode_Advance.h"
#include "PWM_Compare.h"
#include "PWM_Capture.h"


u8 string[20];

int main(void)
{
	STM3210B_LCD_Init();
	LCD_Clear(Blue);
	LCD_SetBackColor(Blue);
	LCD_SetTextColor(White);
	
	LEDInit();
	KeyInit();
	BeepInit();
	TIM2Init(2000, 72);//定时2ms
	
	TIM1_PWMInit(2000, 40, 80);//频率2K CH2占空比40% CH3占空比80%
	TIM4_PWMCompare(2000, 1000, 40, 80);//CH1占空比2K 40% CH2 1K占空比80%
	
	TIM3_CaptureInit();
	
	while(1)
	{	
		KeyDriver();
		
		if(TIM3_CH1_CAPTURE_MODE == 3)
		{
			sprintf((char*)string,"ch1_fre:%d           ",1000000 / TIM3_CH1_CAPTURE_HL);
			LCD_DisplayStringLine(Line3, string);
			sprintf((char*)string,"ch1_duty:%d          ",TIM3_CH1_CAPTURE_H * 100/TIM3_CH1_CAPTURE_HL);
			LCD_DisplayStringLine(Line4, string);
			TIM3_CH1_CAPTURE_MODE = 0;
		}

		if(TIM3_CH2_CAPTURE_MODE == 3)
		{
			sprintf((char*)string,"ch2_fre:%d           ",1000000 / TIM3_CH2_CAPTURE_HL);
			LCD_DisplayStringLine(Line7, string);
			sprintf((char*)string,"ch2_duty:%d          ",TIM3_CH2_CAPTURE_H * 100/TIM3_CH2_CAPTURE_HL);
			LCD_DisplayStringLine(Line8, string);
			TIM3_CH2_CAPTURE_MODE = 0;
		}
	}
}

void KeyAction(int code)
{
	if(code == 1)//按下B1,切换灯状态,蜂鸣器鸣叫0.1s
	{
		GPIOC->ODR ^= (1<<8);//PC8不断取反
		GPIOD->ODR |= (1<<2);//PD2置1,使能573锁存器
		GPIOD->ODR &= ~(1<<2);//PD2清0,关闭573锁存器
		Beep(100);
	}
	else if(code == 2)
	{
		Beep(-1);
	}
	else if(code == 3)
	{
		Beep(0);
	}
	else if(code == 4)
	{
		
	}
}

PWM_Capture.c

#include "PWM_Capture.h"

u8 TIM3_CH1_CAPTURE_MODE = 0;
u32 TIM3_CH1_CAPTURE_H = 0, TIM3_CH1_CAPTURE_HL = 0;

u8 TIM3_CH2_CAPTURE_MODE = 0;
u32 TIM3_CH2_CAPTURE_H = 0, TIM3_CH2_CAPTURE_HL = 0;

u8 CAPTURE_MODE;

void TIM3_IOInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	//***注意这里是APB2***
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能 GPIOA时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //TIM3通道1-PA6 TIM3通道2-PA7
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//***输入捕获使用IO模式为上拉输入或者浮空输入
//	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输入就所谓输出速度了,这句可省略。
	GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIO
}

void NVIC_TIM3Enable(void)
{
    NVIC_InitTypeDef NVIC_initstructure;

    NVIC_initstructure.NVIC_IRQChannel = TIM3_IRQn;           //选择TIM4中断通道
    NVIC_initstructure.NVIC_IRQChannelCmd = ENABLE;           //使能中断通道
    NVIC_initstructure.NVIC_IRQChannelPreemptionPriority = 0; //设定抢占优先级为0
    NVIC_initstructure.NVIC_IRQChannelSubPriority = 0;        //设定响应优先级为0
    NVIC_Init(&NVIC_initstructure);
}

void TIM3Init(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	
	//***注意这里是APB1***
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);  //使能定时器3时钟 
	TIM_TimeBaseStructure.TIM_Period = 0xFFFF; //***设置为0xFFFF
	TIM_TimeBaseStructure.TIM_Prescaler = 72-1; //设置预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //初始化 TIM3
}

void TIM3_CaptureInit(void)
{
	TIM_ICInitTypeDef TIM_ICInitStructure;

	TIM3_IOInit();//TIM3通道1-PA6 TIM3通道2-PA7配置
	TIM3Init();//TIM3定时器配置
	NVIC_TIM3Enable();//中断向量配置

	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//选择通道
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//上升沿触发
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//必须选择指向TI寄存器
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//不分频
	TIM_ICInitStructure.TIM_ICFilter = 0;//不滤波
	TIM_ICInit(TIM3, &TIM_ICInitStructure);

	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICFilter = 0;
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
	
	TIM_ITConfig(TIM3, TIM_IT_CC1|TIM_IT_CC2, ENABLE);	
	TIM_Cmd(TIM3, ENABLE);
}

PWM_Capture.h

#ifndef _PWM_CAPTURE_H
#define _PWM_CAPTURE_H

#include "config.h"


void TIM3_CaptureInit(void);

extern u8 CAPTURE_MODE;

extern u8 TIM3_CH1_CAPTURE_MODE;
extern u32 TIM3_CH1_CAPTURE_H, TIM3_CH1_CAPTURE_HL;

extern u8 TIM3_CH2_CAPTURE_MODE;
extern u32 TIM3_CH2_CAPTURE_H, TIM3_CH2_CAPTURE_HL;

#endif

stm32f10x_it.c

void TIM2_IRQHandler(void)
{
	static u16 tmr500ms = 0;
	
	if(TIM_GetITStatus(TIM2, TIM_FLAG_Update))
	{
		TIM_ClearITPendingBit(TIM2, TIM_FLAG_Update);
		tmr500ms++;
		KeyScan();
		BeepScan(2);//2ms扫描
		if(tmr500ms >= 250)
		{
			tmr500ms = 0;
			CAPTURE_MODE ^= 1;
		}
	}
}

void TIM3_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM3, TIM_IT_CC1) == 1)
	{
		TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);
		if(CAPTURE_MODE)
		{
			switch(TIM3_CH1_CAPTURE_MODE)
			{
				case 0: 
					TIM3_CH1_CAPTURE_H = 0;//
					TIM3_CH1_CAPTURE_HL = 0;
					TIM3_CH1_CAPTURE_MODE = 1;
					TIM_SetCounter(TIM3, 0);//计数值清0
					TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Falling);//设置为下降沿触发
					break;
				case 1: 
					TIM3_CH1_CAPTURE_H = TIM_GetCounter(TIM3);//第一次获取计数值
					TIM3_CH1_CAPTURE_MODE = 2;
					TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Rising);//设置为上升沿触发
					break;
				case 2: 
					TIM3_CH1_CAPTURE_HL = TIM_GetCounter(TIM3);//第二次获取计数值
					TIM3_CH1_CAPTURE_MODE = 3;
					TIM_OC1PolarityConfig( TIM3,TIM_ICPolarity_Rising);//设置为上升沿触发
					break;
				default:
					break;
			}
		}
		else
		{
			TIM3_CH1_CAPTURE_MODE = 0;
		}
	}

	if(TIM_GetITStatus(TIM3, TIM_IT_CC2) == 1)
	{
		TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);
		if(!CAPTURE_MODE)
		{
			switch(TIM3_CH2_CAPTURE_MODE)
			{
				case 0: 
					TIM3_CH2_CAPTURE_H = 0;
					TIM3_CH2_CAPTURE_HL = 0;
					TIM3_CH2_CAPTURE_MODE = 1;
					TIM_SetCounter(TIM3, 0);
					TIM_OC2PolarityConfig( TIM3,TIM_ICPolarity_Falling);
					break;
				case 1: 
					TIM3_CH2_CAPTURE_H = TIM_GetCounter(TIM3);
					TIM3_CH2_CAPTURE_MODE = 2;
					TIM_OC2PolarityConfig( TIM3,TIM_ICPolarity_Rising);
					break;
				case 2: 
					TIM3_CH2_CAPTURE_HL = TIM_GetCounter(TIM3);
					TIM3_CH2_CAPTURE_MODE = 3;
					TIM_OC2PolarityConfig( TIM3,TIM_ICPolarity_Rising);
					break;
				default:
					break;
			}
		}
		else
		{
			TIM3_CH2_CAPTURE_MODE = 0;
		}
	}

}

二、需要注意的地方

1、实验结果
在这里插入图片描述

2、由于我是直接用之前的工程,之前TIM3做了通用定时器的PWM模式输出实验,而现在要配置为输入捕获,两个同时存在会出现如下错误:
在这里插入图片描述
所以要删除pwmmode相关文件,包括以下几个:
在这里插入图片描述
在这里插入图片描述

3、程序中的变量释义

TIM3_CH1_CAPTURE_MODE 用来记录当前捕获在哪个位置
TIM3_CH1_CAPTURE_H 用来记录第一次捕获的时间
TIM3_CH1_CAPTURE_HL 用来记录第二次捕获的时间

4、CAPTURE_MODE 的优秀所在

两个通道肯定不能同时进行捕获,首先两者同时捕获,同时记录这个无所谓,关键是它们还都涉及计算。计算最怕被中断打断,这样可能导致结果不正确。这里目前有两种解决方案,一种是分时复用,就像上面程序中写的那样,另外一种解决办法就像我们51操作步进电机计算节拍或者就像控制DHT11时序一样,开关总中断,这个方法稍后再做调试,会一并挂上来。

5、中断函数里总的处理思想

当TIM3_CH1_CAPTURE_MODE=0时,如果发生了中断,那么就代表我们捕获到了上升沿,当前在上面简图的A点,我们把定时器计数值清0,让定时器重新计数,同时还要把触发方式设置为下降沿触发。

当TIM3_CH1_CAPTURE_MODE=1时候,发生了中断,那么就代表我们捕获到了下降沿,当前的位置在上面简图的B点,我们获取一次定时器的计数值,同时还得把我们触发方式设置为上升沿触发。

当TIM3_CH1_CAPTURE_MODE=2时候,发生中断,那么就代表我们一个周期已经捕获完毕了,当前位置在上面简图的C点,我们再获取一次定时器的计数值,随后就可以根据我们两次获取的计数值,算出我们方波的频率和周期。

当TIM3_CH1_CAPTURE_MODE=3时,就意味着我们可以开始计算周期和频率,当我们处理好数据后,我们就可以把TIM3_CH1_CAPTURE_MODE清0,准备开始下一次捕获。

然后在主函数中,进行TIM3_CH1_CAPTURE_MODE状态判断,进而计算出占空比和频率的值,然后显示到LCD上,随后再开启下一次捕获。