打印
[51单片机]

从单片机基础到程序框架(连载)

[复制链接]
楼主: jianhong_wu
手机看帖
扫描二维码
随时随地手机跟帖
321
jianhong_wu|  楼主 | 2017-10-29 10:57 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
本帖最后由 jianhong_wu 于 2017-10-29 11:49 编辑

第九十三节: 独立按键鼠标式的单击与双击。
第九十三节_pdf文件.pdf (111.12 KB)
【93.1   鼠标式的单击与双击。】




                上图93.1.1  独立按键电路


     
                上图93.1.2  LED电路

              
                上图93.1.3  有源蜂鸣器电路

        鼠标的左键,可以触发单击,也可以触发双击。双击的规则是这样的,两次单击,如果第1次单击与第2次单击的时间比较“短”的时候,则这两次单击就构成双击。编写这个程序的最大亮点是如何控制好第1次单击与第2次单击的时间间隔。程序例程要实现的功能是:(1)单击改变LED灯的显示状态,单击一次LED从原来“灭”的状态变成“亮”的状态,或者从原来“亮”的状态变成“灭”的状态,依次循环切换。(2)双击则蜂鸣器发出“嘀”的一声。代码如下:
#include "REG52.H"  

#define KEY_VOICE_TIME   50     //按键触发后发出的声音长度  
#define KEY_FILTER_TIME  25     //按键滤波的“稳定时间”25ms
#define KEY_INTERVAL_TIME  250  //连续两次单击之间的最大有效时间250ms

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void BeepOpen(void);   
void BeepClose(void);
void LedOpen(void);   
void LedClose(void);

void VoiceScan(void);
void KeyScan(void);    //按键识别的驱动函数,放在定时中断里
void SingleKeyTask(void);   //单击按键任务函数,放在主函数内
void DoubleKeyTask(void);   //双击按键任务函数,放在主函数内

sbit P3_4=P3^4;       //蜂鸣器
sbit P1_4=P1^4;       //LED

sbit KEY_INPUT1=P2^2;  //K1按键识别的输入口。

volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  

unsigned char Gu8LedStatus=0; //记录LED灯的状态,0代表灭,1代表亮
volatile unsigned char vGu8SingleKeySec=0;  //单击按键的触发序号
volatile unsigned char vGu8DoubleKeySec=0;  //双击按键的触发序号

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
   SingleKeyTask();    //单击按键任务函数
   DoubleKeyTask();    //双击按键任务函数
    }
}

void T0_time() interrupt 1     
{
VoiceScan();  
KeyScan();    //按键识别的驱动函数

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{
/* 注释一:
* 把LED的初始化放在PeripheralInitial而不是放在SystemInitial,是因为LED显示内容对上电
* 瞬间的要求不高。但是,如果是控制继电器,则应该把继电器的输出初始化放在SystemInitial。
*/

    //根据Gu8LedStatus的值来初始化LED当前的显示状态,0代表灭,1代表亮
if(0==Gu8LedStatus)
{
    LedClose();  //关闭LED
}
else
{
    LedOpen();   //点亮LED
}
}

void BeepOpen(void)
{
P3_4=0;  
}

void BeepClose(void)
{
P3_4=1;  
}
void LedOpen(void)
{
P1_4=0;  
}

void LedClose(void)
{
P1_4=1;  
}

void VoiceScan(void)
{

          static unsigned char Su8Lock=0;  

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;  
BeepOpen();
     }
    else  
{     

                       vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;     
BeepClose();  
                   }

}
          }         
}

/* 注释二:
* 双击按键扫描的详细过程:
* 第一步:平时没有按键被触发时,按键的自锁标志,去抖动延时计数器一直被清零。
*         如果之前已经有按键触发过1次单击,那么启动时间间隔计数器Su16KeyIntervalCnt1,
*         在KEY_INTERVAL_TIME这个允许的时间差范围内,如果一直没有第2次单击触发,
*         则把累加按键触发的次数Su8KeyTouchCnt1也清零,上一次累计的单击数被清零,
*         就意味着下一次新的双击必须重新开始累加两次单击数。
* 第二步:一旦有按键被按下,去抖动延时计数器开始在定时中断函数里累加,在还没累加到
*         阀值KEY_FILTER_TIME时,如果在这期间由于受外界干扰或者按键抖动,而使
*         IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16KeyTimeCnt1
*         清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰,以后凡是用到开关感应器的时候,
*         都可以用类似这样的方法去干扰。
* 第三步:如果按键按下的时间超过了阀值KEY_FILTER_TIME,马上把自锁标志Su8KeyLock1置1,
*         防止按住按键不松手后一直触发。与此同时,累加1次按键次数,如果按键次数累加有2次,
*         则认为触发双击按键,并把编号vGu8DoubleKeySec赋值。
* 第四步:等按键松开后,自锁标志Su8KeyLock1及时清零解锁,为下一次自锁做准备。并且累加间隔时间,
*         防止两次按键的间隔时间太长。如果连续2次单击的间隔时间太长达到了KEY_INTERVAL_TIME
*         的长度,立即清零当前按键次数的计数器,这样意味着上一次的累加单击数无效,下一次双击
*         必须重新累加新的单击数。
*/

