独立按键
基础知识
红方框框出来的按键(S4-S7)就是独立按键,蓝色框框出来的4x4键盘就是矩阵键盘,下方红色圈圈圈出来的就是进行独立按键和矩阵键盘切换的跳帽,BIN就是独立按键,KBD就是矩阵键盘。
ps:应该注意到了这一堆按键右边有一个孤零零的按键,那个被孤立的RESET按键是单片机的复位键,按下程序就会重头开始跑。
独立按键电路原理
可以从上图看出来,独立按键S4、S5、S6、S7一端分别连在P30、P31、P32、P33上,一端连在GND上,所以当按键被按下时读取P3口,相应的位会变成低电平,所以只要判断P30-P33口的电平,就能知道那个按键被按下了。
消抖处理
通常按键所用的开关都是机械弹性开关,当机械触电断开、闭合时,由于机械触电的弹性作用,一个按键开关在闭合时不会马上就稳定的连通,在断开时也不是一下子就彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动。抖动时间是由按键的机械特性决定的 ,一般都会在10ms以内,为了确保程序对按键的一次闭合或者一次断开只响应一次,就必须进行按键的消抖处理!!!
编程注意事项
每次按下按键需要单次触发
需要有松手检测
不要阻塞程序,不要有delay,更不要有类似while(!P30);等待按键弹起的操作,这样子会耗费大量的CPU资源
最好要有长按功能
以下就是一种不太好的编程方式,虽然咱们尊敬的单片机大师郭天祥老师写的书里面是这么教的,但是总归是有更好的方法的。
while(1)
{
if(S4==0)
{
Delay_Ms(10);//代码中最好不要出现delay,尽可能的用中断来处理
if(S4==0)
{
number++;
}
}
while(S4==0);//用while来卡时间,来等待按键弹起,如果按键一直被按下,
//那么CPU就会一直在这里等待,会一直耗费CPU资源,此时CPU不能干任何事情
}
三行按键法
以下就是上面提出问题的完美解决方案,不仅不会耗费CPU资源,还能实现单次触发以及长按触发。
此外还有一种状态机的方法也能够完美解决以上问题,并且很多很多地方都可以用状态机的思想,例如红外线的编解码等,但是代码量相对较多,所以在此不做介绍
u8 trg; //单次触发
u8 cont; //连续触发
void btn_read()
{
u8 read_data=P3^0xff; //将P3每一位按位取反
trg=read_data & (read_data^cont);
cont=read_data;
}
三次按键法应用
/*
每按下一次S4,数码管的数字+1 ;(默认数字是0)
每按下一次S5,数码管的数字-1 ;
每按下一次S6,数码管的数字+10 ;
每按下一次S7,数码管的数字-10 ;
长按S7,数码管的数字清0;
*/
#include <STC15F2K60S2.H>
typedef unsigned char u8;
typedef unsigned int u16;
u8 code t_display[]={ //标准字库
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black - H J K L N o P U t G Q r M y
0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46}; //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1
u8 code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; //位码
void Timer2Init(void) //1毫秒@12.000MHz
{
AUXR &= 0xFB; //定时器时钟12T模式
T2L = 0x18; //设置定时初始值
T2H = 0xFC; //设置定时初始值
AUXR |= 0x10; //定时器2开始计时
IE2 |= 0x04; //开定时器2中断
EA = 1;
}
void device_ctrl(u8 p2data,u8 p0data)
{
P0=p0data;
P2=p2data;
P2=0;
}
void system_init()
{
device_ctrl(0xa0,0);
device_ctrl(0X80,0XFF);
}
u8 trg; //单次触发
u8 cont; //连续触发
void btn_read()
{
u8 read_data=P3^0xff; //将P3每一位按位取反
trg=read_data & (read_data^cont);
cont=read_data;
}
u8 key_cnt;
u8 key_number;
u16 cont_number;
void btn_process()
{
if(key_cnt==10)
{
key_cnt=0;
btn_read();
if(trg&0x08)
{
key_number=key_number+1;
}
if(trg&0x04)
{
key_number=key_number-1;
}
if(trg&0x02)
{
key_number=key_number+10;
}
if(trg&0x01)
{
key_number=key_number-10;
}
if(cont&0x01)
{
cont_number++;
}
if(cont_number>=300)
{
cont_number=0;
key_number=0;
}
}
}
u8 smg_buf[8];
void smg_process()
{
smg_buf[0]=t_display[key_number/100];
smg_buf[1]=t_display[key_number/10%10];
smg_buf[2]=t_display[key_number%10];
smg_buf[3]=0x00;
smg_buf[4]=0x00;
smg_buf[5]=0x00;
smg_buf[6]=0x00;
smg_buf[7]=0x00;
}
void smg_display()
{
static u8 i;
device_ctrl(0xc0,0);
device_ctrl(0xe0,~smg_buf[i]);
device_ctrl(0xc0,T_COM[i]);
i++;
if(i==8)
{i=0;}
}
void main()
{
system_init();
Timer2Init();
while(1)
{
smg_process();
btn_process();
}
}
void t2int() interrupt 12 //中断入口
{
smg_display();
key_cnt++;
}
矩阵键盘
注意J5跳帽要连接在KBD上
矩阵键盘电路原理
P30-P35、P42、P44低四位是矩阵键盘的行,高四位是矩阵键盘的列,
编程思路:
给高四位赋值0,检测低四位哪一位是0,确定按键所在行
给低四位赋值0,检测高四位哪一位是0,确定按键所在列
根据行和列,确定哪个按键按下
编程需要的注意事项与独立按键一样。
程序控制
与独立按键一样,使用三行按键法,只不过需要在使用之前获取到矩阵键盘对应的IO状态
/*
每按下一次S4,数码管的前三位数字+1 ;(默认数字是0)
每按下一次S9,数码管的前三位数数字-1 ;
每按下一次S14,数码管的前三位数数字+10 ;
每按下一次S19,数码管的前三位数数字-10 ;
数码管后三位显示当前按键对应16进制取反
*/
#include <STC15F2K60S2.H>
typedef unsigned char u8;
typedef unsigned int u16;
u8 code t_display[]={ //标准字库
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black - H J K L N o P U t G Q r M y
0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46}; //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1
u8 code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; //位码
void device_ctrl(u8 p2data,u8 p0data)
{
P0=p0data;
P2=p2data;
P2=0;
}
void system_init()
{
device_ctrl(0xa0,0);
device_ctrl(0x80,0xff);
}
void Timer2Init(void) //1毫秒@12.000MHz
{
AUXR &= 0xFB; //定时器时钟12T模式
T2L = 0x18; //设置定时初始值
T2H = 0xFC; //设置定时初始值
AUXR |= 0x10; //定时器2开始计时
IE2 |= 0x04; //开定时器2中断
EA = 1;
}
u8 key_io()
{
u8 key_io=0xff;
P3=0xf0;P4=0xff;//列扫描
if(P44==0) key_io=0x70;
if(P42==0) key_io=0xb0;
if(P35==0) key_io=0xd0;
if(P34==0) key_io=0xe0;
P3=0x0f;P4=0x00;//行扫描
if(P33==0) key_io=key_io|0x07;
if(P32==0) key_io=key_io|0x0b;
if(P31==0) key_io=key_io|0x0d;
if(P30==0) key_io=key_io|0x0e;
return key_io;
}
u8 trg;
u8 cont;
void key_read()
{
u8 readData=key_io()^0xff;
trg=readData & (readData ^ cont);
cont=readData;
}
u8 key_cnt;
u8 key_val;
u8 key_number;
u16 cont_number;
void key_process()
{
if(key_cnt==10)
{
key_cnt=0;
key_read();
key_val=cont;
if(trg==0x88) key_number=key_number+1;
if(trg==0x44) key_number=key_number-1;
if(trg==0x22) key_number=key_number+10;
if(trg==0x11) key_number=key_number-10;
}
}
u8 smg_buf[8];
void smg_process()
{
smg_buf[0]=t_display[key_number/100];
smg_buf[1]=t_display[key_number/10%10];
smg_buf[2]=t_display[key_number%10];
smg_buf[3]=0x00;
smg_buf[4]=0x00;
smg_buf[5]=0x00;
smg_buf[6]=t_display[key_val/16];
smg_buf[7]=t_display[key_val%16];
}
void smg_display()
{
static u8 i=0;
device_ctrl(0xc0,0);
device_ctrl(0xe0,~smg_buf[i]);
device_ctrl(0xc0,T_COM[i]);
i++;
if(i==8) i=0;
}
void main()
{
system_init();
Timer2Init();
while(1)
{
smg_process();
key_process();
}
}
void t2int() interrupt 12 //中断入口
{
smg_display();
key_cnt++;
}