jianhong_wu 发表于 2017-11-26 11:32:35

本帖最后由 jianhong_wu 于 2017-11-26 11:46 编辑

第九十七节: 独立按键按住不松手的连续均匀触发。

【97.1   按住不松手的连续均匀触发。】




                上图97.1.1独立按键电路


               
                上图97.1.2灌入式驱动8个LED

               
                上图97.1.3有源蜂鸣器电路

      在电脑上删除某个文件某行文字的时候,单击一次“退格按键”,就删除一个文字,如果按住“退格按键”不松手,就会“连续均匀”的触发“删除”的功能,自动逐个把整行文字删除清空,这就是“按住不松手的连续均匀触发”应用案例之一。除此之外,在很多需要人机交互的项目中都有这样的功能,为了快速加减某个数值,按住某个按键不松手,某个数值有节奏地快速往上加或者快速往下减。这种“按住不松手连续均匀触发”的按键识别,在程序上有“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_TIME25      //按键单击的“滤波”时间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 intSu16KeyCnt1;
   static unsigned intSu16KeyContinuityCnt1;//连击计数器

   static unsigned char Su8KeyLock2;      
   static unsigned intSu16KeyCnt2;
   static unsigned intSu16KeyContinuityCnt2;//连击计数器

   //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();
                   }

}
          }         
}


jianhong_wu 发表于 2017-12-7 11:32:42

本帖最后由 jianhong_wu 于 2017-12-7 11:50 编辑

第九十八节: 独立按键按住不松手的“先加速后匀速”的触发。

【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_TIME25      //按键单击的“滤波”时间
#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 intSu16KeyCnt1;
   static unsigned intSu16KeyContinuityCnt1;//连击计数器
   static unsigned intSu16KeyContinuityTime1=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值

   static unsigned char Su8KeyLock2;      
   static unsigned intSu16KeyCnt2;
   static unsigned intSu16KeyContinuityCnt2;//连击计数器
   static unsigned intSu16KeyContinuityTime2=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();
                   }

}
          }         
}




jianhong_wu 发表于 2017-12-10 11:09:03

本帖最后由 jianhong_wu 于 2017-12-10 11:18 编辑

第九十九节: “行列扫描式”矩阵按键的单个触发(原始版)。

【99.1   “行列扫描式”矩阵按键。】

               
                上图99.1.1有源蜂鸣器电路


      
                上图99.1.23*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_TIME20   //按键去抖动的“滤波”时间

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 intSu16KeyCnt=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();
                   }

}
          }         
}

jianhong_wu 发表于 2017-12-17 11:28:54

本帖最后由 jianhong_wu 于 2017-12-17 11:50 编辑

第一百节: “行列扫描式”矩阵按键的单个触发(优化版)。

【100.1   “行列扫描式”矩阵按键。】

                  
                上图100.1.1有源蜂鸣器电路


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

       写程序,凡是出现“重复性、相似性”的代码,都可以加入“循环,判断,数组”这类语句对代码进行压缩优化。上一节讲的矩阵按键,代码是记流水账式的,出现很多“重复性、相似性”的代码,是没有经过优化的“原始版”,本节的目的是对上一节的代码进行优化,让大家从中发现一些技巧。
       多说一句,我一直认为,只要单片机容量够,代码多一点少一点并不重要,只要不影响运行效率就行。而且有时候,代码写多一点,可读性非常强,修改起来也非常方便。如果一味的追求压缩代码,就会刻意用很多“循环,判断,数组”等元素,代码虽然紧凑了,但是可分离性,可更改性,可阅读性就没那么强。因此,做项目的时候,某些代码要不要进行压缩,是没有绝对标准的,能因敌而取胜者谓之神。
       本节例程实现的功能:9个矩阵按键,每按下1个按键都触发一次蜂鸣器鸣叫。#include "REG52.H"

#define KEY_VOICE_TIME   50   

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

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 intSu16KeyCnt=0;
   static unsigned char Su8KeyStep=1;

   static unsigned char Su8ColumnRecord=0;//本节多增加此变量用来切换当前列的输出

   switch(Su8KeyStep)
   {
   case 1:   
          if(0==Su8ColumnRecord)//按键扫描输出第一列低电平
{
          COLUMN_OUTPUT1=0;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=1;
}
          else if(1==Su8ColumnRecord)//按键扫描输出第二列低电平
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=0;
          COLUMN_OUTPUT3=1;
}
          else   //按键扫描输出第三列低电平
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=0;
}
          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=1;//如果没有按键按下,返回到第一个运行步骤重新开始扫描!!!!!!
             Su8KeyLock=0;//按键自锁标志清零
             Su16KeyCnt=0;//按键去抖动延时计数器清零,此行非常巧妙   
             Su8ColumnRecord++;//输出下一列
             if(Su8ColumnRecord>=3)
             {
                Su8ColumnRecord=0; //依次输出完第3列之后,继续从第1列开始输出低电平
             }   
          }
          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,避免一直触发,只有松开按键,此标志位才会被清零

                      if(0==Su8ColumnRecord)//第1列输出低电平
                      {
                           vGu8KeySec=1;//触发1号键 对应S1键
                      }
                      else if(1==Su8ColumnRecord)//第2列输出低电平
                      {
                           vGu8KeySec=2;//触发2号键 对应S2键
                      }
                      else if(2==Su8ColumnRecord)//第3列输出低电平
                      {
                           vGu8KeySec=3;//触发3号键 对应S3键
                      }
                  }

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

                      if(0==Su8ColumnRecord)//第1列输出低电平
                      {
                           vGu8KeySec=4;//触发4号键 对应S4键
                      }
                      else if(1==Su8ColumnRecord)//第2列输出低电平
                      {
                           vGu8KeySec=5;//触发5号键 对应S5键
                      }
                      else if(2==Su8ColumnRecord)//第3列输出低电平
                      {
                           vGu8KeySec=6;//触发6号键 对应S6键
                      }
                  }   
            }
            else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
            {
                  Su16KeyCnt++;//去抖动延时计数器
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su16KeyCnt=0;
                      Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
                      if(0==Su8ColumnRecord)//第1列输出低电平
                      {
                           vGu8KeySec=7;//触发7号键 对应S7键
                      }
                      else if(1==Su8ColumnRecord)//第2列输出低电平
                      {
                           vGu8KeySec=8;//触发8号键 对应S8键
                      }
                      else if(2==Su8ColumnRecord)//第3列输出低电平
                      {
                           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();
                   }

}
          }         
}





jianhong_wu 发表于 2017-12-31 16:36:01

本帖最后由 jianhong_wu 于 2017-12-31 17:49 编辑

第一百零一节: 矩阵按键鼠标式的单击与双击。

【101.1   矩阵按键鼠标式的单击与双击。】

               
                上图101.1.1有源蜂鸣器电路

      
                上图101.1.2LED电路


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

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

#define KEY_VOICE_TIME   50   
#define KEY_SHORT_TIME20   //按键去抖动的“滤波”时间
#define KEY_INTERVAL_TIME80//连续两次单击之间的最大有效时间。因为是矩阵,80不一定是80ms

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 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;

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();    //双击按键任务函数
    }
}

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

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

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

   static unsigned char Su8ColumnRecord=0;//用来切换当前列的输出

   static unsigned char Su8KeyTouchCnt1;       //S1按键的次数记录
   static unsigned intSu16KeyIntervalCnt1;   //S1按键的间隔时间计数器

   switch(Su8KeyStep)
   {
   case 1:   
          if(0==Su8ColumnRecord)//按键扫描输出第一列低电平
{
          COLUMN_OUTPUT1=0;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=1;
}
          else if(1==Su8ColumnRecord)//按键扫描输出第二列低电平
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=0;
          COLUMN_OUTPUT3=1;
}
          else   //按键扫描输出第三列低电平
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=0;
}
          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=1;//如果没有按键按下,返回到第一个运行步骤重新开始扫描!!!!!!
             Su8KeyLock=0;//按键自锁标志清零
             Su16KeyCnt=0;//按键去抖动延时计数器清零,此行非常巧妙

if(Su8KeyTouchCnt1>=1) //之前已经有按键触发过一次,启动间隔时间的计数器
{
Su16KeyIntervalCnt1++; //按键间隔的时间计数器累加
if(Su16KeyIntervalCnt1>=KEY_INTERVAL_TIME) //达到最大允许的间隔时间,溢出无效
{
Su16KeyIntervalCnt1=0; //时间计数器清零
Su8KeyTouchCnt1=0;   //清零按键的按下的次数,因为间隔时间溢出无效
}
}

             Su8ColumnRecord++;//输出下一列
             if(Su8ColumnRecord>=3)
             {
                Su8ColumnRecord=0; //依次输出完第3列之后,继续从第1列开始输出低电平
             }   
          }
          else if(0==Su8KeyLock)//有按键按下,且是第一次触发
          {
            if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
            {
                  Su16KeyCnt++;//去抖动延时计数器
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零

                      if(0==Su8ColumnRecord)//第1列输出低电平
                      {
                           Su16KeyIntervalCnt1=0;   //按键有效间隔的时间计数器清零
                           Su8KeyTouchCnt1++;       //记录当前单击的次数
                           if(1==Su8KeyTouchCnt1)   //只按了1次
                           {
                               vGu8SingleKeySec=1;   //单击任务,触发1号键 对应S1键
                           }
                           else if(Su8KeyTouchCnt1>=2)//连续按了两次以上
                           {
                              Su8KeyTouchCnt1=0;    //统计按键次数清零
                              vGu8SingleKeySec=1;   //单击任务,触发1号键 对应S1键
vGu8DoubleKeySec=1;   //双击任务,触发1号键 对应S1键
                           }
                      }
                      else if(1==Su8ColumnRecord)//第2列输出低电平
                      {
                           vGu8SingleKeySec=2;//触发2号键 对应S2键
                      }
                      else if(2==Su8ColumnRecord)//第3列输出低电平
                      {
                           vGu8SingleKeySec=3;//触发3号键 对应S3键
                      }
                  }

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

                      if(0==Su8ColumnRecord)//第1列输出低电平
                      {
                           vGu8SingleKeySec=4;//触发4号键 对应S4键
                      }
                      else if(1==Su8ColumnRecord)//第2列输出低电平
                      {
                           vGu8SingleKeySec=5;//触发5号键 对应S5键
                      }
                      else if(2==Su8ColumnRecord)//第3列输出低电平
                      {
                           vGu8SingleKeySec=6;//触发6号键 对应S6键
                      }
                  }   
            }
            else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
            {
                  Su16KeyCnt++;//去抖动延时计数器
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
                      if(0==Su8ColumnRecord)//第1列输出低电平
                      {
                           vGu8SingleKeySec=7;//触发7号键 对应S7键
                      }
                      else if(1==Su8ColumnRecord)//第2列输出低电平
                      {
                           vGu8SingleKeySec=8;//触发8号键 对应S8键
                      }
                      else if(2==Su8ColumnRecord)//第3列输出低电平
                      {
                           vGu8SingleKeySec=9;//触发9号键 对应S9键
                      }
                  }   
            }

          }
          break;
   }

}

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

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

            //通过Gu8LedStatus的状态切换,来反复切换LED的“灭”与“亮”的状态
            if(0==Gu8LedStatus)
            {
                Gu8LedStatus=1; //标识并且更改当前LED灯的状态。0就变成1。
                LedOpen();   //点亮LED
}
            else
            {
                Gu8LedStatus=0; //标识并且更改当前LED灯的状态。1就变成0。
                LedClose();//关闭LED
}

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

   default://其它按键触发的单击

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

}
}

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

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

            vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发双击后,发出“嘀”一声
            vGu8BeepTimerFlag=1;

vGu8DoubleKeySec=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)
{
/* 注释三:
* 把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();
                   }

}
          }         
}





jianhong_wu 发表于 2018-1-7 12:24:28

本帖最后由 jianhong_wu 于 2018-1-7 12:39 编辑

第一百零二节: 两个“任意行输入”矩阵按键的“有序”组合触发。

【102.1   “异行输入”“同行输入”“有序”。】

               
                上图102.1.1有源蜂鸣器电路

      
                上图102.1.2LED电路


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

       “任意行输入”是指能兼容“异行输入”与“同行输入”这两种按键状态。
      何谓“异行输入”何谓“同行输入”?如上图矩阵按键,P2.2,P2.1,P2.0是输入行,P2.5,P2.4,P2.3是输出列。以S1按键为例,很明显,S2和S3都是属于S1的“同行输入”,都是属于P2.2的输入行。除了S2和S3以外,其它所有的按键都是S1的“异行输入”,比如S5按键就是S1的“异行输入”,因为S1是属于P2.2的输入行,而S5是属于P2.1的输入行。
何谓“有序”组合触发?就是两个按键的触发必须遵守“先后顺序”才能构成“组合触发”。比如,像电脑的复制快捷键(Ctrl+C),你必须先按住Ctrl再按住C此时“复制快捷键”才有效,如果你先按住C再按住Ctrl此时“复制快捷键”无效。
       “异行输入”与“同行输入”,相比之下,“同行输入”更难更有代表性,如果把“同行输入”的程序写出来了,那么完全按“同行输入”的思路,就可以把“异行输入”的程序写出来。因此,只要把“同行输入”的程序写出来了,也就意味着“任意行输入”的程序也就实现了。本节以S1和S2的“同行输入”按键为例,S1是主键,类似复制快捷键的Ctrl键;S2是从键,类似复制快捷键的C键。要触发组合键(S1+S2),必须先按住S1再按S2才有效。功能如下:(1)S1每单击一次,LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(2)如果先按住S1再按S2,就认为构造了“有序”组合键,蜂鸣器发出“嘀”的一声。#include "REG52.H"

#define KEY_VOICE_TIME   50   
#define KEY_SHORT_TIME20   

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;      

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;

unsigned char Gu8LedStatus=0;
volatile unsigned char vGu8SingleKeySec=0;
volatile unsigned char vGu8DoubleKeySec=0;

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
      SingleKeyTask();   
      DoubleKeyTask();   
    }
}

/* 注释一:
*两个“任意行输入”矩阵按键“有序”触发的两个最关键地方:
*(1)当S1按键被按下单击触发之后, “马上更新输出列的信号状态”,然后切换到后面的步骤。
*(2)在后面的步骤里,进入到S1和S2两个按键的轮番循环监控之中,如果发现S1按键率先
*   被松开了,就把步骤切换到开始的第一步,重新开始新一轮的按键扫描。
*(3)按照这个模板,只需“更改不同的列输出,判断不同的行输入”,就可以实现“任意行输入”
*   矩阵按键的“有序”组合触发。
*/

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

   static unsigned char Su8ColumnRecord=0;

   switch(Su8KeyStep)
   {
   case 1:   
          if(0==Su8ColumnRecord)
{
          COLUMN_OUTPUT1=0;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=1;
}
          else if(1==Su8ColumnRecord)
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=0;
          COLUMN_OUTPUT3=1;
}
          else   
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=0;
}
          Su16KeyCnt=0;
          Su8KeyStep++;
          break;

   case 2:      //等待列输出稳定,但不是去抖动延时
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)
          {
             Su16KeyCnt=0;
             Su8KeyStep++;   
          }
          break;

   case 3:
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
          {
             Su8KeyStep=1;
             Su8KeyLock=0;
             Su16KeyCnt=0;

             Su8ColumnRecord++;
             if(Su8ColumnRecord>=3)
             {
                Su8ColumnRecord=0;
             }   
          }
          else if(0==Su8KeyLock)
          {
            if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;

                      if(0==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=1;   //单击任务,触发1号键 对应S1键

                           //“马上更新输出列的信号状态”
                     COLUMN_OUTPUT1=1;   
                     COLUMN_OUTPUT2=0;   //列2也输出0,非常关键的代码!
                     COLUMN_OUTPUT3=1;

                     Su16KeyCnt=0;//去抖动延时清零,为下一步计时做准备
                           Su8KeyStep++;   //切换到下一步步骤
                      }
                      else if(1==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=2;
                      }
                      else if(2==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=3;
                      }
                  }

            }
            else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;

                      if(0==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=4;
                      }
                      else if(1==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=5;
                      }
                      else if(2==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=6;
                      }
                  }   
            }
            else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;
                      if(0==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=7;
                      }
                      else if(1==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=8;
                      }
                      else if(2==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=9;
                      }
                  }   
            }

          }
          break;
   case 4:             //等待列输出稳定,但不是去抖动延时
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)   
          {
            Su16KeyCnt=0;
Su8KeyLock=0;   //关键语句!自锁清零,为下一步自锁组合按键做准备
            Su8KeyStep++;   
          }
          break;

   case 5:    //判断S2按键
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S2按键没有被按下
          {
             Su8KeyLock=0;
             Su16KeyCnt=0;

             //“马上更新输出列的信号状态”
         COLUMN_OUTPUT1=0;   //列1输出0,非常关键的代码!
         COLUMN_OUTPUT2=1;
         COLUMN_OUTPUT3=1;

             Su8KeyStep++;    //切换到下一个步骤,监控S1是否率先已经松开
          }
