18.【NXP 号令者RT1052】开发——实战-电容触摸按键
18.【NXP 号令者RT1052】开发——实战-电容触摸按键
上一章,我们介绍了 RT1052 的输入捕获功能及其使用。这一章,我们将向大家介绍如何通过输入捕获功能,来做一个电容触摸按键。在本章中,我们将用 QTMR3 的通道 2(P118)来做输入捕获,并实现一个简单的电容触摸按键,通过该按键控制 DS1 的亮灭。
18.1 电容触摸按键简介
18.1.1 电容触摸按键原理简介
电容式触摸按键利用手指与覆铜间形成的寄生电容变化实现检测,寿命长、省空间、免开孔,已成为手机等轻薄设备的主流方案。
本章通过号令者 RT1052 的 TSI 模块读取板载 TPAD 覆铜电容值,一旦检测到触摸即点亮 DS1,手指离开则熄灭,全程仅需一块铜箔即可实现稳定可靠的触摸开关。

这里我们使用的是检测电容充放电时间的方法来判断是否有触摸,图中 R 是外接的电容充电电阻,Cs 是没有触摸按下时 TPAD 与 PCB 之间的杂散电容。而 Cx 则是有手指按下的时候,手指与 TPAD 之间形成的电容。图中的开关是电容放电开关(由实际使用时,由 RT1052 的 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 为检测阀值)。
本章,我们使用 P118(GPIO1_IO18,即 QTMR3_CH2))来检测 TPAD 是否有触摸,在每次检测之前,我们先配置 P118 为推挽输出,输出低电平,将电容 Cs(或 Cs+Cx)放电,然后配置 P118 为浮空输入,利用外部上拉电阻给电容 Cs(Cs+Cx)充电,同时开启 QTMR3_CH2 的输入捕获,检测上升沿,当检测到上升沿的时候,就认为电容充电完成了,完成一次捕获检测。
在 MCU 每次复位重启的时候,我们执行一次捕获检测(可以认为没触摸),记录此时的值,记为 tpad_default_val,作为判断的依据。在后续的捕获检测,我们就通过与 tpad_default_val 的对比,来判断是不是有触摸发生
18.1.2 四定时器输入捕获原理简介
关于四定时器(QTMR)我们在第十五章和第十六章有过一些介绍,RT1052 内部包含了 4个四定时器,每个四定时器又有 4 个通道,每个通道又可以作为 PWM 输出或者作为输入捕获。
本章,我们就是利用四定时器 3 的通道 2(QTMR3_CH2)来做输入捕获。
图中 CNTR 是 QTMR3 的计数器,向上计数模式,范围为 0~65535;CAPT1 是没触摸按下时的输入捕获值;CAPT2 是有触摸按下时的输入捕获值;Vth 为 RT1052 IO 口的高电平阈值,查数据手册可知为:0.7NVCC_GPIOx=0.73.3=2.31V ,QTMR3 定时器工作在上升沿捕获模式,只要检测到通道 2(TPAD)有上升沿,就立即读取 CAPT 的值,以此为依据来判定是否有电容触摸按键按下了。
没触摸按下时,识别过程为:首先控制捕获 IO 口(P118)为推挽输出,然后输出低电平,给 TPAD 和 PCB 之间的杂散电容(Cs)放电,让 TPAD 电压恢复低电平,并重置 CNTR 计数器(t1 时刻),然后设置捕获 IO 为浮空输入并立即启动定时器(工作在上升沿捕获模式),然后等待 IO 口变为高电平(电压≥Vth,t2 时刻),此时可以将捕获到的值(记为 CAPT1)保存起来,完成一次检查,再经过一段时间的充电,TPAD 的电压将达到 3.3V(t3 时刻)。
有触摸按下时,识别过程为:首先控制捕获 IO 口(P118)为推挽输出,然后输出低电平,给手指+TPAD 和 PCB 之间的杂散电容(Cx)放电,让 TPAD 电压恢复低电平,并重置 CNTR计数器(t5 时刻),然后设置捕获 IO 为浮空输入并立即启动定时器(工作在上升沿捕获模式),然后等待 IO 口变为高电平(电压≥Vth,t6 时刻),此时可以将捕获到的值(记为 CAPT2)保存起来,完成一次检查。
因为有手指按下 TPAD 时,杂散电容(Cx)大于没按下时的(Cs),因此有触摸时,TPAD的上升沿会缓慢一些,从而 CAPT2 也会大一些。因此可以通过 CAPT2 和 CAPT1 的比较,来识别是否有触摸按键按下,这就是电容式触摸按键的工作原理。
RT1052 QTIMER3 输入捕获配置步骤
1. 设置 GPIO1_IO18 复用功能
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_02_QTIMER3_TIMER2,0);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_02_QTIMER3_TIMER2,0x10C1);
2. 使能 QTIMER3 时钟
CLOCK_EnableClock(kCLOCK_Timer3);
此函数会被
QTMR_Init调用,无需显式调用。
3. 初始化 QTIMER3
qtmr_config_t qtimer3_config;
QTMR_GetDefaultConfig(&qtimer3_config); //先设置为默认配置
qtimer3_config.primarySource=kQTMR_ClockDivide_4; //设置第一时钟源
QTMR_Init(TMR3,kQTMR_Channel_2,&qtimer3_config); //初始化 QTIMER
这里选择 IPG_CLK_ROOT 的 4 分频作为 QTIMER3 第一时钟源。
4. 设置 QTIMER3_TIMER2 为输入捕获
void QTMR_SetupInputCapture(TMR_Type * base,qtmr_channel_selection_t channel,qtmr_input_source_t capturePin,bool inputPolarity,bool reloadOnCapture,qtmr_input_capture_edge_t captureMode)
输入源枚举
typedef enum _qtmr_input_source
{kQTMR_Counter0InputPin = 0, //counter0 输入引脚kQTMR_Counter1InputPin, //counter1 输入引脚kQTMR_Counter2InputPin, //counter2 输入引脚kQTMR_Counter3InputPin //counter3 输入引脚
} qtmr_input_source_t;
捕获边沿枚举
typedef enum _qtmr_input_capture_edge
{kQTMR_NoCapture = 0, //关闭捕获kQTMR_RisingEdge, //上升沿捕获kQTMR_FallingEdge, //下降沿捕获kQTMR_RisingAndFallingEdge //双边沿捕获
} qtmr_input_capture_edge_t;
示例代码
QTMR_SetupInputCapture(TMR3,kQTMR_Channel_2,kQTMR_Counter2InputPin,0,0,kQTMR_RisingEdge);
5. 开启 QTIMER3
//通道 0 在 primary 时钟源的上升沿计数
QTMR_StartTimer(TMR3, kQTMR_Channel_2, kQTMR_PriSrcRiseEdge);
18.2 硬件设计
本实验用到的硬件资源有:
- 指示灯 DS0 和 DS1
- 定时器 QTMR3
- 触摸按键 TPAD
前面两个之前均有介绍,我们需要通过 QTMR3_TIMER2(P118)采集 TPAD 的信号,所以本实验需要用跳线帽短接多功能端口(P11)的 TPAD 和 ADC,以实现 TPAD 连接到 P118。

