STM32电容按键

STM32电容按键实验

一、电容按键实验简介

触摸按键相对于传统的机械按键有寿命长、占用空间少、易于操作等诸多优点。大家看看如今的手机,触摸屏、触摸按键大行其道,而传统的机械按键,正在逐步从手机上面消失。本章,我们将给大家介绍一种简单的触摸按键:电容式触摸按键。我们将利用精英 STM32F103 上的触摸按键(TPAD),来实现对 DS1 的亮灭控制。这里 TPAD其实就是精英 STM32F103 上的一小块覆铜区域,实现原理如图 16.1.1 所示:
Alt
这里我们使用的是检测电容充放电时间的方法来判断是否有触摸, 图中 R 是外接的电容充电电阻, Cs 是没有触摸按下时 TPAD 与 PCB 之间的杂散电容。 而 Cx 则是有手指按下的时候,手指与 TPAD 之间形成的电容。图中的开关是电容放电开关(由实际使用时,由 STM32F1 的IO 代替)。
先用开关将 Cs(或 Cs+Cx)上的电放尽,然后断开开关,让 R 给 Cs(或 Cs+Cx)充电,当没有手指触摸的时候, Cs 的充电曲线如图中的 A 曲线。 而当有手指触摸的时候, 手指和 TPAD之间引入了新的电容 Cx,此时 Cs+Cx 的充电曲线如图中的 B 曲线。 从上图可以看出, A、 B两种情况下, Vc 达到 Vth 的时间分别为 Tcs 和 Tcs+Tcx。
其中, 除了 Cs 和 Cx 我们需要计算,其他都是已知的,根据电容充放电公式:Vc=V0*(1-e^(-t/RC))
其中 Vc 为电容电压, V0 为充电电压, R 为充电电阻, C 为电容容值, e 为自然底数, t 为充电时间。根据这个公式,我们就可以计算出 Cs 和 Cx。 利用这个公式,我们还可以把精英开发板作为一个简单的电容计,直接可以测电容容量了,有兴趣的朋友可以捣鼓下。
其实我们只要能够区分 Tcs 和 Tcs+Tcx,就已经可以实现触摸检测了,当充电时间在 Tcs 附近,就可以认为没有触摸,而当充电时间大于 Tcs+Tx 时,就认为有触摸按下(Tx为检测阀值)。
本次实验我们使用 PA1(TIM5_CH2)来检测 TPAD 是否有触摸,在每次检测之前,我们先配置PA1 为推挽输出,将电容 Cs(或 Cs+Cx)放电,然后配置 PA1 为浮空输入,利用外部上拉电阻给电容 Cs(Cs+Cx)充电,同时开启 TIM5_CH2 的输入捕获,检测上升沿,当检测到上升沿的时候,就认为电容充电完成了,完成一次捕获检测。
在 MCU 每次复位重启的时候,我们执行一次捕获检测(可以认为没触摸),记录此时的值,记为 tpad_default_val,作为判断的依据。在后续的捕获检测,我们就通过与 tpad_default_val 的对比,来判断是不是有触摸发生。

二、硬件设计

本实验用到的硬件资源有:
1) 指示灯 DS0 和 DS1
2) 定时器 TIM5
3) 触摸按键 TPAD
我们需要通过 TIM5_CH2(PA1)采集 TPAD 的信号,所以本实验需要用跳线帽短接多功能端口(P7)的 TPAD 和 ADC,以实现 TPAD 连接到 PA1。如图 16.2.1所示:在这里插入图片描述

三、软件设计

在 HARDWARE 文件夹下新建 TPAD 的文件夹。然后打开 USER 文件夹下的工程,新建一个 tpad.c 的文件和 tpad.h 的头文件,保存在 TAPD 文件夹下,并将 TPAD 文件夹加入头文件包含路径。我们在 tpad.c 里输入如下代码:

