简单,细致的菜单程序讲解

来源:互联网 发布:淘宝还能赚钱吗 编辑:程序博客网 时间:2024/05/03 21:48

原作者:吴坚鸿

第二十六节:按键操作数码管菜单的基本程序框架


(1)        开场白:
数码管不像液晶屏那样可以使用反显或者光标来聚焦在某个显示的数据上,因此当需要修改某位数码管的数据时,我们最常用的方法就是让某位数码管闪烁,闪烁表示提醒客户现在可以更改某位数据了。当我们让它不闪烁时,就表示退出了更改数据的界面,更改数据完毕。
在吴坚鸿的程序风格中,不管是液晶屏,点阵屏,还是数码管,凡是用来做显示界面的程序,都必须要有一个窗口变量wd_sec,这个窗口变量是人机交互程序的一条主线,所有的按键操作都围绕着这条主线展开。如果是静态驱动的方式,还要用到一个更新变量_update,但是本程序是动态扫描数码管的方式,因此不需要用到更新变量_update。

(2)        功能需求:
用3个独立按键,第1个按键用来做《加》键,第2个按键用来做《减》键,第3个按键用来做《设置》键。刚上电时,两位数码管显示”00”,当按一次《设置》键时,数码管上面显示的数据开始闪烁(一亮一灭),此时代表可以更改此数码管上显示的数据,按《加》键可以一直加到99,然后又从00开始加到99,依次循环。如果按《减》键,可以从00变成99后,又从99减到00,依次循环。当再次按《设置》键时,数码管又不闪烁了,这时表示退出设置数据状态,此时按《加》或《减》键都不能更改数据。

(3)        硬件原理:
(a)        独立按键的电路请参考第二节的内容。
(b)        动态扫描两位数码管的电路请参考第二十一节。

(4)源码适合的单片机: PIC16F73,晶振为3.579545MHz。

(5)源代码讲解如下:

#include<pic.h>

#define cnt_delay_cnt1   40   //按键去抖动延时阀值
#define cnt_voice_time   150  //蜂鸣器响的声音长短的延时阀值

#define  seg_0_dr RB6   //任意7个IO口接数码管的seg引脚
#define  seg_1_dr RB5
#define  seg_2_dr RB4
#define  seg_3_dr RB3
#define  seg_4_dr RC7
#define  seg_5_dr RB0
#define  seg_6_dr RB1

#define  com_left_dr RB2    //任意2个IO口接数码管的com引脚
#define  com_right_dr RB7


#define  beep_dr  RA2  //蜂鸣器输出

#define  key_sr1 RC4    //独立按键输入<加>键
#define  key_sr2 RC5    //独立按键输入<减>键
#define  key_sr3 RC6    //独立按键输入<设置>键


void initial();//初始化
void delay1(unsigned int de)  ;//小延时程序,时间不宜太长,因为内部没有喂看门狗
void display_drive();  //数码管驱动程序,放在定时中断里
void display_seg(unsigned char seg);  //编码转换程序,放在display_drive里

void key_scan(); //按键扫描函数,放在定时中断里,有人说不应该把子程序放在中断里,别听他们,信鸿哥无坎坷。
void key_service();        //按键服务函数,放在main函数循环里

unsigned char number_set=0;    //按键修改和数码管显示的数据
unsigned char number_left=0;   //左边数码管显示的内容
unsigned char number_right=0;  //右边数码管显示的内容
unsigned char dis_step=1; //扫描的步骤

unsigned char key_lock1=0;   //按键自锁标志
unsigned char key_lock2=0;   //按键自锁标志
unsigned char key_lock3=0;   //按键自锁标志

unsigned int  delay_cnt1=0;     //延时计数器的变量
unsigned int  delay_cnt2=0;     //延时计数器的变量
unsigned int  delay_cnt3=0;     //延时计数器的变量

unsigned int voice_time_cnt;   //蜂鸣器响的声音长短的计数延时

unsigned char key_sec=0;  //哪个按键被触发

unsigned int flashlight_cnt=0; //延时计数器清零
unsigned char flashlight_flag=0; //传递闪烁时间信息的标志位

