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分频后12Mwhile(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; //通道2TIM_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;//直接映射到TI1TIM_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上接出一块小的触摸板,这实验是可以做成功的,这没有什么疑问。