void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
{
   static unsigned char Su8KeyLock1;           //1号按键的自锁
   static unsigned int  Su16KeyCnt1;           //1号按键的计时器
   static unsigned char Su8KeyTouchCnt1;       //1号按键的次数记录
   static unsigned int  Su16KeyIntervalCnt1;   //1号按键的间隔时间计数器

   //1号按键
   if(0!=KEY_INPUT1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
   {
       Su8KeyLock1=0; //按键解锁
       Su16KeyCnt1=0; //按键去抖动延时计数器清零,此行非常巧妙。      
       if(Su8KeyTouchCnt1>=1) //之前已经有按键触发过一次,再来一次就构成双击
       {
           Su16KeyIntervalCnt1++; //按键间隔的时间计数器累加
           if(Su16KeyIntervalCnt1>=KEY_INTERVAL_TIME) //达到最大允许的间隔时间
           {
               Su16KeyIntervalCnt1=0; //时间计数器清零
               Su8KeyTouchCnt1=0;     //清零按键的按下的次数
           }
       }
   }
   else if(0==Su8KeyLock1)//有按键按下,且是第一次被按下。此行如有疑问,请看第92节的讲解。
   {
      Su16KeyCnt1++; //累加定时中断次数
      if(Su16KeyCnt1>=KEY_FILTER_TIME) //滤波的“稳定时间”KEY_FILTER_TIME,长度是25ms。
      {
         Su8KeyLock1=1;  //按键的自锁,避免一直触发
         Su16KeyIntervalCnt1=0;   //按键有效间隔的时间计数器清零
         Su8KeyTouchCnt1++;       //记录当前单击的次数
         if(1==Su8KeyTouchCnt1)   //只按了1次
         {
              vGu8SingleKeySec=1;     //单击任务
         }
         else if(Su8KeyTouchCnt1>=2)  //连续按了两次以上
         {
              Su8KeyTouchCnt1=0;    //统计按键次数清零
              vGu8SingleKeySec=1;   //单击任务
vGu8DoubleKeySec=1;   //双击任务
         }
      }
   }

}

void SingleKeyTask(void)    //单击按键任务函数,放在主函数内
{
if(0==vGu8SingleKeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}

switch(vGu8SingleKeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:     //单击任务
        //通过Gu8LedStatus的状态切换,来反复切换LED的“灭”与“亮”的状态
        if(0==Gu8LedStatus)
        {
            Gu8LedStatus=1; //标识并且更改当前LED灯的状态。0就变成1。
            LedOpen();   //点亮LED
}
        else
        {
            Gu8LedStatus=0; //标识并且更改当前LED灯的状态。1就变成0。
            LedClose();  //关闭LED
}

vGu8SingleKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
}

}

void DoubleKeyTask(void)    //双击按键任务函数,放在主函数内
{
if(0==vGu8DoubleKeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}

switch(vGu8DoubleKeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:     //双击任务

        vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发双击后,发出“嘀”一声
        vGu8BeepTimerFlag=1;  
vGu8DoubleKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
}

}



使用特权

评论回复
322
wangxueq| | 2017-10-31 22:24 | 只看该作者
鼓励 加油!

使用特权

评论回复
323
jianhong_wu|  楼主 | 2017-11-5 11:24 | 只看该作者
第九十四节: 两个独立按键构成的组合按键。
第九十四节_pdf文件.pdf (109.85 KB)
【94.1   组合按键。】




                上图94.1.1  独立按键电路


  
                上图94.1.2  LED电路

              
                上图94.1.3  有源蜂鸣器电路

        组合按键的触发,是指两个按键同时按下时的“非单击”触发。一次组合按键的产生,必然包含了三类按键的触发。比如,K1与K2两个独立按键,当它们产生一次组合按键的操作时,就包含了三类触发:K1单击触发,K2单击触发,K1与K2的组合触发。这三类触发可以看作是底层的按键驱动程序,在按键应用层的任务函数SingleKeyTask和CombinationKeyTask中,可以根据项目的实际需要进行响应。本节程序例程要实现的功能是:(1)K1单击让LED变成“亮”的状态。(2)K2单击让LED变成“灭”的状态。(3)K1与K2的组合按键触发让蜂鸣器发出“嘀”的一声。代码如下:

#include "REG52.H"  

#define KEY_VOICE_TIME   50     //组合按键触发后发出的声音长度  
#define KEY_FILTER_TIME  25     //按键滤波的“稳定时间”25ms

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void BeepOpen(void);   
void BeepClose(void);
void LedOpen(void);   
void LedClose(void);

void VoiceScan(void);
void KeyScan(void);    //按键识别的驱动函数,放在定时中断里
void SingleKeyTask(void);   //单击按键任务函数,放在主函数内
void CombinationKeyTask(void);   //组合按键任务函数,放在主函数内

sbit P3_4=P3^4;       //蜂鸣器
sbit P1_4=P1^4;       //LED

sbit KEY_INPUT1=P2^2;  //K1按键识别的输入口。
sbit KEY_INPUT2=P2^1;  //K2按键识别的输入口。

volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  

volatile unsigned char vGu8SingleKeySec=0;  //单击按键的触发序号
volatile unsigned char vGu8CombinationKeySec=0;  //组合按键的触发序号

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
   CombinationKeyTask();  //组合按键任务函数
   SingleKeyTask();    //单击按键任务函数
    }
}

void T0_time() interrupt 1     
{
VoiceScan();  
KeyScan();    //按键识别的驱动函数

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{
LedClose();  //初始化关闭LED
}

void BeepOpen(void)
{
P3_4=0;  
}

void BeepClose(void)
{
P3_4=1;  
}
void LedOpen(void)
{
P1_4=0;  
}

void LedClose(void)
{
P1_4=1;  
}

void VoiceScan(void)
{

          static unsigned char Su8Lock=0;  

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;  
BeepOpen();
     }
    else  
{     

                       vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;     
BeepClose();  
                   }

}
          }         
}


/* 注释一:
* 组合按键扫描的详细过程:
* 第一步:平时只要K1与K2两个按键中有一个没有被按下时,按键的自锁标志,去抖动延时计数器
* 一直被清零。
* 第二步:一旦两个按键都处于被按下的状态,去抖动延时计数器开始在定时中断函数里累加,在还没
*         累加到阀值KEY_FILTER_TIME时,如果在这期间由于受外界干扰或者按键抖动,而使其中一个
*         IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16CombinationKeyTimeCnt
*         清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。
* 第三步:如果两个按键按下的时间超过了阀值KEY_FILTER_TIME,马上把自锁标志Su8CombinationKeyLock
*         置1,防止按住两个按键不松手后一直触发。并把按键编号vGu8CombinationKeySec赋值,
*         触发一次组合按键。
* 第四步:等其中一个按键松开后,自锁标志Su8CombinationKeyLock及时清零,为下一次自锁做准备。
*/

void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
{
   static unsigned char Su8KeyLock1;        
   static unsigned int  Su16KeyCnt1;         
   static unsigned char Su8KeyLock2;         
   static unsigned int  Su16KeyCnt2;           

   static unsigned char Su8CombinationKeyLock;  //组合按键的自锁
   static unsigned int  Su16CombinationKeyCnt;  //组合按键的计时器


   //K1按键与K2按键的组合触发
   if(0!=KEY_INPUT1||0!=KEY_INPUT2)//两个按键只要有一个按键没有按下,处于“非组合按键”的状态。
   {
      Su8CombinationKeyLock=0; //组合按键解锁
      Su16CombinationKeyCnt=0;  //组合按键去抖动延时计数器清零,此行非常巧妙,是全场的亮点。      
   }
   else if(0==Su8CombinationKeyLock)//两个按键被同时按下,且是第一次被按下。此行请看专题分析。
   {
      Su16CombinationKeyCnt++; //累加定时中断次数
      if(Su16CombinationKeyCnt>=KEY_FILTER_TIME) //滤波的“稳定时间”KEY_FILTER_TIME。
      {
         Su8CombinationKeyLock=1;  //组合按键的自锁,避免一直触发
         vGu8CombinationKeySec=1;   //触发K1与K2的组合键操作
      }
   }

   //K1按键的单击
   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;      
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;
      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
         Su8KeyLock1=1;  
         vGu8SingleKeySec=1;    //触发K1的单击键
      }
   }

   //K2按键的单击
   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;  
         vGu8SingleKeySec=2;    //触发K2的单击键
      }
   }

}

void CombinationKeyTask(void)    //组合按键任务函数,放在主函数内
{
if(0==vGu8CombinationKeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}

switch(vGu8CombinationKeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:     //K1与K2的组合按键任务

        vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发一次组合按键后,发出“嘀”一声
        vGu8BeepTimerFlag=1;  
vGu8CombinationKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
}

}

