单片机的按键种类
基于Megae16单片机 -【Leep_H】
AD按键
利用AD按键是利用不同的电压大小区分相对应的按键,优点呢节省io口,但是缺点也很明显,一旦电路负载的开关过多,期间的差值就很小,分辨不出。主要用于电视机的按键,MP3,MP4等等。
本电路图如下
代码如下:
#include
#include
#define CLR_SHCLK() PORTB&=~(1<<1)
#define SET_SHCLK() PORTB|=(1<<1)
#define CLR_STCLK() PORTB&=~(1<<0)
#define SET_STCLK() PORTB|=(1<<0)
#define CLR_DS() PORTA&=~(1<<0)
#define SET_DS() PORTA|=(1<<0)
#define U8 unsigned char
unsigned char led_7[16] ={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,
0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
int disp_buff[4]={0,0,0,0};
int count=0,time_100=0;
float adcread;
int adcc,flag;
int a;int b;
static int readi=0;
unsigned char posit = 0;
void adc_init(void)
{
ADMUX=0X67;
ADCSR=0X87;
}
void Hc595send(unsigned char SndData)
{
U8 i;
for(i=0;i<8;i++)
{
if(SndData&(1<<(7-i))){
SET_DS();
}else{
CLR_DS();
}
CLR_SHCLK();
SET_SHCLK();
}
CLR_STCLK();
SET_STCLK();
}
void port_init(void)
{
PORTA = 0x00;
DDRA = 0x01;
PORTB = 0x00;
DDRB = 0x03;
PORTC = 0x00;
DDRC = 0x00;
PORTD = 0x08;
DDRD = 0xF0;
}
void timer0_init(void)
{
TCCR0 = 0x00;
TCNT0 = 0x8D;
OCR0 = 0x73;
TCCR0 = 0x0B;
}
#pragma interrupt_handler timer0_comp_isr:iv_TIM0_COMP
void timer0_comp_isr(void)
{
if(++count>=100)
{
count=0;
time_100=1;
}
PORTD |= 0xF0;
Hc595send(~led_7[disp_buff[posit]]);
PORTD &= ~(1<<(7-posit));
if (++posit >=4 ) posit = 0;
}
unsigned int AD_GetData()
{
ADCSRA |= (1 <<6);
while(!(ADCSRA&(1<<4)));
ADCSRA |= (1 << 4);
return ADC;
}
void init_devices(void)
{
CLI();
port_init();
timer0_init();
adc_init();
MCUCR = 0x00;
GICR = 0x00;
TIMSK = 0x02;
SEI();
}
int main(void)
{
unsigned char i;
init_devices();
while (1)
{
if(time_100==1)
{
time_100=0;
adcread=AD_GetData();
adcc=adcread/1023*3300;
}
if(adcc>=0 && adcc<829adcc!=3300)
disp_buff[3]=1;
if(adcc>1 && adcc<=830 && adcc!=3300)
disp_buff[3]=2;
if(adcc>830 && adcc<=1322 && adcc!=3300)
disp_buff[3]=4;
if(adcc>1322 && adcc<=1655 && adcc!=3300)
disp_buff[3]=3;
if(adcc>1655 && adcc<=1887 && adcc!=3300)
disp_buff[3]=6;
if(adcc>1887 && adcc<=2161 && adcc!=3300)
disp_buff[3]=5;
}
return 0;
}
行列式扫描按键
为了应付IO口的紧缺,还引入了行列式按键扫描的方式。确定代码较为复杂,这里使用了状态机,稍后会有状态机的说明,并附有文档,看不懂的可以现看状态机的原理。
图例题
类似预电话机
代码如下:
#include
#include
#define INPUT ((PIND&(1<<0))|(PIND&(1<<1))|(PIND&(1<<2)))
#define key1_1 0x0e
#define key1_2 0x0d
#define key1_3 0x0b
#define key2_1 0x16
#define key2_2 0x15
#define key2_3 0x13
#define key3_1 0x26
#define key3_2 0x25
#define key3_3 0x23
#define key4_1 0x46
#define key4_2 0x45
#define key4_3 0x43
#define key_mask 0x07
int key;
int j=0;int point=0;
int count=0,flag=0;
unsigned char led_7[16] ={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,
0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
int a[6]={0,0,0,0,0,0};
void port_init(void)
{
PORTA = 0x00;
DDRA = 0xFF;
PORTB = 0x00;
DDRB = 0x00;
PORTC = 0xff;
DDRC = 0xff;
PORTD = 0xff;
DDRD = 0xF8;
}
void timer0_init(void)
{
TCCR0 = 0x00;
TCNT0 = 0x8D;
OCR0 = 0x73;
TCCR0 = 0x0B;
}
#pragma interrupt_handler timer0_comp_isr:iv_TIM0_COMP
void timer0_comp_isr(void)
{
if(++count>=10)
{
count=0;
flag=1;
}
PORTC=0XFF;
PORTA=led_7[a[j]];
PORTC&=~(1<if(j>=7)
j=0;
}
void init_devices(void)
{
CLI();
port_init();
timer0_init();
MCUCR = 0x00;
GICR = 0x00;
TIMSK = 0x02;
SEI();
}
int readkey()
{
static int key_state=0, line,key_v;
int key_return=255,i;
switch(key_state)
{
case 0:
line=0x08;
for(i=1;i<=4;i++)
{
PORTD=~line;
PORTD=~line;
key_v=key_mask&INPUT;
if((key_v)!=key_mask)
{
key_state=1;
break;
}
else
line<<=1;
}
break;
case 1:
if(key_v==(INPUT&key_mask))
{
switch(line|key_v)
{
case key1_1:
key_return=1;
break;
case key1_2:
key_return=2;
break;
case key1_3:
key_return=3;
break;
case key2_1:
key_return=4;
break;
case key2_2:
key_return=5;
break;
case key2_3:
key_return=6;
break;
case key3_1:
key_return=7;
break;
case key3_2:
key_return=8;
break;
case key3_3:
key_return=9;
break;
case key4_1:
key_return=10;
break;
case key4_2:
key_return=0;
break;
case key4_3:
key_return=12;
break;
}
key_state=2;
}
else
key_state=0;
break;
case 2:
PORTD=key_mask;
if((key_mask&INPUT)==0x07)
key_state=0;
break;
}
return key_return;
}
int main()
{
init_devices();
while(1)
{
if(flag)
{
flag=0;
key=readkey();
if(key!=255)
{
for(point=5;point>0;point--)
a[point]=a[point-1];
a[0]=key;
}
}
};
return 0;
}
状态机
顾名思义,状态机就是分析按键的状态。实体按键会产生抖动,而消抖成为重要的事情。
1.硬件消抖,采用R-S触发器或者RC积分电路,但是这个方法不实际,大大的增加的成本。
2.软件消抖,对按键进行两次确认,即在首次检测到按键按下之后间隔10ms再次确认按键的状态。
状态机结合定时器来处理按键十分好用。
1/0: ——-1表示按键未按下(按键按下低电平),右边的0表示输出值为0;(设置定时器每隔10ms检测状态机)
状态0:
均为(1/0),按键未按下,输出为0的状态。检测但当按键按下时变为(0/0).
状态1:
均为(0/0),此时有两种可能
1 隔了10ms后,发现是一次抖动,返回0状态(1/0)
2 隔了10ms后,发现确实是一次按键按下的状态,(0/1)此时进入状态2
***注意!此时是所有状态里面唯一输出1的状态,输出1则说明按键按下,这是代码的主要判断。***
状态2:
若按键依旧按着,则直至按键释放,回到(0/0)状态0。
还是不好理解的话,下面为代码部分加深:
足够用就不用进阶了
状态机进阶:
引入状态机3,目的为了使得同一个按键有不同的功能,或者有按键的连击(类似长按电视音量加)
图中的时间可自己设计。
2016/10/20
个人难免狭隘,不足之处,请指出。