1.电容触摸按键原理介绍
触摸按键与传统的机械按键相比,不仅美观而且耐用、寿命长,它颠覆了传统意义上的机械按键控制,只要轻轻触摸,就可以实现按键开关的控制、量化调节甚至方向控制。触摸按键已广泛应用于手机、DVD、洗衣机等消费类电子产品中。本章我们就介绍一种简单的触摸按键:电容式触摸按键。
我们PZ6806D开发板上的电容触摸按键其实就是一小块覆铜区域,也称之为触摸感应区。
通常我们会将四周的铜片与电路板地信号连通,触摸感应区设计成方便手指触摸大小,并将其连接在输入捕获通道上。
触摸感应区与四周的铜片区域就形成了一个电容,通过检测电容充放
电时间即可判断是否有触摸。实现原理:
电容充放电公式:Vc=V0*(1-e^(-t/RC))
在上图中,R是外接电阻,开关就是STM32管脚的内部开关。CS是触摸感应区与电路板GND之间的杂散电容。当手指按到触摸区时,等于增加了一个CX电容并到CS上,所以电源通过RC对电容的充电时间就会变长。
本实验中,使用TIM5的通道2(PA1管脚)来检测触摸按键是否按下。具体步骤是:
1)在每次检测前,我们需要先将电容Cs(或 Cs+Cx)放电,即配置PA1引脚为推挽输出模式,输出一个低电平,才能使电容放电。这等效于上图中的开关闭合。
2)然后配置PA1 为浮空输入模式,利用外部上拉电阻给电容 Cs(Cs+Cx)充电,同时开启TIM5_CH2的输入捕获,配置极性为上升沿,当检测到上升沿的时候,就认为电容充电完
成了,完成一次捕获检测。 每次系统重启时,我们执行一次捕获检测(可认为没有触摸),记录此时捕获到上升沿时,需要多少时间即TCS的值。
3)在后续的捕获检测中,即不断重复上面的第1步和第2步,我们就可以通过与记录的值进行对比,判断是否发生触摸。很明显,如果没有发生触摸,每次捕获发生的时间是基本上相等的,如果有触摸,那么时间必然明显延长。这样,就知道了是否发生触摸了
这就是电容触摸工作的原理!搞清楚了就很简单。
2.编写电容触摸按键控制程序
本实验所要实现的功能是:通过TIM5的通道2(PA1)捕获电容触摸按键输入信号的高电平脉宽,根据捕获到的高电平时间长短,来判断是否有按键按下,如果有按下,则翻转D2指示灯的状态以提示检测到了一次按下。同时D1指示灯不断闪烁表示系统正常运行。程序框架如下:
(1)初始化PA1管脚为TIM5通道2输入捕获功能,设置上升沿捕获等
(2)读取一次捕获高电平的值
(3)电容触摸按键初始化
(4)检测电容触摸按键是否按下
(5)编写主函数
main.c
#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "input.h"
#include "touch_key.h"
int main()
{
u8 i=0;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断优先级分组
LED_Init();
USART1_Init(9600);
Touch_Key_Init(6);//72M 6分频后12M
while(1)
{
if((Touch_Key_Scan(0)==1))//判断触摸是否有效
{
led2=led2;//有效则翻转指示灯
}
i++;
if(i%20 ==0)
{
led1=!led1;//LED1闪,用来指示主程序循环是否运行
}
delay_ms(50);
}
}
touch_key.c
#include "touch_key.h"
#include "SysTick.h"
#include "usart.h"
#define Touch_ARR_MAX_Value 0xffff
u16 touch_default_value =0;
void TIM5_CH2_Input_Init(u16 period,u16 prescaler)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//结构体变量声明
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);//使能TIM5时钟
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;//
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_TimeBaseInitStructure.TIM_Period=period; //装入函数传过来的自动装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=prescaler; //装入函数传过来的分频系数
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//1分频(没有分频)
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //设置向上计数模式:从0开始计数到自动重载值后溢出产生中断
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);//初始化TIM5各参数:自动重装值、分频系统、计数方式等
TIM_ICInitStructure.TIM_Channel=TIM_Channel_2; //通道2
TIM_ICInitStructure.TIM_ICFilter=0x00; //无滤波
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;//捕获极性设为上升沿
TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1; //分频系数
TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;//直接映射到TI1
TIM_ICInit(TIM5,&TIM_ICInitStructure);
TIM_Cmd(TIM5,ENABLE );//使能定时器
}
void Touch_Reset()
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//先设为输出模式以方便输出低电平
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_1);//将IO口输出低电平以将触摸按键的电容放电
delay_ms(5);
TIM_ClearFlag(TIM5,TIM_FLAG_Update|TIM_FLAG_CC2);//清除定时器的状态标志
TIM_SetCounter(TIM5,0); //设定定时器初值为0以重新计数
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;//重新改为浮空模式以对电容充电
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
u16 Touch_Get_Value()
{
Touch_Reset();
while(TIM_GetFlagStatus(TIM5,TIM_FLAG_CC2)==0)//
{
if(TIM_GetCounter(TIM5)>Touch_ARR_MAX_Value-500)
{
return TIM_GetCounter(TIM5);//如果没有发生捕获事件,计数器计到最大值-500后返回当前计数器的值
}
}
return TIM_GetCapture2(TIM5);//如果发生了捕获事件,则返回捕获发生时的计数器值
}
u8 Touch_Key_Init(u8 psc)
{
u8 i,j;
u16 buf[10];
u16 temp;
TIM5_CH2_Input_Init(Touch_ARR_MAX_Value,psc);//定时器5通道2输入捕获初始化
for (i=0; i<10;i++)
{
buf[i]= Touch_Get_Value();//得到10个值
delay_ms(10);
}
//将得到的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;
}
}
//去掉最小的两个,去掉最大的两个,余下的6个取平均值
temp=0;
for(i=2; i<8;i++)
{
temp+=buf[i];
}
touch_default_value = temp/6;
printf("touch_default_value=%d\r\n",touch_default_value);
if(touch_default_value>touch_default_value/2)//这个判断条件是通过实际调试测试决定的
return 1;//如果这个值大于最大值的一半,也就对应前面的没有发生捕获的情况,那么认为初始化失败
//返回1 表明初始化失败
return 0; //返回0表示初始化成功,得到了没有触摸时的缺省充电时间
}
u16 Touch_Get_MaxVal(u8 n)//得到n次捕获中的最大值
{
u16 temp=0;
u16 max=0;
while(n--)
{
temp=Touch_Get_Value();
if(temp>max)
max=temp;
}
return max;
}
//mode =0 单次扫描,mode =1,连续扫描
//返回Touch_Status,其值为1则表示触摸有效
#define TOUCH_GATE_VAL 100
u8 Touch_Key_Scan(u8 mode)
{
u8 Touch_Status;
u8 sample =3;
u16 MaxVal =0;
static u8 keyen =0;
if(mode)//mode如为1,则表示连续扫描,因此,每次调用Touch_Key_Scan时都将keyen=0,所以每次都能得到触摸值
{ //反之,因为keyen后续=3,所以需要三次之后,才能降为0,才能得到触摸值
sample =6;
keyen=0;
}
MaxVal=Touch_Get_MaxVal(sample);//得到三次采样中的最大值
if(MaxVal>(touch_default_value+TOUCH_GATE_VAL)&&MaxVal<(10*touch_default_value))
{
if((keyen==0)&&(touch_default_value+TOUCH_GATE_VAL))
{
Touch_Status=1;
}
}
printf("触摸后捕获高电平值为:%d\r\n",MaxVal);
keyen=3;
if(keyen) keyen--;
return Touch_Status;
}
touch_key.h
#ifndef _touch_key_H
#define _touch_key_H
#include "system.h"
void TIM5_CH2_Input_Init(u16 period,u16 prescaler);
u8 Touch_Key_Init(u8 psc);
u8 Touch_Key_Scan(u8 mode);
#endif
这个程序因为只能在PZ6806D开发板上才可以运行,而我手上的是PZ6806L,所以没有实际烧录测试,但原理我是完全理解的。
因此,如果在PZ6806L上接出一块小的触摸板,这实验是可以做成功的,这没有什么疑问。