void SingleKeyTask(void)    //单击按键任务函数,放在主函数内
{
if(0==vGu8SingleKeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}

switch(vGu8SingleKeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:     //K1单击任务
        LedOpen();    //LED亮

vGu8SingleKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;

   case 2:     //K2单击任务
        LedClose();   //LED灭

vGu8SingleKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;

}

}


【94.2   专题分析:else if(0==Su8CombinationKeyLock)。】

        疑问:
   if(0!=KEY_INPUT1||0!=KEY_INPUT2)
   {
      Su8CombinationKeyLock=0;
      Su16CombinationKeyCnt=0;      
   }
   else if(0==Su8CombinationKeyLock)//两个按键被同时按下,且是第一次被按下。为什么?
   {
      Su16CombinationKeyCnt++;
      if(Su16CombinationKeyCnt>=KEY_FILTER_TIME)
      {
         Su8CombinationKeyLock=1;  
         vGu8CombinationKeySec=1;   
      }
   }


       解答:
       首先,我们要明白C语言的语法中,
if(条件1)
{

}
else if(条件2)
{

}

       以上语句是一对组合语句,不能分开来看。当(条件1)成立的时候,它是绝对不会判断(条件2)的。当(条件1)不成立的时候,才会判断(条件2)。
       回到刚才的问题,当程序执行到(条件2) else if(0==Su8CombinationKeyLock)的时候,就已经默认了(条件1) if(0!=KEY_INPUT1||0!=KEY_INPUT2)不成立,这个条件不成立,就意味着0==KEY_INPUT1和0==KEY_INPUT2,也就是有两个按键被同时按下,因此,这里的else if(0==Su8CombinationKeyLock)等效于else if(0==Su8CombinationKeyLock&&0==KEY_INPUT1&&0==KEY_INPUT2),而Su8CombinationKeyLock是一个自锁标志位,一旦组合按键被触发后,这个标志位会变1,防止两个按键按住不松手的时候不断触发组合按键。这样,组合按键只能同时按下一次触发一次,任意松开其中一个按键后再同时按下一次两个按键,又触发一次新的组合按键。

使用特权

评论回复
324
jianhong_wu|  楼主 | 2017-11-12 15:31 | 只看该作者
第九十五节: 两个独立按键的“电脑键盘式”组合按键。
第九十五节_pdf文件.pdf (106.57 KB)
【95.1   “电脑键盘式”组合按键。】




                上图95.1.1  独立按键电路



                上图95.1.2  LED电路

               
                上图95.1.3  有源蜂鸣器电路

        上一节也讲了由K1和K2构成的组合按键,但是这种组合按键是普通的组合按键,因为它们的K1和K2是不分先后顺序的,你先按住K1然后再按K2,或者你先按住K2然后再按K1,效果都一样。本节讲的组合按键是分先后顺序的,比如,像电脑的复制快捷键(Ctrl+C),你必须先按住Ctrl再按住C此时“复制快捷键”才有效,如果你先按住C再按住Ctrl此时“复制快捷键”无效。本节讲的例程就是要实现这个功能,用K1代表C这类“字符数字键”,用K2代表Ctrl这类“辅助按键”,因此,要触发组合键(K2+K1),必须先按住K2再按K1才有效。本节讲的例程功能如下:(1)K1每单击一次,LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(2)如果先按住K2再按K1,就认为构造了“电脑键盘式”组合键,蜂鸣器发出“嘀”的一声。代码如下:
#include "REG52.H"  

#define KEY_VOICE_TIME   50     //组合按键触发后发出的声音长度 50ms  
#define KEY_FILTER_TIME  25     //按键滤波的“稳定时间”25ms

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void BeepOpen(void);   
void BeepClose(void);
void LedOpen(void);   
void LedClose(void);

void VoiceScan(void);
void KeyScan(void);    //按键识别的驱动函数,放在定时中断里
void SingleKeyTask(void);   //单击按键任务函数,放在主函数内
void CombinationKeyTask(void);   //组合按键任务函数,放在主函数内

sbit P3_4=P3^4;       //蜂鸣器
sbit P1_4=P1^4;       //LED

sbit KEY_INPUT1=P2^2;  //K1按键识别的输入口。
sbit KEY_INPUT2=P2^1;  //K2按键识别的输入口。

volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  

unsigned char Gu8LedStatus=0; //记录LED灯的状态,0代表灭,1代表亮

volatile unsigned char vGu8SingleKeySec=0;  //单击按键的触发序号
volatile unsigned char vGu8CombinationKeySec=0;  //组合按键的触发序号

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
   CombinationKeyTask();  //组合按键任务函数
   SingleKeyTask();       //单击按键任务函数
    }
}

void T0_time() interrupt 1     
{
VoiceScan();  
KeyScan();    //按键识别的驱动函数

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{
if(0==Gu8LedStatus)
{
LedClose();  
}
else
{
LedOpen();  
}
}

void BeepOpen(void)
{
P3_4=0;  
}

void BeepClose(void)
{
P3_4=1;  
}
void LedOpen(void)
{
P1_4=0;  
}

void LedClose(void)
{
P1_4=1;  
}

void VoiceScan(void)
{

          static unsigned char Su8Lock=0;  

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;  
BeepOpen();
     }
    else  
{     

                       vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;     
BeepClose();  
                   }

}
          }         
}


/* 注释一:
* “电脑键盘式”组合按键扫描的详细过程:
* 第一步:K2与K1构成的组合按键触发是融合在K1单击按键程序里的,只需稍微更改一下K1单击的程序
*         ,就可以兼容到K2与K1构成的“电脑键盘式”组合按键。平时只要K1没有被按下时,按
*         键的自锁标志Su8KeyLock1和去抖动延时计数器Su16KeyCnt1一直被清零。
* 第二步:一旦K1按键被按下,去抖动延时计数器Su16KeyCnt1开始在定时中断函数里累加,在还没
*         累加到阀值KEY_FILTER_TIME时,如果在这期间由于受外界干扰或者按键抖动,而使
*         IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16KeyCnt1清零了,
*         这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。
* 第三步:如果K1按键按下的时间超过了阀值KEY_FILTER_TIME,马上把自锁标志Su8KeyLock1置1,
*         防止按住按键不松手后一直触发,此时才开始判断一次K2按键的电平状态,如果K2为低电
*         平就认为是组合按键,并给按键编号vGu8CombinationKeySec赋值,否则,就认为是K1的单击
*         按键,并给按键编号vGu8SingleKeySec赋值。
* 第四步:等K1按键松开后,自锁标志Su8KeyLock1及时清零,为下一次自锁做准备。
*/

