首先,了解一下什么是Qtouch,关于这个网上的资料很少,大多都说的一些Qtouch的背景而不方便别人理解什么是Qtouch。这就是一个触摸方案,媒介是电容,核心是统计充电的次数差值。
Qtouch的原理图,其中电容Cs远大于Cx。
1、S2闭合,Cs和Cx开始充电,因为电容充电的期间电路是有电流的,经过Cs和Cx的电流理论上是一样的,所以Cx上和Cs上积攒的电荷量也是一样的。
2、S2开,S1闭合,Cx放电至电荷量为0,而Cs上的电荷会保留下来。
3、S1开,S2闭合,再进行一个充电周期,Cs上的电荷量就是2倍的Cx,如此循环,知道Cs上的电压等于VDD,结束充电,统计充电次数。
那个圆的灰的就相当于按键,人手接触,相当于增大了Cx的容量,减小充电次数,即以前要1000次才充满,现在900次就充满了,检测到这个差值,MCU判断有按键按下,实现触摸按键的功能。
Atmel为这个Qtouch准备了专门的库函数供他人调用,实现其触摸按键功能,因为ICCAVR不支持做QTouch的芯片,所以改用AtmelStudio6,用这个开发环境就注意两点,一是要加载Qtouch的库,头文件等要用到的东西,还有就是要手动设定头文件的路径,就算复制到当前文件夹也要指定,不然会报错。
Qtouch实现流程
先把流程的代码贴出来再作详细解释
int main(void)
{
uint16_t status_flag = 0u;
uint16_t burst_flag = 0u;
uint8_t sense_state = 0u;
stay_on();
#ifdef QTOUCH_STUDIO_MASKS //
key_connect(); //按键关联
#endif
enable_key(); //使能按键
qt_init_sensing(); //初始化传感器
qt_set_parameters(); //设置传感器参数
time_init(); //定时器初始化
while(1)
{
if(inter_flag)
{
inter_flag = 0;
status_flag = qt_measure_sensors( current_time_ms_touch ); //获取传感器值
burst_flag = status_flag & QTLIB_RESOLVE_DI; //是否处于按键检测
sense_state = qt_measure_data.qt_touch_status.sensor_states[0]; //按键位置}
}
1、单片机引脚跟库函数关联
因为现在要调用库函数来操作单片机了,所以需要将两者关联起来,相当于单片机引脚在库函数中产生一个映射,关联函数的代码,用AVR Qtouch stdio软件对照单片机引脚配置好后自动生成的代码复制到函数主体中就可以了,根据代码定义一下缺少的数组就OK了。
uint8_t SNS_array[2][2];
uint8_t SNSK_array[2][2];
void key_connect(void)
{
SNS_array[0][0]= 0x55;
SNS_array[0][1]= 0x0;
SNS_array[1][0]= 0x0;
SNS_array[1][1]= 0x0;
SNSK_array[0][0]= 0xAA;
SNSK_array[0][1]= 0x0;
SNSK_array[1][0]= 0x0;
SNSK_array[1][1]= 0x0;
}
2、使能按键
按键关联了,下一步当然是使能这些按键了。
函数:qt_enable_key(通道号,所在组,阀值,指定传感器滞后)
void enable_key(void)
{
qt_enable_key(CHANNEL_0,NO_AKS_GROUP,10u,HYST_6_25);
qt_enable_key(CHANNEL_1,AKS_GROUP_2,10u,HYST_6_25);
qt_enable_key(CHANNEL_2,AKS_GROUP_3,10u,HYST_6_25);
qt_enable_key(CHANNEL_3,AKS_GROUP_4,10u,HYST_6_25);
}
通道号:从channel_0开始,mega168支持最多到channel_64。一个按键在单片机上由两个端口SNS和SNSK,这2个算一个按键,占用一个通道。
组:如果2个按键在一个组里,都满足触摸的条件,这时候会比较哪个的差值更大,小的那个就忽略不触发。不在一个组里就不会进行这种比较。
阀值:就是前面提到的充电次数差值。
指定传感器滞后:暂不清楚,不作描述
3、传感器初始化
这是一个很有难度的东西,虽然这个部分只需要执行一条语句:qt_init_sensing();但是执行之前,要做很多的准备工作。
首先要把AS6安装目录下的touch_api.h包含进头文件,然后把qt_asm_avr.h和qt_asm_tiny_mega.S添加进工程,还需要手动创建一个touch_config.h文件,里面要定义这些常量:
#define _QTOUCH_ //表明用Qtouch这个函数库
#define _POWER_OPTIMIZATION_ 0 //能源优化
#define QT_NUM_CHANNELS 4 //最多按键数,也就是通道数
#define QT_DELAY_CYCLES 3 //跟单片机功率有关
#define QTOUCH_STUDIO_MASKS //按键关联用到的,但是对这个函数也有影响
看起来有些常量跟这函数调用没有任何关系,但是我亲身经验告诉你,少一个都不行!
4、传感器参数设置
这个没什么难度,一些参数自己考虑着配置了,使用推荐配置也是不错的选择,然后将这些赋值给结构体qt_config_data。
void qt_set_parameters(void)
{
qt_config_data.qt_recal_threshold = RECAL_50; //自动校准阀值标准50%
qt_config_data.qt_di = 4u; //按键重复检测次数
qt_config_data.qt_drift_hold_time = 20; //漂移保持时间,200ms*20 = 4s
qt_config_data.qt_max_on_duration = 0; //失能(最长触摸时间后功能恢复)
qt_config_data.qt_neg_drift_rate = 20; //传感器负漂移率4s
qt_config_data.qt_pos_drift_rate = 5; //传感器正漂移率1s
qt_config_data.qt_pos_recal_delay = 3u; //传感器调整延迟
}
5、定时检测,中断处理
这个步骤其实在整体Qtouch的实现中来说是可有可无的,写上来是因为在这边遇到了两个挺有价值的问题,对以后程序的编写也算一个前车之鉴
我用定时器的初衷是想实现一个定时检测,而不是无限循环得检测,所有设置一个标志位,定时触发中断将标志位置1,只有当标志位为1时才会进行按键检测。
ISR(TIMER0_COMPA_vect)
{
current_time_ms_touch += qt_measurement_period_msec;
inter_flag = 1u;
}
这就是一个中断处理函数,这里的ISR相当于#pragma interrupt_handler。还要注意TIMER0_COMPA_vect是中断向量而不是中断向量号,所以写一个具体的数字7啊之类的是会报错的。
第一个问题是我中断函数确实执行了,将标志位置1后发现到主函数中却进不了按键检测的部分,就是说那个标志位还是0,两者十分矛盾。后来发现这个标志位被编译器给自动优化掉了!以后用全局变量时,最好加volatile修饰,以防万一。
定时器函数照常写
void time_init(void)
{
SREG = 0x80;
TCCR0A |= (1 << COM0A1)|(1 << WGM01);
TCNT0 = 0x00;
OCR0A = 0x10; //16*0.078125 = 1.25us;
TIMSK0 |= (1 << OCIE0A);
TCCR0B |= (1 << CS00);
}
这边就有第二个问题,注意代码中红色的部分,就是比较匹配时OC0A清零,就因为这位置1,导致了2个问题。第一个是因为我写完之后发现按键可以检测和区分,但是按下,松开都有个1S的延时没有合理解释,采用排除法查到这边发现这个原因,因为这个引脚跟按键key4所需要的引脚是共用的,两者的共同操作带来冲突导致了延时。因为查到这个,顺带还发现了这个冲突还会导致按键无法检测,之前还以为是芯片没有焊接好而导致的。算是意外收获了。正确代码把红色部分去掉,不操作引脚电平就行。
6、获取传感器状态和区分按键
这部分就是理解问题了,一共需要用到三个函数
status_flag = qt_measure_sensors( current_time_ms_touch ); //获取传感器值
burst_flag = status_flag & QTLIB_RESOLVE_DI; //是否处于按键检测
sense_state = qt_measure_data.qt_touch_status.sensor_states[0]; //获取按键位置
第一个函数中的参数current_time_ms_touch,数据手册的描述是当前时间,以ms为单位,乍一看这数字怎么得啊,还要精确到ms,名字唬人,其实只要是变化的数字就行。
第二个函数QTLIB_RESOLVE_DI就是按键检测标志位了。看看传感器是不是在按键检测,不是必须,调试过程中用的。能获取按键位置的话这一步根本不需要。
第三个函数就是区别哪个按键检测到了,这边的sensor_states[0]需要注意的是,sense_state得到的是一个8位的数据,也就是能检测channel_0~channel_7的按键状态,而不仅仅是检测channel_0的状态,由此sensor_states[1]就是8~15的按键状态了。
PS:我做这个模块因为触摸按键包括MCU和PSU之间都是用的导线连接的,所以实验过程中导线的干扰也是必须要注意的,如果你导线多的话,没检测到按键按下也有可能是导线干扰,理分开一点,交叉的话很容易导致无法检测的。
总结:
整个Qtouch的实现流程讲完了,做完了之后觉得很简单,因为就那么几步,函数还不要自己实现,代码量少得很。但是在做的时候还是挺难的,尤其是刚开始的时候,开发环境不熟悉,要用的函数又不是自己写的,不知道什么用。从开始看到理清思绪搭好框架花了1周多的时间,不管怎么说,有收获就行。至于slider和wheel的功能,暂时还没研究,以后有机会再学习。
Qtouch这个功能使用方面来看的话,检测非常灵敏,也没遇到过误判之类的问题,实验过程中最大的缺点就是导线干扰,但是如果是投入使用的话那肯定可以通过PCB布线解决这个问题,而且这个按键的外设部分就一个铁片,成本极低而且不易损坏。如果说还有局限性的话,就是如果按键多的话,那那些按键所占用的引脚就不可以用以电平变化,这样就减少了单片机一些变化的余地,还有atmel产的芯片并不是都支持Qtouch的,如果主芯片恰巧不支持,那就必须采用一个单独的芯片用作按键。