打印
[51单片机]

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

[复制链接]
楼主: jianhong_wu
手机看帖
扫描二维码
随时随地手机跟帖
361
jianhong_wu|  楼主 | 2018-1-22 15:23 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
第一百零四节: 矩阵按键“一键两用”的短按与长按。
第一百零四节_pdf文件.pdf (111.23 KB)
【104.1   “一键两用”的短按与长按。】

            
                上图104.1.1  有源蜂鸣器电路

   
                上图104.1.2  LED电路


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

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

#include "REG52.H"  

#define KEY_VOICE_TIME   50     

#define KEY_SHORT_TIME  20    //按键的“短按”兼“滤波”的“稳定时间”
#define KEY_LONG_TIME  400    //按键的“长按”兼“滤波”的“稳定时间”

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

}
          }         
}


使用特权

评论回复
362
pgdw| | 2018-1-23 16:43 | 只看该作者
谢谢吴老师的无私奉献,感恩感德。

使用特权

评论回复
363
jianhong_wu|  楼主 | 2018-1-28 12:58 | 只看该作者
第一百零五节: 矩阵按键按住不松手的连续均匀触发。
第一百零五节_pdf文件.pdf (130.76 KB)
【105.1   按住不松手的连续均匀触发。】

           
                上图105.1.1  有源蜂鸣器电路

     
                上图105.1.2  LED电路


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

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

#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 int  Su16KeyCnt=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();  
                   }

}
          }         
}


使用特权

评论回复
364
jianhong_wu|  楼主 | 2018-2-4 12:22 | 只看该作者
第一百零六节: 矩阵按键按住不松手的“先加速后匀速”触发。
第一百零六节_pdf文件.pdf (137.08 KB)
【106.1   按住不松手的先加速后匀速触发。】

            
                上图106.1.1  有源蜂鸣器电路

      
                上图106.1.2  LED电路


      
                上图106.1.3  3*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_TIME  20    //按键单击的“滤波”时间

#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 int  Su16KeyCnt=0;  
   static unsigned char Su8KeyStep=1;  
   static unsigned int  Su16KeyContinuityTime=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();  
                   }

}
          }         
}


使用特权

评论回复
365
冷画| | 2018-2-7 09:11 | 只看该作者
为楼主点赞  

使用特权

评论回复
366
yufei138| | 2018-2-7 10:22 | 只看该作者
佩服楼主的格局与境界。

使用特权

评论回复
367
jianhong_wu|  楼主 | 2018-2-11 11:10 | 只看该作者
第一百零七节: 开关感应器的识别与软件滤波。
第一百零七节_pdf文件.pdf (91.16 KB)
【107.1   开关感应器的识别与软件滤波。】


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

   
                上图107.1.2  LED电路

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

#include "REG52.H"  

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

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

}


使用特权

评论回复
评论
tianqi911 2018-9-30 12:46 回复TA
总是在教材上看见几个大字“软件滤波即可”。TMD究竟怎么弄的,没一本书具体操作的,都是原理。 
368
jianhong_wu|  楼主 | 2018-2-23 14:20 | 只看该作者
第一百零八节: 按键控制跑马灯的启动和暂停和停止。
第一百零八节_pdf文件.pdf (104.79 KB)
【108.1   按键控制跑马灯的启动和暂停和停止。】



                上图108.1.1  独立按键

     
                上图108.1.2  LED电路

               
                上图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_TIME  25  
#define RUN_TIME  200   //跑马灯的跑动速度的时间参数

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 int  Su16KeyCnt1;
   static unsigned char Su8KeyLock2;
   static unsigned int  Su16KeyCnt2;

   //【启动暂停】按键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;

}

}


使用特权

评论回复
369
jianhong_wu|  楼主 | 2018-2-23 14:26 | 只看该作者
本帖最后由 jianhong_wu 于 2018-2-23 14:28 编辑

对不起,重发了一次回复。

使用特权

评论回复
370
eatpeanut| | 2018-2-23 15:19 | 只看该作者
厉害   !!! 在哪儿点收藏啊???

使用特权

评论回复
371
jianhong_wu|  楼主 | 2018-3-1 09:14 | 只看该作者
第一百零九节: 按键控制跑马灯的方向。
第一百零九节_pdf文件.pdf (109.97 KB)
【109.1   按键控制跑马灯的方向。】



                上图109.1.1  独立按键

  
                上图109.1.2  LED电路

              
                上图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_TIME  25  
#define RUN_TIME  200   //跑马灯的跑动速度的时间参数

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 int  Su16KeyCnt1;
   static unsigned char Su8KeyLock2;
   static unsigned int  Su16KeyCnt2;
   static unsigned char Su8KeyLock3;
   static unsigned int  Su16KeyCnt3;


   //【启动暂停】按键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;

}

}


使用特权

评论回复
评论
zhangtao3b608 2021-6-4 10:57 回复TA
mark 
372
wlhuangcn| | 2018-3-2 11:33 | 只看该作者
感谢分享~

使用特权

评论回复
373
jianhong_wu|  楼主 | 2018-3-11 13:55 | 只看该作者
第一百一十一节: 工业自动化设备的开关信号的运动控制。
第一百一十一节_pdf文件.pdf (117.78 KB)
【111.1   开关信号的运动控制。】



                上图111.1.1  独立按键

     
                上图111.1.2  LED电路

              
                上图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_TIME  25  
#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 int  Gu16RunCnt=0;       //计数器
unsigned int  Gu16ReturnLeftTime=3000;   //水平往左跑的延时变量,默认为3秒
unsigned int  Gu16GoDownTime=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 int  Su16KeyCnt1;

   //【启动】按键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;
}
}


使用特权

评论回复
374
a51278207| | 2018-3-21 21:56 | 只看该作者
楼主写得非常好,收藏起来慢慢一点点看完,期待楼主一直更新下去,支持

使用特权

评论回复
375
arima| | 2018-3-26 19:31 | 只看该作者
经典啊!
期待楼主一直更新下去,支持!!!

使用特权

评论回复
376
EngineerLin| | 2018-3-26 22:31 | 只看该作者
支持666!

使用特权

评论回复
评论
ycxandy 2019-4-27 21:23 回复TA
en 
377
入坑的小学生| | 2018-3-28 14:39 | 只看该作者
楼主加油!回复一下暖贴!

使用特权

评论回复
378
jianhong_wu|  楼主 | 2018-3-30 10:18 | 只看该作者
第一百一十二节: 数码管显示的基础知识。
第一百一十二节_pdf文件.pdf (104.92 KB)
【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_TIME  1000    //数码管切换显示数字的时间

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[Gu8Number]; //从编码转换表中提取出来的编码。

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

}


使用特权

评论回复
379
panpanpanpan| | 2018-4-6 20:26 | 只看该作者
。。。要等2020年是不是有点久了

使用特权

评论回复
380
yongxiang6091| | 2018-4-7 23:59 | 只看该作者
关注了好久,学习了,谢谢老师分享!!!

使用特权

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

本版积分规则