void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
{
   static unsigned char Su8KeyLock1;        
   static unsigned int  Su16KeyCnt1;         


   //K1的单击,或者K2与K1构成的“电脑键盘式组合按键”。
   if(0!=KEY_INPUT1)//单个K1按键没有按下,及时清零一些标志。
   {
      Su8KeyLock1=0; //按键解锁
      Su16KeyCnt1=0;  //去抖动延时计数器清零,此行非常巧妙,是全场的亮点。      
   }
   else if(0==Su8KeyLock1)//单个按键K1被按下
   {
      Su16KeyCnt1++; //累加定时中断次数
      if(Su16KeyCnt1>=KEY_FILTER_TIME) //滤波的“稳定时间”KEY_FILTER_TIME。
      {
         if(0==KEY_INPUT2)  //此时才开始判断一次K2的电平状态,为低电平则是组合按键。
         {
            Su8KeyLock1=1;  
            vGu8CombinationKeySec=1;  //组合按键的触发
}
else   
         {
            Su8KeyLock1=1;  
            vGu8SingleKeySec=1;    //K1单击按键的触发
}
      }
   }
}

void CombinationKeyTask(void)    //组合按键任务函数,放在主函数内
{
if(0==vGu8CombinationKeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}

switch(vGu8CombinationKeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:     //K1与K2的组合按键任务

        vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发一次组合按键后,发出“嘀”一声
        vGu8BeepTimerFlag=1;  
vGu8CombinationKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
}

}

void SingleKeyTask(void)    //单击按键任务函数,放在主函数内
{
if(0==vGu8SingleKeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}

switch(vGu8SingleKeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:     //K1单击任务
if(0==Gu8LedStatus)
{
Gu8LedStatus=1;
LedOpen();    //LED亮   
}
else
{
Gu8LedStatus=0;
LedClose();    //LED灭   
}

vGu8SingleKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;
}

}



使用特权

评论回复
评论
龙魔123 2018-12-8 21:36 回复TA
应该在KeyScan()函数中在加一个K2是在K1按下前或者按下后按下的一个状态变量来进行判断。 
龙魔123 2018-12-8 21:21 回复TA
此处代码如果是K2先提前按下,在按K1,蜂鸣器也是会响的 
评分
参与人数 1威望 +1 收起 理由
龙魔123 + 1 很给力!
325
jianhong_wu|  楼主 | 2017-11-26 11:40 | 只看该作者
第九十七节: 独立按键按住不松手的连续均匀触发。
第九十七节_pdf文件.pdf (117.81 KB)
【97.1   按住不松手的连续均匀触发。】




                上图97.1.1  独立按键电路


                 
                上图97.1.2  灌入式驱动8个LED

               
                上图97.1.3  有源蜂鸣器电路

        在电脑上删除某个文件某行文字的时候,单击一次“退格按键[Backspace]”,就删除一个文字,如果按住“退格按键[Backspace]”不松手,就会“连续均匀”的触发“删除”的功能,自动逐个把整行文字删除清空,这就是“按住不松手的连续均匀触发”应用案例之一。除此之外,在很多需要人机交互的项目中都有这样的功能,为了快速加减某个数值,按住某个按键不松手,某个数值有节奏地快速往上加或者快速往下减。这种“按住不松手连续均匀触发”的按键识别,在程序上有“3个时间”需要留意,第1个是按键单击的“滤波”时间,第2个是按键“从单击进入连击”的间隔时间(此时间是“单击”与“连击”的分界线),第3个是按键“连击”的间隔时间,
        本节例程实现的功能如下:(1)8个受按键控制的跑马灯在某一时刻只有1个LED亮,每触发一次K1按键,“亮的LED”就“往左边跑一步”;相反,每触发一次K2按键,“亮的LED”就“往右边跑一步”。如果按住K1或者K2不松手就连续触发,“亮的LED”就“连续跑”,一直跑到左边或者右边的尽头。(2)按键每“单击”一次蜂鸣器就鸣叫一次,但是,当按键“从单击进入连击”后,蜂鸣器就不鸣叫。

#include "REG52.H"  

#define KEY_VOICE_TIME   50     

#define KEY_SHORT_TIME  25      //按键单击的“滤波”时间25ms
#define KEY_ENTER_CONTINUITY_TIME    300  //按键“从单击进入连击”的间隔时间300ms
#define KEY_CONTINUITY_TIME    80  //按键“连击”的间隔时间80ms

#define BUS_P0    P0     //8个LED灯一一对应单片机的P0口总线

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void BeepOpen(void);   
void BeepClose(void);

void VoiceScan(void);
void KeyScan(void);   
void KeyTask(void);   
void DisplayTask(void);   //显示的任务函数(LED显示状态)

sbit P3_4=P3^4;       //蜂鸣器

sbit KEY_INPUT1=P2^2;  //K1按键识别的输入口。
sbit KEY_INPUT2=P2^1;  //K2按键识别的输入口。

volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  

unsigned char Gu8LedStatus=0; //LED灯的状态
unsigned char Gu8DisplayUpdate=1; //显示的刷新标志

volatile unsigned char vGu8KeySec=0;  //按键的触发序号
volatile unsigned char vGu8ShieldVoiceFlag=0;  //屏蔽声音的标志

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
    KeyTask();       //按键的任务函数
DisplayTask();   //显示的任务函数(LED显示状态)
    }
}


/* 注释一:
* Gu8DisplayUpdate这类“显示刷新变量”在“显示框架”里是很常见的,而且屡用屡爽。
* 目的是,既能及时刷新显示,又能避免主函数“不断去执行显示代码”而影响程序效率。
*/

void DisplayTask(void)   //显示的任务函数(LED显示状态)
{
if(1==Gu8DisplayUpdate)  //需要刷新一次显示
{
Gu8DisplayUpdate=0;  //及时清零,避免主函数“不断去执行显示代码”而影响程序效率

//Gu8LedStatus是左移的位数,范围(0至7),决定了跑马灯的显示状态。
BUS_P0=~(1<<Gu8LedStatus);  //“左移<<”之后的“取反~”,因为LED电路是灌入式驱动方式。
}
}

/* 注释二:
* 按键“连续均匀触发”的识别过程:
* 第一步:平时只要K1没有被按下,按键的自锁标志Su8KeyLock1、去抖动延时计数器Su16KeyCnt1、
*         连击计数器Su16KeyContinuityCnt1,一直被清零。
* 第二步:一旦K1按键被按下,去抖动延时计数器Su16KeyCnt1开始在定时中断函数里累加,在还没
*         累加到阀值KEY_SHORT_TIME时,如果在这期间由于受外界干扰或者按键抖动,
*         而使IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16KeyCnt1清零,
*         这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。
* 第三步:如果K1按键按下的时间超过了阀值KEY_SHORT_TIME,则触发一次“单击”, 同时,马上把自锁
*         标志Su8KeyLock1置1防止按住按键不松手后一直触发,并且把计数器Su16KeyCnt1清零为了下
*         一步用来累加“从单击进入连击的间隔时间1000ms”。如果此时还没有松手,直到发现按下的时
*         间超过“从单击进入连击的间隔时间”阀值KEY_ENTER_CONTINUITY_TIME时,从此进入“连击”
*         的模式,连击计数器Su16KeyContinuityCnt1开始累加,每到达一次阀值
*         KEY_CONTINUITY_TIME就触发1次按键,为了屏蔽按键声音及时把vGu8ShieldVoiceFlag也置1,
*         同时,Su16KeyContinuityCnt1马上清零为继续连击作准备。
* 第四步:等K1按键松手后,自锁标志Su8KeyLock1、去抖动延时计数器Su16KeyCnt1、
*         连击计数器Su16KeyContinuityCnt1,及时清零,为下一次按键触发做准备。
*/