else if(0==Su8KeyLock)
{   
            if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S2按键被按下
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                        Su8KeyLock=1;//组合按键的自锁
vGu8DoubleKeySec=1;   //触发组合按键(S1+S2)
                  }
            }



}
          break;

   case 6:       //等待列输出稳定,但不是去抖动延时
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)   
          {
            Su16KeyCnt=0;
Su8KeyLock=0;   //关键语句!自锁清零,为下一步自锁组合按键做准备
            Su8KeyStep++;   
          }
          break;

   case 7:    //监控S1按键是否率先已经松开
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
            Su16KeyCnt=0;
Su8KeyLock=0;   
            Su8KeyStep=1;   //如果S1按键已经松开,返回到第一个运行步骤重新开始扫描

            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
                  Su8ColumnRecord=0;
            }
}
else
{
            //“马上更新输出列的信号状态”
          COLUMN_OUTPUT1=1;   
          COLUMN_OUTPUT2=0;   //列2输出0,非常关键的代码!
          COLUMN_OUTPUT3=1;
            Su8KeyStep=4;   //如果S1按键没有松开,继续返回判断S2是否已按下
}
          break;
   }

}

void SingleKeyTask(void)   
{
if(0==vGu8SingleKeySec)
{
return;
}

switch(vGu8SingleKeySec)
{
   case 1:   //S1按键的单击任务,更改LED灯的显示状态

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

vGu8SingleKeySec=0;
break;

   default:

vGu8SingleKeySec=0;
break;

}
}

void DoubleKeyTask(void)   
{
if(0==vGu8DoubleKeySec)
{
return;
}

switch(vGu8DoubleKeySec)
{
      case 1:   //S1与S2的组合按键触发,发出“嘀”一声

            vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;
            vGu8BeepTimerFlag=1;

vGu8DoubleKeySec=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)
{
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();
                   }

}
          }         
}





jianhong_wu 发表于 2018-1-14 10:31:53

本帖最后由 jianhong_wu 于 2018-1-14 10:47 编辑

第一百零三节: 两个“任意行输入”矩阵按键的“无序”组合触发。

【103.1   “无序”组合触发。】

               
                上图103.1.1有源蜂鸣器电路

   
                上图103.1.2LED电路


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

       “无序”是指两个组合按键不分先后顺序,都能构成组合触发。比如,要触发组合键(S1+S2),先按S1再按S2,或者先按S2再按S1,功能都是一样的。
       本节程序功能如下:(1)S1每单击一次,P1.4所在的LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(2)S2每单击一次,P1.5所在的LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(3)如果先按住S1再按S2,或者先按住S2再按S1,都认为构造了“无序”组合键,蜂鸣器发出“嘀”的一声。#include "REG52.H"

#define KEY_VOICE_TIME   50   
#define KEY_SHORT_TIME20   

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

void BeepOpen(void);   
void BeepClose(void);
void LedOpen_P1_4(void);   
void LedClose_P1_4(void);
void LedOpen_P1_5(void);   
void LedClose_P1_5(void);

void VoiceScan(void);
void KeyScan(void);   
void SingleKeyTask(void);   
void DoubleKeyTask(void);   

sbit P3_4=P3^4;      
sbit P1_4=P1^4;    //P1.4所在的LED
sbit P1_5=P1^5;    //P1.5所在的LED

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;

unsigned char Gu8LedStatus_P1_4=0;//P1.4所在的LED的状态
unsigned char Gu8LedStatus_P1_5=0;//P1.5所在的LED的状态
volatile unsigned char vGu8SingleKeySec=0;
volatile unsigned char vGu8DoubleKeySec=0;

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
      SingleKeyTask();   
      DoubleKeyTask();   
    }
}

/* 注释一:
*矩阵按键“无序”触发的两个最关键地方:
*(1)如果是S1按键先被按下并且单击触发之后,“马上更新输出列的信号状态”,然后切换到
*   “S1后面所在的步骤里”,进入到S1和S2两个按键的轮番循环监控之中,如果发现S1按键率先
*   被松开了,就把步骤切换到开始的第一步,重新开始新一轮的按键扫描。
*(2)如果是S2按键先被按下并且单击触发之后,“马上更新输出列的信号状态”,然后切换到
*   “S2后面所在的步骤里”,进入到S1和S2两个按键的轮番循环监控之中,如果发现S2按键率先
*   被松开了,就把步骤切换到开始的第一步,重新开始新一轮的按键扫描。
*(3)上面两个描述中的两种步骤,“S1后面所在的步骤里”和“S2后面所在的步骤里”是分开的,
*   不共用的,这是本节破题的关键。
*/

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

   static unsigned char Su8ColumnRecord=0;

   switch(Su8KeyStep)
   {
   case 1:   
          if(0==Su8ColumnRecord)
{
          COLUMN_OUTPUT1=0;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=1;
}
          else if(1==Su8ColumnRecord)
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=0;
          COLUMN_OUTPUT3=1;
}
          else   
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=0;
}
          Su16KeyCnt=0;
          Su8KeyStep++;
          break;

   case 2:      //等待列输出稳定,但不是去抖动延时
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)
          {
             Su16KeyCnt=0;
             Su8KeyStep++;   
          }
          break;

   case 3:
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
          {
             Su8KeyStep=1;
             Su8KeyLock=0;
             Su16KeyCnt=0;

             Su8ColumnRecord++;
             if(Su8ColumnRecord>=3)
             {
                Su8ColumnRecord=0;
             }   
          }
          else if(0==Su8KeyLock)
          {
            if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;

                      if(0==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=1;   //单击任务,触发1号键 对应S1键

                           //“马上更新输出列的信号状态”
                     COLUMN_OUTPUT1=1;   
                     COLUMN_OUTPUT2=0;   //列2也输出0,下一步监控S2,非常关键的代码!
                     COLUMN_OUTPUT3=1;

                     Su16KeyCnt=0;//去抖动延时清零,为下一步计时做准备
                           Su8KeyStep=4;   //切换到“S1后面所在的步骤里”,破题的关键!!!
                      }
                      else if(1==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=2;    //单击任务,触发2号键 对应S2键

                           //“马上更新输出列的信号状态”
                     COLUMN_OUTPUT1=0;//列1也输出0,下一步监控S1,非常关键的代码!
                     COLUMN_OUTPUT2=1;
                     COLUMN_OUTPUT3=1;

                     Su16KeyCnt=0;//去抖动延时清零,为下一步计时做准备
                           Su8KeyStep=8;   //切换到“S2后面所在的步骤里”,破题的关键!!!
                      }
                      else if(2==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=3;
                      }
                  }

            }
            else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;

                      if(0==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=4;
                      }
                      else if(1==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=5;
                      }
                      else if(2==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=6;
                      }
                  }   
            }
            else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;
                      if(0==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=7;
                      }
                      else if(1==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=8;
                      }
                      else if(2==Su8ColumnRecord)
                      {
                           vGu8SingleKeySec=9;
                      }
                  }   
            }

          }
          break;

/*--------------“S1后面所在的步骤里”------------------*/
   case 4:             //等待列输出稳定,但不是去抖动延时
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)   
          {
            Su16KeyCnt=0;
Su8KeyLock=0;   //关键语句!自锁清零,为下一步自锁组合按键做准备
            Su8KeyStep++;   
          }
          break;

   case 5:    //判断S2按键
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S2按键没有被按下
          {
             Su8KeyLock=0;
             Su16KeyCnt=0;

             //“马上更新输出列的信号状态”
         COLUMN_OUTPUT1=0;   //列1输出0,下一步监控S1,非常关键的代码!
         COLUMN_OUTPUT2=1;
         COLUMN_OUTPUT3=1;

             Su8KeyStep++;    //切换到下一个步骤,监控S1是否率先已经松开
          }
else if(0==Su8KeyLock)
{   
            if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S2按键被按下
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                        Su8KeyLock=1;//组合按键的自锁
vGu8DoubleKeySec=1;   //触发组合按键(S1+S2)
                  }
            }



}
          break;

   case 6:       //等待列输出稳定,但不是去抖动延时
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)   
          {
            Su16KeyCnt=0;
Su8KeyLock=0;   //关键语句!自锁清零,为下一步自锁组合按键做准备
            Su8KeyStep++;   
          }
          break;

   case 7:    //监控S1按键是否率先已经松开
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
            Su16KeyCnt=0;
Su8KeyLock=0;   
            Su8KeyStep=1;   //如果S1按键已经松开,返回到第一个运行步骤重新开始扫描

            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
                  Su8ColumnRecord=0;
            }
}
else
{
            //“马上更新输出列的信号状态”
          COLUMN_OUTPUT1=1;   
          COLUMN_OUTPUT2=0;   //列2输出0,下一步监控S2,非常关键的代码!
          COLUMN_OUTPUT3=1;
            Su8KeyStep=4;   //如果S1按键没有松开,继续返回判断S2是否已按下
}
          break;

/*--------------“S2后面所在的步骤里”------------------*/
   case 8:             //等待列输出稳定,但不是去抖动延时
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)   
          {
            Su16KeyCnt=0;
Su8KeyLock=0;   //关键语句!自锁清零,为下一步自锁组合按键做准备
            Su8KeyStep++;   
          }
          break;

   case 9:    //判断S1按键
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S1按键没有被按下
          {
             Su8KeyLock=0;
             Su16KeyCnt=0;

             //“马上更新输出列的信号状态”
         COLUMN_OUTPUT1=1;   
         COLUMN_OUTPUT2=0;//列2输出0,下一步监控S2,非常关键的代码!
         COLUMN_OUTPUT3=1;

             Su8KeyStep++;    //切换到下一个步骤,监控S2是否率先已经松开
          }
else if(0==Su8KeyLock)
{   
            if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S1按键被按下
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                        Su8KeyLock=1;//组合按键的自锁
vGu8DoubleKeySec=1;   //触发组合按键(S1+S2)
                  }
            }



}
          break;

   case 10:       //等待列输出稳定,但不是去抖动延时
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)   
          {
            Su16KeyCnt=0;
Su8KeyLock=0;   //关键语句!自锁清零,为下一步自锁组合按键做准备
            Su8KeyStep++;   
          }
          break;

   case 11:    //监控S2按键是否率先已经松开
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
{
            Su16KeyCnt=0;
Su8KeyLock=0;   
            Su8KeyStep=1;   //如果S2按键已经松开,返回到第一个运行步骤重新开始扫描

            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
                  Su8ColumnRecord=0;
            }
}
else
{
            //“马上更新输出列的信号状态”
          COLUMN_OUTPUT1=0;    //列1输出0,下一步监控S1,非常关键的代码!
          COLUMN_OUTPUT2=1;   
          COLUMN_OUTPUT3=1;
            Su8KeyStep=8;   //如果S2按键没有松开,继续返回判断S1是否已按下
}
          break;


   }

}

void SingleKeyTask(void)   
{
if(0==vGu8SingleKeySec)
{
return;
}

switch(vGu8SingleKeySec)
{
   case 1:   //S1按键的单击任务,更改P1.4所在的LED灯的显示状态

            if(0==Gu8LedStatus_P1_4)
            {
                Gu8LedStatus_P1_4=1;
                LedOpen_P1_4();   
}
            else
            {
                Gu8LedStatus_P1_4=0;
                LedClose_P1_4();
}

vGu8SingleKeySec=0;
break;

   case 2:   //S2按键的单击任务,更改P1.5所在的LED灯的显示状态

            if(0==Gu8LedStatus_P1_5)
            {
                Gu8LedStatus_P1_5=1;
                LedOpen_P1_5();   
}
            else
            {
                Gu8LedStatus_P1_5=0;
                LedClose_P1_5();
}

vGu8SingleKeySec=0;
break;

   default:

vGu8SingleKeySec=0;
break;

}
}

void DoubleKeyTask(void)   
{
if(0==vGu8DoubleKeySec)
{
return;
}

switch(vGu8DoubleKeySec)
{
      case 1:   //S1与S2的组合按键触发,发出“嘀”一声

            vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;
            vGu8BeepTimerFlag=1;

vGu8DoubleKeySec=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)
{
if(0==Gu8LedStatus_P1_4)
{
    LedClose_P1_4();
}
else
{
    LedOpen_P1_4();   
}

if(0==Gu8LedStatus_P1_5)
{
    LedClose_P1_5();
}
else
{
    LedOpen_P1_5();   
}

}

void BeepOpen(void)
{
P3_4=0;
}

void BeepClose(void)
{
P3_4=1;
}

void LedOpen_P1_4(void)
{
P1_4=0;
}

void LedClose_P1_4(void)
{
P1_4=1;
}

void LedOpen_P1_5(void)
{
P1_5=0;
}

void LedClose_P1_5(void)
{
P1_5=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();
                   }

}
          }         
}





jianhong_wu 发表于 2018-1-22 15:15:05

本帖最后由 jianhong_wu 于 2018-1-22 15:32 编辑

第一百零四节: 矩阵按键“一键两用”的短按与长按。

【104.1   “一键两用”的短按与长按。】

            
                上图104.1.1有源蜂鸣器电路

      
                上图104.1.2LED电路


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

      矩阵按键与前面章节独立按键的“短按与长按”的处理思路是一样的,本节讲矩阵按键的“短按与长按”,也算是重温之前章节讲的内容。“短按与长按”的原理是依赖“按键按下的时间长度”来区分识别。“短按”是指从按下的“下降沿”到松手的“上升沿”时间,“长按”是指从按下的“下降沿”到一直按住不松手的“低电平持续时间”。本节的例程功能如下:(1)S1每“短按”一次,LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(2)S1每“长按”一次,蜂鸣器发出“嘀”的一声。代码如下:
#include "REG52.H"

#define KEY_VOICE_TIME   50   

#define KEY_SHORT_TIME20    //按键的“短按”兼“滤波”的“稳定时间”
#define KEY_LONG_TIME400    //按键的“长按”兼“滤波”的“稳定时间”

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

void BeepOpen(void);   
void BeepClose(void);
void LedOpen_P1_4(void);   
void LedClose_P1_4(void);

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

sbit P3_4=P3^4;      
sbit P1_4=P1^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;

unsigned char Gu8LedStatus_P1_4=0;
volatile unsigned char vGu8KeySec=0;//短按与长按共用一个全局变量vGu8KeySec来传递按键信息

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
      KeyTask();   
    }
}

/* 注释一:
*本节破题的关键:
*矩阵按键涉及的按键数量很多,但是实际项目上一般只需要少数个别按键具备这种
*“短按”与“长按”的特殊技能,因此,在代码上,必须把这类“特殊技能按键”与
*“大众按键”区分开来,才能相互清晰互不干扰。本节的“特殊技能按键”是S1。
*/

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

   static unsigned char Su8ColumnRecord=0;

   static unsigned char Su8KeyShortFlag_S1=0;//S1按键专属的“短按”触发标志

   switch(Su8KeyStep)
   {
   case 1:   
          if(0==Su8ColumnRecord)
{
          COLUMN_OUTPUT1=0;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=1;
}
          else if(1==Su8ColumnRecord)
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=0;
          COLUMN_OUTPUT3=1;
}
          else   
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=0;
}
          Su16KeyCnt=0;
          Su8KeyStep++;
          break;

   case 2:      //等待列输出稳定,但不是去抖动延时
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)
          {
             Su16KeyCnt=0;
             Su8KeyStep++;   
          }
          break;

   case 3:
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
          {
             Su8KeyStep=1;
             Su8KeyLock=0;
             Su16KeyCnt=0;

             if(1==Su8KeyShortFlag_S1)//松手的时候,如果“短按”标志有效就触发一次“短按”
             {
Su8KeyShortFlag_S1=0;   //先清零“短按”标志避免一直触发。
vGu8KeySec=1;    //触发S1的“短按”
}

             Su8ColumnRecord++;
             if(Su8ColumnRecord>=3)
             {
                Su8ColumnRecord=0;
             }   
          }
          else if(0==Su8KeyLock)
          {
            //以下第1行,直接把S1按键单独扣出来,用“&&0==Su8ColumnRecord”作为筛选条件
            if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3&&0==Su8ColumnRecord)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME) //“短按”兼“滤波”的“稳定时间”      
{
                      //注意,这里不能“自锁”。后面“长按”触发的时候才“自锁”。
                      Su8KeyShortFlag_S1=1;    //S1的“短按”标志有效,待松手时触发。
               }

               if(Su16KeyCnt>=KEY_LONG_TIME) //“长按”兼“滤波”的“稳定时间”
               {
                      Su8KeyLock=1;      //此时“长按”触发才“自锁”
Su8KeyShortFlag_S1=0;//既然此时“长按”有效,那么就要废除潜在的“短按”。
                      vGu8KeySec=21; //触发S1的“长按”
               }

            }
            else if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;

