欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

笔记14:STM32F4之电容触摸按键

程序员文章站 2022-06-09 08:26:23
...

一. 电容触摸按键原理
1.电路原理介绍
笔记14:STM32F4之电容触摸按键

上图是基于整点原子STM32F4的原理图,其中蓝色TPAD部分就是我们电容触摸按键位置,红色的TPAD是悬空的,需要我们将其与其他引脚相连接起来。在开发板上,我们需要用跳线帽将STM ADC与TAPD连接起来,这样TAPD引脚就可以与PA5连接起来了。(也可以用其他引脚)
由于要用到捕获,因为我们需要在数据手册上查看PA5可以对应哪一个定时器通道。
笔记14:STM32F4之电容触摸按键
由上图我们选择定时器2通道1。
笔记14:STM32F4之电容触摸按键
上图是按键按下前后对比图。R:外接电容充放电电阻。
按键按下之前,电路之中只连接了一个电容Cs(TPAD和PCB间的杂散电容),按键按下之后,与Cs并联了一个Cx(手指按下时,手指和TPAD之间的电容),电路总电容变大。其中开关部分是电容放电开关,由STM32 IO口代替。也就是对应着第一张图片的红色TAPD脚。
根据电路知识可知,电容上的充放电公式为:
笔记14:STM32F4之电容触摸按键
V0 为电容上的初始电压值;
V1 为电容最终可充到或放到的电压值;
Vt 为t时刻电容上的电压值。
RC为充电时间常数,是电容的端电压达到最大值的0.63倍时所需要的时间,通常认为时间达到5倍的充电时间常数后就认为充满了。时间常数越大,电容充放电就越满(大电容和小电容相比肯定是大电容充电慢;大电阻和小电阻相比,大电阻起始电流较小,因此大电阻充电时间慢)。根据这个原理我便可涉及触摸按键的使用原理了。
2. 具体原理介绍
由以上分析我们得知,在电阻相同的条件下,电容越大,充电到某一电压值的时间越长,因此我们记录触摸按键没有触摸时电容的充电时间T1,之后的检测以这个时间做标准,按下TPAD,电容变大,所以充电时间为T2。我们可以通过检测充放电时间,来判断是否按下。如果T2-T1大于某个值,就可以判断有按键按下。
3. 检测电容触摸按键的过程
①. TPAD引脚设置为推挽输出,输出0,实现电容放电到0。(相当于将红色TPAD接地)
②. TPAD引脚设置为浮空输入(IO复位后的状态),电容开始充电。(相当于将红色TAPD断开)
③. 同时开启TPAD引脚的输入捕获开始捕获。
④. 等待充电完成(充电到底Vx,检测到上升沿)。
⑤. 计算充电时间。
二. 程序设计
1.具体程序及详细说明

头文件"tpad.h"里面就是对tapd.c里面的函数内容进行申明,具体内容如下:

#ifndef __TPAD_H
#define __TPAD_H
#include "sys.h"							   	    
void TPAD_Reset(void);
u16 TPAD_Get_Val(void);
u16 TPAD_Get_MaxVal(u8 n);
u8 TPAD_Init(u32 psc);		//初始化触摸按键,记录没有按下的电容充电时间,初始化成功返回0,未成功返回1;
u8 TPAD_Scan(u8 mode);
void TIM2_CH1_Cap_Init(u32 arr,u16 psc);

#endif

宏定义及头文件部分:对于代码vu16类型说明:
typedef __IO uint16_t vu16; 这是本身在stm2f4xx.h头文件里面定义了的。

#include "tpad.h"
#include "delay.h"		    
#include "usart.h"
#define TPAD_ARR_MAX_VAL  0XFFFFFFFF	//最大的ARR值(TIM2是32位定时器),防止捕获的时候CCRX>ARR	  
vu16 tpad_default_val=0;				//空载的时候(没有手按下),计数器需要的时间
#define TPAD_GATE_VAL 100							//触摸的门限值,也就是必须tpad_default_val+TPAD_GATE_VAL,才认为是有效触摸

捕获相关程序-----TIM2_CH1_Cap_Init(),当电容开始充电并且达到能够捕获上升沿的电压时,就会捕获到一个上升沿。

void TIM2_CH1_Cap_Init(u32 arr,u16 psc)		//定时器 2 通道 1 输入捕获配置
{
	GPIO_InitTypeDef GPIO_InitStruct;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_ICInitTypeDef TIM_ICInitStruct;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);			//TIM2时钟使能
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);		//GPIOA时钟使能
	
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;						//GPIOA5
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;						//复用功能
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_100MHz;
	GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;					//推挽复用输出
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;					//不带上下拉
	GPIO_Init(GPIOA,&GPIO_InitStruct);							//初始化PA5
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource5,GPIO_AF_TIM2);		//将GPIO5复用映射到TIM2
	
	TIM_TimeBaseInitStruct.TIM_Prescaler=psc;					//TIM2分频系数
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;	//TIM2向上计数模式
	TIM_TimeBaseInitStruct.TIM_Period=arr;						//重载值
	TIM_TimeBaseInitStruct.	TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);				//初始化定时器2
	
	TIM_ICInitStruct.TIM_Channel=TIM_Channel_1;					//TIM2的通道1
	TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Rising;		//上升沿捕获
	TIM_ICInitStruct.TIM_ICSelection=TIM_ICSelection_DirectTI;	//IC1映射到TI1上
	TIM_ICInitStruct.TIM_ICPrescaler=TIM_ICPSC_DIV1;					//不分频
	TIM_ICInitStruct.TIM_ICFilter=0000; 								//不滤波
	TIM_ICInit(TIM2,&TIM_ICInitStruct);							//初始化TIM2捕获通道1
	TIM_Cmd(TIM2,ENABLE);
}



