STM32F4_电容触摸按键

目录

1. 电容触摸按键简介

2. 触摸按键的原理

3. 硬件分析

4. 检测电容触摸按键的过程

5. 库函数配置触摸按键

6. 实验程序

6.1 main.c

6.2 tpad.c

6.3 tpad.h


1. 电容触摸按键简介

        在STM32F4的开发板的右下侧,有一小块的覆铜区域TPAD,此区域称为STM32的触摸按键

        触摸按键相对于传统的机械按键有着寿命长、占用空间少、易于操作等诸多优点。如今的手机,触摸屏,触摸按键大行其道,而传统的机械按键,正在逐渐从手机上面消失。在本节,我们将学习一种简单的触摸按键:电容式触摸按键

2. 触摸按键的原理

        触摸按键实质上是通过一个RC电路来实现的,RC电路如下图: 

STM32F4_电容触摸按键_第1张图片

        最上边的 V1 是电路电源电压,对于开发板来说就是3.3V供电电源电压;如图,电阻R是外接的电容充电电阻,图中CX为电容,当KEY未闭合时,电容充电电阻R的上端电压是0V,下端电压也是0V;充电电容CX的上端电压和电阻R下端的电压是一样的,都是0V;电容CX的下端接GND,所以电容下端的电压也是0V。

        当闭合KEY的瞬间,电容充电电阻的上端电压就是电路供电电压,电阻R的下端电压还是0V;此时电阻R两端有压差,所以有电流流经R为电容CX充电;随着电子的累积,电压值也不断的增加,电阻R两端的电压变为V1和Vt,电容两端的电压都会变成Vt;此时,电阻R两端仍然存在电压差,会有源源不断的电子流经电容,电容两端的电压Vt不断增大,电容持续充电。

STM32F4_电容触摸按键_第2张图片

STM32F4_电容触摸按键_第3张图片

        电容两端的电压值随时间的变化曲线如图所示,因为电阻R两端的电压的压差在不断的减少,因此充电的效率会相应的变低,所以该曲线的变化趋势越来越平缓。该曲线表明:电容两边的电压和时间成正比,时间越长,对应的充电电容的电压也就越大

STM32F4_电容触摸按键_第4张图片

3. 硬件分析

STM32F4_电容触摸按键_第5张图片

4. 检测电容触摸按键的过程

        本节中,使用PA5的复用TIM2的1通道来检测TPAD是否被触摸在每次检测之前,我们先配置PA5为推挽输出,对电容进行放电,然后配置PA5为浮空输入,利用外部上拉电阻给电容充电同一时刻开启输入捕获,每当检测到上升沿,就认为电容充电完成了

        在MCU每次复位重启的时候,我们执行一次捕获检测,记录此时的值,记为 tpad_default_val,作为判断的依据。在后续的检测中,通过与 tpad_default_val 进行比较,来判断是不是有触摸发生。

①TPAD引脚设置为推挽输出,输出0,实现电容放电到 0。   首先通过推挽输出将电容的电放至0;

②TPAD引脚设置为浮空输入,电容开始充电。  通过浮空输入对电容进行充电

③开启TPAD引脚的输入捕获开始捕获。  使能TPAD对应芯片的引脚输入捕获计数器的值

④等待充电完成   检测上升沿

⑤计算充电时间

5. 库函数配置触摸按键

通过该框图的过程配置相关的库函数:       注意:纠正图中的TIM5_CH2_Cap_Init,本实验使用TIM2的1通道;

STM32F4_电容触摸按键_第6张图片

①void TPAD-Reset(void)函数:复位TPAD

设置IO口为推挽输出输出0,电容放电。等待放电完成之后,设置为浮空输入,从而开始充电。同时把计数器的CNT设置为0。

②TPAD_Get_Val()函数:获取一次捕获值(得到充电时间)

复位TPAD,等待捕获上升沿,捕获之后,得到定时器的值,计算充电时间。

③TPAD_Get_MaxVal()函数:

多次调用TPAD_Get_Val函数获取充电时间。获取最大的值。

④TPAD_Init()函数:初始化TPAD

在系统启动后,初始化输入捕获。先10次调用TPAD_Get_Val()函数获取10次充电时间,然后获取中间N(N等于8或者6)次的平均值,作为在没有电容触摸按键按下的时候的充电时间比较值tpad_default_val;

⑤TPAD_Scan()函数:扫描TPAD

调用TPAD_Get_MaxVal函数获取多次充电中最大的充电时间,跟tpad_default_val比较,如果大于某个值,则认为有触摸动作。

⑥void TIM2_CH1_Cap_Init(u16 arr,u16 psc)

输入捕获通道初始化;