硬件设置(用跳线帽短接多功能端口的 ADC 和 TPAD 即可)好之后,下面我们开始软件设计.
18.3 软件设计
软件设计我们在之前的工程上面增加,在 HARDWARE 文件夹下新建 TPAD 的文件夹。然后打开 USER 文件夹下的工程,新建一个 tpad.c 的文件和 tpad.h 的头文件,保存在 TAPD 文件夹下,并将 TPAD 文件夹加入头文件包含路径.
tpad.c
#include "tpad.h"
#include "delay.h"
#include "lpuart.h"#define TPAD_CAP_MAX_VAL 0XFFFF //捕获最大值
vu16 tpad_default_val=0; //空载的时候(没有手按下),计数器需要的时间//初始化触摸按键
//获得空载的时候触摸按键的取值.
//psc:分频系数,越小,灵敏度越高.
//返回值:0,初始化成功;1,初始化失败
u8 TPAD_Init(u8 psc)
{u16 buf[10];u16 temp;u8 j,i;QTMR3_CH2_CAP_Init(psc); //设置分频系数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];//取中间的6个数据进行平均tpad_default_val=temp/6;printf("tpad_default_val:%d\r\n",tpad_default_val); if(tpad_default_val>TPAD_CAP_MAX_VAL/2)return 1;//初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常!return 0;
}//复位一次
//释放电容电量,并清除定时器的计数值
void TPAD_Reset(void)
{gpio_pin_config_t tpad_config; IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_02_GPIO1_IO18,0); IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_02_GPIO1_IO18,0x10B0);tpad_config.direction=kGPIO_DigitalOutput; //输出tpad_config.interruptMode=kGPIO_NoIntmode; //不使用中断功能tpad_config.outputLogic=1; //默认高电平GPIO_PinInit(GPIO1,18,&tpad_config); //初始化GPIO1_3GPIO_PinWrite(GPIO1,18,0); //GPIO1_IO18输出0,放电delay_ms(5);QTMR_ClearStatusFlags(TMR3,kQTMR_Channel_2,kQTMR_EdgeFlag); //清除边沿捕获标记TMR3->CHANNEL[kQTMR_Channel_2].CNTR=0; //归零 //配置QTIMER_TIMER3相关IO(GPIO_AD_B1_02)的功能IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_02_QTIMER3_TIMER2,0); IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_02_QTIMER3_TIMER2,0x10C1);
}//得到定时器捕获值
//如果超时,则直接返回定时器的计数值.
//返回值:捕获值/计数值(超时的情况下返回)
u16 TPAD_Get_Val(void)
{ TPAD_Reset();while((QTMR_GetStatus(TMR3,kQTMR_Channel_2)&(kQTMR_EdgeFlag))!=kQTMR_EdgeFlag)//等待捕获上升沿{if(TMR3->CHANNEL[kQTMR_Channel_2].CNTR>TPAD_CAP_MAX_VAL-500)return TMR3->CHANNEL[kQTMR_Channel_2].CNTR;//超时了,直接返回CNT的值}; return TMR3->CHANNEL[kQTMR_Channel_2].CAPT;
} //读取n次,取最大值
//n:连续获取的次数
//返回值:n次读数里面读到的最大读数值
u16 TPAD_Get_MaxVal(u8 n)
{ u16 temp=0; u16 res=0; u8 lcntnum=n*2/3;//至少2/3*n的有效个触摸,才算有效u8 okcnt=0;while(n--){temp=TPAD_Get_Val();//得到一次值if(temp>(tpad_default_val*5/4))okcnt++;//至少大于默认值的5/4才算有效if(temp>res)res=temp;}if(okcnt>=lcntnum)return res;//至少2/3的概率,要大于默认值的5/4才算有效else return 0;
}
//扫描触摸按键
//mode:0,不支持连续触发(按下一次必须松开才能按下一次);1,支持连续触发(可以一直按下)
//返回值:0,没有按下;1,有按下;
u8 TPAD_Scan(u8 mode)
{static u8 keyen=0; //0,可以开始检测;>0,还不能开始检测 u8 res=0;u8 sample=3; //默认采样次数为3次 u16 rval;if(mode){sample=6; //支持连按的时候,设置采样次数为6次keyen=0; //支持连按 }rval=TPAD_Get_MaxVal(sample); if(rval>(tpad_default_val*4/3)&&rval<(10*tpad_default_val))//大于tpad_default_val+(1/3)*大于tpad_default_val,且小于10倍tpad_default_val,则有效{ if(keyen==0)res=1; //keyen==0,有效 //printf("r:%d\r\n",rval); keyen=3; //至少要再过3次之后才能按键有效 } if(keyen)keyen--; return res;
} //初始化QTMR3定时器CH2 输入捕获
//prisrc : 第一时钟源选择
// 0000~0011,通道0~3的输入引脚.
// 0100~0111,通道0~3的输出.可用于级联.
// 1000~1111,IPG_CLK_ROOT时钟的:1,2,4,8,16,32,64,128分频.
//捕获计时频率=QTMR3_CLK=IPG_CLK_ROOT/2^(prisrc-8);
//假设prisrc=1011,则QTMR3_CLK=IPG_CLK_ROOT/8=18.75MHz.
void QTMR3_CH2_CAP_Init(u8 prisrc)
{qtmr_config_t qtimer3_config;qtmr_primary_count_source_t qtimer3_source;u32 ipgclk=CLOCK_GetFreq(kCLOCK_IpgClk); //获取IPG_CLK=150Mhzqtimer3_source=(qtmr_primary_count_source_t)prisrc;//IO设置 IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_02_QTIMER3_TIMER2,0); IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_02_QTIMER3_TIMER2,0x10C1);//IOMUXC->SELECT_INPUT[kIOMUXC_QTIMER3_TIMER2_SELECT_INPUT]=1; //选择TMR3_CH2的输入接到GPIO_AD_B1_02(GPIO1_IO18,ALT1)QTMR_GetDefaultConfig(&qtimer3_config); //先设置为默认配置,后面在根据实际情况配置qtimer3_config.primarySource=qtimer3_source; //设置第一时钟源QTMR_Init(TMR3,kQTMR_Channel_2,&qtimer3_config); //初始化QTIMER//设置输入捕获QTMR_SetupInputCapture(TMR3,kQTMR_Channel_2,kQTMR_Counter2InputPin,0,0,kQTMR_RisingEdge);//开始通道0QTMR_StartTimer(TMR3, kQTMR_Channel_2,kQTMR_PriSrcRiseEdge); //通道0在primary时钟源的上升沿计数
}
此部分代码包含 6 个函数,接下来我们分别介绍这几个函数。
TPAD_Init 函数,用于初始化输入捕获,并获取默认的 TPAD 值。该函数有一个参数,用来设置 QTMR3 的第一时钟源,其实是为了配置 QTMR3_CH2_CAP_Init 的计数频率。在该函数中连续 10 次读取 TPAD 值,将这些值升序排列后取中间 6 个值再做平均(这样做的目的是尽量减少误差),并赋值给 tpad_default_val,用于后续触摸判断的标准。
TPAD_Reset 函数,用于复位 TPAD,其实就是为了给 TPAD 的杂散电容放电,以便启动下一次测量。该函数先设置 P118 为推挽输出,然后输出低电平,延时 5ms,充分放电。然后再清除 QTMR3 通道 2 的捕获标志,并清零其计数器,最后配置 P118 为浮空输入,以便后续检测。
TPAD_Get_Val 函数,用于获取一次捕获值,相当于扫描一次 TPAD 按键。该函数先复位TPAD,然后等待捕获上升沿,并返回捕获值。
TPAD_Get_MaxVal 函数,用于连续获取 n 次捕获值,如果其中 2/3 的概率获取的值大于默认值的 5/4,则返回 n 次捕获里面的最大值。否则返回 0,视为无效。这里的 2/3 和 5/4 是自己定义的,大家可以根据自己的需要,进行修改。
TPAD_Scan 函数,用于扫描 TPAD 是否有触摸,该函数的参数 mode,用于设置是否支持连续触发。返回值如果是 0,说明没有触摸,如果是 1,则说明有触摸。该函数包含了一个静态变量,用于检测控制,类似第七章的 KEY_Scan 函数。所以该函数同样是不可重入的。在函数中,我们通过连续读取 3 次(不支持连续按的时候)TPAD 的值,取这他们的最大值,和tpad_default_val4/3 比较(且必须小于 10tpad_default_val),如果大于则说明有触摸,如果小于,则说明无触摸。其中 tpad_default_val 是我们在调用 TPAD_Init 函数的时候得到的值,然后取其 4/3 为门限值。该函数,我们还做了一些其他的条件限制,让触摸按键有更好的效果,这个就请大家看代码自行参悟了。
QTMR3_CH2_CAP_Init 函数,用于初始化四定时器 3 的通道 2,用作输入捕获功能,其初始化步骤和我们 18.1.2 节介绍的步骤一样。最终配置 QTMR3 的通道 2 作为上升沿捕获。
tpad.h
在这里插入代码片#ifndef __TPAD_H
#define __TPAD_H
#include "sys.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 QTMR3_CH2_CAP_Init(u8 prisrc);
#endif
main.c
#include "sys.h"
#include "lpuart.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "tpad.h" int main(void)
{u8 t=0;MPU_Memory_Protection(); //初始化MPURT1052_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);//中断优先级分组4RT1052_Clock_Init(); //配置系统时钟DELAY_Init(600); //延时函数初始化LPUART1_Init(115200); //初始化串口1LED_Init(); //初始化LED KEY_Init(); //初始化按键TPAD_Init(10); //初始化触摸按键,以150/1=150Mhz频率计数while(1){ if(TPAD_Scan(0)) //成功捕获到了一次上升沿(此函数执行时间至少15ms){LED1_Toggle; //LED1取反}t++;if(t==15) {t=0;LED0_Toggle; //LED0取反,提示程序运行}delay_ms(10);}
}
该 main 函数比较简单,TPAD_Init(10)函数执行之后,就开始触摸按键的扫描,当有触摸的时候,对 DS1 取反,而 DS0 则有规律的间隔取反,提示程序正在运行。注意在修改 main 函数之后,还需要在 test.c 里面添加 tpad.h 头文件,否则会报错哦。
这里还要提醒一下大家,不要把 lpuart1_init(115200)去掉,因为在 TPAD_Init 函数里面,我们有用到 printf,如果你去掉了 uart_init,就会导致 printf 无法执行,从而死机。
至此,我们的软件设计就完成了
编译,下载!
同时大家可以打开串口调试助手,每次复位的时候,会收到 tpad_default_val 的值,一般
为 74 左右。
总结
电容触摸按键的原理是利用手指靠近或触摸电极时引入额外电容,使得电极与电阻形成的充放电时间发生变化;通过定时器输入捕获检测电容充电达到阈值电压的时间差,就能区分是否有触摸发生,从而实现按键功能。
OK!谢谢大家!