unsigned char wd_sec=1;  //主窗口变量,人机交互界面的主线。等于1时表示退出修改参数的界面,等于2时表示正在修改参数的界面。

main()  //主程序
{

     initial();  //初始化
     while(1)
     {
         CLRWDT();      //喂单片机内部自带的看门狗,大家可以不管它
         key_service();   //按键服务函数
    }
}


void interrupt timer1rbint(void)  //中断程序入口
{


    if(TMR1IE==1&&TMR1IF==1)  //定时中断程序
        {

            TMR1IF=0;
      TMR1ON=0; 

      if(voice_time_cnt)    //控制蜂鸣器声音的长短
      {
         beep_dr=1;         //蜂鸣器响
         --voice_time_cnt;        //蜂鸣器响的声音长短的计数延时
      }
      else 
      {
         asm("nop");     //没别的意思,纯粹为了保持if和else各自括号内的队伍对称。不是鸿哥有"对称控"的洁癖,我只是感觉这样可以起到耗时平衡的小作用。
         beep_dr=0;      //蜂鸣器停止
      }

      if(wd_sec==2) //在闪烁的界面,也就是说在更改数据模式的界面
      {
         flashlight_cnt++;  //闪烁的计数延时
         if(flashlight_cnt>150) //一亮一灭的时间间隔
         {
            flashlight_cnt=0; //延时计数器清零

            if(flashlight_flag==0) //此标志在0和1之间反复切换,起到闪烁的效果
            {
               flashlight_flag=1;  //如果是用bit变量,直接flashlight_flag=!flashlight_flag就可以。但是鸿哥不喜欢用bit变量,感觉用char变量程序可以兼容更多单片机
            }
            else
            {
               flashlight_flag=0;
            }

         }

      }

      key_scan();       //按键扫描函数
      display_drive(); //数码管驱动程序,放在定时中断里

      TMR1H=0xFF;
      TMR1L=0xC8;
      TMR1ON=1;
        
    }

}



void initial()//初始化
{

        ADCON0=0x41;  //设置AD模式
        ADCON1=0x04;  //RA0作为AD输入通道



    TRISB=0x00;  //数码管IO口设置成输出
    TRISC7=0;


    TRISA2=0;   //蜂鸣器输出

    TRISC4=1;   //按键输入
    TRISC5=1;
    TRISC6=1;



    T1CON=0x24;   //定时中断配置
        TMR1H=0xFE;
        TMR1L=0xEF;
    INTCON=0xC0;
    TMR1IF=0;
    TMR1IE=1;
        PEIE=1;                                //外围中断允许
    GIE=1;

    TMR1ON=1;








}


void display_drive()  //数码管驱动程序,放在定时中断里
{
   switch(wd_sec)  //窗口显示变量,人机界面的主线
   {
      case 1:  //在正常显示的窗口下
           number_left=number_set/10;  //把需要显示的数据分解出十位
           number_right=number_set%10;  //把需要显示的数据分解出个位
           break;

      case 2:  //在闪烁的窗口下,也就是在修改数据的窗口下
           if(flashlight_flag==0) //此标志每隔一段时间自动在0和1之间变化
           {
              number_left=number_set/10;  //把需要显示的数据分解出十位,相当于亮
              number_right=number_set%10;  //把需要显示的数据分解出个位,相当于亮
           }
           else
           {
              number_left=10;  //等于10时,此编码是什么都没显示,相当于灭
              number_right=10; //等于10时,此编码是什么都没显示,相当于灭
           }
           
           break;
   }


   seg_0_dr=0;   
   seg_1_dr=0;
   seg_2_dr=0;
   seg_3_dr=0;
   seg_4_dr=0;
   seg_5_dr=0;
   seg_6_dr=0;
   com_left_dr=1;    
   com_right_dr=1; //在即将更换下一位数码管时,先把让它两个什么都不显示,让显示过度更加平稳
   asm("nop");  //空指令延时
   asm("nop");  
   asm("nop");
   asm("nop"); 
   asm("nop"); 
   asm("nop");


   switch(dis_step)
   {
      case 1:    //扫描左边的数码管

           display_seg(number_left);  //如果不是任意IO口,可以直接用查表的方式取代此子程序
           com_left_dr=0;    //选中左数码管
           com_right_dr=1;
           break;


     case 2:   //扫描右边的数码管
  
           display_seg(number_right); //如果不是任意IO口,可以直接用查表的方式取代此子程序
           com_left_dr=1;  
           com_right_dr=0;  //选中右数码管

           break;

   }
   delay1(50); //每一位数码管显示的停留延时时间,有疑问的朋友请自己尝试改成计数延时的方式,
               //鸿哥认为在此种环境下,在定时中断里用死延时delay1(50)是最佳的方式


  ++dis_step;    //下一次中断扫描另外一位的数码管,轮流扫描
  if(dis_step>2)
  {
      dis_step=1;
  }

}