6. 实验程序

STM32F4_电容触摸按键_第7张图片

6.1 main.c

#include "stm32f4xx.h"
#include "delay.h"
#include "LED.h"
#include "BEEP.h"
#include "Key.h"
#include "usart.h"
#include "exti.h"
#include "iwdg.h"
#include "wwdg.h"
#include "Timer.h"
#include "pwm.h"
#include "IntputCapture.h"
#include "tpad.h"


int main()
{
	u8 t=0;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级分组
	delay_init(168); //初始化延迟函数
	uart_init(115200); //初始化串口 这里再次强调一下,如果没有初始化串口,是不能使用printf打印函数的
	LED_Init(); //LED初始化
	TPAD_Init(8); //初始化触摸按键  预分频值设置为8
	while(1)
	{
		if(TPAD_Scan(0))//成功捕获一次上升沿
		{
			LED1=!LED1;
		}
//		if(TPAD_Scan(1))	//支持连按
//		{
//			LED0=!LED0;
//			LED1=!LED1;		
//			delay_ms(10);
//		}
		t++;
		if(t==15)
		{
			t=0;
			LED0=!LED0;
		}
		delay_ms(10);
	}
}

6.2 tpad.c

#include "stm32f4xx.h"                
#include "delay.h"
#include "usart.h"
#include "tpad.h"

//PrioritySendCount:预分频值(预分频值越小,触摸的灵敏度越高)   AutomaticReload:自动重装载值
#define TPAD_ARR_MAX_VAL 0xFFFFFFFF //TPAD最大自动重装载值 也就是计数器能达到的最大值
vu16 tpad_default_val=0;//没有手按下时,计数器需要的时间 vu16的意思是:typedef __IO uint16_t vu16;

//TPAD_Init初始化所要实现的事:
//1. TIM2_CH1_Cap_Init初始化输入捕获
//2. 10次调用TPAD_Get_Val;
//3. 取中间6次的平均值赋值给tpad_default_val

u8 TPAD_Init(u8 PrioritySendCount)
{
	u16 buf[10],temp; //数组用来接收10次充电的时间;
	u8 i,j;
	TIM2_CH1_Cap_Init(TPAD_ARR_MAX_VAL,PrioritySendCount-1); //初始化输入捕获
	for(i=0;i<10;i++) //连续捕获10次充电时间
	{
		buf[i]=TPAD_Get_Val();//TPAD_Get_Val用来获取充电时间
		delay_ms(10);
	}
	for(i=0;i<9;i++) //冒泡排序
	{//之所以说i=0;i<9是因为排序是建立在数组的基础之上的,需要从下角标0到下角标9开始排序
		for(j=i+1;j<10;j++)
		{//j=i+1;j<10是因为冒泡排序是当前元素和下一个元素进行比较,所以j永远要是i的下一个元素,当下一个元素比前一个元素大,就交换顺序
			if(buf[i]>buf[j]) //以升序的方式进行排列
			{
				temp=buf[i];
				buf[i]=buf[j];
				buf[j]=temp;
			}
		}
	}
	temp=0;//temp是一个中间变量,此时将temp置为0,以免被temp刚刚离开for循环的值所影响
	for(i=2;i<8;i++)//取中间的6个数据进行加和
	{//存储在数组中的10个元素的下角标分别是0 1 2 3 4 5 6 7 8 9,所以i=2;i<8表示取出中间的6个元素
		temp=temp+buf[i];
	}//离开该for循环,temp的值表示中间6个值的和
	tpad_default_val=temp/6;//取中间6个数的平均值赋值给tpad_default_val作为比较值
	printf("tpad_default_val:%d\r\n",tpad_default_val);//在串口打印出tpad_default_val的值
	if(tpad_default_val>TPAD_ARR_MAX_VAL/2)//初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常!
		return 1;//初始化失败
	return 0;//初始化成功
}//tpad_default_val是 MCU 重启后输入捕获检测的值,也可以说是没有手指触摸时检测到的值

//TPAD_Reset所要实现的事:
//1. 初始化PA5为推挽输出,输出0,放电
//2. 计数器的值初始化为0
//3. 初始化PA5位浮空输入,等待按下充电

void TPAD_Reset(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;//模式为输出
	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;//PA5引脚
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN;//下拉
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_ResetBits(GPIOA,GPIO_Pin_5);//Reset输出低电平,放电
	delay_ms(5);
	TIM_ClearITPendingBit(TIM2,TIM_IT_CC1|TIM_IT_Update);//清除中断标志
	TIM_SetCounter(TIM2,0);//TIM2定时器归0
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;//模式为复用
	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不带上下拉,浮空模式,进行充电
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
}