//既然S1按键已经被上面几行代码单独扣出来,这里就直接从S2按键开始判断
                      if(1==Su8ColumnRecord)
                      {
                           vGu8KeySec=2;   
                      }
                      else if(2==Su8ColumnRecord)
                      {
                           vGu8KeySec=3;
                      }
                  }

            }
            else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;

                      if(0==Su8ColumnRecord)
                      {
                           vGu8KeySec=4;
                      }
                      else if(1==Su8ColumnRecord)
                      {
                           vGu8KeySec=5;
                      }
                      else if(2==Su8ColumnRecord)
                      {
                           vGu8KeySec=6;
                      }
                  }   
            }
            else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;
                      if(0==Su8ColumnRecord)
                      {
                           vGu8KeySec=7;
                      }
                      else if(1==Su8ColumnRecord)
                      {
                           vGu8KeySec=8;
                      }
                      else if(2==Su8ColumnRecord)
                      {
                           vGu8KeySec=9;
                      }
                  }   
            }

          }
          break;

   }

}

void KeyTask(void)   
{
if(0==vGu8KeySec)
{
return;
}

switch(vGu8KeySec)
{
   case 1:   //S1按键的“短按”任务,更改P1.4所在的LED灯的显示状态

            if(0==Gu8LedStatus_P1_4)
            {
                Gu8LedStatus_P1_4=1;
                LedOpen_P1_4();   
}
            else
            {
                Gu8LedStatus_P1_4=0;
                LedClose_P1_4();
}

vGu8KeySec=0;
break;

//以下S1按键的“长按”直接选择case 21的“21”,是为了不占用前排其它按键的编号。
   case 21:   //S1按键的“长按”任务,蜂鸣器发出“嘀”一声
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//蜂鸣器发出“嘀”一声
vGu8BeepTimerFlag=1;

vGu8KeySec=0;
break;

   default:

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)
{
if(0==Gu8LedStatus_P1_4)
{
    LedClose_P1_4();
}
else
{
    LedOpen_P1_4();   
}

}

void BeepOpen(void)
{
P3_4=0;
}

void BeepClose(void)
{
P3_4=1;
}

void LedOpen_P1_4(void)
{
P1_4=0;
}

void LedClose_P1_4(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();
                   }

}
          }         
}




jianhong_wu 发表于 2018-1-28 12:42:30

本帖最后由 jianhong_wu 于 2018-1-28 13:09 编辑

第一百零五节: 矩阵按键按住不松手的连续均匀触发。

【105.1   按住不松手的连续均匀触发。】

               
                上图105.1.1有源蜂鸣器电路

      
                上图105.1.2LED电路


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

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

#define KEY_VOICE_TIME   50   

#define KEY_SHORT_TIME20    //按键单击的“滤波”时间

#define KEY_ENTER_CONTINUITY_TIME    240//按键“从单击进入连击”的间隔时间
#define KEY_CONTINUITY_TIME    64   //按键“连击”的间隔时间

#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 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;

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电路是灌入式驱动方式。
}
}

/* 注释二:
*本节破题的关键:
*矩阵按键涉及的按键数量很多,但是实际项目上一般只需要少数个别按键具备这种
*“单击”与“连续均匀触发”的特殊技能,因此,在代码上,必须把这类“特殊技能按键”与
*“大众按键”区分开来,才能相互清晰互不干扰。本节的“特殊技能按键”是S1和S9。
*如果觉得本节的讲解不够详细具体,请先阅读一下前面章节“独立按键按住不松手的连续均匀触发”。
*/

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

   static unsigned char Su8ColumnRecord=0;

   switch(Su8KeyStep)
   {
   case 1:   
          if(0==Su8ColumnRecord)
{
          COLUMN_OUTPUT1=0;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=1;
}
          else if(1==Su8ColumnRecord)
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=0;
          COLUMN_OUTPUT3=1;
}
          else   
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=0;
}
          Su16KeyCnt=0;
          Su8KeyStep++;
          break;

   case 2:      //等待列输出稳定,但不是去抖动延时
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)
          {
             Su16KeyCnt=0;
             Su8KeyStep++;   
          }
          break;

   case 3:
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
          {
            Su8KeyStep=1;    //返回步骤1继续扫描
            Su8KeyLock=0;
            Su16KeyCnt=0;

            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
               Su8ColumnRecord=0;
            }   
          }
          else if(0==Su8KeyLock)
          {
            if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;

                      if(0==Su8ColumnRecord)
                      {
                            vGu8KeySec=1;    //触发一次单击
Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
                            Su8KeyStep=4;    //跳到S1按键的专属区,脱离大众按键
                      }
                      else if(1==Su8ColumnRecord)
                      {
                            vGu8KeySec=2;   
                      }
                      else if(2==Su8ColumnRecord)
                      {
                            vGu8KeySec=3;
                      }
                  }

            }
            else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;

                      if(0==Su8ColumnRecord)
                      {
                           vGu8KeySec=4;
                      }
                      else if(1==Su8ColumnRecord)
                      {
                           vGu8KeySec=5;
                      }
                      else if(2==Su8ColumnRecord)
                      {
                           vGu8KeySec=6;
                      }
                  }   
            }
            else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;
                      if(0==Su8ColumnRecord)
                      {
                           vGu8KeySec=7;
                      }
                      else if(1==Su8ColumnRecord)
                      {
                           vGu8KeySec=8;
                      }
                      else if(2==Su8ColumnRecord)
                      {
                            vGu8KeySec=9;    //触发一次单击
Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
                            Su8KeyStep=6;    //跳到S9按键的专属区,脱离大众按键
                      }
                  }   
            }

          }
          break;

/*---------S1按键的专属区----------------*/
   case 4:
          if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //仅判断S1按键,避免交叉影响
          {
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_ENTER_CONTINUITY_TIME)//该时间是“单击”与“连击”的分界线
            {
Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
                  Su8KeyStep=5;    //S1按键进入有节奏的连续触发      
}
}
          else //如果期间检查到S1按键已经松手
          {
            Su8KeyStep=1;    //返回步骤1继续扫描
            Su8KeyLock=0;
            Su16KeyCnt=0;

            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
               Su8ColumnRecord=0;
            }   
          }

          break;
   case 5://S1按键进入有节奏的连续触发
          if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //仅判断S1按键,避免交叉影响
          {
Su16KeyCnt++;
            if(Su16KeyCnt>=KEY_CONTINUITY_TIME)//该时间是“连击”的时间
            {
                  Su16KeyCnt=0;         //清零,为了继续连击。
                  vGu8KeySec=1;         //触发一次S1按键   
vGu8ShieldVoiceFlag=1;//因为连击,把当前按键触发的声音屏蔽掉   
}
}
          else //如果期间检查到S1按键已经松手
          {
            Su8KeyStep=1;    //返回步骤1继续扫描
            Su8KeyLock=0;
            Su16KeyCnt=0;

            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
               Su8ColumnRecord=0;
            }   
          }
          break;

/*---------S9按键的专属区----------------*/
   case 6:
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3) //仅判断S9按键,避免交叉影响
          {
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_ENTER_CONTINUITY_TIME)//该时间是“单击”与“连击”的分界线
            {
Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
                  Su8KeyStep=7;    //S9按键进入有节奏的连续触发      
}
}
          else //如果期间检查到S9按键已经松手
          {
            Su8KeyStep=1;    //返回步骤1继续扫描
            Su8KeyLock=0;
            Su16KeyCnt=0;

            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
               Su8ColumnRecord=0;
            }   
          }

          break;
   case 7://S9按键进入有节奏的连续触发
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3) //仅判断S9按键,避免交叉影响
          {
Su16KeyCnt++;
            if(Su16KeyCnt>=KEY_CONTINUITY_TIME)//该时间是“连击”的时间
            {
                  Su16KeyCnt=0;         //清零,为了继续连击。
                  vGu8KeySec=9;         //触发一次S9按键   
vGu8ShieldVoiceFlag=1;//因为连击,把当前按键触发的声音屏蔽掉   
}
}
          else //如果期间检查到S9按键已经松手
          {
            Su8KeyStep=1;    //返回步骤1继续扫描
            Su8KeyLock=0;
            Su16KeyCnt=0;

            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
               Su8ColumnRecord=0;
            }   
          }
          break;


   }

}

void KeyTask(void)   
{
if(0==vGu8KeySec)
{
return;
}

switch(vGu8KeySec)
{
   case 1:   //S1按键的任务
if(Gu8LedStatus>0)
{
Gu8LedStatus--;//控制LED“往左边跑”
Gu8DisplayUpdate=1;//刷新显示
}

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

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

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

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

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

   default:

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();
                   }

}
          }         
}




jianhong_wu 发表于 2018-2-4 12:14:19

本帖最后由 jianhong_wu 于 2018-2-4 12:31 编辑

第一百零六节: 矩阵按键按住不松手的“先加速后匀速”触发。

【106.1   按住不松手的先加速后匀速触发。】

            
                上图106.1.1有源蜂鸣器电路

      
                上图106.1.2LED电路


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

      矩阵按键与前面章节“独立按键按住不松手的先加速后匀速的触发”的处理思路是一样的。 当“连续加”或者“连续减”的数据范围很大的时候,就需要按键的加速与匀速相结合的触发方式。“加速”是指按住按键不松手,按键刚开始触发是从慢到快的渐进过程,当“加速”到某个特别快的速度的时候,就“不再加速”,而是以该“恒定高速”进行“连续匀速”触发。这种触发方式,“加速”和“匀速”是相辅相成缺一不可的,为什么?假如没有“加速”只有“匀速”,那么刚按下按键就直接以最高速的“匀速”进行,就会跑过头,缺乏微调功能;而假如没有“匀速”只有“加速”,那么按下按键不松手后,速度就会一直不断飙升,最后失控过冲。
       本节例程实现的功能如下:
      (1)要更改一个“设置参数”(一个全局变量),参数的范围是0到800。
      (2)8个受“设置参数”控制的跑马灯在某一时刻只有1个LED亮,每触发一次S1按键,该“设置参数”就自减1,最小值为0;相反,每触发一次S9按键,该“设置参数”就自加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_TIME20    //按键单击的“滤波”时间

#define KEY_ENTER_CONTINUITY_TIME    240//按键“从单击进入连击”的间隔时间
#define KEY_CONTINUITY_INITIAL_TIME    64//按键“连击”起始的预设间隔时间
#define KEY_SUB_DT_TIME   6      //按键在“加速”时每次减小的时间。
#define KEY_CONTINUITY_MIN_TIME   8//按键时间减小到最后的“匀速”间隔时间。

#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 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;

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个灯亮
}
}
}

/* 注释二:
*本节破题的关键:
*矩阵按键涉及的按键数量很多,但是实际项目上一般只需要少数个别按键具备这种
*“单击”与“先加速后均匀触发”的特殊技能,因此,在代码上,必须把这类“特殊技能按键”与
*“大众按键”区分开来,才能相互清晰互不干扰。本节的“特殊技能按键”是S1和S9。
*如果觉得本节的讲解不够详细具体,请先阅读一下前面章节“独立按键按住不松手的先加速后匀速触发”。
*/

void KeyScan(void)//此函数放在定时中断里每1ms扫描一次
{
   static unsigned char Su8KeyLock=0;      
   static unsigned intSu16KeyCnt=0;
   static unsigned char Su8KeyStep=1;
   static unsigned intSu16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值

   static unsigned char Su8ColumnRecord=0;

   switch(Su8KeyStep)
   {
   case 1:   
          if(0==Su8ColumnRecord)
{
          COLUMN_OUTPUT1=0;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=1;
}
          else if(1==Su8ColumnRecord)
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=0;
          COLUMN_OUTPUT3=1;
}
          else   
{
          COLUMN_OUTPUT1=1;      
          COLUMN_OUTPUT2=1;
          COLUMN_OUTPUT3=0;
}
          Su16KeyCnt=0;
          Su8KeyStep++;
          break;

   case 2:      //等待列输出稳定,但不是去抖动延时
          Su16KeyCnt++;
          if(Su16KeyCnt>=2)
          {
             Su16KeyCnt=0;
             Su8KeyStep++;   
          }
          break;

   case 3:
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
          {
            Su8KeyStep=1;    //返回步骤1继续扫描
            Su8KeyLock=0;
            Su16KeyCnt=0;
Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值。重装初始值。

            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
               Su8ColumnRecord=0;
            }   
          }
          else if(0==Su8KeyLock)
          {
            if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;

                      if(0==Su8ColumnRecord)
                      {
                            vGu8KeySec=1;    //触发一次单击
Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
                            Su8KeyStep=4;    //跳到S1按键的专属区,脱离大众按键
                      }
                      else if(1==Su8ColumnRecord)
                      {
                            vGu8KeySec=2;   
                      }
                      else if(2==Su8ColumnRecord)
                      {
                            vGu8KeySec=3;
                      }
                  }

            }
            else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;

                      if(0==Su8ColumnRecord)
                      {
                           vGu8KeySec=4;
                      }
                      else if(1==Su8ColumnRecord)
                      {
                           vGu8KeySec=5;
                      }
                      else if(2==Su8ColumnRecord)
                      {
                           vGu8KeySec=6;
                      }
                  }   
            }
            else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
            {
                  Su16KeyCnt++;
                  if(Su16KeyCnt>=KEY_SHORT_TIME)
                  {
                      Su8KeyLock=1;
                      if(0==Su8ColumnRecord)
                      {
                           vGu8KeySec=7;
                      }
                      else if(1==Su8ColumnRecord)
                      {
                           vGu8KeySec=8;
                      }
                      else if(2==Su8ColumnRecord)
                      {
                            vGu8KeySec=9;    //触发一次单击
Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
                            Su8KeyStep=6;    //跳到S9按键的专属区,脱离大众按键
                      }
                  }   
            }

          }
          break;

/*---------S1按键的专属区----------------*/
   case 4:
          if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //仅判断S1按键,避免交叉影响
          {
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_ENTER_CONTINUITY_TIME)//该时间是“单击”与“连击”的分界线
            {
Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
                  Su8KeyStep=5;    //S1按键进入有节奏的连续触发      
}
}
          else //如果期间检查到S1按键已经松手
          {
            Su8KeyStep=1;    //返回步骤1继续扫描
            Su8KeyLock=0;
            Su16KeyCnt=0;
Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值。重装初始值。

            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
               Su8ColumnRecord=0;
            }   
          }

          break;
   case 5://S1按键进入有节奏的连续触发
          if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //仅判断S1按键,避免交叉影响
          {
Su16KeyCnt++;
            if(Su16KeyCnt>=Su16KeyContinuityTime)//该时间是“刚开始不断减小,最后不变”
            {
                  Su16KeyCnt=0;         //清零,为了继续连击。
                  vGu8KeySec=1;         //触发一次S1按键   
vGu8ShieldVoiceFlag=1;//因为连击,把当前按键触发的声音屏蔽掉   
if(Su16KeyContinuityTime>=KEY_SUB_DT_TIME)
{
          //Su16KeyContinuityTime数值不断被减小,按键的触发速度就不断变快
Su16KeyContinuityTime=Su16KeyContinuityTime-KEY_SUB_DT_TIME;//变快节奏
}

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

}
}
          else //如果期间检查到S1按键已经松手
          {
            Su8KeyStep=1;    //返回步骤1继续扫描
            Su8KeyLock=0;
            Su16KeyCnt=0;
Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值。重装初始值。


            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
               Su8ColumnRecord=0;
            }   
          }
          break;