#define TPAD_ARR_MAX_VAL 0XFFFF //最大的 ARR 值
vu16 tpad_default_val=0;//空载的时候(没有手按下),计数器需要的时间
//初始化触摸按键
//获得空载的时候触摸按键的取值.
//systick:系统时钟频率
//返回值:0,初始化成功;1,初始化失败
u8 TPAD_Init(u8 systick)
{
u16 buf[10];
u16 temp;
u8 j,i;
TIM5_CH2_Cap_Init(TPAD_ARR_MAX_VAL,systick-1);//以 1Mhz 的频率计数
for(i=0;i<10;i++)//连续读取 10 次
{
buf[i]=TPAD_Get_Val(); 
delay_ms(10);
}
for(i=0;i<9;i++)//排序
{
for(j=i+1;j<10;j++)
{
if(buf[i]>buf[j])//升序排列
{
temp=buf[i];
buf[i]=buf[j];
buf[j]=temp;
}
}
}
temp=0;
for(i=2;i<8;i++)temp+=buf[i];//取中间的 8 个数据进行平均
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;//初始化数值不正常!
return 0;
}
//复位一次
//释放电容电量,并清除定时器的计数值
void TPAD_Reset(void)
{
GPIOA->CRL&=0XFFFFFF0F; //PA1 输入
GPIOA->CRL|=0X00000030; 
GPIOA->ODR&=~(1<<1); 
delay_ms(5);
TIM5->SR=0; 
TIM5->CNT=0; //推挽输出
//输出 0,放电//清除标记
//归零GPIOA->CRL&=0XFFFFFF0F; //PA1 输入GPIOA->CRL|=0X00000040; //浮空输入}
//得到定时器捕获值
//如果超时,则直接返回定时器的计数值.
//返回值:捕获值/计数值(超时的情况下返回)
u16 TPAD_Get_Val(void)
{
TPAD_Reset();
while((TIM5->SR&0X04)==0)//等待捕获上升沿
{
if(TIM5->CNT>TPAD_ARR_MAX_VAL-500)return TIM5->CNT;//超时了 
};
return TIM5->CCR2;
}
//读取 n 次,取最大值
//n:连续获取的次数
//返回值: n 次读数里面读到的最大读数值
u16 TPAD_Get_MaxVal(u8 n)
{
u16 temp=0;
u16 res=0;
while(n--)
{
temp=TPAD_Get_Val();//得到一次值
if(temp>res)res=temp;
};
return res;
}
//扫描触摸按键
//mode:0,不支持连续触发(按一次必须松开才能按下一次);1,支持连续触发(可一直按下)
//返回值:0,没有按下;1,有按下;
#define TPAD_GATE_VAL 100 //门限值,必须大于 tpad_default_val+TPAD_GATE_VAL
u8 TPAD_Scan(u8 mode)
{
static u8 keyen=0; //0,可以开始检测;>0,还不能开始检测
u8 res=0;
u8 sample=3; 
u16 rval;
if(mode)
{
sample=6; 
keyen=0; //默认采样次数为 3 次//支持连按的时候,设置采样次数为 6 次
//支持连按}
rval=TPAD_Get_MaxVal(sample);
if(rval>(tpad_default_val+TPAD_GATE_VAL))
//大于 tpad_default_val+TPAD_GATE_VAL,有效
{
if(keyen==0)res=1; 
//printf("r:%d\r\n",rval);
keyen=3; //keyen==0,有效//至少要再过 3 次之后才能按键有效}
if(keyen)keyen--;
return res;
} 

//定时器 5 通道 2 输入捕获配置
//arr:自动重装值
//psc:时钟预分频数
void TIM5_CH2_Cap_Init(u16 arr,u16 psc)
{
//此部分需手动修改 IO 口设置
RCC->APB1ENR|=1<<3; //TIM5 时钟使能
RCC->APB2ENR|=1<<2; //使能 PORTA 时钟
GPIOA->CRL&=0XFFFFFF0F; //PA1 输入
GPIOA->CRL|=0X00000040; //浮空输入
TIM5->ARR=arr; //设定计数器自动重装值//刚好 1ms
TIM5->PSC=psc; //预分频器,1M 的计数频率
TIM5->CCMR1|=1<<8; //CC2S=01 选择输入端 IC2 映射到 TI2 上
TIM5->CCMR1|=0<<12; //IC2F=0011 配置输入滤波器 8 个定时器时钟周期滤波
TIM5->CCMR1|=0<<10; //IC2PS=00 配置输入分频,不分频
TIM5->CCER|=0<<5; //CC2P=0 上升沿捕获
TIM5->CCER|=1<<4; //CC2E=1 允许捕获计数器的值到捕获寄存器中
TIM5->CR1|=0x01; //使能定时器 5
} 

此部分代码包含 6 个函数,我们将介绍其中 4 个比较重要的函数:TIM5_CH2_Cap_Init、TPAD_Get_Val、 TPAD_Init 和 TPAD_Scan。
首先介绍TIM5_CH2_Cap_Init函数,该函数和上一章的输入捕获函数基本一样,不同的是,这里我们设置的是 CH2 通道,并开启了输入滤波器。通过该函数的设置,我们将可以捕获 PA1上的上升沿。我们再来看看 TPAD_Get_Val 函数,该函数用于得到定时器的一次捕获值。该函数先调用TPAD_Reset,将电容放电,同时设置 TIM5_CNT 寄存器为 0,然后死循环等待发生上升沿捕获(或计数出),将捕获到的值(或溢出值)作为返回值返回。
接着我们介绍 TPAD_Init 函数,该函数用于初始化输入捕获,并获取默认的 TPAD 值。该函数有一个参数,用来传递分频系数,其实是为了配置TIM5_CH2_Cap_Init 的分频系数,该值设置的越小,触摸越灵敏,不过也越容易受干扰。在该函数中连续 10 次读取 TPAD 值,将这些值升序排列后取中间 6 个值再做平均(这样做的目的是尽量减少误差),并赋值给tpad_default_val,用于后续触摸判断的标准。
最后,我们来看看 TPAD_Scan 函数,该函数用于扫描 TPAD 是否有触摸, 该函数的参数mode,用于设置是否支持连续触发。返回值如果是 0,说明没有触摸,如果是 1,则说明有触摸。该函数同样包含了一个静态变量,用于检测控制,类似第八章的 KEY_Scan 函数。所以该函数同样是不可重入的。在函数中,我们通过连续读取 3 次(不支持连续按的时候)TPAD 的值,取这他们的最大值,和 tpad_default_val+TPAD_GATE_VAL 比较,如果大于则说明有触摸,如果小于,则说明无触摸。其中 tpad_default_val 是我们在调用 TPAD_Init 函数的时候得到的值,而 TPAD_GATE_VAL 则是我们设定的一个门限值(这个大家可以通过实验数据得出,根据实际情况选择适合的值就好了,越小越灵敏),这里我们设置为 100。该函数,我们还做了一些其他的条件限制,让触摸按键有更好的效果,这个就请大家看代码自行参悟了。
我们将 tpad.c 文件保存,然后加入到 HARDWARE 组下。接下来,在 tpad.h 文件里,我们输入如下代码:

#ifndef __TPAD_H
#define __TPAD_H
#include "sys.h"
#include "timer.h"
//空载的时候(没有手按下),计数器需要的时间
//这个值应该在每次开机的时候被初始化一次
extern vu16 tpad_default_val;
void TPAD_Reset(void);
u16 TPAD_Get_Val(void);
u16 TPAD_Get_MaxVal(u8 n);
u8 TPAD_Init(u8 systick);
u8 TPAD_Scan(u8 mode);
void TIM5_CH2_Cap_Init(u16 arr,u16 psc);
#endif 

主函数的代码主要如下:

int main(void)
{
u8 t=0;
Stm32_Clock_Init(9); 
uart_init(72,115200); 
delay_init(72); 
LED_Init(); 
TPAD_Init(6); 
while(1)
{//系统时钟设置
//串口初始化为 115200
//延时初始化
//初始化与 LED 连接的硬件接口
//初始化触摸按键if(TPAD_Scan(0)) //成功捕获到了一次上升沿(此函数执行时间至少 15ms)
{
LED1=!LED1;//LED1 取反
}
t++;
if(t==15)
{
t=0;
LED0=!LED0;//LED0 取反,提示程序正在运行
}
delay_ms(10);
}
} 


main 函数比较简单, TPAD_Init(6)函数执行之后,就开始触摸按键的扫描,当有触摸的时候,对 DS1 取反,而 DS0 则有规律的间隔取反,提示程序正在运行。 注意在修改 main 函数之后,还需要在 test.c 里面添加 tpad.h 头文件,否则会报错哦。
这里还要提醒一下大家,不要把 uart_init(72,115200);去掉,因为在 TPAD_Init 函数里我们有用到 printf,如果你去掉了 uart_init,就会导致 printf 无法执行,从而死机。

发布了37 篇原创文章 · 获赞 92 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44895651/article/details/100636678