void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
{
   static unsigned char Su8KeyLock1;        
   static unsigned int  Su16KeyCnt1;  
   static unsigned int  Su16KeyContinuityCnt1;  //连击计数器

   static unsigned char Su8KeyLock2;        
   static unsigned int  Su16KeyCnt2;  
   static unsigned int  Su16KeyContinuityCnt2;  //连击计数器

   //K1按键
   if(0!=KEY_INPUT1)//单个K1按键没有按下,及时清零一些标志。
   {
      Su8KeyLock1=0; //按键解锁
      Su16KeyCnt1=0;  //去抖动延时计数器清零,此行非常巧妙,是全场的亮点。
Su16KeyContinuityCnt1=0;  //连击计数器  
   }
   else if(0==Su8KeyLock1)//单个按键K1被按下
   {
      Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms
      if(Su16KeyCnt1>=KEY_SHORT_TIME) //按键的“滤波”时间25ms
      {
            Su8KeyLock1=1;      //“自锁”
            vGu8KeySec=1;       //触发一次K1按键      
            Su16KeyCnt1=0;      //清零,为了下一步用来累加“从单击进入连击的间隔时间300ms”
      }
   }
   else if(Su16KeyCnt1<=KEY_ENTER_CONTINUITY_TIME)//按住不松手累加到300ms
   {
      Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms
   }
   else  //按住累加到300ms后仍然不放手,这个时候进入有节奏的连续触发
   {
       Su16KeyContinuityCnt1++; //连击计数器开始累加,每一次累加额度是1ms
       if(Su16KeyContinuityCnt1>=KEY_CONTINUITY_TIME)  //按住没松手,每0.08秒就触发一次
       {
            Su16KeyContinuityCnt1=0; //清零,为了继续连击。
            vGu8KeySec=1;       //触发一次K1按键   
vGu8ShieldVoiceFlag=1;  //把当前按键触发的声音屏蔽掉
       }

  }

   //K2按键
   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;  
Su16KeyContinuityCnt2=0;   
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_SHORT_TIME)
      {
            Su8KeyLock2=1;     
            vGu8KeySec=2;       //触发一次K2按键      
            Su16KeyCnt2=0;      
      }
   }
   else if(Su16KeyCnt2<=KEY_ENTER_CONTINUITY_TIME)
   {
      Su16KeyCnt2++;
   }
   else
   {
       Su16KeyContinuityCnt2++;
       if(Su16KeyContinuityCnt2>=KEY_CONTINUITY_TIME)  
       {
            Su16KeyContinuityCnt2=0;
            vGu8KeySec=2;       //触发一次K2按键   
vGu8ShieldVoiceFlag=1;  //把当前按键触发的声音屏蔽掉
       }

  }
}

void KeyTask(void)    //按键任务函数,放在主函数内
{
if(0==vGu8KeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}

switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:     //K1触发的任务
        if(Gu8LedStatus>0)
{
Gu8LedStatus--;  //控制LED“往左边跑”
Gu8DisplayUpdate=1;  //刷新显示
}

        if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
{
                vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发一次“长按”后,发出“嘀”一声
                vGu8BeepTimerFlag=1;  
            }

vGu8ShieldVoiceFlag=0;  //及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;

   case 2:     //K2触发的任务
        if(Gu8LedStatus<7)
{
Gu8LedStatus++;  //控制LED“往右边跑”
Gu8DisplayUpdate=1;  //刷新显示
}

        if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
{
                vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发一次“长按”后,发出“嘀”一声
                vGu8BeepTimerFlag=1;  
            }

vGu8ShieldVoiceFlag=0;  //及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;

}
}



void T0_time() interrupt 1     
{
VoiceScan();  
KeyScan();    //按键识别的驱动函数

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}

void BeepOpen(void)
{
P3_4=0;  
}

void BeepClose(void)
{
P3_4=1;  
}

void VoiceScan(void)
{

          static unsigned char Su8Lock=0;  

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;  
BeepOpen();
     }
    else  
{     

                       vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;     
BeepClose();  
                   }

}
          }         
}


使用特权

评论回复
326
一路向北lm| | 2017-11-26 12:42 | 只看该作者
感谢分享,这么全的资料,大家有福了。

使用特权

评论回复
327
arima| | 2017-11-26 23:26 | 只看该作者
看着这么详细的资料,也是一种享受....

持之以恒,佩服!!!!

加油!!!!

使用特权

评论回复
328
jianhong_wu|  楼主 | 2017-12-7 11:40 | 只看该作者
第九十八节: 独立按键按住不松手的“先加速后匀速”的触发。
第九十八节_pdf文件.pdf (119.05 KB)
【98.1   “先加速后匀速”的触发。】




                上图98.1.1  独立按键电路


                 
                上图98.1.2  灌入式驱动8个LED

               
                上图98.1.3  有源蜂鸣器电路

        当“连续加”或者“连续减”的数据范围很大的时候,就需要按键的加速与匀速相结合的触发方式。“加速”是指按住按键不松手,按键刚开始触发是从慢到快的渐进过程,当“加速”到某个特别快的速度的时候,就“不再加速”,而是以该“恒定高速”进行“连续匀速”触发。这种触发方式,“加速”和“匀速”是相辅相成缺一不可的,为什么?假如没有“加速”只有“匀速”,那么刚按下按键就直接以最高速的“匀速”进行,就会跑过头,缺乏微调功能;而假如没有“匀速”只有“加速”,那么按下按键不松手后,速度就会一直不断飙升,最后失控过冲。
        本节例程实现的功能如下:
       (1)要更改一个“设置参数”(一个全局变量),参数的范围是0到800。
       (2)8个受“设置参数”控制的跑马灯在某一时刻只有1个LED亮,每触发一次K1按键,该“设置参数”就自减1,最小值为0;相反,每触发一次K2按键,该“设置参数”就自加1,最大值为800。
       (3)LED灯实时显示“设置参数”的范围状态:
                只有第0个LED灯亮:0<=“设置参数”<100。
                只有第1个LED灯亮:100<=“设置参数”<200。
                只有第2个LED灯亮:200<=“设置参数”<300。
                只有第3个LED灯亮:300<=“设置参数”<400。
                只有第4个LED灯亮:400<=“设置参数”<500。
                只有第5个LED灯亮:500<=“设置参数”<600。
                只有第6个LED灯亮:600<=“设置参数”<700。
                只有第7个LED灯亮:700<=“设置参数”<=800。
       (4)按键每“单击”一次蜂鸣器就鸣叫一次,但是,当按键“从单击进入连击”后,蜂鸣器就不鸣叫。

