2016-04-07 20:02:38 zxnsirius 阅读数 1694
  • 按键-第1季第9部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第9个课程,综合解决了独立按键和矩阵式按键的处理方法,涉及到:IO的输入输出、按键抖动和消抖、中断的引入和相关概念、矩阵式键盘的原理和编程等。目的是对单片机常见输入设备按键进行全方位学习。

    2176 人正在学习 去看看 朱有鹏

按键去抖


这里写图片描述

由上图可以看出理想波形与实际波形之间是有区别的,实际波形在按下和释放的瞬间都有抖动的现象,抖动时间的长短和按键的机械特性有关,一般为5~10ms。通常我们手动按键然后释放,这个动作中稳定闭合的时间超过了20ms。因此单片机在检测键盘是否按下时都要加上去抖动操作,有专用的去抖动电路,也有专门的去抖动芯片,但通常我们采用软件延时的方法就可以解决抖动问题。


/*   软件去抖  */                                                                                                                                  
if0 == K1 )          //如果有键按下                                                                                                                  
{                                                                                                                                                
    delay_ms(8);        //延时一段时间去抖                                                                                                    
    if (0 == K1)        //如果真的有键按下,检测到得是稳定闭合状态                                                                                       
      {                                                                                                                                
             ...        //按键以后需要做的事情                                                                                
      }                                                                                                                              
      while(!K1);    //松手检测,如果按住不放则一直在循环里                                                                                      
}          
2019-06-14 17:25:12 weixin_42976659 阅读数 252
  • 按键-第1季第9部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第9个课程,综合解决了独立按键和矩阵式按键的处理方法,涉及到:IO的输入输出、按键抖动和消抖、中断的引入和相关概念、矩阵式键盘的原理和编程等。目的是对单片机常见输入设备按键进行全方位学习。

    2176 人正在学习 去看看 朱有鹏

写在前面:在这里插入图片描述

STM32单片机按键消抖和FPGA按键消抖大全

按键去抖:由上图可以看出理想波形与实际波形之间是有区别的,实际波形在按下和释放的瞬间都有抖动的现象,抖动时间的长短和按键的机械特性有关,一般为5~10ms。通常我们手动按键然后释放,这个动作中稳定闭合的时间超过了20ms。因此单片机在检测键盘是否按下时都要加上去抖动操作,有专用的去抖动电路,也有专门的去抖动芯片,但通常我们采用软件延时的方法就可以解决抖动问题。

1、单片机中按键消抖程序

1.1 单片机中,比如STM32中,一般的方法(最简单的方法)

软件消抖程序:

if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)==1)
{
delay_ms(20);//延时20ms再去检测按键值
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)==0) // 相当于下降沿

{

KEY1 = 1; //表示KEY1被按下

}

}

1.2 比较全面的按键消抖程序及按键状态检测程序

第一步:初始化全局时间戳的定时器,一般采用SysTick定时器来产生,每ms一次tick即可。

第二步:初始化按键对应的IO,复用为边沿触发的外部中断。

第三步:在外部中断函数中添加按键事件处理函数。

代码部分:

typedef struct _Key_t

{

u32 last_time;

enum

{

May_Press,  

Release,  

}private_state;

enum

{

No_Press,  

Short_Press,  

Long_Press,  

}state;

}Key_t;

#define Is_ShortPress_Threshold 1500

简单定义一个按键状态的结构体,用于管理每个按键的状态。顺便再定义一个长短按的识别阈值,用于区分按键的长短按。

if(key_state.private_state==Release)

{

if(KEY==0)

{

key_state.private_state=May_Press;  

key_state.last_time=course_ms();  

}

}

else if(key_state.private_state==May_Press)