/*---------S9按键的专属区----------------*/
   case 6:
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3) //仅判断S9按键,避免交叉影响
          {
Su16KeyCnt++;
if(Su16KeyCnt>=KEY_ENTER_CONTINUITY_TIME)//该时间是“单击”与“连击”的分界线
            {
Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
                  Su8KeyStep=7;    //S9按键进入有节奏的连续触发      
}
}
          else //如果期间检查到S9按键已经松手
          {
            Su8KeyStep=1;    //返回步骤1继续扫描
            Su8KeyLock=0;
            Su16KeyCnt=0;
Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值。重装初始值。

            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
               Su8ColumnRecord=0;
            }   
          }

          break;
   case 7://S9按键进入有节奏的连续触发
          if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3) //仅判断S9按键,避免交叉影响
          {
Su16KeyCnt++;
            if(Su16KeyCnt>=Su16KeyContinuityTime)//该时间是“刚开始不断减小,最后不变”
            {
                  Su16KeyCnt=0;         //清零,为了继续连击。
                  vGu8KeySec=9;         //触发一次S9按键   
vGu8ShieldVoiceFlag=1;//因为连击,把当前按键触发的声音屏蔽掉   
if(Su16KeyContinuityTime>=KEY_SUB_DT_TIME)
{
          //Su16KeyContinuityTime数值不断被减小,按键的触发速度就不断变快
Su16KeyContinuityTime=Su16KeyContinuityTime-KEY_SUB_DT_TIME;//变快节奏
}

//最小间隔时间KEY_CONTINUITY_MIN_TIME就是“高速匀速”
if(Su16KeyContinuityTime<KEY_CONTINUITY_MIN_TIME)
{
//最后以KEY_CONTINUITY_MIN_TIME时间为最高速进行“匀速”
Su16KeyContinuityTime=KEY_CONTINUITY_MIN_TIME;
}
}
}
          else //如果期间检查到S9按键已经松手
          {
            Su8KeyStep=1;    //返回步骤1继续扫描
            Su8KeyLock=0;
            Su16KeyCnt=0;
Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;//动态时间阀值。重装初始值。

            Su8ColumnRecord++;
            if(Su8ColumnRecord>=3)
            {
               Su8ColumnRecord=0;
            }   
          }
          break;


   }
}

void KeyTask(void)   
{
if(0==vGu8KeySec)
{
return;
}

switch(vGu8KeySec)
{
   case 1:   //S1按键的任务
      if(Gu16SetData>0)
{
Gu16SetData--;   //“设置参数”
Gu8DisplayUpdate=1;//刷新显示
}

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

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

   case 9:   //S9按键的任务

      if(Gu16SetData<800)
{
Gu16SetData++;       //“设置参数”
Gu8DisplayUpdate=1;//刷新显示
}

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

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

   default:

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();
                   }

}
          }         
}





jianhong_wu 发表于 2018-2-11 10:58:35

本帖最后由 jianhong_wu 于 2018-2-11 11:17 编辑

第一百零七节: 开关感应器的识别与软件滤波。

【107.1   开关感应器的识别与软件滤波。】


         
                上图107.1.1独立按键模拟开关感应器

      
                上图107.1.2LED电路

       什么叫开关感应器?凡是只能输出0和1这两种状态的感应器都可以统称为开关感应器。前面花了大量的章节讲按键,按键的识别主要是识别电平变化状态的“下降沿”,程序代码中有1个特别的变量标志叫“自锁标志”,还有1个用来消除抖动的“计时器”。本节讲的开关感应器跟按键很相似,差别在于,开关感应器是识别电平变化状态的“电平”,程序代码中没有“自锁标志”,但是多增加了1个用来消除抖动的“计时器”,也就是一共有两个用来消除抖动的“计时器”,这两个“计时器”相互“清零”相互“抗衡”,从而实现了开关感应器的“消抖”处理,专业术语也叫“软件滤波”。消抖的时间跟按键差不多,我的经验值是20ms到30ms之间,我平时在项目中喜欢用20ms。
       在显示框架方面,除了之前讲过Gu8DisplayUpdate这类“显示刷新变量”,本节介绍另外一种常用的显示框架,原理是“某数值跟上一次对比,如果发生了变化(两数值不一样),则自动刷新显示,并及时记录当前值”。
       本节例程实现的功能如下:用K1独立按键模拟开关感应器,K1独立按键“没有被按下”时是高电平,单片机识别到这种“高电平”,就让P1.4所在的LED灯发亮;K1独立按键“被按下”时是低电平,单片机识别到这种“低电平”,就让P1.4所在的LED灯熄灭。

#include "REG52.H"

#define SENSOR_TIME20    //开关感应器的“滤波”时间

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

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

sbit P1_4=P1^4;      
sbit Sensor_K1_sr=P2^2;   //开关感应器K1所在的引脚

volatile unsigned char vGu8Sensor_K1=0;//K1开关感应器的当前电平状态。

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

/* 注释一:
* 后缀为_Last这类“对比上一次数值发生变化而自动刷新显示”在“显示框架”里是很常见的,
* 目的是,既能及时刷新显示,又能避免主函数“不断去执行显示代码”而影响程序效率。
*/

void DisplayTask(void)   //显示的任务函数(LED显示状态)
{
   // Su8Sensor_K1_Last初始化取值255,只要不为0或者1就行,目的是让上电就发生第一次刷新。
static unsigned char Su8Sensor_K1_Last=255;//记录K1开关感应器上一次的电平状态。

if(Su8Sensor_K1_Last!=vGu8Sensor_K1)//如果当前值与上一次值不一样,就自动刷新
{
Su8Sensor_K1_Last=vGu8Sensor_K1;//及时记录最新值,避免主函数“不断去执行显示代码”

      if(0==vGu8Sensor_K1) //如果当前电平状态为“低电平”,LED熄灭
{
P1_4=1;//LED熄灭
}
      else//如果当前电平状态为“高电平”,LED发亮
{
P1_4=0;//LED发亮
}
}
}

/* 注释二:
*本节破题的关键:
*两个“计时器”相互“清零”相互“抗衡”,从而实现了开关感应器的“消抖”处理,
*专业术语也叫“软件滤波”。这种滤波方式,不管是从“高转成低”,还是“低转成高”,
*如果在某个瞬间出现干扰抖动,某个计数器都会及时被“清零”,从而起到非常高效的消抖滤波作用。
*/

void SensorScan(void)//此函数放在定时中断里每1ms扫描一次,用来识别和滤波开关感应器
{
      static unsigned int Su16Sensor_K1_H_Cnt=0;//判断高电平的计时器
      static unsigned int Su16Sensor_K1_L_Cnt=0;//判断低电平的计时器

      if(0==Sensor_K1_sr)
          {
                  Su16Sensor_K1_H_Cnt=0;//在判断低电平的时候,高电平的计时器被清零,巧妙极了!
                  Su16Sensor_K1_L_Cnt++;
                  if(Su16Sensor_K1_L_Cnt>=SENSOR_TIME)
                  {
                      Su16Sensor_K1_L_Cnt=0;
                        vGu8Sensor_K1=0;   //此全局变量反馈当前电平的状态
                  }
         
          }
          else
          {
                  Su16Sensor_K1_L_Cnt=0;   //在判断高电平的时候,低电平的计时器被清零,巧妙极了!
                  Su16Sensor_K1_H_Cnt++;
                  if(Su16Sensor_K1_H_Cnt>=SENSOR_TIME)
                  {
                      Su16Sensor_K1_H_Cnt=0;
                        vGu8Sensor_K1=1;//此全局变量反馈当前电平的状态
                  }
          }
}

void T0_time() interrupt 1   
{
SensorScan();//开关感应器的识别与软件滤波处理

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)
{

}



jianhong_wu 发表于 2018-2-23 14:15:22

本帖最后由 jianhong_wu 于 2018-2-23 14:44 编辑

第一百零八节: 按键控制跑马灯的启动和暂停和停止。

【108.1   按键控制跑马灯的启动和暂停和停止。】



                上图108.1.1独立按键

         
                上图108.1.2LED电路

                     
                上图108.1.3有源蜂鸣器的电路

       在我眼里,按键不仅仅是按键,跑马灯不仅仅是跑马灯。按键是输入设备,跑马灯是应用程序。本节表面上讲按键控制跑马灯的简单项目,实际上作者用心良苦立意深远,试图通过按键与跑马灯,来分享一种输入设备如何关联应用程序的程序框架。
       本节例程实现的功能如下:
      (1)【启动暂停】按键K1。按下【启动暂停】按键K1启动之后,跑马灯处于“启动”状态,4个LED灯从左到右依次循环的变亮,给人“跑”起来的感觉。此时如果再按一次【启动暂停】按键K1,则跑马灯处于“暂停”状态,如果再按一次【启动暂停】按键K1,跑马灯又变回“启动”状态。因此,【启动暂停】按键K1是专门用来切换“启动”和“暂停”这两种状态。
      (2)【停止】按键K2。当跑马灯处于“启动”或者“暂停”或者“停止”的状态时,只要按下【停止】按键K2,当前的运动状态就终止,强制变回初始的“停止”状态,类似“复位”按键的作用。当跑马灯处于“停止”状态时,此时再按下【启动暂停】按键K1之后,跑马灯又处于“启动”状态。
#include "REG52.H"

#define KEY_VOICE_TIME   50
#define KEY_FILTER_TIME25
#define RUN_TIME200   //跑马灯的跑动速度的时间参数

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 RunTask(void);   //跑马灯的任务函数

//4个跑马灯的输出口
sbit P1_4=P1^4;
sbit P1_5=P1^5;
sbit P1_6=P1^6;
sbit P3_3=P3^3;

//蜂鸣器的输出口
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;

volatile unsigned char vGu8KeySec=0;

unsigned char Gu8RunStart=0;   //控制跑马灯启动的总开关
unsigned char Gu8RunStatus=0;//标识跑马灯当前的状态。0代表停止,1代表启动,2代表暂停。

volatile unsigned char vGu8RunTimerFlag=0;   //用于控制跑马灯跑动速度的定时器
volatile unsigned int vGu16RunTimerCnt=0;

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

void T0_time() interrupt 1   
{
VoiceScan();
KeyScan();   

if(1==vGu8RunTimerFlag&&vGu16RunTimerCnt>0)//用于控制跑马灯跑动速度的定时器
{
vGu16RunTimerCnt--;
}

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)
{
//跑马灯处于初始化的状态
P1_4=0;   //第1个灯亮
P1_5=1;   //第2个灯灭
P1_6=1;   //第3个灯灭
P3_3=1;   //第4个灯灭

}

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();
                   }

}
          }         
}

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

   //【启动暂停】按键K1的扫描识别
   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;
      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
         Su8KeyLock1=1;
         vGu8KeySec=1;    //触发1号键
      }
   }

   //【停止】按键K2的扫描识别
   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;
         vGu8KeySec=2;    //触发2号键
      }
   }


}

/* 注释一:
*本节破题的关键:
*在KeyTask和RunTask两个任务函数之间,主要是靠Gu8RunStart和Gu8RunStatus这两个
*全局变量来传递信息。
*/

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

switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:   //1号按键。【启动暂停】按键K1
      if(0==Gu8RunStatus) //当跑马灯处于“停止”状态时
      {
Gu8RunStart=1;   //总开关“打开”。
Gu8RunStatus=1;//状态切换到“启动”状态
}
      else if(1==Gu8RunStatus) //当跑马灯处于“启动”状态时
      {
Gu8RunStatus=2;//状态切换到“暂停”状态
}
      else//当跑马灯处于“暂停”状态时
      {
Gu8RunStatus=1;//状态切换到“启动”状态
}


      vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
      vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 2:   //2号按键。【停止】按键K2

Gu8RunStart=0;   //总开关“关闭”。
Gu8RunStatus=0;//状态切换到“停止”状态

      vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
      vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

}
}

void RunTask(void)    //跑马灯的任务函数,放在主函数内
{
static unsigned char Su8RunStep=0; //运行的步骤


//当总开关处于“停止”并且“步骤不为0”时,强制把步骤归零,跑马灯初始化。
if(0!=Su8RunStep&&0==Gu8RunStart)
{
Su8RunStep=0; //步骤归零

//跑马灯处于初始化的状态
P1_4=0;   //第1个灯亮
P1_5=1;   //第2个灯灭
P1_6=1;   //第3个灯灭
P3_3=1;   //第4个灯灭

}

switch(Su8RunStep) //屡见屡爱的switch又来了
{
   case 0:
       if(1==Gu8RunStart) //总开关“打开”
{
vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=0;//定时器清零
            Su8RunStep=1;//切换到下一步,启动
}
       break;
   case 1:
       if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=0;   //第1个灯亮
P1_5=1;   //第2个灯灭
P1_6=1;   //第3个灯灭
P3_3=1;   //第4个灯灭

    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1;   //启动定时器
            Su8RunStep=2;//切换到下一步
}

       break;
   case 2:
       if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1;   //第1个灯灭
P1_5=0;   //第2个灯亮
P1_6=1;   //第3个灯灭
P3_3=1;   //第4个灯灭

    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1;   //启动定时器
            Su8RunStep=3;//切换到下一步
}

       break;
   case 3:
       if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1;   //第1个灯灭
P1_5=1;   //第2个灯灭
P1_6=0;   //第3个灯亮
P3_3=1;   //第4个灯灭

    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1;   //启动定时器
            Su8RunStep=4;//切换到下一步
}

       break;
   case 4:
       if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1;   //第1个灯灭
P1_5=1;   //第2个灯灭
P1_6=1;   //第3个灯灭
P3_3=0;   //第4个灯亮

    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1;   //启动定时器
            Su8RunStep=1;//返回到第1步,重新开始下一轮的循环!!!
}

       break;

}

}





jianhong_wu 发表于 2018-3-1 09:05:48

本帖最后由 jianhong_wu 于 2018-3-1 09:28 编辑

第一百零九节: 按键控制跑马灯的方向。

【109.1   按键控制跑马灯的方向。】



                上图109.1.1独立按键

      
                上图109.1.2LED电路

            
                上图109.1.3有源蜂鸣器的电路

      之前108节讲到跑马灯的启动、暂停、停止,本节在此基础上,增加一个“方向”的控制,除了加深理解输入设备如何关联应用程序的程序框架之外,还有一个知识点值得一提,就是如何通过灵活切换switch的“步骤变量”来达到随心所欲的过程控制,本节的“方向”的控制就用到这个方法。
      本节例程的功能如下:
   (1)【启动暂停】按键K1。按下【启动暂停】按键K1启动之后,跑马灯处于“启动”状态,4个LED灯挨个依次循环的变亮,给人“跑”起来的感觉。此时如果再按一次【启动暂停】按键K1,则跑马灯处于“暂停”状态,如果再按一次【启动暂停】按键K1,跑马灯又变回“启动”状态。因此,【启动暂停】按键K1是专门用来切换“启动”和“暂停”这两种状态。
   (2)【停止】按键K2。当跑马灯处于“启动”或者“暂停”或者“停止”的状态时,只要按下【停止】按键K2,当前的运动状态就终止,强制变回初始的“停止”状态,类似“复位”按键的作用。当跑马灯处于“停止”状态时,此时再按下【启动暂停】按键K1之后,跑马灯又处于“启动”状态。
   (3)【方向】按键K3。跑马灯上电后默认处于“往右跑”的方向。每按一次【方向】按键K3,跑马灯就在“往右跑”与“往左跑”两个方向之间切换。#include "REG52.H"

#define KEY_VOICE_TIME   50
#define KEY_FILTER_TIME25
#define RUN_TIME200   //跑马灯的跑动速度的时间参数

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 RunTask(void);   //跑马灯的任务函数

//4个跑马灯的输出口
sbit P1_4=P1^4;
sbit P1_5=P1^5;
sbit P1_6=P1^6;
sbit P3_3=P3^3;

//蜂鸣器的输出口
sbit P3_4=P3^4;

sbit KEY_INPUT1=P2^2;//【启动暂停】按键K1的输入口。
sbit KEY_INPUT2=P2^1;//【停止】按键K2的输入口。
sbit KEY_INPUT3=P2^0;//【方向】按键K3的输入口。

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

volatile unsigned char vGu8KeySec=0;

unsigned char Gu8RunStart=0;      //控制跑马灯启动的总开关
unsigned char Gu8RunStatus=0;   //标识跑马灯当前的状态。0代表停止,1代表启动,2代表暂停。
unsigned char Gu8RunDirection=0;//标识跑马灯当前的方向。0代表往右跑,1代表往左跑。

volatile unsigned char vGu8RunTimerFlag=0;   //用于控制跑马灯跑动速度的定时器
volatile unsigned int vGu16RunTimerCnt=0;

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

