笔记14:STM32F4之电容触摸按键
一. 电容触摸按键原理
1.电路原理介绍
上图是基于整点原子STM32F4的原理图,其中蓝色TPAD部分就是我们电容触摸按键位置,红色的TPAD是悬空的,需要我们将其与其他引脚相连接起来。在开发板上,我们需要用跳线帽将STM ADC与TAPD连接起来,这样TAPD引脚就可以与PA5连接起来了。(也可以用其他引脚)
由于要用到捕获,因为我们需要在数据手册上查看PA5可以对应哪一个定时器通道。
由上图我们选择定时器2通道1。
上图是按键按下前后对比图。R:外接电容充放电电阻。
按键按下之前,电路之中只连接了一个电容Cs(TPAD和PCB间的杂散电容),按键按下之后,与Cs并联了一个Cx(手指按下时,手指和TPAD之间的电容),电路总电容变大。其中开关部分是电容放电开关,由STM32 IO口代替。也就是对应着第一张图片的红色TAPD脚。
根据电路知识可知,电容上的充放电公式为:
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;
}
整体思路:
流程图:
上一篇: jQuery 获取/设置/删除DOM元素的属性以a元素为例
下一篇: 第二篇 点亮led灯