#include "REG52.H"  

#define KEY_VOICE_TIME   50     

#define KEY_SHORT_TIME  25      //按键单击的“滤波”时间
#define KEY_ENTER_CONTINUITY_TIME    300  //按键“从单击进入连击”的间隔时间
#define KEY_CONTINUITY_INITIAL_TIME    80  //按键“连击”起始的预设间隔时间
#define KEY_SUB_DT_TIME     8      //按键在“加速”时每次减小的时间。
#define KEY_CONTINUITY_MIN_TIME   10  //按键时间减小到最后的“匀速”间隔时间。


#define BUS_P0    P0     //8个LED灯一一对应单片机的P0口总线

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void BeepOpen(void);   
void BeepClose(void);

void VoiceScan(void);
void KeyScan(void);   
void KeyTask(void);   
void DisplayTask(void);   //显示的任务函数(LED显示状态)

sbit P3_4=P3^4;       //蜂鸣器

sbit KEY_INPUT1=P2^2;  //K1按键识别的输入口。
sbit KEY_INPUT2=P2^1;  //K2按键识别的输入口。

volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  

unsigned int Gu16SetData=0; //“设置参数”。范围从0到800。LED灯反映该当前值的范围状态
unsigned char Gu8DisplayUpdate=1; //显示的刷新标志

volatile unsigned char vGu8KeySec=0;  //按键的触发序号
volatile unsigned char vGu8ShieldVoiceFlag=0;  //屏蔽声音的标志

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
    KeyTask();       //按键的任务函数
DisplayTask();   //显示的任务函数(LED显示状态)
    }
}


/* 注释一:
* Gu8DisplayUpdate这类“显示刷新变量”在“显示框架”里是很常见的,而且屡用屡爽。
* 目的是,既能及时刷新显示,又能避免主函数“不断去执行显示代码”而影响程序效率。
*/
void DisplayTask(void)   //显示的任务函数(LED显示状态)
{
if(1==Gu8DisplayUpdate)  //需要刷新一次显示
{
Gu8DisplayUpdate=0;  //及时清零,避免主函数“不断去执行显示代码”而影响程序效率

        if(Gu16SetData<100)
{
BUS_P0=~(1<<0);  //第0个灯亮
}
        else if(Gu16SetData<200)
{
BUS_P0=~(1<<1);  //第1个灯亮
}
        else if(Gu16SetData<300)
{
BUS_P0=~(1<<2);  //第2个灯亮
}
        else if(Gu16SetData<400)
{
BUS_P0=~(1<<3);  //第3个灯亮
}
        else if(Gu16SetData<500)
{
BUS_P0=~(1<<4);  //第4个灯亮
}
        else if(Gu16SetData<600)
{
BUS_P0=~(1<<5);  //第5个灯亮
}
        else if(Gu16SetData<700)
{
BUS_P0=~(1<<6);  //第6个灯亮
}
        else
{
BUS_P0=~(1<<7);  //第7个灯亮
}
}
}

/* 注释二:
* 按键“先加速后匀速”的识别过程:
* 第一步:每次按一次就触发一次“单击”,如果按下去到松手的时间不超过1秒,则不会进入
*        “连击”模式。
* 第二步:如果按下去不松手的时间超过1秒,则进入“连击”模式。按键触发的节奏
*         不断加快,直至到达某个极限值,然后以此极限值间隔匀速触发。这就是“先加速后匀速”。
*/

void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
{
   static unsigned char Su8KeyLock1;        
   static unsigned int  Su16KeyCnt1;  
   static unsigned int  Su16KeyContinuityCnt1;  //连击计数器
   static unsigned int  Su16KeyContinuityTime1=KEY_CONTINUITY_INITIAL_TIME;  //动态时间阀值

   static unsigned char Su8KeyLock2;        
   static unsigned int  Su16KeyCnt2;  
   static unsigned int  Su16KeyContinuityCnt2;  //连击计数器
   static unsigned int  Su16KeyContinuityTime2=KEY_CONTINUITY_INITIAL_TIME;  //动态时间阀值

   //K1按键
   if(0!=KEY_INPUT1)//单个K1按键没有按下,及时清零一些标志。
   {
      Su8KeyLock1=0; //按键解锁
      Su16KeyCnt1=0;  //去抖动延时计数器清零,此行非常巧妙,是全场的亮点。
Su16KeyContinuityCnt1=0;  //连击计数器  
Su16KeyContinuityTime1=KEY_CONTINUITY_INITIAL_TIME;  //动态时间阀值。重装初始值。
   }
   else if(0==Su8KeyLock1)//单个按键K1被按下
   {
      Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms
      if(Su16KeyCnt1>=KEY_SHORT_TIME) //按键的“滤波”时间25ms
      {
            Su8KeyLock1=1;      //“自锁”
            vGu8KeySec=1;       //触发一次K1按键      
            Su16KeyCnt1=0;      //清零,为了下一步用来累加“从单击进入连击的间隔时间300ms”
      }
   }
   else if(Su16KeyCnt1<=KEY_ENTER_CONTINUITY_TIME)//按住不松手累加到300ms
   {
      Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms
   }
   else  //按住累加到300ms后仍然不放手,这个时候进入有节奏的连续触发
   {
       Su16KeyContinuityCnt1++; //连击计数器开始累加,每一次累加额度是1ms
       if(Su16KeyContinuityCnt1>=Su16KeyContinuityTime1)  //按住没松手,每隔一会就触发一次
       {
            Su16KeyContinuityCnt1=0; //清零,为了继续连击。
            vGu8KeySec=1;       //触发一次K1按键   
vGu8ShieldVoiceFlag=1;  //把当前按键触发的声音屏蔽掉
if(Su16KeyContinuityTime1>=KEY_SUB_DT_TIME)
{
    //此数值不断被减小,按键的触发速度就不断变快
Su16KeyContinuityTime1=Su16KeyContinuityTime1-KEY_SUB_DT_TIME;//变快节奏
}

if(Su16KeyContinuityTime1<KEY_CONTINUITY_MIN_TIME) //最小间隔时间就是“高速匀速”
{
Su16KeyContinuityTime1=KEY_CONTINUITY_MIN_TIME; //最后以此最高速进行“匀速”
}

       }

  }

   //K2按键
   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;  
Su16KeyContinuityCnt2=0;
Su16KeyContinuityTime2=KEY_CONTINUITY_INITIAL_TIME;
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_SHORT_TIME)
      {
            Su8KeyLock2=1;      
            vGu8KeySec=2;       //触发一次K2按键      
            Su16KeyCnt2=0;           
      }
   }
   else if(Su16KeyCnt2<=KEY_ENTER_CONTINUITY_TIME)
   {
      Su16KeyCnt2++;
   }
   else
   {
       Su16KeyContinuityCnt2++;
       if(Su16KeyContinuityCnt2>=Su16KeyContinuityTime2)
       {
            Su16KeyContinuityCnt2=0;
            vGu8KeySec=2;       //触发一次K2按键   
vGu8ShieldVoiceFlag=1;
if(Su16KeyContinuityTime2>=KEY_SUB_DT_TIME)
{
Su16KeyContinuityTime2=Su16KeyContinuityTime2-KEY_SUB_DT_TIME;
}

if(Su16KeyContinuityTime2<KEY_CONTINUITY_MIN_TIME)
{
Su16KeyContinuityTime2=KEY_CONTINUITY_MIN_TIME;
}
       }
  }
}