void T0_time() interrupt 1   
{
VoiceScan();
KeyScan();   

if(1==vGu8RunTimerFlag&&vGu16RunTimerCnt>0)//用于控制跑马灯跑动速度的定时器
{
vGu16RunTimerCnt--;
}

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)
{
//跑马灯处于初始化的状态
P1_4=0;   //第1个灯亮
P1_5=1;   //第2个灯灭
P1_6=1;   //第3个灯灭
P3_3=1;   //第4个灯灭

}

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();
                   }

}
          }         
}

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


   //【启动暂停】按键K1的扫描识别
   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;
      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
         Su8KeyLock1=1;
         vGu8KeySec=1;    //触发1号键
      }
   }

   //【停止】按键K2的扫描识别
   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;
         vGu8KeySec=2;    //触发2号键
      }
   }

   //【方向】按键K3的扫描识别
   if(0!=KEY_INPUT3)
   {
      Su8KeyLock3=0;
      Su16KeyCnt3=0;      
   }
   else if(0==Su8KeyLock3)
   {
      Su16KeyCnt3++;
      if(Su16KeyCnt3>=KEY_FILTER_TIME)
      {
         Su8KeyLock3=1;
         vGu8KeySec=3;    //触发3号键
      }
   }
}

/* 注释一:
*本节破题的关键:
*在KeyTask和RunTask两个任务函数之间,主要是靠Gu8RunStart、Gu8RunStatus、Gu8RunDirection
*这三个全局变量来传递信息。
*/

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

switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:   //1号按键。【启动暂停】按键K1
      if(0==Gu8RunStatus) //当跑马灯处于“停止”状态时
      {
Gu8RunStart=1;   //总开关“打开”。
Gu8RunStatus=1;//状态切换到“启动”状态
}
      else if(1==Gu8RunStatus) //当跑马灯处于“启动”状态时
      {
Gu8RunStatus=2;//状态切换到“暂停”状态
}
      else//当跑马灯处于“暂停”状态时
      {
Gu8RunStatus=1;//状态切换到“启动”状态
}


      vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
      vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 2:   //2号按键。【停止】按键K2

Gu8RunStart=0;   //总开关“关闭”。
Gu8RunStatus=0;//状态切换到“停止”状态

      vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
      vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 3:   //3号按键。【方向】按键K3
      //每按一次K3按键,Gu8RunDirection就在0和1之间切换,从而控制方向
      if(0==Gu8RunDirection)
{
Gu8RunDirection=1;
}
else
{
Gu8RunDirection=0;
}

      vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
      vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

}
}

/* 注释二:
* “方向”的控制,是通过Gu8RunDirection的判断,来灵活切换switch的“步骤变量”来达到
*随心所欲的过程控制。
*/

void RunTask(void)    //跑马灯的任务函数,放在主函数内
{
static unsigned char Su8RunStep=0; //运行的步骤


//当总开关处于“停止”并且“步骤不为0”时,强制把步骤归零,跑马灯初始化。
if(0!=Su8RunStep&&0==Gu8RunStart)
{
Su8RunStep=0; //步骤归零

//跑马灯处于初始化的状态
P1_4=0;   //第1个灯亮
P1_5=1;   //第2个灯灭
P1_6=1;   //第3个灯灭
P3_3=1;   //第4个灯灭

}

switch(Su8RunStep) //屡见屡爱的switch又来了
{
   case 0:
       if(1==Gu8RunStart) //总开关“打开”
{
vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=0;//定时器清零
            Su8RunStep=1;//切换到下一步,启动
}
       break;
   case 1:
       if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=0;   //第1个灯亮
P1_5=1;   //第2个灯灭
P1_6=1;   //第3个灯灭
P3_3=1;   //第4个灯灭

    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1;   //启动定时器

//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
               Su8RunStep=2;
}
else//往左跑
{
               Su8RunStep=4;
}

}

       break;
   case 2:
       if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1;   //第1个灯灭
P1_5=0;   //第2个灯亮
P1_6=1;   //第3个灯灭
P3_3=1;   //第4个灯灭

    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1;   //启动定时器

//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
               Su8RunStep=3;
}
else//往左跑
{
               Su8RunStep=1;
}
}

       break;
   case 3:
       if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1;   //第1个灯灭
P1_5=1;   //第2个灯灭
P1_6=0;   //第3个灯亮
P3_3=1;   //第4个灯灭

    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1;   //启动定时器

//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
               Su8RunStep=4;
}
else//往左跑
{
               Su8RunStep=2;
}
}

       break;
   case 4:
       if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1;   //第1个灯灭
P1_5=1;   //第2个灯灭
P1_6=1;   //第3个灯灭
P3_3=0;   //第4个灯亮

    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
vGu8RunTimerFlag=1;   //启动定时器

//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
               Su8RunStep=1;
}
else//往左跑
{
               Su8RunStep=3;
}
}

       break;

}

}


jianhong_wu 发表于 2018-3-4 10:13:53

本帖最后由 jianhong_wu 于 2018-3-4 10:28 编辑

第一百一十节: 按键控制跑马灯的速度。

【110.1   按键控制跑马灯的速度。】



                上图110.1.1独立按键

   
                上图110.1.2LED电路

                  
                上图110.1.3有源蜂鸣器的电路

      之前109节讲到跑马灯的启动、暂停、停止、方向,本节在此基础上,把原来的“停止”更改为“速度”,
加深理解输入设备如何关联应用程序的程序框架。
      本节例程的功能如下:
   (1)【启动暂停】按键K1。上电后,按下【启动暂停】按键K1启动之后,跑马灯处于“启动”状态,4个LED灯挨个依次循环的变亮,给人“跑”起来的感觉。此时如果再按一次【启动暂停】按键K1,则跑马灯处于“暂停”状态,如果再按一次【启动暂停】按键K1,跑马灯又变回“启动”状态。因此,【启动暂停】按键K1是专门用来切换“启动”和“暂停”这两种状态。
   (2)【速度】按键K2。每按一次【速度】按键K2,跑马灯就在“慢”、“中”、“快”三档速度之间切换。
   (3)【方向】按键K3。跑马灯上电后默认处于“往右跑”的方向。每按一次【方向】按键K3,跑马灯就在“往右跑”与“往左跑”两个方向之间切换。
#include "REG52.H"

#define KEY_VOICE_TIME   50
#define KEY_FILTER_TIME25

#define RUN_TIME_SLOW    500   //“慢”档速度的时间参数
#define RUN_TIME_MIDDLE300   //“中”档速度的时间参数
#define RUN_TIME_FAST    100   //“快”档速度的时间参数

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 RunTask(void);   //跑马灯的任务函数

//4个跑马灯的输出口
sbit P1_4=P1^4;
sbit P1_5=P1^5;
sbit P1_6=P1^6;
sbit P3_3=P3^3;

//蜂鸣器的输出口
sbit P3_4=P3^4;

sbit KEY_INPUT1=P2^2;//【启动暂停】按键K1的输入口。
sbit KEY_INPUT2=P2^1;//【速度】按键K2的输入口。
sbit KEY_INPUT3=P2^0;//【方向】按键K3的输入口。

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

volatile unsigned char vGu8KeySec=0;

unsigned char Gu8RunStart=0;      //控制跑马灯启动的总开关
unsigned char Gu8RunStatus=0;   //标识跑马灯当前的状态。0代表停止,1代表启动,2代表暂停。
unsigned char Gu8RunDirection=0;//标识跑马灯当前的方向。0代表往右跑,1代表往左跑。
unsigned char Gu8RunSpeed=0;      //当前的速度档位。0代表“慢”,1代表“中”,2代表“快”。
unsigned intGu16RunSpeedTimeDate=0; //承接各速度档位的时间参数的变量

volatile unsigned char vGu8RunTimerFlag=0;   //用于控制跑马灯跑动速度的定时器
volatile unsigned int vGu16RunTimerCnt=0;

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

void T0_time() interrupt 1   
{
VoiceScan();
KeyScan();   

if(1==vGu8RunTimerFlag&&vGu16RunTimerCnt>0)//用于控制跑马灯跑动速度的定时器
{
vGu16RunTimerCnt--;
}

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)
{
//跑马灯处于初始化的状态
P1_4=0;   //第1个灯亮
P1_5=1;   //第2个灯灭
P1_6=1;   //第3个灯灭
P3_3=1;   //第4个灯灭

//根据当前的速度档位Gu8RunSpeed,来初始化速度时间参数Gu16RunSpeedTimeDate
if(0==Gu8RunSpeed)
{
Gu16RunSpeedTimeDate=RUN_TIME_SLOW; //赋值“慢”档的时间参数
}
else if(1==Gu8RunSpeed)
{
Gu16RunSpeedTimeDate=RUN_TIME_MIDDLE; //赋值“中”档的时间参数
}
else
{
Gu16RunSpeedTimeDate=RUN_TIME_FAST; //赋值“快”档的时间参数
}


}

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();
                   }

}
          }         
}

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


   //【启动暂停】按键K1的扫描识别
   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;
      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
         Su8KeyLock1=1;
         vGu8KeySec=1;    //触发1号键
      }
   }

   //【速度】按键K2的扫描识别
   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;
         vGu8KeySec=2;    //触发2号键
      }
   }

   //【方向】按键K3的扫描识别
   if(0!=KEY_INPUT3)
   {
      Su8KeyLock3=0;
      Su16KeyCnt3=0;      
   }
   else if(0==Su8KeyLock3)
   {
      Su16KeyCnt3++;
      if(Su16KeyCnt3>=KEY_FILTER_TIME)
      {
         Su8KeyLock3=1;
         vGu8KeySec=3;    //触发3号键
      }
   }
}

/* 注释一:
*本节破题的关键:
*在KeyTask和RunTask两个任务函数之间,主要是靠Gu8RunStart、Gu8RunStatus、Gu8RunDirection、
*Gu16RunSpeedTimeDate这四个全局变量来传递信息。
*/

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

switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:   //1号按键。【启动暂停】按键K1
      if(0==Gu8RunStatus) //当跑马灯处于“停止”状态时
      {
Gu8RunStart=1;   //总开关“打开”。
Gu8RunStatus=1;//状态切换到“启动”状态
}
      else if(1==Gu8RunStatus) //当跑马灯处于“启动”状态时
      {
Gu8RunStatus=2;//状态切换到“暂停”状态
}
      else//当跑马灯处于“暂停”状态时
      {
Gu8RunStatus=1;//状态切换到“启动”状态
}

      vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
      vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 2:   //2号按键。【速度】按键K2

      //每按一次K2按键,Gu8RunSpeed就在0、1、2三者之间切换,并且根据Gu8RunSpeed的数值,
//对Gu16RunSpeedTimeDate赋值不同的速度时间参数,从而控制速度档位
      if(0==Gu8RunSpeed)
{
Gu8RunSpeed=1;//“中”档
Gu16RunSpeedTimeDate=RUN_TIME_MIDDLE; //赋值“中”档的时间参数
}
      else if(1==Gu8RunSpeed)
{
Gu8RunSpeed=2;   //“快”档
Gu16RunSpeedTimeDate=RUN_TIME_FAST; //赋值“快”档的时间参数
}
else
{
Gu8RunSpeed=0;   //“慢”档
Gu16RunSpeedTimeDate=RUN_TIME_SLOW; //赋值“慢”档的时间参数
}

      vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
      vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

   case 3:   //3号按键。【方向】按键K3
      //每按一次K3按键,Gu8RunDirection就在0和1之间切换,并且对从而控制方向
      if(0==Gu8RunDirection)
{
Gu8RunDirection=1;
}
else
{
Gu8RunDirection=0;
}

      vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
      vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

}
}

/* 注释二:
* “速度”是受Gu16RunSpeedTimeDate具体数值大小的影响
*/

void RunTask(void)    //跑马灯的任务函数,放在主函数内
{
static unsigned char Su8RunStep=0; //运行的步骤


//当总开关处于“停止”并且“步骤不为0”时,强制把步骤归零,跑马灯初始化。
if(0!=Su8RunStep&&0==Gu8RunStart)
{
Su8RunStep=0; //步骤归零

//跑马灯处于初始化的状态
P1_4=0;   //第1个灯亮
P1_5=1;   //第2个灯灭
P1_6=1;   //第3个灯灭
P3_3=1;   //第4个灯灭

}

switch(Su8RunStep) //屡见屡爱的switch又来了
{
   case 0:
       if(1==Gu8RunStart) //总开关“打开”
{
vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=0;//定时器清零
            Su8RunStep=1;//切换到下一步,启动
}
       break;
   case 1:
       if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=0;   //第1个灯亮
P1_5=1;   //第2个灯灭
P1_6=1;   //第3个灯灭
P3_3=1;   //第4个灯灭

    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度
vGu8RunTimerFlag=1;   //启动定时器

//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
               Su8RunStep=2;
}
else//往左跑
{
               Su8RunStep=4;
}

}

       break;
   case 2:
       if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1;   //第1个灯灭
P1_5=0;   //第2个灯亮
P1_6=1;   //第3个灯灭
P3_3=1;   //第4个灯灭

    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度
vGu8RunTimerFlag=1;   //启动定时器

//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
               Su8RunStep=3;
}
else//往左跑
{
               Su8RunStep=1;
}
}

       break;
   case 3:
       if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1;   //第1个灯灭
P1_5=1;   //第2个灯灭
P1_6=0;   //第3个灯亮
P3_3=1;   //第4个灯灭

    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度
vGu8RunTimerFlag=1;   //启动定时器

//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
               Su8RunStep=4;
}
else//往左跑
{
               Su8RunStep=2;
}
}

       break;
   case 4:
       if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
{
P1_4=1;   //第1个灯灭
P1_5=1;   //第2个灯灭
P1_6=1;   //第3个灯灭
P3_3=0;   //第4个灯亮

    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度
vGu8RunTimerFlag=1;   //启动定时器

//灵活切换“步骤变量”
if(0==Gu8RunDirection) //往右跑
{
               Su8RunStep=1;
}
else//往左跑
{
               Su8RunStep=3;
}
}

       break;

}

}





jianhong_wu 发表于 2018-3-11 13:48:05

本帖最后由 jianhong_wu 于 2018-3-11 14:01 编辑

第一百一十一节: 工业自动化设备的开关信号的运动控制。

【111.1   开关信号的运动控制。】



                上图111.1.1独立按键

   
                上图111.1.2LED电路

               
                上图111.1.3有源蜂鸣器的电路

       本节涉及的知识点有,switch的过程控制,时间延时,开关感应器的软件滤波,工件计数器,以及整体的软件框架。
       现在有一台设备,水平方向有一个滑块,能左右移动,滑块上安装了一个能垂直伸缩的“机械手”。按下启动按键后,滑块先从左边往右边移动,移到最右边碰到“右感应器”后,滑块上的“机械手”开始往下移动2秒,移动2秒后开始原路返回,“机械手”向上移动,碰到“上感应器”后,滑块开始往左边移动,移动3秒后默认已经回到原位最左边,此时“计数器”累加1,完成一次过程,如果再按下启动按键,继续重复这个过程。
       这个设备用了2个气缸。1个“水平气缸”驱动滑块水平方向的左右移动,当控制“水平气缸”的输出信号为0时往左边跑,当控制“水平气缸”的输出信号为1时往右边跑。另1个“垂直气缸”驱动“机械手”的上下移动,当控制“垂直气缸”的输出信号为0时往上边跑,当控制“垂直气缸”的输出信号为1时往下边跑。
       这个设备用了2个开关感应器。分别是“右感应器”和“上感应器”。当感应器没有被碰到的时候信号为1,当感应器被碰到的时候信号为0。
       这个设备用了1个独立按键。控制运动的启动。
       2个气缸是输出信号,用P1.4和P1.5所控制的两个LED模拟。2个开关感应器是输入信号,用K2和K3这两个独立按键模拟。1个独立按键用K1按键。如上图。
#include "REG52.H"

#define KEY_VOICE_TIME   50
#define KEY_FILTER_TIME25
#define SENSOR_TIME   20      //开关感应器的“滤波”时间


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

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

void GoLeft(void) ; //“水平气缸”往左跑
void GoRight(void); //“水平气缸”往右跑
void GoUp(void);    //“垂直气缸”往上跑
void GoDown(void);//“垂直气缸”往下跑

void VoiceScan(void);
void SensorScan(void);//开关感应器的消抖,在定时中断里调用处理
void KeyScan(void);   
void KeyTask(void);   
void RunTask(void);    //运动控制的任务函数

sbit P1_4=P1^4;//水平气缸的输出
sbit P1_5=P1^5;//垂直气缸的输出

sbit P3_4=P3^4;//蜂鸣器的输出口

sbit KEY_INPUT1=P2^2;//【启动】按键K1的输入口。