{

if(KEY==1)

{

if((course_ms()-key_state.last_time>10)&&(course_ms()-key_state.last_time

{  

  key_state.state=Short_Press;  

  key_state.private_state=Release;  

}  

else if(course_ms()-key_state.last_time>Is_ShortPress_Threshold)  

{  

  key_state.state=Long_Press;  

  key_state.private_state=Release;  

}  

else  

  key_state.private_state=Release;  

}

}

以上为需要添加到中断处理函数的按键事件处理函数,算法的核心是一个状态机。在本例中,按键被默认上拉,按下接地。course_ms()为获取全局时间戳的函数。

思路解释如下:按键状态结构体有一个用于识别的状态位,默认处于Release,也就是释放的状态。一旦按键被按下,中断触发,此时检查是否是Relase状态,如果是就检查按键是否被拉低,如果是,此时进入May_Press状态,也就是可能是按下的,并且记录此时的时间戳,这一步是消抖的关键。当按键被释放,由于是边沿触发,会再次进行处理,此时检查和上一次触发之间的时间戳之差,如果小于10ms我们就认为是抖动,此时不会对按键输出状态进行修改,而是直接将按键状态置回Relase状态,反之检查差值和长短按阈值之间的关系,将state置位为对应的状态。消抖的核心在于记录时间戳,而这只是一个简单的赋值操作,并不耗费时间。

效率上来说,延时消抖花费时间在无意义延时上,而相对较好的定时轮询还是不可避免的在轮询,而现在这种方式完全是中断性质的。唯一多出的开销(全局时间戳)并不是只可以用于按键消抖,另外在HAL库中存在直接获取tick的函数,这样实现就更方便了。经实际测试,消抖效果可以达到其他两种消抖算法的水平。

2、FPGA按键消抖程序

首先,做两个假定,以方便后面的描述:

假定按键的默认状态为0,被按下后为1

假定按键抖动时长小于20ms,也即使用20ms的消抖时间

核心:方案

最容易想到的方案

在按键电平稳定的情况下,当第一次检测到键位电平变化,开始20ms计时,计时时间到后将按键电平更新为当前电平。

或许这才是最容易想的方案

在20ms计时的过程中,有任何的电平变化都立即复位计时

消除按键反应延时抖方案

在有电平变化时立即改变按键输出电平,并开始20ms计时,忽略这其中抖动

测试平台设计(修改代码以仿真的1us代替实际1ms)

无抖动 上升沿抖动5毫秒

下降沿抖动15毫秒

上升和下降沿均抖动19毫秒

附加测试(可以不通过)

抖动25毫秒

代码

方案1

module debounce( input wire clk, nrst, input wire key_in, output reg key_out
); // 20ms parameter// localparam TIME_20MS = 1_000_000;
localparam TIME_20MS = 1_000; // just for test // variable
reg [20:0] cnt; reg key_cnt;
// debounce time passed, refresh key state
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_out <= 0; else if(cnt == TIME_20MS - 1)
key_out <= key_in; end

// while in debounce state, count, otherwise 0
always @(posedge clk or negedge nrst) begin
    if(nrst == 0)
        cnt <= 0;        else if(key_cnt)
        cnt <= cnt + 1'b1;
    else
        cnt <= 0; 
end
 
 // 
 always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            key_cnt <= 0;            else if(key_cnt == 0 && key_in != key_out)
            key_cnt <= 1;            else if(cnt == TIME_20MS - 1)
            key_cnt <= 0;     endendmodule

方案2

module debounce( input wire clk, nrst, input wire key_in, output reg key_out
);// localparam TIME_20MS = 1_000_000;
localparam TIME_20MS = 1_000; reg key_cnt; reg [20:0] cnt; always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_cnt <= 0; else if(cnt == TIME_20MS - 1)
key_cnt <= 0; else if(key_cnt == 0 && key_out != key_in)
key_cnt <= 1; end

always @(posedge clk or negedge nrst) begin
    if(nrst == 0)
        cnt <= 0;        else if(key_cnt) begin
        if(key_out == key_in)
            cnt <= 0;            else
            cnt <= cnt + 1'b1;
    end
    else
        cnt <= 0;    end
 
 always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            key_out <= 0;            else if(cnt == TIME_20MS - 1)
            key_out <= key_in;     endendmodule

方案3

module debounce( input wire clk, nrst, input wire key_in, output reg key_out
);// localparam TIME_20MS = 1_000_000;
localparam TIME_20MS = 1_000; // just for test

reg key_cnt;    reg [20:0] cnt;    always @(posedge clk or negedge nrst) begin
    if(nrst == 0)
        key_cnt <= 0;        else if(key_cnt == 0 && key_out != key_in)
        key_cnt <= 1;        else if(cnt == TIME_20MS - 1)
        key_cnt <= 0;    end

always @(posedge clk or negedge nrst) begin
    if(nrst == 0)
        cnt <= 0;        else if(key_cnt)
        cnt <= cnt + 1'b1;
    else
        cnt <= 0;    end

always @(posedge clk or negedge nrst) begin
    if(nrst == 0)
        key_out <= 0;        else if(key_cnt == 0 && key_out != key_in)
        key_out <= key_in;    endendmodule

测试代码

// 按键消抖测试电路// 时间单位`timescale 1ns/10ps// modulemodule debounce_tb; // time period parameter
localparam T = 20; // variable
reg clk, nrst; reg key_in; wire key_out; // instantiate debounce uut(
.clk (clk ),
.nrst (nrst ),
.key_in (key_in ),
.key_out(key_out)
); // clock
initial begin
clk = 1; forever #(T/2) clk = ~clk; end

// reset
initial begin
    nrst = 1;
    @(negedge clk) nrst = 0;
    @(negedge clk) nrst = 1;    end

// key_in
initial begin
    // initial value
    key_in = 0;        
    // wait reset
    repeat(3) @(negedge clk);        
    // no bounce        // key down
    key_in = 1;        // last 60ms
    repeat(3000) @(negedge clk);        // key up
    key_in = 0;        // wait 50ms
    repeat(2500) @(negedge clk);        // down 5ms, up 15ms        // key down, bounce 5ms
    repeat(251) @(negedge clk) key_in = ~key_in;        // last 60ms
    repeat(3000) @(negedge clk);        // key up, bounce 15ms
    repeat(751) @(negedge clk) key_in = ~key_in;        // wait 50ms
    repeat(2500) @(negedge clk);        // down 19ms, up 19ms        // key down, bounce 19ms
    repeat(951) @(negedge clk) key_in = ~key_in;        // last 60ms
    repeat(3000) @(negedge clk);        // key up, bounce 19ms
    repeat(951) @(negedge clk) key_in = ~key_in;        // wait 50ms
    repeat(2500) @(negedge clk);        
    // additional, this situation shoud not ever happen        // down 25ms, up 25ms        // key down, bounce 25ms
    repeat(1251) @(negedge clk) key_in = ~key_in;        // last 60ms
    repeat(3000) @(negedge clk);        // key up, bounce 25ms
    repeat(1251) @(negedge clk) key_in = ~key_in;        // wait 50ms
    repeat(2500) @(negedge clk);        // stop        $stop;    endendmodule

放在最后的,并不一定是最不重要的

对于上面的三种方案,我比较喜欢第三种方案,它更贴合实际的按键状态,以上的代码我都做过modelsim仿真,但还没有在实际的项目中验证。在整理准备这个博客的时候,我又想到了一个感觉是更巧妙的方案,具体是这样的:在第三个方案的基础上,因为按键输入有变化的第一时刻,输出就已经改变了,在这种情况下,我可以把计时的时长改为一个很小的值,该值只要比抖动中的最长高低电平变化时间长即可。但想想也没这个必要,且这个抖动的高低电平变化时长我也很难去给它界定一个值。
在这里插入图片描述

2019-02-01 17:34:25 weixin_43786907 阅读数 1624
  • 按键-第1季第9部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第9个课程,综合解决了独立按键和矩阵式按键的处理方法,涉及到:IO的输入输出、按键抖动和消抖、中断的引入和相关概念、矩阵式键盘的原理和编程等。目的是对单片机常见输入设备按键进行全方位学习。

    2176 人正在学习 去看看 朱有鹏

初学单片机时,讲到了一个按键“消抖”概念,视屏教程中只是说到要确定按键是不是真正按下,所以需要加一个延时来判断。

附上延时消抖程序代码:
代码1

void keypros()
{
	if(k1==0)		  //检测按键K1是否按下
	{	
		delay(1000);   //消除抖动 一般大约10ms
		if(k1==0)	 //再次判断按键是否按下
		{
			                          //此处添加相应操作
		}
		while(!k1);	 //检测按键是否松开
	}		
}

前不久,又了解到另外两个按键消抖的代码

状态机消抖:
代码2

#define key_input P3
#define key_state_0  0  //判断是否按下
#define key_state_1  1 //判断是否为抖动
#define key_state_2  2 //判断是否弹起

unsigned char t0count;
char key_value; 
  
char read_key(void) 
{ 
 static char key_state = 0; 
 char key_press, key_return = 0; 
 key_press = key_input&key_mask; 
 switch (key_state) 
{ 	
   case key_state_0:     
   if (key_press!=key_mask) key_state = key_state_1; 
   break; 
	 
   case key_state_1:    
   if (key_press!=key_input&key_mask) 
   { 
  	 if(key_press==0x0e) key_return = 1;  //S7
		 if(key_press==0x0d) key_return = 2;  //S6
		 if(key_press==0x0b) key_return = 3;  //S5
		 if(key_press==0x07) key_return = 4;  //S4
  	 key_state = key_state_2;  
   } 
   else 
   	 key_state = key_state_0;  
   break;  
   case key_state_2: 
   if (key_press==0x0f) key_state = key_state_0; 
   break; 
} 
return key_return; 
} 

void tm0_isr() interrupt 1
{
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	t0count++;
	if(t0count>=2)
	{
		t0count=0;
		key_value=read_key();
	}
}
...
if(key_value==...)
{
   //对于操作
}

三行代码消抖:
代码2

#define KEYPORT P3

typedef unsigned char u8;

u8 Trg;
u8 Cont;
u8 t0count;

void Read_key()
{
	u8 readdata=KEYPORT^0xff;                   //核心代码1
	Trg=readdata&(readdata^Cont);              //核心代码2
	Cont=readdata;                             //核心代码3
}

void tm0_isr() interrupt 1
{
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	t0count++;
	if(t0count>=10)
	{
		t0count=0;
		Read_key();
	}
}
...
if(Trg==...)    //短按
{
      //对应操作
}

if(Cont==..)   //长按
{
      //对应操作
}

三种方法为什么都可以“消抖”?
这一问题引起了我的思考。

于是我查询了什么是按键抖动:按键抖动通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。

如下图:
在这里插入图片描述
由于单片机的运行速度非常快,按下一次按键,可能在A点检测到一次低电平,在B点检测到一次高电平,在C点又检测到一次低电平。同时抖动是随机,不可测的。那么按下一次按键,抖动可能对会让单片机误以为按下多次按键。

延时消抖

最简单的消抖原理,就是当检测到按键状态变化后,先等待一个 10ms 左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作了。

状态机消抖

与延时消抖大同小异,间隔10ms检测一次按键电位变化,状态0时检测到有按键电位变化就进入状态1,状态1再来确认是否还有变化,有则是按下按键。但是由于是利用了定时器,所以在间隔等待10ms时单片机仍可以进行其他进程。

三行代码消抖

真的佩服。
说起来与状态机消抖原理差不多也是利用定时器间隔10ms检测一次按键。但是构思非常巧妙。
当没有按键按下时,Trg与Cont均为0x00。
当单片机执行到此程序且按键按下时(假设P3^0按下),Trg与Cont均为0x01。
当单片机再次执行到此程序且按键按下时(假设P3^0按下),Trg为0x00,Cont为0x01。
而当按键释放,再次检测时,Trg与Cont又均为0x00。(恢复到没有按键按下状态)

2014-12-16 14:44:23 hengliwuyou 阅读数 3427
  • 按键-第1季第9部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第9个课程,综合解决了独立按键和矩阵式按键的处理方法,涉及到:IO的输入输出、按键抖动和消抖、中断的引入和相关概念、矩阵式键盘的原理和编程等。目的是对单片机常见输入设备按键进行全方位学习。

    2176 人正在学习 去看看 朱有鹏

抖动是机械按键存在的现象,是必须要进行处理的。一般处理有两种方式:一种是硬件消抖,另一种是软件消抖。硬件消抖不仅会增加开发成本,而且有时不稳定。一般都会选择软件消抖。

  因为单片机最重要的是测试状态,而不是测试过程。一般不会选择通过状态延时来消抖,而是通过定时循环测试按键的状态来消抖.下面是针对51单片机的独立按键写的一个通过定时器来消抖的程序:

#include<reg52.h>

sbit  ADDR0 = P1^0;
sbit  ADDR1 = P1^1;
sbit  ADDR2 = P1^2;
sbit  ADDR3 = P1^3;
sbit  ENLED = P1^4;
sbit  KEY4 = P2^7;
//数码管的数字十六进制表示
unsigned char code LedChar[]=
{
 0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
 0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E
};


bit KeySta = 1;


void main()
{
 bit backup = 1;
 unsigned char cnt = 0;

//打开总中断
 EA = 1;
 ENLED = 0;
 ADDR3 = 1;
 ADDR2 = 0;
 ADDR1 = 0;

 ADDR0 = 0;

//设置TMOD的状态

 TMOD = 0x01;

//定时为2ms

 TH0 = 0xF8;
 TL0 = 0xCD;
 ET0 = 1;
 TR0 = 1;
 P2  = 0xF7;
 P0 = LedChar[cnt];


 while(1)
 {
  if(KeySta !=backup)
   {
    if(backup==0)
{
cnt++;
if(cnt>=10)
{
 cnt=0;
}
P0 = LedChar[cnt];
}
backup=KeySta;
    
   }
 }


}


void InterruptTimer0() interrupt 1
{
  static unsigned char keybuf = 0xFF;
  TH0 = 0xF8;
  TL0 = 0xCD;
  keybuf = (keybuf<<1) |KEY4;
  if(keybuf == 0x00)
  {
   KeySta = 0;
  }
  else if(keybuf == 0xff)
  {
   KeySta = 1;
  }
  
}
2019-12-16 20:56:03 Linux_student625 阅读数 24
  • 按键-第1季第9部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第9个课程,综合解决了独立按键和矩阵式按键的处理方法,涉及到:IO的输入输出、按键抖动和消抖、中断的引入和相关概念、矩阵式键盘的原理和编程等。目的是对单片机常见输入设备按键进行全方位学习。

    2176 人正在学习 去看看 朱有鹏

开发板:普中A7开发板
核心板:51和STM32F103C8T6

关于按键状态检测要注意的就一个消抖问题,消抖方式需要高效简单。这里需要搞清楚一点,按键状态检测用的是轮询方式还是中断方式

在这里需要感谢一下金沙滩的51单片机视频课程老师宋老师。因为其中一种按键消抖方法就是学自宋老师课程,需要详细查看的话可以看《手把手教你51单片机.pdf》文档。

我知道的软件消抖(轮询方式)有两种方式:
1、在绝大多数情况下,我们是用软件即程序来实现消抖的。最简单的消抖原理,就是当检测到按键状态变化后,先等待一个 10ms 左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作了。实际应用中,这种做法局限性大(实时性差)。
2、第二种就是学自宋老师的启用一个定时中断,每 2ms 进一次中断,扫描一次按键状态并且存储起来,连续扫描 8 次后,看看这连续 8 次的按键状态是否是一致的。8 次按键的时间大概是 16ms,这 16ms 内如果按键状态一直保持一致,那就可以确定现在按键处于稳定的阶段,而非处于抖动的阶段。(这里需要注意的是这个16ms的时间不是固定的,2ms进一次中断检测一个位,一个字节8位,所以用一个字节来判断按键状态就是需要16ms,如果用4位来判断那么就是8ms,具体可以查看下面代码)如下图所示。
在这里插入图片描述

在这里需要提一下的是,在STM32F103C8T6核心板上做实验的时候,使用第二种按键消抖方式(用GPIO外部中断来实现,而不是用时间片轮询来实现)始终没调试出来,按键操作之后容易卡死,也不知道卡死在哪,代码就不贴出来了。但是网上查了一下资料,有人是通过定时器延迟到了给flag标志来进行消抖的,比如:如果定时没到的话flag = 0,在主程序中检测按键就用if(flag)来判断是否经过了延时消抖,定时到了就给flag,那么主程序中的检测按键代码就执行。下面看一下按键的常识性知识:

1、通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如图所示。
图1
2、按键稳定闭合时间长短是由操作人员决定的,通常都会在 100ms 以上,刻意快速按的话能达到 40-50ms 左右,很难再低了。抖动时间是由按键的机械特性决定的,一般都会在 10ms以内,为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。

下面代码中第1种方式的软件消抖是用的STM32平台的,挑取的是按键扫描一个函数;第2种方式的软件消抖是51平台的,为了便于理解,把整个工程代码都贴出来了,结合上下文容易理解。
/********************************方法1 ********************************/

/**************************************************************************************
 * 函数功能:扫描矩阵按键,期间用延时函数做按键消抖;保存按键状态以及按键键值
 * 参    数:
 * 返 回 值:
 * 注意事项:一定要在函数最后重新设置按键为全部监听状态,否则只能监听到最后一排的按键中断
 **************************************************************************************/
void KeyScan(void)
{
	u8 i, j;
	u16 tmp = GPIO_ReadOutputData(GPIOB);

	for(j=0; j<4; j++)
	{
		delayms(10);
		tmp |= (0xF<<8);
		tmp &= ~(1<<(8+j));
		GPIO_Write(GPIOB, tmp);
		for(i=0; i<4; i++)
		{
			if(GPIO_ReadInputDataBit(GPIOB, 1<<(12+i)) == 0x00)
			{
				key_value = key_map[j][i];
				key_state = PUSH;
				return;
			}	
		}	
	}

	key_state = POP;
	KeyAllListening();		/* 一定要重新设置按键监听状态 */

	return;
}

/********************************方法2 *********************************/

/**************************************************************************************
 *实验现象:根据按键键值做加法器,所按数字在数码管上显示,加法所得值也在数码管显示
 *接    线:P0.0 -> RC, P0.1 -> SC, P0.2 -> SE; J29 -> JP3; J6 -> J27; P1.0 -> A_138,
 			P1.1 -> B_138, P1.2 -> C_138;
 *注意事项:1、先弄明白矩阵按键和动态数码管显示的原理,也就是说实现该加法器功能的时候
 *			矩阵按键的状态和动态数码管的显示所需要的条件由谁生产,又由谁消费。所以这里
 *			中断函数的功能有2个:第1个是生产按键状态、第二个是消费数码管显示,也就是根
 *			据主程序给的数码管显示位数和数值进行刷新显示。
 *			2、业务逻辑要调试清楚,			
 **************************************************************************************/
#include <reg52.h>
#include <intrins.h>

#define BIT_16_LOAD		0x01
#define BIT_8_AUTOLOAD	0x00
#define ON				0x01
#define OFF				0x00
#define PUSH			0x00
#define POP				0x01


typedef unsigned char	uchar;
typedef unsigned int	uint;
typedef unsigned long	ulong;

sbit RC = P0^0;
sbit SC = P0^1;
sbit SE = P0^2;
sbit A_138 = P1^0;
sbit B_138 = P1^1;
sbit C_138 = P1^2;

sbit KEY_H1 = P3^0;
sbit KEY_H2 = P3^1;
sbit KEY_H3 = P3^2;
sbit KEY_H4 = P3^3;
sbit KEY_L1 = P3^4;
sbit KEY_L2 = P3^5;
sbit KEY_L3 = P3^6;
sbit KEY_L4 = P3^7;

/* 数码管字模 (0->9) */
unsigned char code byte_buff[] = {0xFC, 0x60, 0xDA, 0xF2, 0x66, 0xB6, 0xBE, 0xE0, 0xFE, 0xF6};
/* 138译码器真值表 (最右边->最左边) */
unsigned char code ctl_138[] = {0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0};
uchar cur_state[4][4] = {    
	{1, 1, 1, 1}, {1, 1, 1, 1}, 
	{1, 1, 1, 1}, {1, 1, 1, 1},
	};
uchar bak_state[4][4] = {
	{1, 1, 1, 1}, {1, 1, 1, 1}, 
	{1, 1, 1, 1}, {1, 1, 1, 1},
	};
uchar code key_map[4][4] = {
	{0x31, 0x32, 0x33, 0x25},//数字键 1、数字键 2、数字键 3、向上键
	{0x34, 0x35, 0x36, 0x26},//数字键 4、数字键 5、数字键 6、向左键
	{0x37, 0x38, 0x39, 0x27},//数字键 7、数字键 8、数字键 9、向下键
	{0x30, 0x1B, 0x0D, 0x28},//数字键 0、 ESC 键、 回车键、 向右键
	};
uchar key_tmp[8] = {0,0,0,0,0,0,0,0,};	
uchar val_tmp[8] = {0,0,0,0,0,0,0,0,};
uchar tube_pos;	
ulong display_val = 0;
uchar num_138;
	
void KeyDriver(uchar key_value);	
ulong SetNumber(uchar *array, uchar num);
void SelectDisplay(ulong value);
void ScanKey();
void ScanDisplay(uchar num);
void ConfigTimer0(uint ms);
void InitTimer0(uint ms);
void CleanDisplay();
void Display_138(unsigned char num);
void SendByte(uchar byte);


void main()
{
	uchar i, j;
	
	/* 定时2ms */
	InitTimer0(2);
	
	CleanDisplay();
	SelectDisplay(0);
		
	while(1)
	{
		for(j=0; j<4; j++)
		{
			for(i=0; i<4; i++)
			{
				if(cur_state[j][i] != bak_state[j][i])
				{
					if(cur_state[j][i] == PUSH)
					{
						KeyDriver(key_map[j][i]);
					}
					
					bak_state[j][i] = cur_state[j][i];
				}
			}
		}		
	}

	return;
} 


void KeyDriver(uchar key_value)
{
	static ulong tmp_value = 0;
	static ulong add_value = 0;
	
	/* 按第9个按键数字的时候,数码管显示最右边的0 */
	if(tube_pos > 7)
	{
		tube_pos = 0;
		num_138 = 0;		/* 只显示最右边一个数码管 */
		SelectDisplay(0);	/* 显示数字为0 */
		return;
	}

	/* 数字0-9的话就按输入顺序显示在数码管上 */
	if(key_value >= 0x30 && key_value <= 0x39)
	{
		CleanDisplay();
		key_tmp[tube_pos] = key_value - 0x30;		/* 计算键值是数字几 */
		tmp_value = SetNumber(key_tmp, tube_pos);	/* 按了几次按键,生成对应的数值 */
		display_val = tmp_value;
		SelectDisplay(display_val);
		tube_pos++;
	}	
	else if(key_value == 0x25)		/* 向上键设置为'+'号,数码管显示原来的数字 */
	{
		if(tmp_value != 0x25 && tmp_value != 0x0D && tmp_value != 0x26 && tmp_value != 0x27 && tmp_value != 0x28)
		{
			add_value += tmp_value;
			if(add_value > 999999999)
			{
				add_value = 0;
			}
			SelectDisplay(display_val);
			tmp_value = key_value;
			tube_pos = 0;
		}		
	}
	else if(key_value == 0x0D)		/* 回车键结束输入并计算值,数码管显示计算值 */
	{
		if(tmp_value != 0x25 && tmp_value != 0x0D && tmp_value != 0x26 && tmp_value != 0x27 && tmp_value != 0x28)
		{
			add_value += tmp_value;
			display_val = add_value;
			SelectDisplay(display_val);	
			tube_pos = 0;
			tmp_value = key_value;
			add_value = 0;
		}
	}
	else if(key_value == 0x1B)
	{
		CleanDisplay();
		SelectDisplay(0);
		tube_pos = 0;
		tmp_value = 0;
		add_value = 0;
	}
	else if(key_value == 0x26)
	{
		if(tmp_value != 0x25 && tmp_value != 0x0D && tmp_value != 0x26 && tmp_value != 0x27 && tmp_value != 0x28)
		{
			tmp_value = key_value;
			SelectDisplay(display_val);		
		}
	}
	else if(key_value == 0x27)
	{
		if(tmp_value != 0x25 && tmp_value != 0x0D && tmp_value != 0x26 && tmp_value != 0x27 && tmp_value != 0x28)
		{
			tmp_value = key_value;
			SelectDisplay(display_val);		
		}
	}
	else if(key_value == 0x28)
	{
		if(tmp_value != 0x25 && tmp_value != 0x0D && tmp_value != 0x26 && tmp_value != 0x27 && tmp_value != 0x28)
		{
			tmp_value = key_value;
			SelectDisplay(display_val);		
		}
	}

	return;
}

/*实现数码管数字显示 */
void SelectDisplay(ulong value)
{
	uchar i;
	
	val_tmp[0] = byte_buff[value%10];
	val_tmp[1] = byte_buff[(value/10)%10];
	val_tmp[2] = byte_buff[(value/100)%10];
	val_tmp[3] = byte_buff[(value/1000)%10];
	val_tmp[4] = byte_buff[(value/10000)%10];
	val_tmp[5] = byte_buff[(value/100000)%10];
	val_tmp[6] = byte_buff[(value/1000000)%10];
	val_tmp[7] = byte_buff[(value/10000000)%10];

	for(i=0; i<8; i++)
	{
		if(val_tmp[7-i] != byte_buff[0])		/* 判断需要显示几个数码管 */
		{
			num_138 = 7 - i;
			break;
		}
	}	
	
	return;
}

/* 根据保存的数组来计算按键输入的值是多少 */
ulong SetNumber(uchar *array, uchar num)
{
	ulong tmp = 0;
	
	if(num > 7)
		return 1;
	
	switch(num)
	{
		case 0:
			tmp = (ulong)array[0];
			break;
		case 1:
			tmp = (ulong)array[0]*10 + (ulong)array[1];
			break;
		case 2:
			tmp = (ulong)array[0]*100 + (ulong)array[1]*10 + (ulong)array[2];
			break;
		case 3:
			tmp = (ulong)array[0]*1000 + (ulong)array[1]*100 + (ulong)array[2]*10 + (ulong)array[3];
			break;
		case 4:
			tmp = (ulong)array[0]*10000 + (ulong)array[1]*1000 + (ulong)array[2]*100 + (ulong)array[3]*10 + (ulong)array[4];
			break;
		case 5:
			tmp = (ulong)array[0]*100000 + (ulong)array[1]*10000 + (ulong)array[2]*1000 + (ulong)array[3]*100 + (ulong)array[4]*10 + (ulong)array[5];				
			break;
		case 6:
			tmp = (ulong)array[0]*1000000 + (ulong)array[1]*100000 + (ulong)array[2]*10000 + (ulong)array[3]*1000 + (ulong)array[4]*100 + (ulong)array[5]*10 + (ulong)array[6];	
			break;
		case 7:
			tmp = (ulong)array[0]*10000000 + (ulong)array[1]*1000000 + (ulong)array[2]*100000 + (ulong)array[3]*10000 + (ulong)array[4]*1000 + (ulong)array[5]*100 + (ulong)array[6]*10 + (ulong)array[7];	
			break;
		default:
			break;		
	}
	
	return tmp;
}

/* 中断处理函数 */
void InterruptTimer0() interrupt 1
{
	ConfigTimer0(2);
	
	/* 刷新按键 */
 	ScanKey();
	
	/* 刷新数码管 */
	ScanDisplay(num_138);
}

/*扫描按键并生成状态 */
void ScanKey()
{
	static uchar tmp_buf[4][4] = {
		{0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF},  
	};
	static uchar pos = 0;
	uchar i;
	
	/* 检测按键状态 */
	switch(pos)
	{
		case 0:
			KEY_H1 = 0; KEY_H2 = 1; KEY_H3 = 1; KEY_H4 = 1;
			KEY_L1 = 1; KEY_L2 = 1; KEY_L3 = 1; KEY_L4 = 1;
			break;
		case 1:
			KEY_H1 = 1; KEY_H2 = 0; KEY_H3 = 1; KEY_H4 = 1;
			KEY_L1 = 1; KEY_L2 = 1; KEY_L3 = 1; KEY_L4 = 1;
			break;
		case 2:
			KEY_H1 = 1; KEY_H2 = 1; KEY_H3 = 0; KEY_H4 = 1;
			KEY_L1 = 1; KEY_L2 = 1; KEY_L3 = 1; KEY_L4 = 1;
			break;
		case 3:
			KEY_H1 = 1; KEY_H2 = 1; KEY_H3 = 1; KEY_H4 = 0;
			KEY_L1 = 1; KEY_L2 = 1; KEY_L3 = 1; KEY_L4 = 1;
			break;			
		default:
			break;		
	}
	
	/* 巧妙利用定时器来隔断时间检测按键状态并保存值 */
	tmp_buf[pos][0] = (tmp_buf[pos][0] << 1) | KEY_L1;
	tmp_buf[pos][1] = (tmp_buf[pos][1] << 1) | KEY_L2;
	tmp_buf[pos][2] = (tmp_buf[pos][2] << 1) | KEY_L3;
	tmp_buf[pos][3] = (tmp_buf[pos][3] << 1) | KEY_L4;
	
	for(i=0; i<4; i++)
	{
		if((tmp_buf[pos][i] & 0x0F) == 0x00)
		{
			cur_state[pos][i] = PUSH;
		}
		else if((tmp_buf[pos][i] & 0x0F) == 0x0F)
		{
			cur_state[pos][i] = POP;
		}
	}
	
	/* 自加超过4就从0开始 */
	pos++;
	pos = pos & 0x03; 

	return;
}

/* 根据数值和对应的显示位数进行刷新数码管显示 */
void ScanDisplay(uchar num)
{
	static uchar tmp_num = 0;
	
	if(tmp_num > num)
	{
		tmp_num = 0;
	}
	
	switch(tmp_num)
	{
		case 0:
			Display_138(0);	break;
		case 1:
			Display_138(1);	break;	
		case 2:
			Display_138(2);	break;
		case 3:
			Display_138(3);	break;	
		case 4:
			Display_138(4);	break;
		case 5:
			Display_138(5);	break;	
		case 6:
			Display_138(6);	break;
		case 7:
			Display_138(7);	break;	
		default:
			break;					
	}
	
	tmp_num++; 
}

/* 根据函数参数配置定时时间 */
void ConfigTimer0(uint ms)
{
	uint tmp = 0;
	tmp = 65536 - (12/12)*ms*1000;
	
	TH0 = (tmp >> 8) & 0xFF;
	TL0 = tmp & 0xFF;

	return;
}

/* 初始化定时器中断模式 */
void InitTimer0(uint ms)
{
	TMOD = BIT_16_LOAD;
	ConfigTimer0(ms);
	TR0 = ON;
	ET0 = ON;
	EA = ON;
}

/*清屏函数 */
void CleanDisplay()
{
	num_138 = 0;
	SendByte(0);

	return;
}

/* 选择显示数码管并发送显示数据到数码管 */
void Display_138(unsigned char num)
{
	SendByte(0);		/* 必须清屏,不然显示有虚影 */
	A_138 = (ctl_138[num] >> 0) & 0x1;
	B_138 = (ctl_138[num] >> 1) & 0x1;
	C_138 = (ctl_138[num] >> 2) & 0x1;
	SendByte(val_tmp[num]);
}


/* 74HC595发送数据 */
void SendByte(uchar byte)
{
	uchar i;
	
	RC = 1;
	SC = 1;
	
	for(i=0; i<8; i++)
	{
		SC = 0;
		SE = (byte >> i) & 0x01;
		SC = 1;	
	}
	
	RC = 0;
	_nop_();
	_nop_();
	RC = 1;

	return;
}

单片机之按键消抖

阅读数 179

没有更多推荐了,返回首页