void KeyTask(void)    //按键任务函数,放在主函数内
{
if(0==vGu8KeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}

switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:     //K1触发的任务
        if(Gu16SetData>0)
{
Gu16SetData--;     //“设置参数”
Gu8DisplayUpdate=1;  //刷新显示
}

        if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
{
                vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
                vGu8BeepTimerFlag=1;  
            }

vGu8ShieldVoiceFlag=0;  //及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;

   case 2:     //K2触发的任务
        if(Gu16SetData<800)
{
Gu16SetData++;       //“设置参数”
Gu8DisplayUpdate=1;  //刷新显示
}

        if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
{
                vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
                vGu8BeepTimerFlag=1;  
            }

vGu8ShieldVoiceFlag=0;  //及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
break;

}
}



void T0_time() interrupt 1     
{
VoiceScan();  
KeyScan();    //按键识别的驱动函数

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}

void BeepOpen(void)
{
P3_4=0;  
}

void BeepClose(void)
{
P3_4=1;  
}

void VoiceScan(void)
{

          static unsigned char Su8Lock=0;  

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;  
BeepOpen();
     }
    else  
{     

                       vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;     
BeepClose();  
                   }

}
          }         
}


使用特权

评论回复
329
一路向北lm| | 2017-12-7 11:54 | 只看该作者
**分享

使用特权

评论回复
330
赵成玉1| | 2017-12-8 08:22 | 只看该作者
先赞一下再说

使用特权

评论回复
331
as791026747| | 2017-12-8 15:57 | 只看该作者
厉害厉害

使用特权

评论回复
332
家长不监护| | 2017-12-9 14:00 | 只看该作者
embassy 发表于 2016-10-8 13:59
很简单的 c语言, 被你用的多么多么牛叉的样子.  大部分在工作中根本不需要这么复杂,      c语言本身就是很 ...

对您可能没帮助,但是对别人也许是有帮助的。您也可以写一部集成芯片手册来,帮助大家。

使用特权

评论回复
333
jianhong_wu|  楼主 | 2017-12-10 11:15 | 只看该作者
第九十九节: “行列扫描式”矩阵按键的单个触发(原始版)。
第九十九节_pdf文件.pdf (117.38 KB)
【99.1   “行列扫描式”矩阵按键。】

                 
                上图99.1.1  有源蜂鸣器电路


         
                上图99.1.2  3*3矩阵按键的电路

       上图是3*3的矩阵按键电路,其它4*4或者8*8的矩阵电路原理是一样的,编程思路也是一样的。相对独立按键,矩阵按键因为采用动态行列扫描的方式,能更加节省IO口,比如3*3的3行3列,1行占用1根IO口,1列占用1根IO口,因此3*3矩阵按键占用6个IO口(3+3=6),但是能识别9个按键(3*3=9)。同理,8*8矩阵按键占用16个IO口(8+8=16),但是能识别64个按键(8*8=64)。
       矩阵按键的编程原理。如上图3*3矩阵按键的电路,行IO口(P2.2,P2.1,P2.0)定为输入,列IO口(P2.5,P2.4,P2.3)定为输出。同一时刻,列输出的3个IO口只能有1根是输出L(低电平),其它2根必须全是H(高电平),然后依次轮番切换输出状态,列输出每切换一次,就分别读取一次行输入的3个IO口,这样一次就能识别到3个按键的状态,如果列连续切换3次就可以读取全部9个按键的状态。列的3种输出状态分别是:(P2.5为L,P2.4为H,P2.3为H),(P2.5为H,P2.4为L,P2.3为H),(P2.5为H,P2.4为H,P2.3为L)。为什么列输出每切换一次就能识别到3个按键的状态?因为,首先要明白一个前提,在没有任何按键“被按下”的时候,行输入的3个IO口因为内部上拉电阻的作用,默认状态都是H电平。并且,H与H相互短接输出为H,H与L相互短接输出L,也就是,L(低电平)的优先级最大,任何H(高电平)碰到L(低电平)输出的结果都是L(低电平)。L(低电平)就像数学乘法运算里的数字0,任何数跟0相乘必然等于0。多说一句,这个“L最高优先级”法则是有前提的,就是H(高电平)的产生必须是纯粹靠上拉电阻拉高的H(高电平)才行,比如刚好本教程所用的51单片机内部IO口输出的H(高电平)是依靠内部的上拉电阻产生,如果是其它“非上拉电阻产生的高电平”与“低电平”短接就有“短路烧坏芯片”的风险,这时就需要额外增加“三极管开漏式输出”电路或者外挂“开漏式输出集成芯片”电路。继续回到正题,为什么列输出每切换一次就能识别到3个按键的状态?举个例子,比如当列输出状态处于(P2.5为L,P2.4为H,P2.3为H)下,我们读取行输入的P2.2口,行输入的P2.2与列输出P2.5,P2.4,P2.3的“交叉处”有3个按键S1,S2,S3,此时,如果P2.2口是L(低电平),那么必然是S1“被按下”,因为想让P2.2口是L,只有S1有这个能力,而如果S1没有“被按下”,另外两个S2,S3即使“被按下”,P2.2口也是H而绝对不会为L,因为S2,S3的列输出P2.4为H,P2.3为H,H与H相互短接输出的结果必然为H。
        本节例程实现的功能:9个矩阵按键,每按下1个按键都触发一次蜂鸣器鸣叫。

#include "REG52.H"  

#define KEY_VOICE_TIME   50     

#define KEY_SHORT_TIME  20     //按键去抖动的“滤波”时间

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void BeepOpen(void);   
void BeepClose(void);

void VoiceScan(void);
void KeyScan(void);   
void KeyTask(void);   

sbit P3_4=P3^4;       //蜂鸣器

sbit ROW_INPUT1=P2^2;  //第1行输入口。
sbit ROW_INPUT2=P2^1;  //第2行输入口。
sbit ROW_INPUT3=P2^0;  //第3行输入口。

sbit COLUMN_OUTPUT1=P2^5;  //第1列输出口。
sbit COLUMN_OUTPUT2=P2^4;  //第2列输出口。
sbit COLUMN_OUTPUT3=P2^3;  //第3列输出口。

volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  


volatile unsigned char vGu8KeySec=0;  //按键的触发序号

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
    KeyTask();       //按键的任务函数
    }
}