sbit SensorRight_sr=P2^1;   //右感应器的输入口
sbit SensorUp_sr=P2^0;      //上感应器的输入口

volatile unsigned char vGu8SensorRight=0;//右感应器经过滤波后的当前电平状态。
volatile unsigned char vGu8SensorUp=0;//上感应器经过滤波后的当前电平状态。

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

volatile unsigned char vGu8KeySec=0;

unsigned char Gu8RunStart=0;      //启动的总开关
unsigned char Gu8RunStatus=0;   //运动的状态,0为停止,1为运行

unsigned intGu16RunCnt=0;       //计数器
unsigned intGu16ReturnLeftTime=3000;   //水平往左跑的延时变量,默认为3秒
unsigned intGu16GoDownTime=2000;       //垂直往下跑的延时变量,默认为2秒

volatile unsigned char vGu8RunTimerFlag=0;   //用于控制运动过程中的延时的定时器
volatile unsigned int vGu16RunTimerCnt=0;

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

/* 注释一:
*两个“计时器”相互“清零”相互“抗衡”,从而实现了开关感应器的“消抖”处理,
*专业术语也叫“软件滤波”。这种滤波方式,不管是从“高转成低”,还是“低转成高”,
*如果在某个瞬间出现干扰抖动,某个计数器都会及时被“清零”,从而起到非常高效的消抖滤波作用。
*/

void SensorScan(void)//此函数放在定时中断里每1ms扫描一次,用来识别和滤波开关感应器
{
      static unsigned int Su16SensorRight_H_Cnt=0;//判断高电平的计时器
      static unsigned int Su16SensorRight_L_Cnt=0;//判断低电平的计时器

      static unsigned int Su16SensorUp_H_Cnt=0;//判断高电平的计时器
      static unsigned int Su16SensorUp_L_Cnt=0;//判断低电平的计时器

      //右感应器的滤波
      if(0==SensorRight_sr)
      {
         Su16SensorRight_H_Cnt=0;//在判断低电平的时候,高电平的计时器被清零,巧妙极了!
         Su16SensorRight_L_Cnt++;
         if(Su16SensorRight_L_Cnt>=SENSOR_TIME)
         {
               Su16SensorRight_L_Cnt=0;
               vGu8SensorRight=0;   //此全局变量反馈经过滤波后“右感应器”当前电平的状态
         }

       }
       else
       {
         Su16SensorRight_L_Cnt=0;   //在判断高电平的时候,低电平的计时器被清零,巧妙极了!
         Su16SensorRight_H_Cnt++;
         if(Su16SensorRight_H_Cnt>=SENSOR_TIME)
         {
               Su16SensorRight_H_Cnt=0;
               vGu8SensorRight=1;//此全局变量反馈经过滤波后“右感应器”当前电平的状态
         }
       }

      //上感应器的滤波
      if(0==SensorUp_sr)
      {
         Su16SensorUp_H_Cnt=0;
         Su16SensorUp_L_Cnt++;
         if(Su16SensorUp_L_Cnt>=SENSOR_TIME)
         {
               Su16SensorUp_L_Cnt=0;
               vGu8SensorUp=0;   //此全局变量反馈经过滤波后“上感应器”当前电平的状态
         }

       }
       else
       {
         Su16SensorUp_L_Cnt=0;   
         Su16SensorUp_H_Cnt++;
         if(Su16SensorUp_H_Cnt>=SENSOR_TIME)
         {
               Su16SensorUp_H_Cnt=0;
               vGu8SensorUp=1;//此全局变量反馈经过滤波后“上感应器”当前电平的状态
         }
       }


}

void T0_time() interrupt 1   
{
VoiceScan();
KeyScan();   
SensorScan();//用来识别和滤波开关感应器

if(1==vGu8RunTimerFlag&&vGu16RunTimerCnt>0)//用于控制运动延时的定时器
{
vGu16RunTimerCnt--;
}

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)
{
   //上电初始化气缸的开机位置
GoLeft() ;   //“水平气缸”往左跑,上电初始化时滑块处于左边
GoUp();      //“垂直气缸”往上跑,上电初始化时“机械臂”处于上方

}

void BeepOpen(void)
{
P3_4=0;
}

void BeepClose(void)
{
P3_4=1;
}

void GoLeft(void) //“水平气缸”往左跑
{
P1_4=0;   
}

void GoRight(void) //“水平气缸”往右跑
{
P1_4=1;   
}

void GoUp(void)//“垂直气缸”往上跑
{
P1_5=0;   
}

void GoDown(void) //“垂直气缸”往下跑
{
P1_5=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();
                   }

}
          }         
}

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

   //【启动】按键K1的扫描识别
   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;
      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
         Su8KeyLock1=1;
         vGu8KeySec=1;    //触发1号键
      }
   }

}

/* 注释二:
*在KeyTask中只改变Gu8RunStart的值,用于总启动开关。而运动状态Gu8RunStatus是在运动函数
*RunTask中改变,用于对外反馈实时的运动状态。
*/

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

switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
{
   case 1:   //1号按键。【启动】按键K1

      if(0==Gu8RunStatus) //根据当前运动的状态来决定“总开关”是否能受按键的控制
      {
Gu8RunStart=1;   //总开关“打开”。
}

      vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=KEY_VOICE_TIME;//触发按键后,发出固定长度的声音
      vGu8BeepTimerFlag=1;
vGu8KeySec=0;//响应按键服务处理程序后,按键编号必须清零,避免一直触发
break;

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

}
}

/* 注释三:
*本节故意引入三个变量:计数器Gu16RunCnt,左延时Gu16ReturnLeftTime,下延时Gu16GoDownTime。
*在人机界面的场合,这三个变量可以用来扩展实现设置参数的功能。比如,如果有数码管,可以通过
*显示Gu16RunCnt的数值来让客户看到当前设备的计数器。如果有数码管和按键,可以通过切换到某个
*界面下,修改Gu16ReturnLeftTime和Gu16GoDownTime的数值,让客户对设备进行延时参数的设置。
*/

void RunTask(void)    //运动控制的任务函数,放在主函数内
{
static unsigned char Su8RunStep=0; //运行的步骤


//当总开关处于“停止”并且“步骤不为0”时,强制把步骤归零。
if(0!=Su8RunStep&&0==Gu8RunStart)
{
Su8RunStep=0; //步骤归零
}

switch(Su8RunStep) //屡见屡爱的switch又来了
{
   case 0:
       if(1==Gu8RunStart) //总开关“打开”
{
Gu8RunStatus=1;//及时设置Gu8RunStatus的运动状态为“运行”

GoRight() ;      //“水平气缸”往右跑。P1.4的LED灯“灭”。

            Su8RunStep=1;//切换到下一步
}
       break;
   case 1:
       if(0==vGu8SensorRight) //直到碰到了“右感应器”(按下K2),“机械臂”才往下移动。
{
GoDown();//“垂直气缸”往下跑。P1.5的LED灯“灭”。
    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=Gu16GoDownTime; //向下移动3秒的延时赋值
vGu8RunTimerFlag=1;   //启动定时器

            Su8RunStep=2;//切换到下一步
}
       break;
   case 2:
       if(0==vGu16RunTimerCnt) //当定时的3秒时间到,“机械臂”才往上移动,开始原路返回。
{
GoUp();    //“垂直气缸”往上跑。P1.5的LED灯“亮”。
            Su8RunStep=3;//切换到下一步
}
       break;
   case 3:
       if(0==vGu8SensorUp) //直到碰到了“上感应器”(按下K3),滑块才往左移动。
{
GoLeft() ; //“水平气缸”往左跑。P1.4的LED灯“亮”。
    vGu8RunTimerFlag=0;   
vGu16RunTimerCnt=Gu16ReturnLeftTime; //向左移动2秒的延时赋值
vGu8RunTimerFlag=1;   //启动定时器

            Su8RunStep=4;//切换到下一步
}

       break;
   case 4:
       if(0==vGu16RunTimerCnt) //当定时的2秒时间到,完成一次过程。
{
    Gu16RunCnt++;   //计数器加1,统计设备运行的次数
Gu8RunStatus=0;//及时设置Gu8RunStatus的运动状态为“停止”
Gu8RunStart=0;//总开关“关闭”,为下一次启动作准备
            Su8RunStep=0;   //步骤变量清零,为下一次启动作准备
}
       break;
}
}





jianhong_wu 发表于 2018-3-30 10:11:35

本帖最后由 jianhong_wu 于 2018-3-30 10:23 编辑

第一百一十二节: 数码管显示的基础知识。

【112.1   数码管显示的基础知识。】
   
                上图112.1.1数码管

   
                上图112.1.2等效图

       如上图112.1.1,一个数码管内部有8个段码,每个段码内部对应一颗能发光的LED灯,把相关位置的段码点亮或熄灭就可以显示出不同的数字或者小数点。比如,要显示一个数字“1”,只需要点亮b和c这两个段码LED即可,其它6个a,d,e,f,g,h段码LED熄灭,就可以显示一个数字“1”。再进一步深入分析数码管内部的等效图(上图112.1.2),com4是右起第1位数码管内部8个段码LED的公共端,要点亮任何一个段码LED的前提必须是公共端com4为低电平(P1.0输出0信号)。如果公共端com4为高电平(P1.0输出1信号),则不管段码端P0口的8个IO口输出什么信号,8个段码LED都是熄灭的(无正压差,则无电流无回路)。因此,公共端(比如com4,com3,com2,com1)就是某个数码管的“总开关”。比如,右起第1位数码管要显示数字“1”,要点亮b和c,则P0.1和P0.2必须输出“1”高电平,其它P0.0,P0.3,P0.4,P0.5,P0.6,P0.7必须输出“0”低电平,把这8个IO口二进制的信号转换成十六进制,则整个P0口总线只需输出一个十六进制的0x06,最后,“总开关”打开,公共端com4输出“0”,即可显示一个数字“1”。如果需要显示其它的不同数字,只需要改变段码端P0口的十六进制输出数值即可,如果提前把要显示的数字放在一个数组里,这个数组就是编码转换表,类似于一个字库表。现在编写一个程序例子,右起第1个和第3个数码管循环显示从0到9的数字,另外右起第2个和第4个数码管则关闭不显示,程序代码如下:
#include "REG52.H"

#define CHANGE_TIME1000    //数码管切换显示数字的时间

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

void DisplayTask(void);    //数码管显示的任务函数

sbit P1_0=P1^0;//右起第1位数码管的公共端com4
sbit P1_1=P1^1;//右起第2位数码管的公共端com3
sbit P1_2=P1^2;//右起第3位数码管的公共端com2
sbit P1_3=P1^3;//右起第4位数码管的公共端com1

//根据原理图得出的共阴数码管编码转换表,类似于一个字库表
code unsigned char Cu8DigTable[]=
{
0x3f,//0       序号0
0x06,//1       序号1
0x5b,//2       序号2
0x4f,//3       序号3
0x66,//4       序号4
0x6d,//5       序号5
0x7d,//6       序号6
0x07,//7       序号7
0x7f,//8       序号8
0x6f,//9       序号9
0x00,//不显示序号10
};
volatile unsigned char vGu8ChangeTimerFlag=0;//控制切换数字的时间的定时器
volatile unsigned int vGu16ChangeTimerCnt=0;

unsigned char Gu8Number=0; //从0到9依次循环显示的数字

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
DisplayTask();    //数码管显示的任务函数
    }
}

void DisplayTask(void)    //数码管显示的任务函数
{
static unsigned char Su8GetCode;//从编码转换表中提取出来的编码。

if(0==vGu16ChangeTimerCnt)//定时的时间到,更新显示下一个数字,依次循环显示
{
Su8GetCode=Cu8DigTable; //从编码转换表中提取出来的编码。

    P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=0;//右起第1位数码管的公共端com4,“总开关”打开,输出低电平0
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=0;//右起第3位数码管的公共端com2,“总开关”打开,输出低电平0
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1

Gu8Number++;//显示的数字不断从0到9累加
if(Gu8Number>9)
{
Gu8Number=0;
}

vGu8ChangeTimerFlag=0;
vGu16ChangeTimerCnt=CHANGE_TIME;
vGu8ChangeTimerFlag=1;//启动新一轮的定时器
}
}

void T0_time() interrupt 1   
{
if(1==vGu8ChangeTimerFlag&&vGu16ChangeTimerCnt>0)//数码管显示切换时间的定时器
{
vGu16ChangeTimerCnt--;
}

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
    //初始化上电瞬间数码管的状态
P1_0=1;//右起第1位数码管的公共端com4,“总开关”关闭,输出低电平1
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;//右起第3位数码管的公共端com2,“总开关”关闭,输出低电平1
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1

TMOD=0x01;
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

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

void PeripheralInitial(void)
{

}






jianhong_wu 发表于 2018-4-12 09:24:56

本帖最后由 jianhong_wu 于 2018-4-12 19:31 编辑

第一百一十三节: 动态扫描的数码管显示数字。

【113.1   动态扫描的数码管。】
      
                上图113.1.1数码管

       上一节,看到打开显示的数码管右起第1个(com4)和第3个(com2)在任意时刻显示的数字是一样的,为什么?因为四个数码管的8个段码a,b,c,d,e,f,g,h所连接的单片机IO口是共用的,如果把四个数码管全部打开(com1,com2,com3,com4全部输出低电平),会发现四个数码管在任意时刻显示的四个数字也是一样的!实际应用中,要四个数码管能各自独立显示不同的数字,就需要用到“分时动态扫描”的方式。所谓分时,就是在任意时刻只能显示其中一个数码管(某个com输出低电平),其它三个数码管关闭(其它三个com输出高电平),每个数码管显示停留的时间固定一致并且非常短暂,四个数码管依次循环的切换显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管是同时亮的(实际不是同时亮),跟动画片“1秒钟动态切换显示多少幅画面”的原理一样。现在编写一个程序例子,四个数码管要显示四个不同的数字“1234”,程序代码如下:
#include "REG52.H"

/* 注释一:
*SCAN_TIME是每个数码管停留显示的短暂时间。这里称为“扫描时间”。这个时间既不能太长也不能
*太短,要调试到恰到好处。太长,则影响其它数码管的显示,会让人觉得画面不连贯不是同时亮;
*太短,又会影响显示的亮度。具体的时间应该根据实际项目不断调试修正而得到最佳显示的数值。
*/

#define SCAN_TIME1   


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

void DisplayScan(void);    //数码管的动态扫描函数,放在定时中断里。

sbit P1_0=P1^0;//右起第1位数码管的公共端com4
sbit P1_1=P1^1;//右起第2位数码管的公共端com3
sbit P1_2=P1^2;//右起第3位数码管的公共端com2
sbit P1_3=P1^3;//右起第4位数码管的公共端com1

//根据原理图得出的共阴数码管编码转换表,类似于一个字库表
code unsigned char Cu8DigTable[]=
{
0x3f,//0       序号0
0x06,//1       序号1
0x5b,//2       序号2
0x4f,//3       序号3
0x66,//4       序号4
0x6d,//5       序号5
0x7d,//6       序号6
0x07,//7       序号7
0x7f,//8       序号8
0x6f,//9       序号9
0x00,//不显示序号10
};

volatile unsigned char vGu8ScanTimerFlag=0;//动态扫描的定时器
volatile unsigned int vGu16ScanTimerCnt=0;

/* 注释二:
*vGu8Display_Righ_4,vGu8Display_Righ_3,vGu8Display_Righ_2,vGu8Display_Righ_1,这四个
*全局变量用来传递每位数码管需要显示的数字,作为对上面应用层调用的接口变量。
*/

volatile unsigned char vGu8Display_Righ_4=1;//右起第4位数码管显示的变量。这里显示“1”
volatile unsigned char vGu8Display_Righ_3=2;//右起第3位数码管显示的变量。这里显示“2”
volatile unsigned char vGu8Display_Righ_2=3;//右起第2位数码管显示的变量。这里显示“3”
volatile unsigned char vGu8Display_Righ_1=4;//右起第1位数码管显示的变量。这里显示“4”

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
    }
}

/* 注释三:
*DisplayScan数码管的动态扫描函数,之所以放在定时中断里,是因为动态扫描数码管对时间均匀度
*要求很高,如果放在main主函数中,期间稍微出现一些延时滞后或者超前执行的情况,都会导致
*数码管出现“闪烁”或者“忽暗忽亮”的显示效果。
*/