//不是鸿哥不懂爱,如果不是用任意IO口,而是直接用一个并口(比如51单片机中的P1口),那么就不用那么费力,
//直接用查数组(俗称查表)的方式就可以替代display_seg这个编码转换程序,
void display_seg(unsigned char seg)  //编码转换程序,,放在display_drive里
{

   switch(seg)   //switch指令,单片机中的战斗机,鸿哥的最爱!
        {
         case 0:   //显示"0"
               seg_0_dr=1;
               seg_1_dr=1;
               seg_2_dr=1;
               seg_3_dr=1;
               seg_4_dr=0;
               seg_5_dr=1;
               seg_6_dr=1;
               break;
         case 1:   //显示"1"
               seg_0_dr=1;
               seg_1_dr=1;
               seg_2_dr=0;
               seg_3_dr=0;
               seg_4_dr=0;
               seg_5_dr=0;
               seg_6_dr=0;
               break;
         case 2:   //显示"2"
               seg_0_dr=1;
               seg_1_dr=0;
               seg_2_dr=1;
               seg_3_dr=1;
               seg_4_dr=1;
               seg_5_dr=1;
               seg_6_dr=0;
               break;
         case 3:   //显示"3"
               seg_0_dr=1;
               seg_1_dr=1;
               seg_2_dr=0;
               seg_3_dr=1;
               seg_4_dr=1;
               seg_5_dr=1;
               seg_6_dr=0;
               break;
         case 4:   //显示"4"
               seg_0_dr=1;
               seg_1_dr=1;
               seg_2_dr=0;
               seg_3_dr=0;
               seg_4_dr=1;
               seg_5_dr=0;
               seg_6_dr=1;
               break;
         case 5:   //显示"5"
               seg_0_dr=0;
               seg_1_dr=1;
               seg_2_dr=0;
               seg_3_dr=1;
               seg_4_dr=1;
               seg_5_dr=1;
               seg_6_dr=1;
               break;
         case 6:   //显示"6"
               seg_0_dr=0;
               seg_1_dr=1;
               seg_2_dr=1;
               seg_3_dr=1;
               seg_4_dr=1;
               seg_5_dr=1;
               seg_6_dr=1;
               break;
         case 7:    //显示"7"
               seg_0_dr=1;
               seg_1_dr=1;
               seg_2_dr=0;
               seg_3_dr=0;
               seg_4_dr=0;
               seg_5_dr=1;
               seg_6_dr=0;
               break;
         case 8:    //显示"8"
               seg_0_dr=1;
               seg_1_dr=1;
               seg_2_dr=1;
               seg_3_dr=1;
               seg_4_dr=1;
               seg_5_dr=1;
               seg_6_dr=1;
               break;
         case 9:    //显示"9"
               seg_0_dr=1;
               seg_1_dr=1;
               seg_2_dr=0;
               seg_3_dr=1;
               seg_4_dr=1;
               seg_5_dr=1;
               seg_6_dr=1;
               break;
         case 10:      //什么也不显示,空
               seg_0_dr=0;
               seg_1_dr=0;
               seg_2_dr=0;
               seg_3_dr=0;
               seg_4_dr=0;
               seg_5_dr=0;
               seg_6_dr=0;
               break;


        }
}