/* 注释一:
*  矩阵按键扫描的详细过程:
*  先输出某1列低电平,其它2列输出高电平,延时等待2ms后(等此3列输出同步稳定),
*  再分别判断3行的输入IO口, 如果发现哪一行是低电平,就说明对应的某个按键被触发。
*  依次循环切换输出的3种状态,并且分别判断输入的3行,就可以检测完9个按键。矩阵按键的
*  去抖动处理方法跟我前面讲的独立按键去抖动方法是一样的,不再重复多讲。
*/

void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
{
   static unsigned char Su8KeyLock=0;        
   static unsigned int  Su16KeyCnt=0;  
   static unsigned char Su8KeyStep=1;  

   switch(Su8KeyStep)
   {
     case 1:   //按键扫描输出第一列低电平
          COLUMN_OUTPUT1=0;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=1;   

          Su16KeyCnt=0;  //延时计数器清零
          Su8KeyStep++;  //切换到下一个运行步骤
          break;

     case 2:     //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)
          {
             Su16KeyCnt=0;
             Su8KeyStep++;     //切换到下一个运行步骤
          }
          break;

     case 3:
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
          {  
             Su8KeyStep++;  //如果没有按键按下,切换到下一个运行步骤
             Su8KeyLock=0;  //按键自锁标志清零
             Su16KeyCnt=0; //按键去抖动延时计数器清零,此行非常巧妙        
          }
          else if(0==Su8KeyLock)  //有按键按下,且是第一次触发
          {
              if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
              {
                  Su16KeyCnt++;  //去抖动延时计数器
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su16KeyCnt=0;
                      Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
                      vGu8KeySec=1;  //触发1号键 对应S1键
                  }

              }
              else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
              {
                  Su16KeyCnt++;  //去抖动延时计数器
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su16KeyCnt=0;
                      Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
                      vGu8KeySec=2;  //触发2号键 对应S2键
                  }   
              }
              else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
              {
                  Su16KeyCnt++;  //去抖动延时计数器
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su16KeyCnt=0;
                      Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
                      vGu8KeySec=3;  //触发3号键 对应S3键
                  }   
              }

          }
          break;
     case 4:   //按键扫描输出第二列低电平
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=0;
          COLUMN_OUTPUT3=1;   

          Su16KeyCnt=0;  //延时计数器清零
          Su8KeyStep++;  //切换到下一个运行步骤
          break;

     case 5:     //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)
          {
             Su16KeyCnt=0;
             Su8KeyStep++;     //切换到下一个运行步骤
          }
          break;

     case 6:
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
          {  
             Su8KeyStep++;  //如果没有按键按下,切换到下一个运行步骤
             Su8KeyLock=0;  //按键自锁标志清零
             Su16KeyCnt=0; //按键去抖动延时计数器清零,此行非常巧妙        
          }
          else if(0==Su8KeyLock)  //有按键按下,且是第一次触发
          {
              if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
              {
                  Su16KeyCnt++;  //去抖动延时计数器
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su16KeyCnt=0;
                      Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
                      vGu8KeySec=4;  //触发4号键 对应S4键
                  }

              }
              else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
              {
                  Su16KeyCnt++;  //去抖动延时计数器
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su16KeyCnt=0;
                      Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
                      vGu8KeySec=5;  //触发5号键 对应S5键
                  }   
              }
              else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
              {
                  Su16KeyCnt++;  //去抖动延时计数器
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su16KeyCnt=0;
                      Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
                      vGu8KeySec=6;  //触发6号键 对应S6键
                  }   
              }

          }
          break;
     case 7:   //按键扫描输出第三列低电平
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=0;   

          Su16KeyCnt=0;  //延时计数器清零
          Su8KeyStep++;  //切换到下一个运行步骤
          break;

     case 8:     //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)
          {
             Su16KeyCnt=0;
             Su8KeyStep++;     //切换到下一个运行步骤
          }
          break;

     case 9:
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
          {  
             Su8KeyStep=1;  //如果没有按键按下,返回到第一步,重新开始扫描!!!!!!
             Su8KeyLock=0;  //按键自锁标志清零
             Su16KeyCnt=0; //按键去抖动延时计数器清零,此行非常巧妙        
          }
          else if(0==Su8KeyLock)  //有按键按下,且是第一次触发
          {
              if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
              {
                  Su16KeyCnt++;  //去抖动延时计数器
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su16KeyCnt=0;
                      Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
                      vGu8KeySec=7;  //触发7号键 对应S7键
                  }

              }
              else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
              {
                  Su16KeyCnt++;  //去抖动延时计数器
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su16KeyCnt=0;
                      Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
                      vGu8KeySec=8;  //触发8号键 对应S8键
                  }   
              }
              else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
              {
                  Su16KeyCnt++;  //去抖动延时计数器
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su16KeyCnt=0;
                      Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
                      vGu8KeySec=9;  //触发9号键 对应S9键
                  }   
              }

          }
          break;
   }

}

void KeyTask(void)    //按键任务函数,放在主函数内
{
if(0==vGu8KeySec)
{
return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
}

switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:     //S1触发的任务

            vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
            vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 2:     //S2触发的任务

            vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
            vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 3:     //S3触发的任务

            vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
            vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 4:     //S4触发的任务

            vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
            vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 5:     //S5触发的任务

            vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
            vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 6:     //S6触发的任务

            vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
            vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 7:     //S7触发的任务

            vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
            vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 8:     //S8触发的任务

            vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
            vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 9:     //S9触发的任务

            vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
            vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

}
}



void T0_time() interrupt 1     
{
VoiceScan();  
KeyScan();    //按键识别的驱动函数

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}

void BeepOpen(void)
{
P3_4=0;  
}

void BeepClose(void)
{
P3_4=1;  
}

void VoiceScan(void)
{

          static unsigned char Su8Lock=0;  

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;  
BeepOpen();
     }
    else  
{     

                       vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;     
BeepClose();  
                   }

}
          }         
}


使用特权

评论回复
334
ifxz0123| | 2017-12-11 09:11 | 只看该作者
好贴,必须支持下

使用特权

评论回复
335
avensun| | 2017-12-11 21:33 | 只看该作者
深入浅出,讲的真好!

使用特权

评论回复
336
Dalarl2008| | 2017-12-11 22:07 | 只看该作者

使用特权

评论回复
337
Dalarl2008| | 2017-12-11 22:07 | 只看该作者
谢谢 下载完毕

使用特权

评论回复
338
李劲均| | 2017-12-13 11:49 | 只看该作者
谢谢吴老师,你的无私奉献,造就了你的功德无量。

使用特权

评论回复
339
李劲均| | 2017-12-15 13:58 | 只看该作者
谢谢分享

使用特权

评论回复
340
laosun9911| | 2017-12-16 14:27 | 只看该作者
楼主以前是不是在**论坛?
现在来21IC了?
那个控制欲极强的**有时候确实不可理喻。

使用特权

评论回复
发新帖 本帖赏金 72.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则