void DisplayScan(void)   
{
static unsigned char Su8GetCode;//从编码转换表中提取出来的编码。
static unsigned char Su8ScanStep=1;//扫描步骤

if(0==vGu16ScanTimerCnt)//定时的时间到,切换显示下一个数码管,依次动态快速循环切换显示
{

/* 注释四:
*在即将切换显示到下一个新的数码管之前,应该先关闭显示所有的数码管,避免因关闭不彻底而导致
*数码管某些段位出现“漏光”,也就是数码管因程序处理不善而出现常见的“鬼影”显示情况。
*/

    P0=0x00; //输出显示先清零,先关闭显示所有的数码管
    //先关闭所有的com口,先关闭显示所有的数码管
P1_0=1;//右起第1位数码管的公共端com4,“总开关”关闭,输出低电平1
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;//右起第3位数码管的公共端com2,“总开关”关闭,输出低电平1
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1

    switch(Su8ScanStep)
{
       case 1: //显示右起第1个数码管
Su8GetCode=Cu8DigTable; //从编码转换表中提取出来的编码。
    P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=0;//右起第1位数码管的公共端com4,“总开关”打开,输出低电平0
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;//右起第3位数码管的公共端com2,“总开关”关闭,输出高电平1
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1            
break;

       case 2: //显示右起第2个数码管
Su8GetCode=Cu8DigTable; //从编码转换表中提取出来的编码。
    P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=1;//右起第1位数码管的公共端com4,“总开关”关闭,输出高电平1
P1_1=0;//右起第2位数码管的公共端com3,“总开关”打开,输出低电平0
P1_2=1;//右起第3位数码管的公共端com2,“总开关”关闭,输出高电平1
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1            
break;

       case 3: //显示右起第3个数码管
Su8GetCode=Cu8DigTable; //从编码转换表中提取出来的编码。
    P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=1;//右起第1位数码管的公共端com4,“总开关”关闭,输出高电平1
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=0;//右起第3位数码管的公共端com2,“总开关”打开,输出低电平0
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1            
break;

       case 4: //显示右起第4个数码管
Su8GetCode=Cu8DigTable; //从编码转换表中提取出来的编码。
    P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=1;//右起第1位数码管的公共端com4,“总开关”关闭,输出高电平1
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;//右起第3位数码管的公共端com2,“总开关”关闭,输出高电平1
P1_3=0;//右起第4位数码管的公共端com1,“总开关”打开,输出低电平0            
break;

}

Su8ScanStep++;
if(Su8ScanStep>4) //如果扫描步骤大于4,继续从第1步开始扫描
{
Su8ScanStep=1;

}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;
vGu8ScanTimerFlag=1;//启动新一轮的定时器
}
}

void T0_time() interrupt 1   
{
DisplayScan(); //数码管的动态扫描函数

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)//数码管显示切换时间的定时器
{
vGu16ScanTimerCnt--;
}

   
TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
//初始化上电瞬间数码管的状态,关闭显示所有的数码管
P0=0x00;
P1_0=1;//右起第1位数码管的公共端com4,“总开关”关闭,输出低电平1
P1_1=1;//右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;//右起第3位数码管的公共端com2,“总开关”关闭,输出低电平1
P1_3=1;//右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1
   
TMOD=0x01;
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

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

void PeripheralInitial(void)
{

}









jianhong_wu 发表于 2018-4-16 12:47:37

本帖最后由 jianhong_wu 于 2018-4-16 12:58 编辑

第一百一十四节: 动态扫描的数码管显示小数点。

【114.1   动态扫描的数码管显示小数点。】
   
                上图114.1.1数码管

       如上图,小数点的段码是h,对应单片机的P0.7口。数码管编码转换表(类似字库)的11个以字节为单位的数据,把它们从十六进制转换成二进制后,可以发现第7位(对应P0.7口)都是0。因此,从转换表里取数据后,得到的数据默认是让数码管的小数点不显示的。如果想显示这个小数点,就需要用到“或(|)”语句操作。比如,本节程序需要显示“1.234”这个带小数点的数值,代码如下:

#include "REG52.H"

#define SCAN_TIME1   

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

void DisplayScan(void);   

sbit P1_0=P1^0;
sbit P1_1=P1^1;
sbit P1_2=P1^2;
sbit P1_3=P1^3;

//转换表,里面的11个数据,转换成二进制后,第7位数据都是0默认不显示小数点
code unsigned char Cu8DigTable[]=
{
0x3f,//0       序号0
0x06,//1       序号1
0x5b,//2       序号2
0x4f,//3       序号3
0x66,//4       序号4
0x6d,//5       序号5
0x7d,//6       序号6
0x07,//7       序号7
0x7f,//8       序号8
0x6f,//9       序号9
0x00,//不显示序号10
};

volatile unsigned char vGu8ScanTimerFlag=0;
volatile unsigned int vGu16ScanTimerCnt=0;


volatile unsigned char vGu8Display_Righ_4=1;//右起第4位数码管显示的变量。这里显示“1”
volatile unsigned char vGu8Display_Righ_3=2;//右起第3位数码管显示的变量。这里显示“2”
volatile unsigned char vGu8Display_Righ_2=3;//右起第2位数码管显示的变量。这里显示“3”
volatile unsigned char vGu8Display_Righ_1=4;//右起第1位数码管显示的变量。这里显示“4”

/* 注释一:
*vGu8Display_Righ_Dot_4,vGu8Display_Righ_Dot_3,vGu8Display_Righ_Dot_2,
*vGu8Display_Righ_Dot_1,这四个全局变量用来传递每位数码管是否需要显示它的小数点,如果是1
*代表需要显示其小数点,如果是0则不显示小数点。这四个变量作为对上面应用层调用的接口变量。
*/

volatile unsigned char vGu8Display_Righ_Dot_4=1;//右起第4位数码管的小数点。1代表打开显示。
volatile unsigned char vGu8Display_Righ_Dot_3=0;//右起第3位数码管的小数点。0代表关闭显示。
volatile unsigned char vGu8Display_Righ_Dot_2=0;//右起第2位数码管的小数点。0代表关闭显示。
volatile unsigned char vGu8Display_Righ_Dot_1=0;//右起第1位数码管的小数点。0代表关闭显示。

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
    }
}


void DisplayScan(void)   
{
static unsigned char Su8GetCode;
static unsigned char Su8ScanStep=1;

if(0==vGu16ScanTimerCnt)
{


    P0=0x00;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=1;

    switch(Su8ScanStep)
{
       case 1:
Su8GetCode=Cu8DigTable;

/* 注释二:
*这里是本节的关键。通过判断全局的接口变量的数值,来决定是否打开显示小数点。
*从转换表取出字模数据后再跟0x80进行“或”运算即可把第7位数据改为1。
*/

if(1==vGu8Display_Righ_Dot_1) //如果打开了需要显示第1个数码管的小数点
{
Su8GetCode=Su8GetCode|0x80;//把第7位数据改为1,显示小数点
}
    P0=Su8GetCode;
P1_0=0;
P1_1=1;
P1_2=1;
P1_3=1;            
break;

       case 2:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_2) //如果打开了需要显示第2个数码管的小数点
{
Su8GetCode=Su8GetCode|0x80;//把第7位数据改为1,显示小数点
}
    P0=Su8GetCode;
P1_0=1;
P1_1=0;
P1_2=1;
P1_3=1;         
break;

       case 3:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_3) //如果打开了需要显示第3个数码管的小数点
{
Su8GetCode=Su8GetCode|0x80;//把第7位数据改为1,显示小数点
}
    P0=Su8GetCode;
P1_0=1;
P1_1=1;
P1_2=0;
P1_3=1;         
break;

       case 4:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_4) //如果打开了需要显示第4个数码管的小数点
{
Su8GetCode=Su8GetCode|0x80;//把第7位数据改为1,显示小数点
}
    P0=Su8GetCode;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=0;         
break;

}

Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;
vGu8ScanTimerFlag=1;
}
}

void T0_time() interrupt 1   
{
DisplayScan();

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)
{
vGu16ScanTimerCnt--;
}


TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
P0=0x00;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=1;

TMOD=0x01;
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

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

void PeripheralInitial(void)
{

}



jianhong_wu 发表于 2018-4-22 11:40:14

本帖最后由 jianhong_wu 于 2018-4-22 12:13 编辑

第一百一十五节: 按键控制数码管的秒表。

【115.1   按键控制数码管的秒表。】
   
                上图115.1.1数码管




                上图115.1.2独立按键

       本节通过一个秒表的小项目,让大家学会以下4个知识点:
      (1)上层的界面显示框架几乎都要用到更新变量,更新变量包括整屏更新和局部更新,本节只用到整屏更新。更新变量是用全局变量在函数之间传递信息。作用是,当有某个需要显示的数据发生改变的时候,就要给更新变量置1,让显示函数重新更新一次显示,确保最新的数据能及时显示出来,平时没有数据更新改变的时候不用频繁更新显示避免占用CPU过多的时间。
      (2)凡是需要显示数字的地方,都必须涉及如何把一个数据按“个十百千万...”的位逐个提取出来的算法。这个算法比较简单,主要用“求余”和“求商”这两个运算语句就可以随心所欲的把数据位提取出来。除此之外,还要学会如何用if语句判断数据的范围,来把高位尚未用到的某个数码管屏蔽,让该位数码管只显示一个“不显示”的数据(避免直接显示一个0)。
      (3)我常把单片机程序简化成4个代表:按键(人机输入),数码管(人机界面),跑马灯(应用程序),串口(通信)。本节的“应用程序”不是跑马灯,而是秒表。不管是跑马灯,还是秒表,都要用到一个总启动Gu8RunStart和一个总运行步骤Gu8RunStep。建议大家,总启动Gu8RunStart和总运行步骤Gu8RunStep应该成双成对的出现(这样关断响应更及时,并且结构更紧凑,漏洞更少),比如,凡是总启动Gu8RunStart发生改变的时候,总运行步骤Gu8RunStep都复位归零一下。
      (4)一个硬件的定时器中断,可以衍生出N个软件定时器,之前跟大家介绍的是“递减式”的软件定时器,而且实际应用中,“递减式”的软件定时器也是用得最多。本节因为项目的需要,需要用到的是“累加式”的软件定时器。不管是哪种软件定时器,大家都要注意定时器变量在定义时所用到的数据类型,这个数据类型决定了定时时间的长度,比如在51单片机中,unsigned int的范围是0到65535,最大一次性定时65.535秒。而unsigned long的范围是0到4294967295,最大一次性定时4294967.295秒。本节秒表的时间超过65.535秒,因此需要用到unsigned long类型的定时器变量。
本节秒表程序的功能:K1按键是复位按键,每按一次,秒表都停止并且重新归零。K2按键是启动和暂停按键,当秒表处于复位后停止的状态时按一次则开始启动,当秒表处于正在工作的状态时按一次则处于暂停状态,当秒表处于暂停的状态时按一次则继续处于工作的状态。本节4位数码管,显示的时间是带2位小数点的,能显示的时间范围是:0.00秒到99.99秒。代码如下:
#include "REG52.H"

#define KEY_FILTER_TIME25

#define SCAN_TIME1   

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

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

void DisplayScan(void);//底层显示的驱动函数
void DisplayTask(void);//上层显示的任务函数

void RunTask(void);//秒表的应用程序

sbit KEY_INPUT1=P2^2;
sbit KEY_INPUT2=P2^1;

sbit P1_0=P1^0;
sbit P1_1=P1^1;
sbit P1_2=P1^2;
sbit P1_3=P1^3;

//数码管转换表
code unsigned char Cu8DigTable[]=
{
0x3f,//0       序号0
0x06,//1       序号1
0x5b,//2       序号2
0x4f,//3       序号3
0x66,//4       序号4
0x6d,//5       序号5
0x7d,//6       序号6
0x07,//7       序号7
0x7f,//8       序号8
0x6f,//9       序号9
0x00,//不显示序号10
};

//数码管底层驱动扫描的软件定时器
volatile unsigned char vGu8ScanTimerFlag=0;
volatile unsigned int vGu16ScanTimerCnt=0;

//秒表的软件定时器,注意,这里是unsigned long类型,范围是0到4294967295毫秒
volatile unsigned char vGu8StopWatchTimerFlag=0;
volatile unsigned long vGu32StopWatchTimerCnt=0;

//数码管上层每10ms就定时刷新一次显示的软件定时器。用于及时更新显示秒表当前的实时数值
volatile unsigned char vGu8UpdateTimerFlag=0;
volatile unsigned int vGu16UpdateTimerCnt=0;


unsigned char Gu8RunStart=0;//应用程序的总启动
unsigned char Gu8RunStep=0;//应用程序的总运行步骤。建议跟vGu8RunStart成双成对出现
unsigned char Gu8RunStatus=0; //当前秒表的状态。0代表停止,1代表正在工作中,2代表暂停

unsigned char Gu8WdUpdate=1;//开机默认整屏更新一次显示。此变量在显示框架中是非常重要的变量

volatile unsigned char vGu8Display_Righ_4=10;//开机默认最高位数码管显示一个“不显示”数据
volatile unsigned char vGu8Display_Righ_3=0;
volatile unsigned char vGu8Display_Righ_2=0;
volatile unsigned char vGu8Display_Righ_1=0;

volatile unsigned char vGu8Display_Righ_Dot_4=0;
volatile unsigned char vGu8Display_Righ_Dot_3=1;    //开机默认保留显示2个小数点
volatile unsigned char vGu8Display_Righ_Dot_2=0;
volatile unsigned char vGu8Display_Righ_Dot_1=0;

volatile unsigned char vGu8KeySec=0;

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
KeyTask();      //按键的任务函数
DisplayTask();//数码管显示的上层任务函数
RunTask();      //秒表的应用程序
    }
}

void KeyTask(void)    //按键的任务函数
{
if(0==vGu8KeySec)
{
return;
}

switch(vGu8KeySec)
{
   case 1:   //复位按键

Gu8RunStatus=0; //秒表返回停止的状态

Gu8RunStart=0;//秒表停止
            Gu8RunStep=0;//总运行步骤归零。建议跟vGu8RunStart成双成对出现

vGu8StopWatchTimerFlag=0;
vGu32StopWatchTimerCnt=0;   //秒表的软件定时器清零

            Gu8WdUpdate=1;//整屏更新一次显示

vGu8KeySec=0;
break;

   case 2:   //启动与暂停的按键
      if(0==Gu8RunStatus) //在停止状态下
      {
            Gu8RunStatus=1;//秒表处于工作状态

vGu8StopWatchTimerFlag=0;
vGu32StopWatchTimerCnt=0;
vGu8StopWatchTimerFlag=1;//启动秒表的软件定时器


Gu8RunStart=1;   //秒表总开关启动
                Gu8RunStep=0;    //总运行步骤归零。建议跟vGu8RunStart成双成对出现
}
      else if(1==Gu8RunStatus) //在工作状态下
      {
            Gu8RunStatus=2;//秒表处于暂停状态
}
      else//在暂停状态下
      {
            Gu8RunStatus=1;//秒表处于工作状态
}

      Gu8WdUpdate=1;//整屏更新一次显示,确保在暂停的时候能显示到最新的数据

vGu8KeySec=0;
break;

}
}

void DisplayTask(void) //数码管显示的上层任务函数
{
//需要借用的中间变量,用来拆分数据位
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量

/* 注释一:
*此处为什么要多加4个中间过渡变量Su8Temp_X?是因为vGu32StopWatchTimerCnt分解数据的时候
*需要进行除法和求余数的运算,就会用到好多条指令,就会耗掉一点时间,类似延时了一会。我们
*的定时器每隔一段时间都会产生中断,然后在中断里驱动数码管显示,当vGu32StopWatchTimerCnt
*还没完全分解出4位有效数据时,这个时候来的定时中断,就有可能导致显示的数据瞬间产生不完整,
*影响显示效果。因此,为了把需要显示的数据过渡最快,所以采取了先分解,再过渡显示的方法。
*/

if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

          //先分解数据

          //Su8Temp_4提取“十秒”位。
Su8Temp_4=vGu32StopWatchTimerCnt/10000; //实际精度是0.0001秒,但显示精度是0.01秒
Su8Temp_4=Su8Temp_4%10;

         //Su8Temp_3提取“个秒”位。
Su8Temp_3=vGu32StopWatchTimerCnt/1000; //实际精度是0.0001秒,但显示精度是0.01秒
Su8Temp_3=Su8Temp_3%10;

         //Su8Temp_2提取“百毫秒”位。
Su8Temp_2=vGu32StopWatchTimerCnt/100; //实际精度是0.0001秒,但显示精度是0.01秒
Su8Temp_2=Su8Temp_2%10;

          //Su8Temp_1提取“十毫秒”位。
Su8Temp_1=vGu32StopWatchTimerCnt/10; //实际精度是0.0001秒,但显示精度是0.01秒
Su8Temp_1=Su8Temp_1%10;

          //判断数据范围,来决定最高位数码管是否需要显示。
          if(vGu32StopWatchTimerCnt<10000) //10.000秒。实际4位数码管最大只能显示99.99秒
          {
             Su8Temp_4=10;//在数码管转换表里,10代表一个“不显示”的数据
}

//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_4=Su8Temp_4;//过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;
vGu8Display_Righ_2=Su8Temp_2;
vGu8Display_Righ_1=Su8Temp_1;

vGu8Display_Righ_Dot_4=0;
vGu8Display_Righ_Dot_3=1;   //保留显示2位小数点
vGu8Display_Righ_Dot_2=0;
vGu8Display_Righ_Dot_1=0;

}
}