void delay1(unsigned int de)
{
        unsigned int t;
        for(t=0;t<de;t++);
}




void key_scan()  //按键扫描函数
{  

  if(key_sr1==1)    //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     key_lock1=0;  //按键自锁标志清零
     delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙        
  }
  else if(key_lock1==0)  //有按键按下,且是第一次被按下
  {
     ++delay_cnt1;  //延时计数器
     if(delay_cnt1>cnt_delay_cnt1)
     {
        delay_cnt1=0;
        key_lock1=1;  //自锁按键置位,避免一直触发
        key_sec=1;         //触发1号键
     }
  }

  if(key_sr2==1)    //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
      key_lock2=0;  //按键自锁标志清零
      delay_cnt2=0; //按键去抖动延时计数器清零,此行非常巧妙        
  }
  else if(key_lock2==0)  //有按键按下,且是第一次被按下
  {
      ++delay_cnt2;  //延时计数器
      if(delay_cnt2>cnt_delay_cnt1)
      {
         delay_cnt2=0;
         key_lock2=1;  //自锁按键置位,避免一直触发
         key_sec=2;         //触发2号键
      }
  }


  if(key_sr3==1)    //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
      key_lock3=0;  //按键自锁标志清零
      delay_cnt3=0; //按键去抖动延时计数器清零,此行非常巧妙        
  }
  else if(key_lock3==0)  //有按键按下,且是第一次被按下
  {
      ++delay_cnt3;  //延时计数器
      if(delay_cnt3>cnt_delay_cnt1)
      {
         delay_cnt3=0;
         key_lock3=1;  //自锁按键置位,避免一直触发
         key_sec=3;         //触发3号键
      }
  }


}

void key_service()   //按键服务函数
{
    switch(key_sec) //按键服务状态切换
    {
       case 1:// 1号键《加》键
            switch(wd_sec) //所有的按键操作都围绕这窗口变量展开,再复杂也不会乱
            {
               case 1: //在正常显示的窗口下,
                    asm("nop"); //什么也不干
                    break;
               case 2: //在修改参数的窗口下,也就是闪烁的窗口下
                    number_set++; //显示的数据加一
                    if(number_set>99) //如果大于99就变成0
                    {
                        number_set=0; //大于99时从0开始继续加
                    }

                    voice_time_cnt= cnt_voice_time;    //蜂鸣器响“滴”一声就停   
                    break;
            }  
                                  
            key_sec=0;   //相应完按键处理程序之后,把按键选择变量清零,
            break;   
     
       case 2:// 2号键《减》键
            switch(wd_sec) //所有的按键操作都围绕这窗口变量展开,再复杂也不会乱
            {
               case 1: //在正常显示的窗口下,
                    asm("nop"); //什么也不干
                    break;
               case 2: //在修改参数的窗口下,也就是闪烁的窗口下
                    number_set--; //显示的数据减一
                    if(number_set>99) //一直减到0时,单片机的C语言有这个特征:0减去1,就会变成此变量最大的数据,字节变量就是0xff(255),因此肯定大于99.
                    {
                        number_set=99; //小于0时从99开始继续减
                    }
                    voice_time_cnt=cnt_voice_time;    //蜂鸣器响“滴”一声就停   
                    break;
            }  
                                                 
            key_sec=0;   //相应完按键处理程序之后,把按键选择变量清零,
            break;

       case 3://3号键《设置》键

            switch(wd_sec) //所有的按键操作都围绕这窗口变量展开,再复杂也不会乱
            {
               case 1: //在正常显示的窗口下,
                    wd_sec=2;  //切换到修改参数的窗口,也就是闪烁的窗口
                    break;
               case 2: //在修改参数的窗口下,也就是闪烁的窗口下
                    wd_sec=1;  //返回到正常显示的窗口
                    break;
            }  

            voice_time_cnt= cnt_voice_time;    //蜂鸣器响“滴”一声就停                 
            key_sec=0;   //相应完按键处理程序之后,把按键选择变量清零,
            break;                
   }                
}
原创粉丝点击