今天出一期蓝桥杯中一定会出的按键的写法,独立按键用普通扫描和中断扫描都可以,但是我遇到过很复杂的情况就是功能很多时候,反应很慢,后来不管是独立还是矩阵按键,我都用中断的方法,屡试不爽。本文讲的是4乘4扫描的,有松手检测和实现按键长短按。学会之后我们可以任意实现2*2,2*3,2*4都可以
先来重头戏,矩阵按键中断扫描。总共四个函数:
(1):void keyscan()按键扫描函数,扫描按键的状态确定是否有按键按下,这个函数放进中断函数里。
sbit h0 = P3^0;// 第0行
sbit h1 = P3^1;// 第1行
sbit h2 = P3^2;// 第2行
sbit h3 = P3^3;// 第3行
sbit l0 = P4^4;// 第0列
sbit l1 = P4^2;// 第1列
sbit l2 = P3^5;// 第2列
sbit l3 = P3^4;// 第3列
下面四个数组分别是当前按键按下的状态变量和上个按键的状态变量,和按键循环移位扫描确定是否按下的数组keybuff和键值数组keycode
unsigned char keysta[4][4]={{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}};
unsigned char keybuckup[4][4]={{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}};
unsigned char keybuff[4][4]={{0xff,0xff,0xff,0xff},{0xff,0xff,0xff,0xff},{0xff,0xff,0xff,0xff},{0xff,0xff,0xff,0xff}};
unsigned char code keycode[4][4]={{7,11,15,19},
{6,10,14,18},
{5,9,13,17 },
{4,8,12,16 }};
void keyscan()
{
unsigned char i; //列变量
static unsigned char keyout=0;//行变量
//因为keyscan函数放在定时器中断函数里,定时时间一到就会进去,每次进入一次就左移一位,左移完与列值进行或运算,判断是否按下,如果按下该位就会变0。没有按下就一直是1。我们可以规定进多少次之后去判断按键是否按下。
keybuff[keyout][0]=((keybuff[keyout][0]<<1)|l0);//keyout行第0列
keybuff[keyout][1]=((keybuff[keyout][1]<<1)|l1); 第一列
keybuff[keyout][2]=((keybuff[keyout][2]<<1)|l2); 第二列
keybuff[keyout][3]=((keybuff[keyout][3]<<1)|l3); 第三列
//变量i是列变量,for循环扫描4次,判断那一列是否有按键按下,如果4次之后按键都是按下,(keybuff[keyout][i]&0x0f)==0,如果想要进入8次中断才确定按下可以写成:
//(keybuff[keyout][i]&0xff)==0;如果按下keysta[keyout][i]=0; 没有按键按下 keysta[keyout][i]=1;
for(i=0;i<4;i++)
{
if((keybuff[keyout][i]&0x0f)==0)
{
keysta[keyout][i]=0;
}
else if ((keybuff[keyout][i]&0x0f)==0x0f)
{
keysta[keyout][i]=1;
}
}
keyout++; //行加加
if(keyout>=4)//行大于4,行置零
keyout=0;
switch(keyout)//判断哪一行/哪一行就把哪一行置0,因为我们设定按下为0,所以把改行置零按下的时候才为0,我试过设置为1,但是上电的时候会莫名按键启动一次,num会变成一(就是设置变量num,按键按下加一次,然后数码管显示出来,用来验证按键是否正确)。
{
case 0:h0=0;h3=1;break;//哪一行就把哪一行置0,因为我们设定
case 1:h1=0;h0=1;break;
case 2:h2=0;h1=1;break;
case 3:h3=0;h2=1;break;
}
}
(2)按键处理函数 Key() ,判断按键状态执行那个函数
如果 if(keybuckup[i][j]!=keysta[i][j]),上个按键状态量与当前按键状态不相等说明按键按下,然后具体判断按键按下和是否松开,我们可以规定那些操作是按下执行还是松开执行,也就是松手检测,有了松手检测我们还可以实现按键长短按
void Key()
{
static unsigned char i=0,j=0;
for(i=0;i<4;i++) //扫描行
{
for(j=0;j<4;j++) //扫描列
{
if(keybuckup[i][j]!=keysta[i][j])
{
if(keysta[i][j]==0)//按键按下
{
keydiver(keycode[i][j]);num=keycode[i][j];//执行按键按下的操作函数
}
else if(keysta[i][j]==1)//按键松开
{
keysong(keycode[i][j]);//执行按键松开的函数
}
keybuckup[i][j]=keysta[i][j];
}
}
}
}
(3)第三个函数:void keydiver(unsigned char keydat)按键按下执行操作函数
按键按下和松开都会把键值传进来,函数里可以进行具体键值判断那个按键按下以及需要执行的操作,我们来实现一下按键长按1s把led灯反转
假设按键5按下,要实现按键长按操作,按键5一按下就把长按标志置一 chang_B=1;然后在定时器中断函数里判断这个标志位,如果为1,count_chang++;定时器设置1ms,count_chang达到1000就置零,把长按事件执行标志置1:changan_B=1;这会有一个问题,按键松手的时候把长按标志清零和定时时间清零,防止时间累加,这就是第四个函数,按键松手执行函数
void keydiver(unsigned char keydat)
{
if(keydat==5)
{
chang_B=1;
}
}
(4)第四个函数:void keysong(unsigned char n1um)按键松手函数,在第三个函数解释过了,这里就不过多解释了
void keysong(unsigned char n1um)
{
if(n1um==5)//第五个按键松开
{
if(changan_B==1)//长按时间达到执行操作标志
{
P2&=0x1f;P0=0xff;
P2=0x80;
uh=~uh;//灯反转
P0=uh;
P2&=0x1f;P0=0xff;
changan_B=0;//执行完操作标志清零
}
}
num=0;
count_chang=0;//计时时间清零
chang_B=0;//按下标志清零
}
其他剩一个中断函数和main函数,还有一个页面函数还有一个显示函数,页面和显示函数在中断里引用,中断是一个好东西,好好利用
总代码:
#include "stc15f2k60s2.h"
sbit h0 = P3^0;
sbit h1 = P3^1;
sbit h2 = P3^2;
sbit h3 = P3^3;
sbit l0 = P4^4;
sbit l1 = P4^2;
sbit l2 = P3^5;
sbit l3 = P3^4;
unsigned char uh=0;
unsigned char code duanma[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff};
unsigned char num=0;
unsigned char buff[8];
unsigned char keysta[4][4]={{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}};
unsigned char keybuckup[4][4]={{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}};
unsigned char keybuff[4][4]={{0xff,0xff,0xff,0xff},{0xff,0xff,0xff,0xff},{0xff,0xff,0xff,0xff},{0xff,0xff,0xff,0xff}};
unsigned char code keycode[4][4]={{7,11,15,19},
{6,10,14,18},
{5,9,13,17 },
{4,8,12,16 }};
unsigned int count_chang=0;
bit chang_B=0;
bit changan_B=0;
void Timer0Init(void)//1毫秒@11.0592MHz
{
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0xCD; //设置定时初始值
TH0 = 0xD4; //设置定时初始值
TF0 = 0; //清除TF0标志
EA=1;
ET0=1;
TR0 = 1; //定时器0开始计时
}
void Write_buff()
{
buff[0]=10;
buff[1]=num/100;
buff[2]=num/10%10;
buff[3]=num%10;
buff[4]=10;
buff[5]=10;
buff[6]=10;
buff[7]=10;
}
void display()
{
static unsigned char n=0;
P2=0xe0;
P0=duanma[buff[n]];
P2=0xc0;
P0=1<<n;
n++;
if(n>=8)
n=0;
}
void keysong(unsigned char n1um)
{
if(n1um==5)
{
if(changan_B==1)
{
P2&=0x1f;P0=0xff;
P2=0x80;
uh=~uh;
P0=uh;
P2&=0x1f;P0=0xff;
changan_B=0;
}
}
num=0;
count_chang=0;
chang_B=0;
}
void keydiver(unsigned char keydat)
{
if(keydat==5)
{
chang_B=1;
}
}
void Key()
{
static unsigned char i=0,j=0;
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
{
if(keybuckup[i][j]!=keysta[i][j])
{
if(keysta[i][j]==0)
{
keydiver(keycode[i][j]);num=keycode[i][j];
}
else if(keysta[i][j]==1)
{
keysong(keycode[i][j]);
}
keybuckup[i][j]=keysta[i][j];
}
}
}
}
int main()
{
P2=0xa0;
P0=0x00;
P2=0x80;
P0=0xff;
Timer0Init();
while(1)
{
Key();
}
}
void keyscan()
{
unsigned char i;
static unsigned char keyout=0;
keybuff[keyout][0]=((keybuff[keyout][0]<<1)|l0);//更新按键状态
keybuff[keyout][1]=((keybuff[keyout][1]<<1)|l1);
keybuff[keyout][2]=((keybuff[keyout][2]<<1)|l2);
keybuff[keyout][3]=((keybuff[keyout][3]<<1)|l3);
for(i=0;i<4;i++)
{
if((keybuff[keyout][i]&0x0f)==0)
{
keysta[keyout][i]=0;
}
else if ((keybuff[keyout][i]&0x0f)==0x0f)
{
keysta[keyout][i]=1;
}
}
keyout++;
if(keyout>=4)
keyout=0;
switch(keyout)
{
case 0:h0=0;h3=1;break;
case 1:h1=0;h0=1;break;
case 2:h2=0;h1=1;break;
case 3:h3=0;h2=1;break;
}
}
void timer0()interrupt 1
{
if(chang_B==1)
{
count_chang++;
if(count_chang>=1000)
{
count_chang=0;
changan_B=1;
}
}
display();
keyscan();
Write_buff();
}
然后就是独立按键扫描,非常简单,就是变成1*4的矩阵按键,我找了一下第九届省赛是独立按键,就把代码展示出来吧
#include "stc15f2k60s2.h"
#include "iic.h"
#include "Intrins.h"
unsigned char dsbuff[8] = {0};//初始化数组
unsigned char code tab[]={0XC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90,0XBF,0XFF,0XC6, 0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10};
unsigned char dspcom=0;
unsigned int temp1 =0;
bit adc_flag;
unsigned char LED1[8]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f}; //模式1
unsigned char LED2[8]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe}; //模式2
unsigned char LED3[8]={0x7e,0xbd,0xdb,0xe7,0x7e,0xbd,0xdb,0xe7}; //模式3
unsigned char LED4[8]={0xe7,0xdb,0xbd,0x7e,0xe7,0xdb,0xbd,0x7e}; //模式4
unsigned int move=0; //滑动变阻器Rb2定义
char led_mode=1; //led模式
unsigned int gap=400;//设置流转间隔
unsigned char led=0xff; //led显示变量
unsigned char SMG_mode=0; //数码管模式定义
unsigned char shine_mode=0; //定义光强
bit ledenable=0; //led工作使能
unsigned char flash=0x00;
unsigned char i_=0;
sbit s7=P3^0;
sbit s6=P3^1;
sbit s5=P3^2;
sbit s4=P3^3;
unsigned char keysta[4]={1,1,1,1};
unsigned char keybackup[4]={1,1,1,1};
unsigned char keybuff[4]={0xff,0xff,0xff,0xff};
unsigned char keycode[4]={7,6,5,4};
void keyaction(unsigned char keydat);
void keysong(unsigned char keydat);
void yemian();
void Dkey_scan();
void Delay2ms() //@11.0592MHz
{
unsigned char i, j;
_nop_();
_nop_();
i = 22;
j = 128;
do
{
while (--j);
} while (--i);
}
void Delay5ms() //@11.0592MHz
{
unsigned char i, j;
i = 54;
j = 199;
do
{
while (--j);
} while (--i);
}
void SelectHC573(unsigned char channel)
{
P2&=0x1f;
P0=0xff;
switch(channel)
{
case 4:P2=(P2&0x1f)|0x80; //LED
break;
case 5:P2=(P2&0x1f)|0xa0; //蜂鸣器继电器
break;
case 6:P2=(P2&0x1f)|0xc0; // 位选 com 口
break;
case 7:P2=(P2&0x1f)|0xe0; //段选 数码管
break;
}
}
void InitSystem()
{
SelectHC573(5);
P0 = 0x00;// 0 经过 ULN203 变为1 蜂鸣器和继电器都是低电平响和工作
SelectHC573(4);//led
P0=0xff;
SelectHC573(6);
P0=0xff;
SelectHC573(7);
P0=0xff;
}
void display()
{
SelectHC573(7);//消隐
P0=0xff;
SelectHC573(6);
P0=(1<<dspcom);
SelectHC573(7);
if(SMG_mode==0)
{
P0=tab[dsbuff[dspcom]];
}
else if(SMG_mode==1)
{
if(dspcom<3)
P0=tab[dsbuff[dspcom]]|flash;
else
P0=tab[dsbuff[dspcom]];
}
else if(SMG_mode==2)
{
if(dspcom<3)
P0=tab[dsbuff[dspcom]];
else
P0=tab[dsbuff[dspcom]]|flash;
}
P2&=0x1f;
if(++dspcom==8)
{
dspcom=0;
}
}
void Timer0Init(void) //1毫秒@12.000MHz
{
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x20; //设置定时初值
TH0 = 0xD1; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
}
void led_handle()
{
P0=0xff;
SelectHC573(4);
P0=led;
}
//==========================中断函数=============================================================
unsigned char t=0;
unsigned int tt=0;//控制led间隔变量
unsigned int ttt=0;
unsigned int tttt=0;
void Timer0(void) interrupt 1
{
Dkey_scan();
yemian();
display();
led_handle();
t++;
if(t>=50)
{
t=0;
adc_flag=1;
}
if((SMG_mode==1)||(SMG_mode==2))
{
tt++;
if(tt>=800)
{
tt=0;
flash=~flash;
}
}
if(ledenable==1)
{
ttt++;
if(ttt<=gap)
{
if(tttt<(shine_mode*5)+8)
{
switch(led_mode)
{
case 1: led=LED1[i_]; break;
case 2: led=LED2[i_]; break;
case 3: led=LED3[i_]; break;
case 4: led=LED4[i_]; break;
}
}
else
led=0xff;
tttt++;
if(tttt>=28) tttt=0;
}
灭的时间是否需要 需要加上
// else if(ttt<(gap*2))
// {
// led=0xff;
// }
else
{
ttt=0;
i_++;
if(i_>=8)
i_=0;
}
}
}
//==========================按键函数====================================================================================
//===============按键扫描
void Dkey_scan()
{
unsigned char i=0;
keybuff[0]=keybuff[0]<<1|s7;
keybuff[1]=keybuff[1]<<1|s6;
keybuff[2]=keybuff[2]<<1|s5;
keybuff[3]=keybuff[3]<<1|s4;
for(i=0;i<4;i++)
{
if((keybuff[i]&0xff)==0)
keysta[i]=0;
else if((keybuff[i]&0xff)==0xff)
keysta[i]=1;
}
}
//===============按键处理
void key_handle()
{
unsigned char i=0;
for(i=0;i<4;i++)
{
if(keysta[i]!=keybackup[i])
{
if(keysta[i]==0)
{
keyaction(keycode[i]);
}
else if(keysta[i]==1)
{
keysong(keycode[i]);
}
keybackup[i]=keysta[i];
}
}
}
//===============按键按下函数
void keyaction(unsigned char keydat)
{
if(keydat==7)
{
if(ledenable==1) {ledenable=0;} //模式0 led不工作
else if(ledenable==0) {ledenable=1;} //模式1 led工作
}
else if(keydat==6)
{
if(SMG_mode==0) {SMG_mode=1;} //模式1 模式编号
else if(SMG_mode==1) {SMG_mode=2;} //模式2 流传间隔
else
{
SMG_mode=0;//模式0 全部熄灭
write_eeprom(0x00,led_mode);
Delay2ms();
write_eeprom(0x01,(gap/100));
}
}
else if(keydat==5)
{
if(SMG_mode==2)
{
gap+=100;
if(gap>1200) gap=400;
}
else if(SMG_mode==1)
{
led_mode+=1;
if(led_mode>4) led_mode=1;
}
}
else if(keydat==4)
{
if(SMG_mode==2)
{
gap-=100;
if(gap<400) gap=1200;
}
else if(SMG_mode==1)
{
led_mode-=1;
if(led_mode<1) led_mode=4;
}
}
}
//===============按键松开函数
void keysong(unsigned char keydat)
{
if(keydat==4)
{
}
}
//==========================页面函数================================================================================
void yemian()
{
if(SMG_mode==0)
{
if(P33==0)
{
dsbuff[6]=10;
dsbuff[7]=shine_mode;
dsbuff[0]=11;
dsbuff[1]=11;
dsbuff[2]=11;
dsbuff[3]=11;
dsbuff[4]=11;
dsbuff[5]=11;
}
else
{
dsbuff[0]=11;
dsbuff[1]=11;
dsbuff[2]=11;
dsbuff[3]=11;
dsbuff[4]=11;
dsbuff[5]=11;
dsbuff[6]=11;
dsbuff[7]=11;
}
}
else if(SMG_mode==1)
{
dsbuff[0]=10;
dsbuff[1]=led_mode;
dsbuff[2]=10;
// 这句话用来验证让数码管闪烁可以直接对数组进行 |或操作
// dsbuff[3]=9|flash;
dsbuff[3]=10;
dsbuff[4]=gap/1000;
if(dsbuff[4]==0)
{dsbuff[4]=11;}
dsbuff[5]=gap%1000/100;
dsbuff[6]=0;
dsbuff[7]=0;
}
else if(SMG_mode==2)
{
dsbuff[0]=10;
dsbuff[1]=led_mode;
dsbuff[2]=10;
dsbuff[3]=11;
dsbuff[4]=(gap/1000);
if(dsbuff[4]==0)
{dsbuff[4]=11;}
dsbuff[5]=gap%1000/100;
dsbuff[6]=0;
dsbuff[7]=0;
}
}
//==========================main函数===================================================================================
void main()
{
InitSystem();
led_mode=read_eeprom(0x00);
Delay5ms();
gap=read_eeprom(0x01);
gap*=100;
Timer0Init();
while(1)
{
if(adc_flag)
{
adc_flag=0;
temp1=pcf_ad();
if(temp1<63) shine_mode=1;
else if(temp1<127) shine_mode=2;
else if(temp1<191) shine_mode=3;
else if(temp1<255) shine_mode=4;
}
key_handle();
}
}