//TPAD_Get_Val所要实现的事:
//1. TPAD_Reset 复位TPAD
//2. 等待捕获上升沿
//3. 捕获到上升沿,返回捕获的值

u16 TPAD_Get_Val(void)
{				   
	TPAD_Reset(); //复位TPAD
	while(TIM_GetFlagStatus(TIM2, TIM_IT_CC1) == RESET)//TIM_IT_CC1为捕获标志 等待捕获上升沿   ==Reset意味着此时还没有捕获
	{//因为TIM2_CH1_Cap_Init函数设置的是上升沿捕获,所以只要是在while中,就一定还没有捕获到上升沿
		if(TIM_GetCounter(TIM2)>TPAD_ARR_MAX_VAL-500)
            return TIM_GetCounter(TIM2);//超时了,直接返回CNT的值
	};	
	return TIM_GetCapture1(TIM2);//只要离开while循环,就意味着捕获到了上升沿,此时要返回定时器2的计数器值	 
}

//获取N次中的最大值
u16 TPAD_Get_MaxVAL(u8 n)
{
	u16 temp=0,res=0;
	while(n--) //n是连续获取的次数,读取n次,获取n次里面的最大值,返回
	{
		temp=TPAD_Get_Val();//得到一次的值
		if(temp>res)
		{
			res=temp;
		}
		
	}
	return res;//最后得到的res是N次读取中的最大值
}

#define TPAD_GATE_VAL 100   //触摸的门限值,也就是必须大于tpad_default_val+TPAD_GATE_VAL,才认为是有效触摸. 
							//tpad_default_val是没有手指触摸时的值,为了防止数值接近这个值而被误认为是手指触摸,
							//所以在没有手指触摸的值上给定一个范围值,防止接近于tpad_default_val而被错误识别。

u8 TPAD_Scan(u8 mode)//该程序的逻辑等同于按键扫描的逻辑 mode等于0表示不支持连续触摸  mode等于1表示支持连续触摸
{
	static u8 key_up=0;//key_up=0可以开始检测;>0:还不能开始检测
	u8 res=0,sample=3;//默认的采样次数为3次
	u16 rval;
	if(mode)
	{
		sample=6;//支持连按的时候,默认的采样次数是6次
		key_up=0;//支持连按
	}
	rval=TPAD_Get_MaxVAL(sample);//获取最高的3次样本,赋值给rval
	if(rval>(tpad_default_val+TPAD_GATE_VAL)&&rval<(10*tpad_default_val))//有效的情况:3次样本的值大于tpad_default_val+TPAD_GATE_VAL
																		//且小于10倍的tpad_default_val,则有效
	{
		if((key_up==0)&&(rval>(tpad_default_val+TPAD_GATE_VAL)))//key_up=0可以开始检测,并且tpad_default_val+TPAD_GATE_VAL有效
			res=1; //res状态标志位
		key_up=3;//至少要再过3次之后才能按键有效
	}
	if(key_up)
		key_up--;
	return res;//res等于0表示没有按下;res等于1表示有按下,res=1有效(res=0是初始化时为0,一旦res=1表示进入了if判断语句,也就表示是有效的)
}

//TIM2通道1初始化输入
void TIM2_CH1_Cap_Init(u32 AutomaticReload,u16 PrioritySendCount)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//使能TIM2时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//使能GPIOA时钟
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource5,GPIO_AF_TIM2);//设置引脚复用,将GPIOA5复用为TIM2定时器
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不带上下拉
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数
	TIM_TimeBaseInitStructure.TIM_Period=AutomaticReload;
	TIM_TimeBaseInitStructure.TIM_Prescaler=PrioritySendCount;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	TIM_ICInitTypeDef TIM2_InitStructure;//初始化TIM2通道1
	TIM2_InitStructure.TIM_Channel=TIM_Channel_1;//通道1
	TIM2_InitStructure.TIM_ICFilter=0x00;
	TIM2_InitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;
	TIM2_InitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
	TIM2_InitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM2,&TIM2_InitStructure);
	
	TIM_Cmd(TIM2,ENABLE);//使能定时器TIM2
}

6.3 tpad.h

#ifndef _TPAD__H_
#define _TPAD__H_

extern vu16 tpad_default_val;


void TPAD_Reset(void);
u16 TPAD_Get_Val(void);
u16 TPAD_Get_MaxVAL(u8 n);
u8 TPAD_Scan(u8 mode);
void TIM2_CH1_Cap_Init(u32 AutomaticReload,u16 PrioritySendCount);
u8 TPAD_Init(u8 PrioritySendCount);


#endif

你可能感兴趣的:(STM32,stm32)