void RunTask(void)//秒表的应用程序
{
if(0==Gu8RunStart)
{
   return;// 如果秒表处于停止状态,则直接退出当前函数,不执行该函数以下的其它代码
}

switch(Gu8RunStep)
{
    case 0://在这个步骤里,主要用来初始化一些参数

vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;//每10ms更新显示一次当前秒表的时间
vGu8UpdateTimerFlag=1;

Gu8RunStep=1; //跳转到每10ms更新显示一次的步骤里
break;

    case 1://每10ms更新一次显示,确保实时显示秒表当前的时间
       if(0==vGu16UpdateTimerCnt) //每10ms更新显示一次当前秒表的时间
       {
    vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;//重置定时器,为下一个10ms更新做准备
vGu8UpdateTimerFlag=1;

            Gu8WdUpdate=1;//整屏更新一次显示当前秒表的时间
}
break;

}

}


void KeyScan(void)//按键底层的驱动扫描函数,放在定时中断函数里
{
   static unsigned char Su8KeyLock1;
   static unsigned intSu16KeyCnt1;
   static unsigned char Su8KeyLock2;
   static unsigned intSu16KeyCnt2;

   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;
      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
         Su8KeyLock1=1;
         vGu8KeySec=1;   
      }
   }

   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;
         vGu8KeySec=2;   
      }
   }
}

void DisplayScan(void)    //数码管底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8GetCode;
static unsigned char Su8ScanStep=1;

if(0==vGu16ScanTimerCnt)
{


    P0=0x00;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=1;

    switch(Su8ScanStep)
{
       case 1:
Su8GetCode=Cu8DigTable;

if(1==vGu8Display_Righ_Dot_1)
{
Su8GetCode=Su8GetCode|0x80;
}
    P0=Su8GetCode;
P1_0=0;
P1_1=1;
P1_2=1;
P1_3=1;            
break;

       case 2:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_2)
{
Su8GetCode=Su8GetCode|0x80;
}
    P0=Su8GetCode;
P1_0=1;
P1_1=0;
P1_2=1;
P1_3=1;         
break;

       case 3:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_3)
{
Su8GetCode=Su8GetCode|0x80;
}
    P0=Su8GetCode;
P1_0=1;
P1_1=1;
P1_2=0;
P1_3=1;         
break;

       case 4:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_4)
{
Su8GetCode=Su8GetCode|0x80;
}
    P0=Su8GetCode;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=0;         
break;

}

Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;
vGu8ScanTimerFlag=1;
}
}

void T0_time() interrupt 1   
{
KeyScan();      //按键底层的驱动扫描函数
DisplayScan();//数码管底层的驱动扫描函数

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)
{
vGu16ScanTimerCnt--;//递减式的软件定时器
}

//每10ms就定时更新一次显示的软件定时器
if(1==vGu8UpdateTimerFlag&&vGu16UpdateTimerCnt>0)
{
vGu16UpdateTimerCnt--;//递减式的软件定时器
}

//秒表实际走的时间的软件定时器,注意,这里是“累加式”的软件定时器
if(1==vGu8StopWatchTimerFlag&&1==Gu8RunStatus) //秒表处于工作的状态
{
vGu32StopWatchTimerCnt++;//累加式的软件定时器
}


TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
P0=0x00;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=1;

TMOD=0x01;
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

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

void PeripheralInitial(void)
{

}




jianhong_wu 发表于 2018-4-30 11:42:08

本帖最后由 jianhong_wu 于 2018-4-30 11:59 编辑

第一百一十六节: 按键控制数码管的倒计时。

【116.1   按键控制数码管的倒计时。】
   
                上图116.1.1数码管




                上图116.1.2独立按键

            
                上图116.1.3有源蜂鸣器

       上一节讲“累加式”的秒表,这一节讲“递减式”的倒计时。通过此小项目,加深理解在显示框架中常用的更新变量(整屏更新和局部更新),以及应用程序与按键是如何关联的框架。同时,在拆分“个十百千万...”的时候,有一处地方必须注意,“先整除后求余”必须用一行代码一气呵成,不能拆分成两行代码“先整除;后求余”,否则会有隐患会有bug。除非,把四个临时变都改成unsigned long类型。上一节在此处残留了一个bug我发帖后还来不及修改过来,敬请大家注意。比如:

      以下这样是对的:
static unsigned char Su8Temp_1;
Su8Temp_1=vGu32CountdownTimerCnt/10%10; //一气呵成,没毛病。

      以下这样是有隐患有bug的(除非把Su8Temp_1改成unsigned long类型):
static unsigned char Su8Temp_1;
Su8Temp_1=vGu32CountdownTimerCnt/10;
Su8Temp_1=Su8Temp_1%10; //拆分成两行代码后,有隐患有bug。数据溢出的原因引起。
      本节倒计时程序的功能:K1按键是复位按键,每按一次,倒计时停止并且重新恢复初始值10.00秒。K2按键是启动按键,当秒表处于复位后停止的状态时按一次则开始启动倒计时,当倒计时变为0.00秒的时候,蜂鸣器发出一次“滴”的提示声。此时,如果想启动新一轮的倒计时,只需按一次K1复位键,然后再按K2启动按键,就可以启动新一轮10.00秒的倒计时。代码如下:

#include "REG52.H"

#define KEY_FILTER_TIME25

#define SCAN_TIME1   

#define VOICE_TIME   50   //蜂鸣器一次“滴”的声音长度 50ms

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

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

void VoiceScan(void);//蜂鸣器的驱动函数
void DisplayScan(void);//底层显示的驱动函数
void DisplayTask(void);//上层显示的任务函数

void RunTask(void);//倒计时的应用程序

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

sbit KEY_INPUT1=P2^2;
sbit KEY_INPUT2=P2^1;

sbit P1_0=P1^0;
sbit P1_1=P1^1;
sbit P1_2=P1^2;
sbit P1_3=P1^3;

sbit P3_4=P3^4;

//数码管转换表
code unsigned char Cu8DigTable[]=
{
0x3f,//0       序号0
0x06,//1       序号1
0x5b,//2       序号2
0x4f,//3       序号3
0x66,//4       序号4
0x6d,//5       序号5
0x7d,//6       序号6
0x07,//7       序号7
0x7f,//8       序号8
0x6f,//9       序号9
0x00,//不显示序号10
};

//数码管底层驱动扫描的软件定时器
volatile unsigned char vGu8ScanTimerFlag=0;
volatile unsigned int vGu16ScanTimerCnt=0;

//倒计时的软件定时器,注意,这里是unsigned long类型,范围是0到4294967295毫秒
volatile unsigned char vGu8CountdownTimerFlag=0;
volatile unsigned long vGu32CountdownTimerCnt=10000;//开机默认显示初始值10.000秒

//数码管上层每10ms就定时刷新一次显示的软件定时器。用于及时更新显示秒表当前的实时数值
volatile unsigned char vGu8UpdateTimerFlag=0;
volatile unsigned int vGu16UpdateTimerCnt=0;

//蜂鸣器的软件定时器
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;

unsigned char Gu8RunStart=0;//应用程序的总启动
unsigned char Gu8RunStep=0;//应用程序的总运行步骤。建议跟vGu8RunStart成双成对出现
unsigned char Gu8RunStatus=0; //当前倒计时的状态。0代表停止,1代表正在工作中

unsigned char Gu8WdUpdate=1;//开机默认整屏更新一次显示。此变量在显示框架中是非常重要的变量

volatile unsigned char vGu8Display_Righ_4=1;//显示“1”,跟vGu32CountdownTimerCnt高位一致
volatile unsigned char vGu8Display_Righ_3=0;
volatile unsigned char vGu8Display_Righ_2=0;
volatile unsigned char vGu8Display_Righ_1=0;

volatile unsigned char vGu8Display_Righ_Dot_4=0;
volatile unsigned char vGu8Display_Righ_Dot_3=1;    //开机默认保留显示2个小数点
volatile unsigned char vGu8Display_Righ_Dot_2=0;
volatile unsigned char vGu8Display_Righ_Dot_1=0;

volatile unsigned char vGu8KeySec=0;

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
KeyTask();      //按键的任务函数
DisplayTask();//数码管显示的上层任务函数
RunTask();      //倒计时的应用程序
    }
}

void KeyTask(void)    //按键的任务函数
{
if(0==vGu8KeySec)
{
return;
}

switch(vGu8KeySec)
{
   case 1:   //复位按键

Gu8RunStatus=0; //倒计时返回停止的状态

Gu8RunStart=0;//倒计时的运行步骤的停止
            Gu8RunStep=0;//总运行步骤归零。建议跟vGu8RunStart成双成对出现

vGu8CountdownTimerFlag=0;    //禁止倒计时开始(而Gu8RunStart是启动开关,不矛盾)
vGu32CountdownTimerCnt=10000;   //倒计时的软件定时器恢复初始值10.000秒

            Gu8WdUpdate=1;//整屏更新一次显示

vGu8KeySec=0;
break;

   case 2:   //启动的按键
      if(0==Gu8RunStatus) //在停止状态下
      {

vGu8CountdownTimerFlag=0;
vGu32CountdownTimerCnt=10000;//初始值是10.000秒
vGu8CountdownTimerFlag=1;      //允许倒计时的软件定时器的启动

            Gu8RunStatus=1;//倒计时处于工作状态(并且,这一瞬间才正式启动倒计时)

Gu8RunStart=1;   //倒计时的运行步骤的总开关开启
                Gu8RunStep=0;    //总运行步骤归零。建议跟vGu8RunStart成双成对出现

            Gu8WdUpdate=1;//整屏更新一次显示,确保在启动的时候能显示到最新的数据
}


vGu8KeySec=0;
break;

}
}

void DisplayTask(void) //数码管显示的上层任务函数
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量

if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

          //先分解数据,注意,这里分解的时候,“先整除后求余”必须用一行代码一气呵成,不能拆
          //分成两行代码,否则会有隐患会有bug。除非,把四个临时变都改成unsigned long类型。

          //Su8Temp_4提取“十秒”位。
Su8Temp_4=vGu32CountdownTimerCnt/10000%10; //实际精度是0.001秒,但显示精度是0.01秒

         //Su8Temp_3提取“个秒”位。
Su8Temp_3=vGu32CountdownTimerCnt/1000%10; //实际精度是0.001秒,但显示精度是0.01秒

         //Su8Temp_2提取“百毫秒”位。
Su8Temp_2=vGu32CountdownTimerCnt/100%10; //实际精度是0.001秒,但显示精度是0.01秒

          //Su8Temp_1提取“十毫秒”位。
Su8Temp_1=vGu32CountdownTimerCnt/10%10; //实际精度是0.001秒,但显示精度是0.01秒

          //判断数据范围,来决定最高位数码管是否需要显示。
          if(vGu32CountdownTimerCnt<10000) //10.000秒。实际4位数码管最大只能显示99.99秒
          {
             Su8Temp_4=10;//在数码管转换表里,10代表一个“不显示”的数据
}

//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_4=Su8Temp_4;//过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;
vGu8Display_Righ_2=Su8Temp_2;
vGu8Display_Righ_1=Su8Temp_1;

vGu8Display_Righ_Dot_4=0;
vGu8Display_Righ_Dot_3=1;   //保留显示2位小数点
vGu8Display_Righ_Dot_2=0;
vGu8Display_Righ_Dot_1=0;

}
}

void RunTask(void)//倒计时的应用程序
{
if(0==Gu8RunStart)
{
   return;// 如果总开关处于停止状态,则直接退出当前函数,不执行该函数以下的其它代码
}

switch(Gu8RunStep)
{
    case 0://在这个步骤里,主要用来初始化一些参数

vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;//每10ms更新显示一次当前倒计时的时间
vGu8UpdateTimerFlag=1;

Gu8RunStep=1; //跳转到每10ms更新显示一次的步骤里
break;

    case 1://每10ms更新一次显示,确保实时显示当前倒计时的时间
       if(0==vGu16UpdateTimerCnt) //每10ms更新显示一次当前倒计时的时间
       {


    vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;//重置定时器,为下一个10ms更新做准备
vGu8UpdateTimerFlag=1;

            Gu8WdUpdate=1;//整屏更新一次显示当前倒计时的时间

            if(0==vGu32CountdownTimerCnt) //如果倒计时的时间到,则跳转到结束的步骤
            {
Gu8RunStep=2; //跳转到倒计时结束的步骤
}

}
break;

case 2://倒计时结束的步骤
//Gu8RunStatus=0; //这行代码注释掉,让每次新启动之前都必须按一次K1复位按键才有效

Gu8RunStart=0;//倒计时的运行步骤的停止
            Gu8RunStep=0;   //总运行步骤归零。建议跟vGu8RunStart成双成对出现

vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=VOICE_TIME;//蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;

      Gu8WdUpdate=1;//整屏更新一次显示当前倒计时的时间

break;

}

}


void KeyScan(void)//按键底层的驱动扫描函数,放在定时中断函数里
{
   static unsigned char Su8KeyLock1;
   static unsigned intSu16KeyCnt1;
   static unsigned char Su8KeyLock2;
   static unsigned intSu16KeyCnt2;

   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;
      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
         Su8KeyLock1=1;
         vGu8KeySec=1;   
      }
   }

   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;
         vGu8KeySec=2;   
      }
   }
}

void DisplayScan(void)    //数码管底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8GetCode;
static unsigned char Su8ScanStep=1;

if(0==vGu16ScanTimerCnt)
{


    P0=0x00;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=1;

    switch(Su8ScanStep)
{
       case 1:
Su8GetCode=Cu8DigTable;

if(1==vGu8Display_Righ_Dot_1)
{
Su8GetCode=Su8GetCode|0x80;
}
    P0=Su8GetCode;
P1_0=0;
P1_1=1;
P1_2=1;
P1_3=1;            
break;

       case 2:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_2)
{
Su8GetCode=Su8GetCode|0x80;
}
    P0=Su8GetCode;
P1_0=1;
P1_1=0;
P1_2=1;
P1_3=1;         
break;

       case 3:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_3)
{
Su8GetCode=Su8GetCode|0x80;
}
    P0=Su8GetCode;
P1_0=1;
P1_1=1;
P1_2=0;
P1_3=1;         
break;

       case 4:
Su8GetCode=Cu8DigTable;
if(1==vGu8Display_Righ_Dot_4)
{
Su8GetCode=Su8GetCode|0x80;
}
    P0=Su8GetCode;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=0;         
break;

}

Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;
vGu8ScanTimerFlag=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();
                   }

}
          }         
}

void BeepOpen(void)
{
P3_4=0;
}

void BeepClose(void)
{
P3_4=1;
}

void T0_time() interrupt 1   
{
VoiceScan();    //蜂鸣器的驱动函数
KeyScan();      //按键底层的驱动扫描函数
DisplayScan();//数码管底层的驱动扫描函数

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)
{
vGu16ScanTimerCnt--;//递减式的软件定时器
}

//每10ms就定时更新一次显示的软件定时器
if(1==vGu8UpdateTimerFlag&&vGu16UpdateTimerCnt>0)
{
vGu16UpdateTimerCnt--;//递减式的软件定时器
}

//倒计时实际走的时间的软件定时器,注意,这里还附加了启动状态的条件“&&1==Gu8RunStatus”
if(1==vGu8CountdownTimerFlag&&vGu32CountdownTimerCnt>0&&1==Gu8RunStatus)
{
vGu32CountdownTimerCnt--;//递减式的软件定时器
}


TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
P0=0x00;
P1_0=1;
P1_1=1;
P1_2=1;
P1_3=1;

TMOD=0x01;
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

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

void PeripheralInitial(void)
{

}


页: 1 2 3 4 5 [6] 7
查看完整版本: 从单片机基础到程序框架(连载)