TAPD_Reset(),主要作用就是让电容放电

void TPAD_Reset(void)		//复位一次,并让电容放电,清零定时计数器
{
	
	GPIO_InitTypeDef GPIO_InitStruct;
	
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;				//PA5
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;			//普通输出模式
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_100MHz;		//速度100MHZ
	GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;			//推挽输出
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_DOWN;			//下拉
	GPIO_Init(GPIOA,&GPIO_InitStruct);      			//初始化GPIOA
	
	GPIO_ResetBits(GPIOA,GPIO_Pin_5);					//PA5输出0,电容放电
	delay_ms(5);										//等待放电结束
	TIM_SetCounter(TIM2,0);								//TIM2计数器清0
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;				//PA5
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;				//复用功能
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_100MHz;		//速度100MHZ
	GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;			//推挽复用输出
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;			//既不上拉也不下拉
	GPIO_Init(GPIOA,&GPIO_InitStruct);					//初始化GPIOA
	
}

TPAD_Get_Val,这个函数作用是获取到达上升沿时的计数器里面的计数值,为计算电容充电时间提供数据。

u16 TPAD_Get_Val(void)			//得到定时器的捕获值
{
	TPAD_Reset();
	while(TIM_GetFlagStatus(TIM2, TIM_IT_CC1) == RESET)//等到电容充电到一个值后产生上升沿捕获
	{
		if(TIM_GetCounter(TIM2)>TPAD_ARR_MAX_VAL-500)		//超过正常按下时间,直接返回定时器计数值
			return TIM_GetCounter(TIM2);
	}
	return TIM_GetCapture1(TIM2);		//返回定时器捕获值
		
}

TAPD_Get_MaxVal,这个函数的作用是为了后面判断按键触摸时触摸是否有效,取最大值判断更精确。

u16 TPAD_Get_MaxVal(u8 n)//n为读取的次数
{
	u16 temp=0,res=0;
	while(n--)
	{
		temp=TPAD_Get_Val();
		if(temp>res)
			res=temp;
		
	}
	return res;
}				

TAPD_InIT()

u8 TPAD_Init(u32 psc)		//初始化触摸按键,记录没有按下的电容充电时间,初始化成功返回0,未成功返回1
{
	
	u16 ChargingTimeData[10],temp;
	u8 i,j;
	TIM2_CH1_Cap_Init(TPAD_ARR_MAX_VAL,psc-1);
	for(i=0;i<10;i++)		//读取10次值
	{
		ChargingTimeData[i]=TPAD_Get_Val();
		delay_ms(10);
	}
	for(i=0;i<9;i++)			//数组升序排列
	{
		for(j=0;j<10;j++)
		{	
			if(ChargingTimeData[i]>ChargingTimeData[j])
				temp=ChargingTimeData[i];
				ChargingTimeData[i]=ChargingTimeData[j];
				ChargingTimeData[j]=temp;
		}
		
	}
	temp=0;
	for(i=2;i<8;i++)		//取中间6个数据做平均值
		temp+=ChargingTimeData[i];
	tpad_default_val=temp/6;
	printf("tpad_default_val:%d\r\n",tpad_default_val);	//输出初始化时的电容充电相关数据
	if(tpad_default_val>TPAD_ARR_MAX_VAL/2)
		return 1;	//初始化遇到超过 TPAD_ARR_MAX_VAL/2 的数值,不正常!
		return 0;
}

触摸按键扫描部分

u8 TPAD_Scan(u8 mode) //mode=1支持一直按下,mode=0,不支持一直按下
						//返回值为1有按下,返回值为0没有按下
{
	static u8 keyen=0; //0,可以开始检测;>0,还不能开始检测
	u8 res=0,sample=3; //默认采样次数为 3 次
	u16 rval;
	if(mode)
	{
		sample=6; //支持连按的时候,设置采样次数为 6 次
		keyen=0; //支持连按
	}
	//不支持连按的情况下
	rval=TPAD_Get_MaxVal(sample);
	if(rval>(tpad_default_val+TPAD_GATE_VAL)&&rval<(10*tpad_default_val))	//大于 tpad_default_val+TPAD_GATE_VAL,且小于 10 倍 tpad_default_val,则有效
	{
		if((keyen==0)&&(rval>(tpad_default_val+TPAD_GATE_VAL)))				//大于 tpad_default_val+TPAD_GATE_VAL,有效
			res=1;
			keyen=3; //至少要再过 3 次之后才能按键有效
	}
	if(keyen)
		keyen--;
	return res;
		
	
	
}

整体思路:
笔记14:STM32F4之电容触摸按键
流程图:

笔记14:STM32F4之电容触摸按键

相关标签: STM32系列