按键开关是最常见和简单的控制开关,通过短按、长按、双击等不同的行为,能够实现单键多功能开关控制LED灯的运作状态。
而SW180-10P是一款振动开关,其本质和按钮开关一样可以视为一种传感器,在外界条件发生改变的时候,相应地改变其状态使得电路导通或者断开。对于按键开关,外界条件为是否受到按压。按键开关产生的信号波形根据一次按压的持续时间的不同会有所区别,但总体形状为矩形波。需要注意的是,按下和放开的瞬间,即信号的上升和下降阶段,由于按键的物理结构在非常微小的尺度上发生抖动,接触还未达到稳定,而导致电平信号忽高忽低。综上按键开关的一般输出特性为:

对于这样已知的特性,就可以写出相应的驱动程序用来判断识别开关的按下的状态。
而网上对于SW180系列的振动开关的相关资料似乎较少,更是找不到其输出的信号特性描述,因此本文着重记录其信号特性。
一、振动开关的结构和原理
SW180系列的振动开关的外观和内部结构如下图:

仔细看图(a),该开关有粗、细两根引脚,图(b)中将开关外壳拆开之后可以看到,图(a)中细的引脚在内部缠绕成了弹簧的结构,并且套在了粗引脚的外面(图b的开关的细引脚被折断了,这里插一句题外话,SW180xxP中的xx代表了灵敏度,根据商家客服的介绍,xx越大,灵敏度越大;而且不管是哪一个型号,这个细引脚都算是比较容易折断的,所以选型的时候要考虑这一点)。当开关没有被触碰产生振动时,弹簧和粗引脚是分离的,此时开关断开;当开关受到外力作用时,弹簧开始左右振动,振动的过程中就间断地与粗引脚接触,从而使得开关闭合,电路导通。

可以看到,该振动开关被触发时,其导通和断开是瞬时的,因此其输出的信号应该是一系列的脉冲,脉冲信号的频率取决于弹簧(细引脚)的振动频率。
二、输出信号
将一个振动开关和电阻串联在电路中,用手直接敲击振动开关,使用示波器记录下电压的变化。

某一次单击、双击、三击的信号输出如下图;

可以发现,每次敲击会有一个脉冲信号。
将某一次“单击”的信号放大,

上面提到的振动开关中细引脚连接的弹簧在受到外力时是来回振动的,因此一个“单击”中的信号是像上图所示的多个脉冲。并且可以合理的推测,单次敲击的力度越大,信号的持续时间应该会更长,图中这次“单击”产生信号的持续时间是22ms。

而像某一次“双击”中的第一次“单击”,其信号持续时间跨度达到44ms;第二次“单击”的信号持续时间达到了90ms。根据个人的试验,本人的一次普通力度的敲击,产生的脉冲信号的持续时间在20~120ms的范围内。
再看每次单击之间的间隔,理论上一次连击中的各次单击之间的间隔应该和敲击者的反应速度(”手速”)有关系,以下是几个连击的例子。图(a)是一次双击之间各次敲击的间隔时间,图(b)是一次三击之间各次敲击的间隔时间,大概都在400ms左右。

三、开关驱动程序思路
知道了该振动开关的输出的一般特性,就可以考虑通过控制敲击的轻重不同或者敲击的次数不同来输出不同的特征信号,实现一个单键多功能的效果。
这里我结合了STC12C2052AD这款51单片机和LED灯(点灯人上线!),实现了单击切换开关灯,双击熄灭灯进入掉电模式的灯光控制。
/*********************************************************
STC12C2052AD配合振动开关SW-180xxP控制4种不同颜色的LED灯;
SW-180xxP振动开关受到一次敲击用于切换LED灯顺次亮灭;
受到两次敲击使得MCU进入掉电模式,此时关闭所有LED灯;
掉电模式下敲击一下唤醒MCU,恢复到上次关闭的状态
**************************************************************/
#include <STC12C2052AD.H>
#define PORT1 P1
#define KEY_INPUT P32
#define SINGLE_CONFIRM 25 // 该时间长度中没有再次检测到低电平,则确定是单击
#define PAT_INTERVAL 6 // 这个时间间隔是为了第一次检测到低电平之后的属于同一次敲击的低电平,是通过试验得到的经验值
#define N_KEY 0 // 返回无效键值
#define S_KEY 1 // 返回单击键值
#define D_KEY 2 // 返回双击键值
unsigned char judging_time; // 计时
unsigned char key_value; // 振动开关返回值
/**************************************************************/
void delay_ms(unsigned int a); // 不太精准的延时函数
void next_LED(); // 关闭当前灯,打开下一个灯
void power_down(); // 进入掉电模式
void Timer0_Init(void); // 定时器定时10ms初始化函数
void INTE0(void); // 掉电模式下触发外部中断处理函数
void time0(void);// 定时器0中断处理函数
/**************************************************************/
void main(void){
EX0 = 0; // 禁止外部中断,此时该引脚当成普通的I/O接口使用,连接振动开关,导通时为低电平
EA = 1; // 允许总中断
ET0 = 1; // 允许定时器0中断
PORT1 = 0xFE; // LED发光采用灌电流的方式,正极连接VCC,负极连接引脚,输出为0是才发光
while (1){
key_value = N_KEY;
if (!KEY_INPUT){ // 首次检测到低电平
judging_time = 0;
Timer0_Init();
while (judging_time < PAT_INTERVAL){ // 第一次检测到低电平的60ms之内等待即可
}
while (judging_time < SINGLE_CONFIRM){
if (!KEY_INPUT){ // 当时间大于60且小于250ms,如果检测到低电平,就是有第二次敲击
key_value = D_KEY;
}
}
if (key_value != D_KEY){
key_value = S_KEY;
}
}
switch (key_value){
case S_KEY: next_LED(); break;
case D_KEY: power_down(); break;
default: break;
}
}
}
/**************************************************************/
void delay_ms(unsigned int a){
unsigned int i;
while (a-- != 0){
for (i = 0; i < 600; i++); // CPU 空转
}
}
void next_LED(void){ // 关闭当前灯,打开下一个LED灯
unsigned char port_temp = ~PORT1; // 采用灌电流的方式点亮LED,即点亮的灯的I/O为低电平
if ((port_temp > 0x00) && (port_temp < 0x40)){
port_temp = port_temp << 2; // 如果当前P16不是低电平,取反之后向左移两位,再取反
PORT1 = ~port_temp;
}
else{
PORT1 = 0xfe; // P11设置为低电平
}
}
void power_down(void){
PORT1 = 0x00;
delay_ms(1000); // 所有灯亮1s
EX0 = 1; // 允许外部中断0,用来将MCU从掉电模式唤醒
IT0 = 0; // INT0的触发模式,0为低电平触发
PORT1 = 0xFF; // 所有灯灭
delay_ms(500); // 灯灭后500ms再进入掉电模式
PCON |= 0x02; // PCON.1置1,其他位不变,进入掉电模式
}
void Timer0_Init(void){ //10毫秒@6.000MHz
AUXR = 0x00; //定时器时钟12T模式
TMOD |= 0x11; //设置定时器1和0均为16为手动重载
TL0 = 0x78; //设置定时初始值
TH0 = 0xEC; //设置定时初始值
TF0 = 0; //清除TF0标志,定时溢出时被硬件置1
TR0 = 1; //定时器0开始计时
}
/***********************************************************/
void time0(void) interrupt 1 {
if (judging_time < 50000){ // 防止数据溢出
judging_time++;
}
else{
judging_time = 0;
}
TL0 = 0xB0;
TH0 = 0x3C;
TF0 = 0;
TR0 = 1; // 手动重载定时器0
}
void INTE0(void) interrupt 0 { // 外部中断0的处理程序
PORT1 = 0xFB;
delay_ms(500); // P12灯亮半秒
EX0 = 0; // 重新禁止外部中断
EA = 1; // 允许总中断
PORT1 = 0xFE; // p10口的灯亮,表示从掉电模式中恢复
}
这里只是为了内容完整才放出完整的程序,具体的电路连接和程序会放在GitHubDigitChenHN/pattingLightSTC12: A LED light control toy based on STC12C2052AD and SW180-10p sensor上。
就本文的SW180-10p传感器驱动而言,重点在于主函数中的while循环。
while循环做的主要工作为:
1. 当相应的引脚第一次为低电平时,重置单片机的计时器然后开始计时。
2. 开始计时之后,当时间小于某个时间(由PAT_INTERVAL这个常量决定)时,单片机空转等待,这个等待的时间应该要大于在我们的设想中“单击”产生信号的持续时间的可能的最大值。
3. 当时间超过了PAT_INTERVAL但还小于SINGLE_CONFIRM(SINGLE_COMFIRM是我们定义的另一个常量)的时候,单片机不再空转,而是看看这小段时间内是否会再次检测到低电平,如果检测到低电平,证明是双击;如果没有检测到低电平,证明除了第一次检测到低电平(第一次敲击)之后,没有再一次敲击,即是单击。
4. 然后就是根据双击还是单击做出相应的动作。
以上应该是基于SW180-10P最为简单的一个驱动,其中PAT_INTERVAL和SINGLE_CONFIRM的值通过反复试验调试确定,在我的使用场景中可以到达理想的效果。