独闷闷网

 找回密码
 立即注册
搜索
楼主: jianhong_wu
打印 上一主题 下一主题
收起左侧

[原创] 从业将近十年!手把手教你单片机程序框架(连载)

[复制链接]
21#
 楼主| 发表于 2014-1-29 12:27:28 | 显示全部楼层
第十七节:两片联级74HC595驱动16个LED灯的基本驱动程序。

开场白:
      上一节讲了如何把矩阵键盘翻译成独立按键的处理方式。这节讲74HC595的驱动程序。要教会大家两个知识点:
      第一点:朱兆祺的学习板是用74HC595控制LED,因此可以直接把595的OE引脚接地。如果在工控中,用来控制继电器,那么此芯片的片选脚OE不要为了省一个IO口而直接接地,否则会引起上电瞬间继电器莫名其妙地动作。为了解决这个问题,OE脚应该用一个IO口单独驱动,并且千万要记住,此IO必须接一个15K左右的上拉电阻,然后在程序刚上电运行时,先把OE置高,并且尽快把所有的74HC595输出口置低,然后再把OE置低.当然还有另外一种解决办法,就是用一个10uF的电解电容跟一个100K的下拉电阻,组成跟51单片机外围复位电路原理一样的电路,连接到OE口,这样确保上电瞬间OE口有一小段时间是处于高电平状态,在此期间,尽快通过软件把74hc595的所有输出口置低。
      第二点:两个联级74HC595的工作过程:每个74HC595内部都有一个8位的寄存器,两个联级起来就有两个寄存器。ST引脚就相当于一个刷新信号引脚,当ST引脚产生一个上升沿信号时,就会把寄存器的数值输出到74HC595的输出引脚并且锁存起来,DS是数据引脚,SH是把新数据送入寄存器的时钟信号。也就是说,SH引脚负责把数据送入到寄存器里,ST引脚负责把寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来。

具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:两片联级的74HC595驱动的16个LED灯交叉闪烁。比如,先是第1,3,5,7,9,11,13,15八个灯亮,其它的灯都灭。然后再反过来,原来亮的就灭,原来灭的就亮。交替闪烁。

(3)源代码讲解如下:
#include "REG52.H"

#define const_time_level 200  

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker();
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time();  //定时中断函数

/* 注释一:
* 朱兆祺的学习板是用74HC595控制LED,因此可以直接把595的OE引脚接地。如果在工控中,用来控制继电器,
* 那么此芯片的片选脚OE不要为了省一个IO口而直接接地,否则会引起上电瞬间继电器莫名其妙地动作。
* 为了解决这个问题,OE脚应该用一个IO口单独驱动,并且千万要记住,此IO必须接一个15K左右的
* 上拉电阻,然后在程序刚上电运行时,先把OE置高,并且尽快把所有的74HC595输出口置低,然后再把OE置低.
* 当然还有另外一种解决办法,就是用一个10uF的电解电容跟一个100K的下拉电阻,组成跟51单片机外围复位电路原理
* 一样的电路,连接到OE口,这样确保上电瞬间OE口有一小段时间是处于高电平状态,在此 期间,
* 尽快通过软件把74hc595的所有输出口置低。
*/
sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  


unsigned char ucLedStep=0; //步骤变量
unsigned int  uiTimeCnt=0; //统计定时中断次数的延时计数器


void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker();   
   }

}

/* 注释二:
* 两个联级74HC595的工作过程:
* 每个74HC595内部都有一个8位的寄存器,两个联级起来就有两个寄存器。ST引脚就相当于一个刷新
* 信号引脚,当ST引脚产生一个上升沿信号时,就会把寄存器的数值输出到74HC595的输出引脚并且锁存起来,
* DS是数据引脚,SH是把新数据送入寄存器的时钟信号。也就是说,SH引脚负责把数据送入到寄存器里,ST引脚
* 负责把寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来。
*/
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


void led_flicker() ////第三区 LED闪烁应用程序
{
  switch(ucLedStep)
  {
     case 0:
           if(uiTimeCnt>=const_time_level) //时间到
           {
               uiTimeCnt=0; //时间计数器清零
               hc595_drive(0x55,0x55);
               ucLedStep=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeCnt>=const_time_level) //时间到
           {
               uiTimeCnt=0; //时间计数器清零
               hc595_drive(0xaa,0xaa);
               ucLedStep=0; //返回到上一个步骤
           }
           break;
  
   }

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  if(uiTimeCnt<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt++;  //累加定时中断的次数,
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=1;  //开中断
}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

  TMOD=0x01;  //设置定时器0为工作方式1


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
       这节讲了74HC595的驱动程序,它是一次控制16个LED同时亮灭的,在实际中应用不太方便,如果我们想要像单片机IO口直接控制LED那样方便,我们该怎么编写程序呢?欲知详情,请听下回分解-----把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
22#
 楼主| 发表于 2014-1-29 13:21:04 | 显示全部楼层
第十八节:把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。

开场白:
上一节讲了74HC595的驱动程序。为了更加方便操作74HC595输出的每个IO状态,这节讲如何把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。要教会大家两个知识点:
第一点:如何灵活运用与和非的运算符来实现位的操作。
第二点:如何灵活运用一个更新变量来实现静态刷新输出或者静态刷新显示的功能。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:两片联级的74HC595驱动的16个LED灯交叉闪烁。比如,先是第1,3,5,7,9,11,13,15八个灯亮,其它的灯都灭。然后再反过来,原来亮的就灭,原来灭的就亮。交替闪烁。

(3)源代码讲解如下:
#include "REG52.H"

#define const_time_level 200  

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker();
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数


sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。

unsigned char ucLedStep=0; //步骤变量
unsigned int  uiTimeCnt=0; //统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker();   
          led_update();  //LED更新函数
   }

}


/* 注释一:
* 把74HC595驱动程序翻译成类似单片机IO口直接驱动方式的过程。
* 每次更新LED输出,记得都要把ucLed_update置1表示更新。
*/
void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


void led_flicker() ////第三区 LED闪烁应用程序
{
  switch(ucLedStep)
  {
     case 0:
           if(uiTimeCnt>=const_time_level) //时间到
           {
               uiTimeCnt=0; //时间计数器清零

               ucLed_dr1=1;  //每个变量都代表一个LED灯的状态
               ucLed_dr2=0;
               ucLed_dr3=1;
               ucLed_dr4=0;
               ucLed_dr5=1;
               ucLed_dr6=0;
               ucLed_dr7=1;
               ucLed_dr8=0;
               ucLed_dr9=1;
               ucLed_dr10=0;
               ucLed_dr11=1;
               ucLed_dr12=0;
               ucLed_dr13=1;
               ucLed_dr14=0;
               ucLed_dr15=1;
               ucLed_dr16=0;

               ucLed_update=1;  //更新显示
               ucLedStep=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeCnt>=const_time_level) //时间到
           {
               uiTimeCnt=0; //时间计数器清零

               ucLed_dr1=0;  //每个变量都代表一个LED灯的状态
               ucLed_dr2=1;
               ucLed_dr3=0;
               ucLed_dr4=1;
               ucLed_dr5=0;
               ucLed_dr6=1;
               ucLed_dr7=0;
               ucLed_dr8=1;
               ucLed_dr9=0;
               ucLed_dr10=1;
               ucLed_dr11=0;
               ucLed_dr12=1;
               ucLed_dr13=0;
               ucLed_dr14=1;
               ucLed_dr15=0;
               ucLed_dr16=1;

               ucLed_update=1;  //更新显示
               ucLedStep=0; //返回到上一个步骤
           }
           break;
  
   }

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  if(uiTimeCnt<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt++;  //累加定时中断的次数,
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=1;  //开中断
}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

  TMOD=0x01;  //设置定时器0为工作方式1


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
这节讲了把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式,接下来,我们该如何来运用这种驱动方式实现跑马灯的程序?欲知详情,请听下回分解-----依次逐个点亮LED之后,再依次逐个熄灭LED的跑马灯程序。

(未完待续,下节更精彩,不要走开哦)

点评

把hc595_drive(ucLedStatus16_09,ucLedStatus08_01); //74HC595底层驱动函数写在刷新函数里面怎么就成底层驱动函数啦,请求楼主解惑,之前是写在LED_flick()应用程序程序里面,两者什么区别吗?  发表于 2015-11-27 12:39
乐于分享,勇于质疑!
23#
 楼主| 发表于 2014-2-1 05:32:34 | 显示全部楼层
第十九节:依次逐个点亮LED之后,再依次逐个熄灭LED的跑马灯程序。

开场白:
上一节讲了把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。这节在上一节的驱动程序基础上,开始讲跑马灯程序。我的跑马灯程序看似简单而且重复,其实蕴含着鸿哥的大智慧。它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,以后任何所谓复杂的工程项目,都不再复杂。要教会大家一个知识点:通过本跑马灯程序,加深理解鸿哥所有实战项目中switch状态机的思想精髓。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:第1个至第8个LED灯,先依次逐个亮,再依次逐个灭。第9至第16个LED灯一直灭。

(3)源代码讲解如下:
#include "REG52.H"

#define const_time_level_01_08  200  //第1个至第8个LED跑马灯的速度延时时间

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker_01_08(); // 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数


sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。

unsigned char ucLedStep_01_08=0; //第1个至第8个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_01_08=0; //第1个至第8个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker_01_08(); // 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
          led_update();  //LED更新函数
   }

}


void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}

/* 注释一:
* 以下程序,看似简单而且重复,其实蕴含着鸿哥的大智慧。
* 它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,
* 以后任何所谓复杂的工程项目,都不再复杂。
*/
void led_flicker_01_08() //第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
{
  switch(ucLedStep_01_08)
  {
     case 0:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr1=1;  //第1个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr2=1;  //第2个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=2; //切换到下一个步骤
           }
           break;
     case 2:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr3=1;  //第3个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=3; //切换到下一个步骤
           }
           break;
     case 3:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr4=1;  //第4个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=4; //切换到下一个步骤
           }
           break;
     case 4:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr5=1;  //第5个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=5; //切换到下一个步骤
           }
           break;
     case 5:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr6=1;  //第6个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=6; //切换到下一个步骤
           }
           break;
     case 6:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr7=1;  //第7个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=7; //切换到下一个步骤
           }
           break;
     case 7:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr8=1;  //第8个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=8; //切换到下一个步骤
           }
           break;
     case 8:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr8=0;  //第8个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=9; //切换到下一个步骤
           }
           break;
     case 9:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr7=0;  //第7个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=10; //切换到下一个步骤
           }
           break;
     case 10:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr6=0;  //第6个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=11; //切换到下一个步骤
           }
           break;
     case 11:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr5=0;  //第5个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=12; //切换到下一个步骤
           }
           break;
     case 12:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr4=0;  //第4个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=13; //切换到下一个步骤
           }
           break;
     case 13:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr3=0;  //第3个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=14; //切换到下一个步骤
           }
           break;
     case 14:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr2=0;  //第2个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=15; //切换到下一个步骤
           }
           break;
     case 15:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr1=0;  //第1个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=0; //返回到最开始处,重新开始新的一次循环。
           }
           break;

   }

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  if(uiTimeCnt_01_08<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt_01_08++;  //累加定时中断的次数,
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=1;  //开中断
}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

  TMOD=0x01;  //设置定时器0为工作方式1


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
这节讲了在第1个至第8个LED灯中,先依次逐个亮再依次逐个灭的跑马灯程序。下一节我们略作修改,继续做跑马灯的程序,要求在第9个至第16个LED灯中,依次逐个亮灯并且每次只能亮一个灯(其它的都灭),依次循环,我们该如何编写程序?欲知详情,请听下回分解-----依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
24#
 楼主| 发表于 2014-2-1 05:33:35 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-2-1 05:38 编辑

第二十节:依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。

开场白:
上一节讲了先依次逐个亮再依次逐个灭的跑马灯程序。这一节在上一节的基础上,略作修改,继续讲跑马灯程序。我的跑马灯程序看似简单而且重复,其实蕴含着鸿哥的大智慧。它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,以后任何所谓复杂的工程项目,都不再复杂。要教会大家一个知识点:通过本跑马灯程序,加深理解鸿哥所有实战项目中switch状态机的思想精髓。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。第1至第8个LED灯一直灭。

(3)源代码讲解如下:
#include "REG52.H"

#define const_time_level_09_16  300  //第9个至第16个LED跑马灯的速度延时时间

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker_09_16(); // 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数


sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。

unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker_09_16(); // 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
          led_update();  //LED更新函数
   }

}


void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}

/* 注释一:
* 以下程序,看似简单而且重复,其实蕴含着鸿哥的大智慧。
* 它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,
* 以后任何所谓复杂的工程项目,都不再复杂。
*/
void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
  switch(ucLedStep_09_16)
  {
     case 0:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr16=0;  //第16个灭
               ucLed_dr9=1;  //第9个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr9=0;  //第9个灭
               ucLed_dr10=1;  //第10个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=2; //切换到下一个步骤
           }
           break;
     case 2:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr10=0;  //第10个灭
               ucLed_dr11=1;  //第11个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=3; //切换到下一个步骤
           }
           break;
     case 3:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr11=0;  //第11个灭
               ucLed_dr12=1;  //第12个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=4; //切换到下一个步骤
           }
           break;
     case 4:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr12=0;  //第12个灭
               ucLed_dr13=1;  //第13个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=5; //切换到下一个步骤
           }
           break;
     case 5:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr13=0;  //第13个灭
               ucLed_dr14=1;  //第14个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=6; //切换到下一个步骤
           }
           break;
     case 6:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr14=0;  //第14个灭
               ucLed_dr15=1;  //第15个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=7; //切换到下一个步骤
           }
           break;
     case 7:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr15=0;  //第15个灭
               ucLed_dr16=1;  //第16个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
           }
           break;
   
   }

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt_09_16++;  //累加定时中断的次数,
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=1;  //开中断
}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

  TMOD=0x01;  //设置定时器0为工作方式1


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
上一节和这一节讲了两种不同的跑马灯程序,如果要让这两种不同的跑马灯程序都能各自独立运行,就涉及到多任务并行处理的程序框架。没错,下一节就讲多任务并行处理这方面的知识,欲知详情,请听下回分解-----多任务并行处理两路跑马灯。
(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
25#
 楼主| 发表于 2014-2-1 05:34:51 | 显示全部楼层
第二十一节:多任务并行处理两路跑马灯。

开场白:
上一节讲了依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。这一节要结合前面两节的内容,实现多任务并行处理两路跑马灯。要教会大家一个知识点:利用鸿哥的switch状态机思想,实现多任务并行处理的程序。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:
第一路独立运行的任务是:第1个至第8个LED灯,先依次逐个亮,再依次逐个灭。
第二路独立运行的任务是:第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。

(3)源代码讲解如下:
#include "REG52.H"

#define const_time_level_01_08  200  //第1个至第8个LED跑马灯的速度延时时间
#define const_time_level_09_16  300  //第9个至第16个LED跑马灯的速度延时时间

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker_01_08(); //第一路独立运行的任务 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
void led_flicker_09_16(); //第二路独立运行的任务 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数


sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


unsigned char ucLedStep_01_08=0; //第1个至第8个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_01_08=0; //第1个至第8个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker_01_08(); //第一路独立运行的任务 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
      led_flicker_09_16(); //第二路独立运行的任务 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
          led_update();  //LED更新函数
   }

}


void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}

/* 注释一:
* 以下程序,看似简单而且重复,其实蕴含着鸿哥的大智慧。
* 它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,
* 以后任何所谓复杂的工程项目,都不再复杂。
*/

void led_flicker_01_08() //第一路独立运行的任务 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
{
  switch(ucLedStep_01_08)
  {
     case 0:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr1=1;  //第1个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr2=1;  //第2个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=2; //切换到下一个步骤
           }
           break;
     case 2:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr3=1;  //第3个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=3; //切换到下一个步骤
           }
           break;
     case 3:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr4=1;  //第4个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=4; //切换到下一个步骤
           }
           break;
     case 4:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr5=1;  //第5个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=5; //切换到下一个步骤
           }
           break;
     case 5:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr6=1;  //第6个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=6; //切换到下一个步骤
           }
           break;
     case 6:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr7=1;  //第7个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=7; //切换到下一个步骤
           }
           break;
     case 7:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr8=1;  //第8个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=8; //切换到下一个步骤
           }
           break;
     case 8:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr8=0;  //第8个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=9; //切换到下一个步骤
           }
           break;
     case 9:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr7=0;  //第7个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=10; //切换到下一个步骤
           }
           break;
     case 10:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr6=0;  //第6个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=11; //切换到下一个步骤
           }
           break;
     case 11:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr5=0;  //第5个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=12; //切换到下一个步骤
           }
           break;
     case 12:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr4=0;  //第4个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=13; //切换到下一个步骤
           }
           break;
     case 13:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr3=0;  //第3个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=14; //切换到下一个步骤
           }
           break;
     case 14:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr2=0;  //第2个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=15; //切换到下一个步骤
           }
           break;
     case 15:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr1=0;  //第1个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=0; //返回到最开始处,重新开始新的一次循环。
           }
           break;

   }

}


void led_flicker_09_16() //第二路独立运行的任务  第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
  switch(ucLedStep_09_16)
  {
     case 0:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr16=0;  //第16个灭
               ucLed_dr9=1;  //第9个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr9=0;  //第9个灭
               ucLed_dr10=1;  //第10个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=2; //切换到下一个步骤
           }
           break;
     case 2:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr10=0;  //第10个灭
               ucLed_dr11=1;  //第11个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=3; //切换到下一个步骤
           }
           break;
     case 3:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr11=0;  //第11个灭
               ucLed_dr12=1;  //第12个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=4; //切换到下一个步骤
           }
           break;
     case 4:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr12=0;  //第12个灭
               ucLed_dr13=1;  //第13个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=5; //切换到下一个步骤
           }
           break;
     case 5:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr13=0;  //第13个灭
               ucLed_dr14=1;  //第14个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=6; //切换到下一个步骤
           }
           break;
     case 6:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr14=0;  //第14个灭
               ucLed_dr15=1;  //第15个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=7; //切换到下一个步骤
           }
           break;
     case 7:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr15=0;  //第15个灭
               ucLed_dr16=1;  //第16个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
           }
           break;
   
   }

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  if(uiTimeCnt_01_08<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt_01_08++;  //累加定时中断的次数,
  }

  if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt_09_16++;  //累加定时中断的次数,
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=1;  //开中断
}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

  TMOD=0x01;  //设置定时器0为工作方式1


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
这一节讲了多任务并行处理两路跑马灯的程序,从下一节开始,将会在跑马灯的基础上,新加入按键这个元素。如何把按键跟跑马灯的任务有效的关联起来,欲知详情,请听下回分解-----独立按键控制跑马灯的方向。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
26#
 楼主| 发表于 2014-2-1 05:35:35 | 显示全部楼层
第二十二节:独立按键控制跑马灯的方向。

开场白:
上一节讲了多任务并行处理两路跑马灯的程序。这一节要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的任务有效的关联起来。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1键作为改变方向的独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。按一次独立按键S1,将会更改跑马灯的运动方向。

(3)源代码讲解如下:
#include "REG52.H"

#define const_time_level_09_16  300  //第9个至第16个LED跑马灯的速度延时时间

#define const_voice_short  40   //蜂鸣器短叫的持续时间

#define const_key_time1  20    //按键去抖动延时的时间


void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数

void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里

sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

unsigned char ucKeySec=0;   //被触发的按键编号

unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

unsigned char ucLedDirFlag=0;   //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
          led_update();  //LED更新函数
      key_service(); //按键服务的应用程序
   }

}


void key_scan()//按键扫描函数 放在定时中断里
{  

  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
     uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }



}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键

          if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
                  {
                     ucLedDirFlag=1;
                  }
                  else
                  {
                           ucLedDirFlag=0;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;        
                 
  }               
}




void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}

/* 注释一:
* 以下程序,要学会如何通过中间变量,把按键和跑马灯的任务关联起来
*/

void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
  switch(ucLedStep_09_16)
  {
     case 0:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr16=0;  //第16个灭
                  ucLed_dr9=1;  //第9个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=1; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr15=1;  //第15个亮
                  ucLed_dr16=0;  //第16个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=7; //返回上一个步骤
                           }
           }
           break;
     case 1:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr9=0;  //第9个灭
                  ucLed_dr10=1;  //第10个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=2; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr16=1;  //第16个亮
                  ucLed_dr9=0;  //第9个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=0; //返回上一个步骤
                           }
           }
           break;
     case 2:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr10=0;  //第10个灭
                  ucLed_dr11=1;  //第11个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=3; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr9=1;  //第9个亮
                  ucLed_dr10=0;  //第10个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=1; //返回上一个步骤
                           }
           }
           break;
     case 3:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr11=0;  //第11个灭
                  ucLed_dr12=1;  //第12个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=4; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr10=1;  //第10个亮
                  ucLed_dr11=0;  //第11个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=2; //返回上一个步骤
                           }
           }
           break;
     case 4:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr12=0;  //第12个灭
                  ucLed_dr13=1;  //第13个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=5; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr11=1;  //第11个亮
                  ucLed_dr12=0;  //第12个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=3; //返回上一个步骤
                           }
           }
           break;
     case 5:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr13=0;  //第13个灭
                  ucLed_dr14=1;  //第14个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=6; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr12=1;  //第12个亮
                  ucLed_dr13=0;  //第13个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=4; //返回上一个步骤
                           }
           }
           break;
     case 6:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr14=0;  //第14个灭
                  ucLed_dr15=1;  //第15个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=7; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr13=1;  //第13个亮
                  ucLed_dr14=0;  //第14个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=5; //返回上一个步骤
                           }
           }
           break;
     case 7:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr15=0;  //第15个灭
                  ucLed_dr16=1;  //第16个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
                           }
                           else  //反方向
                           {
                  ucLed_dr14=1;  //第14个亮
                  ucLed_dr15=0;  //第15个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=6; //返回上一个步骤
                           }
           }
           break;
   
   }

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断


  if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt_09_16++;  //累加定时中断的次数,
  }

  key_scan(); //按键扫描函数

  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
     beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  }
  else
  {
     ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
     beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=1;  //开中断
}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{
/* 注释二:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  TMOD=0x01;  //设置定时器0为工作方式1


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
这一节讲了独立按键控制跑马灯的方向。如果按键要控制跑马灯的速度,我们该怎么编写程序呢?欲知详情,请听下回分解-----独立按键控制跑马灯的速度。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
27#
 楼主| 发表于 2014-2-1 05:36:23 | 显示全部楼层
第二十三节:独立按键控制跑马灯的速度。

开场白:
上一节讲了独立按键控制跑马灯的方向。这一节继续要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的速度有效关联起来。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。在上一节的基础上,增加一个加速按键和一个减速按键,用矩阵键盘中的S5键作为加速独立按键,用矩阵键盘中的S9键作为减速独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
在上一节的基础上,第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。每按一次独立按键S5,速度都会加快。每按一次独立按键S9,速度都会减慢。跟上一节一样,用S1来改变方向。

(3)源代码讲解如下:
#include "REG52.H"

#define const_voice_short  40   //蜂鸣器短叫的持续时间

#define const_key_time1  20    //按键去抖动延时的时间
#define const_key_time2  20    //按键去抖动延时的时间
#define const_key_time3  20    //按键去抖动延时的时间

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数

void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里

sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键

sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

unsigned char ucKeySec=0;   //被触发的按键编号

unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

unsigned char ucLedDirFlag=0;   //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向
unsigned int  uiSetTimeLevel_09_16=300;  //速度变量,此数值越大速度越慢,此数值越小速度越快。

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
          led_update();  //LED更新函数
      key_service(); //按键服务的应用程序
   }

}


void key_scan()//按键扫描函数 放在定时中断里
{  

  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
     uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }

  if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock2=0; //按键自锁标志清零
     uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt2++; //累加定时中断次数
     if(uiKeyTimeCnt2>const_key_time2)
     {
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;  //自锁按键置位,避免一直触发
        ucKeySec=2;    //触发2号键
     }
  }

  if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock3=0; //按键自锁标志清零
     uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt3++; //累加定时中断次数
     if(uiKeyTimeCnt3>const_key_time3)
     {
        uiKeyTimeCnt3=0;
        ucKeyLock3=1;  //自锁按键置位,避免一直触发
        ucKeySec=3;    //触发3号键
     }
  }

}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键

          if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
                  {
                     ucLedDirFlag=1;
                  }
                  else
                  {
                           ucLedDirFlag=0;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 加速按键 对应朱兆祺学习板的S5键 uiSetTimeLevel_09_16越小速度越快
          uiSetTimeLevel_09_16=uiSetTimeLevel_09_16-10;
                  if(uiSetTimeLevel_09_16<50)  //最快限定在50
                  {
                      uiSetTimeLevel_09_16=50;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  

    case 3:// 减速按键 对应朱兆祺学习板的S9键  uiSetTimeLevel_09_16越大速度越慢
          uiSetTimeLevel_09_16=uiSetTimeLevel_09_16+10;
                  if(uiSetTimeLevel_09_16>550)  //最慢限定在550
                  {
                      uiSetTimeLevel_09_16=550;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;                  
  }               
}




void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}

/* 注释一:
* 以下程序,要学会如何通过中间变量,把按键和跑马灯的任务关联起来
*/

void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
  switch(ucLedStep_09_16)
  {
     case 0:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr16=0;  //第16个灭
                  ucLed_dr9=1;  //第9个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=1; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr15=1;  //第15个亮
                  ucLed_dr16=0;  //第16个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=7; //返回上一个步骤
                           }
           }
           break;
     case 1:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr9=0;  //第9个灭
                  ucLed_dr10=1;  //第10个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=2; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr16=1;  //第16个亮
                  ucLed_dr9=0;  //第9个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=0; //返回上一个步骤
                           }
           }
           break;
     case 2:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr10=0;  //第10个灭
                  ucLed_dr11=1;  //第11个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=3; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr9=1;  //第9个亮
                  ucLed_dr10=0;  //第10个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=1; //返回上一个步骤
                           }
           }
           break;
     case 3:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr11=0;  //第11个灭
                  ucLed_dr12=1;  //第12个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=4; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr10=1;  //第10个亮
                  ucLed_dr11=0;  //第11个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=2; //返回上一个步骤
                           }
           }
           break;
     case 4:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr12=0;  //第12个灭
                  ucLed_dr13=1;  //第13个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=5; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr11=1;  //第11个亮
                  ucLed_dr12=0;  //第12个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=3; //返回上一个步骤
                           }
           }
           break;
     case 5:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr13=0;  //第13个灭
                  ucLed_dr14=1;  //第14个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=6; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr12=1;  //第12个亮
                  ucLed_dr13=0;  //第13个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=4; //返回上一个步骤
                           }
           }
           break;
     case 6:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr14=0;  //第14个灭
                  ucLed_dr15=1;  //第15个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=7; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr13=1;  //第13个亮
                  ucLed_dr14=0;  //第14个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=5; //返回上一个步骤
                           }
           }
           break;
     case 7:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr15=0;  //第15个灭
                  ucLed_dr16=1;  //第16个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
                           }
                           else  //反方向
                           {
                  ucLed_dr14=1;  //第14个亮
                  ucLed_dr15=0;  //第15个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=6; //返回上一个步骤
                           }
           }
           break;
   
   }

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断


  if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt_09_16++;  //累加定时中断的次数,
  }

  key_scan(); //按键扫描函数

  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
     beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  }
  else
  {
     ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
     beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=1;  //开中断
}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{
/* 注释二:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  TMOD=0x01;  //设置定时器0为工作方式1


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
这一节讲了独立按键控制跑马灯的速度。如果按键要控制跑马灯的启动和暂停,我们该怎么编写程序呢?欲知详情,请听下回分解-----独立按键控制跑马灯的启动和暂停。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
28#
 楼主| 发表于 2014-2-1 05:37:35 | 显示全部楼层
第二十四节:独立按键控制跑马灯的启动和暂停。

开场白:
上一节讲了独立按键控制跑马灯的速度。这一节继续要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的启动和暂停有效关联起来。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。在上一节的基础上,增加一个启动和暂停按键,用矩阵键盘中的S13键作为启动和暂停独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
在上一节的基础上,第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。每按一次独立按键S13键,原来运行的跑马灯会暂停,原来暂停的跑马灯会运行。其它跟上一节一样,用S1来改变方向,用S5和S9来改变速度。

(3)源代码讲解如下:
#include "REG52.H"

#define const_voice_short  40   //蜂鸣器短叫的持续时间

#define const_key_time1  20    //按键去抖动延时的时间
#define const_key_time2  20    //按键去抖动延时的时间
#define const_key_time3  20    //按键去抖动延时的时间
#define const_key_time4  20    //按键去抖动延时的时间

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数

void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里

sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

unsigned char ucKeySec=0;   //被触发的按键编号

unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志


unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

unsigned char ucLedDirFlag=0;   //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向
unsigned int  uiSetTimeLevel_09_16=300;  //速度变量,此数值越大速度越慢,此数值越小速度越快。
unsigned char ucLedStartFlag=1;   //启动和暂停的变量,0代表暂停,1代表启动

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
          led_update();  //LED更新函数
      key_service(); //按键服务的应用程序
   }

}


void key_scan()//按键扫描函数 放在定时中断里
{  

  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
     uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }

  if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock2=0; //按键自锁标志清零
     uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt2++; //累加定时中断次数
     if(uiKeyTimeCnt2>const_key_time2)
     {
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;  //自锁按键置位,避免一直触发
        ucKeySec=2;    //触发2号键
     }
  }

  if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock3=0; //按键自锁标志清零
     uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt3++; //累加定时中断次数
     if(uiKeyTimeCnt3>const_key_time3)
     {
        uiKeyTimeCnt3=0;
        ucKeyLock3=1;  //自锁按键置位,避免一直触发
        ucKeySec=3;    //触发3号键
     }
  }

  if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock4=0; //按键自锁标志清零
     uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt4++; //累加定时中断次数
     if(uiKeyTimeCnt4>const_key_time4)
     {
        uiKeyTimeCnt4=0;
        ucKeyLock4=1;  //自锁按键置位,避免一直触发
        ucKeySec=4;    //触发4号键
     }
  }

}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键

          if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
                  {
                     ucLedDirFlag=1;
                  }
                  else
                  {
                           ucLedDirFlag=0;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 加速按键 对应朱兆祺学习板的S5键 uiSetTimeLevel_09_16越小速度越快
          uiSetTimeLevel_09_16=uiSetTimeLevel_09_16-10;
                  if(uiSetTimeLevel_09_16<50)  //最快限定在50
                  {
                      uiSetTimeLevel_09_16=50;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  

    case 3:// 减速按键 对应朱兆祺学习板的S9键  uiSetTimeLevel_09_16越大速度越慢
          uiSetTimeLevel_09_16=uiSetTimeLevel_09_16+10;
                  if(uiSetTimeLevel_09_16>550)  //最慢限定在550
                  {
                      uiSetTimeLevel_09_16=550;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
         
    case 4:// 启动和暂停按键 对应朱兆祺学习板的S13键  ucLedStartFlag为0时代表暂停,为1时代表启动

              if(ucLedStartFlag==1)  //启动和暂停两种状态循环切换
                  {
                     ucLedStartFlag=0;
                  }
                  else                   //启动和暂停两种状态循环切换
                  {
                           ucLedStartFlag=1;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
  }               
}




void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}

/* 注释一:
* 以下程序,要学会如何通过中间变量,把按键和跑马灯的任务关联起来
*/

void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
  if(ucLedStartFlag==1)  //此变量为1时代表启动
  {
     switch(ucLedStep_09_16)
     {
     case 0:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr16=0;  //第16个灭
                  ucLed_dr9=1;  //第9个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=1; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr15=1;  //第15个亮
                  ucLed_dr16=0;  //第16个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=7; //返回上一个步骤
                           }
           }
           break;
     case 1:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr9=0;  //第9个灭
                  ucLed_dr10=1;  //第10个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=2; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr16=1;  //第16个亮
                  ucLed_dr9=0;  //第9个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=0; //返回上一个步骤
                           }
           }
           break;
     case 2:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr10=0;  //第10个灭
                  ucLed_dr11=1;  //第11个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=3; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr9=1;  //第9个亮
                  ucLed_dr10=0;  //第10个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=1; //返回上一个步骤
                           }
           }
           break;
     case 3:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr11=0;  //第11个灭
                  ucLed_dr12=1;  //第12个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=4; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr10=1;  //第10个亮
                  ucLed_dr11=0;  //第11个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=2; //返回上一个步骤
                           }
           }
           break;
     case 4:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr12=0;  //第12个灭
                  ucLed_dr13=1;  //第13个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=5; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr11=1;  //第11个亮
                  ucLed_dr12=0;  //第12个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=3; //返回上一个步骤
                           }
           }
           break;
     case 5:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr13=0;  //第13个灭
                  ucLed_dr14=1;  //第14个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=6; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr12=1;  //第12个亮
                  ucLed_dr13=0;  //第13个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=4; //返回上一个步骤
                           }
           }
           break;
     case 6:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr14=0;  //第14个灭
                  ucLed_dr15=1;  //第15个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=7; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr13=1;  //第13个亮
                  ucLed_dr14=0;  //第14个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=5; //返回上一个步骤
                           }
           }
           break;
     case 7:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr15=0;  //第15个灭
                  ucLed_dr16=1;  //第16个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
                           }
                           else  //反方向
                           {
                  ucLed_dr14=1;  //第14个亮
                  ucLed_dr15=0;  //第15个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=6; //返回上一个步骤
                           }
           }
           break;
   
      }
   }

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断


  if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      if(ucLedStartFlag==1)  //此变量为1时代表启动
          {
         uiTimeCnt_09_16++;  //累加定时中断的次数,
          }
  }

  key_scan(); //按键扫描函数

  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
     beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  }
  else
  {
     ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
     beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=1;  //开中断
}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{
/* 注释二:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  TMOD=0x01;  //设置定时器0为工作方式1


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
这几节循序渐进地讲了独立按键控制跑马灯各种状态的程序。在很多实际工控项目中,经常会涉及到运动的自动控制,运动的自动控制就必然会涉及到感应器。下一节我将会讲感应器和运动控制的程序框架,欲知详情,请听下回分解-----用LED灯和按键来模拟工业自动化设备的运动控制。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
29#
 楼主| 发表于 2014-2-10 02:18:57 | 显示全部楼层
第二十五节:用LED灯和按键来模拟工业自动化设备的运动控制。

开场白:
      前面三节讲了独立按键控制跑马灯的各种状态,这一节我们要做一个机械手控制程序,这个机械手可以左右移动,最左边有一个开关感应器,最右边也有一个开关感应器。它也可以上下移动,最下面有一个开关感应器。左右移动是通过一个气缸控制,上下移动也是通过一个气缸控制。而单片机控制气缸,本质上是通过三极管把信号放大,然后控制气缸上的电磁阀。这个系统机械手驱动部分的输出和输入信号如下:
    2个输出IO口,分别控制2个气缸。对于左右移动的气缸,当IO口为0时往左边跑,当IO口为1时往右边跑。对于上下移动的气缸,当IO口为0时往上边跑,当IO口为1时往下边跑。
      3个输入IO口,分别检测3个开关感应器。感应器没有被触发时,IO口检测为高电平1。被触发时,IO口检测为低电平0。
这一节继续要教会大家两个知识点:
第一点:如何用软件进行开关感应器的抗干扰处理。
第二点:如何用Switch语句搭建工业自动控制的程序框架。还是那句话,我们只要以Switch语句为支点,再复杂再繁琐的程序都可以轻松地编写出来。

具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1键作为启动独立按键,用S5按键模拟左边的开关感应器,用S9按键模拟右边的开关感应器,用S13按键模拟下边的开关感应器。记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
      开机默认机械手在左上方的原点位置。按下启动按键后,机械手从左边开始往右边移动,当机械手移动到最右边时,机械手马上开始往下移动,最后机械手移动到最右下角的位置时,延时1秒,然后原路返回,一直返回到左上角的原点位置。注意:启动按键必须等机械手处于左上角原点位置时,启动按键的触发才有效。

(3)源代码讲解如下:
#include "REG52.H"

#define const_voice_short  40   //蜂鸣器短叫的持续时间

#define const_key_time1  20    //按键去抖动延时的时间


#define const_sensor  20   //开关感应器去抖动延时的时间

#define const_1s  500  //1秒钟大概的定时中断次数

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

void left_to_right();  //从左边移动到右边
void right_to_left(); //从右边返回到左边
void up_to_dowm();   //从上边移动到下边
void down_to_up();    //从下边返回到上边


void run(); //设备自动控制程序
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数

void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里
void sensor_scan(); //开关感应器软件抗干扰处理函数,放在定时中断里。

sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键

sbit left_sr=P0^1; //左边的开关感应器    对应朱兆祺学习板的S5键   
sbit right_sr=P0^2; //右边的开关感应器   有对应朱兆祺学习板的S9键
sbit down_sr=P0^3; //下边的开关感应器    对应朱兆祺学习板的S13键

sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

unsigned char ucKeySec=0;   //被触发的按键编号

unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志


unsigned char ucLeftSr=0;  //左边感应器经过软件抗干扰处理后的状态标志
unsigned char ucRightSr=0;  //右边感应器经过软件抗干扰处理后的状态标志
unsigned char ucDownSr=0;  //下边感应器经过软件抗干扰处理后的状态标志

unsigned int  uiLeftCnt1=0;  //左边感应器软件抗干扰所需的计数器变量
unsigned int  uiLeftCnt2=0;

unsigned int  uiRightCnt1=0;  //右边感应器软件抗干扰所需的计数器变量
unsigned int  uiRightCnt2=0;

unsigned int  uiDownCnt1=0;   //下边软件抗干扰所需的计数器变量
unsigned int  uiDownCnt2=0;

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。



unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量



unsigned int  uiRunTimeCnt=0;  //运动中的时间延时计数器变量
unsigned char ucRunStep=0;  //运动控制的步骤变量

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      run(); //设备自动控制程序
      led_update();  //LED更新函数
      key_service(); //按键服务的应用程序
   }

}


/* 注释一:
* 开关感应器的抗干扰处理,本质上类似按键的去抖动处理。唯一的区别是:
* 按键去抖动关注的是IO口的一种状态,而开关感应器关注的是IO口的两种状态。
* 当开关感应器从原来的1状态切换到0状态之前,要进行软件滤波处理过程,一旦成功地
* 切换到0状态了,再想从0状态切换到1状态的时候,又要经过软件滤波处理过程,符合
* 条件后才能切换到1的状态。通俗的话来说,按键的去抖动从1变成0难,从0变成1容易。
* 开关感应器从1变成0难,从0变成1也难。这里所说的"难"是指要经过去抖处理。
*/

void sensor_scan() //开关感应器软件抗干扰处理函数,放在定时中断里。
{
   if(left_sr==1)  //左边感应器是高电平,说明有可能没有被接触    对应朱兆祺学习板的S5键  
   {
       uiLeftCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiLeftCnt2++; //类似独立按键去抖动的软件抗干扰处理
           if(uiLeftCnt2>const_sensor)
           {
              uiLeftCnt2=0;
                  ucLeftSr=1;   //说明感应器确实没有被接触
           }
   }
   else    //左边感应器是低电平,说明有可能被接触到了
   {
       uiLeftCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiLeftCnt1++;
           if(uiLeftCnt1>const_sensor)
           {
              uiLeftCnt1=0;
                  ucLeftSr=0;   //说明感应器确实被接触到了
           }
   }

   if(right_sr==1)  //右边感应器是高电平,说明有可能没有被接触    对应朱兆祺学习板的S9键  
   {
       uiRightCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiRightCnt2++; //类似独立按键去抖动的软件抗干扰处理
           if(uiRightCnt2>const_sensor)
           {
              uiRightCnt2=0;
                  ucRightSr=1;   //说明感应器确实没有被接触
           }
   }
   else    //右边感应器是低电平,说明有可能被接触到了   
   {
       uiRightCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiRightCnt1++;
           if(uiRightCnt1>const_sensor)
           {
              uiRightCnt1=0;
                  ucRightSr=0;   //说明感应器确实被接触到了
           }
   }

   if(down_sr==1)  //下边感应器是高电平,说明有可能没有被接触    对应朱兆祺学习板的S13键  
   {
       uiDownCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiDownCnt2++; //类似独立按键去抖动的软件抗干扰处理
           if(uiDownCnt2>const_sensor)
           {
              uiDownCnt2=0;
                  ucDownSr=1;   //说明感应器确实没有被接触
           }
   }
   else    //下边感应器是低电平,说明有可能被接触到了
   {
       uiDownCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiDownCnt1++;
           if(uiDownCnt1>const_sensor)
           {
              uiDownCnt1=0;
                  ucDownSr=0;   //说明感应器确实被接触到了
           }
   }
}


void key_scan()//按键扫描函数 放在定时中断里
{  

  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
     uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }



}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 启动按键   对应朱兆祺学习板的S1键
         if(ucLeftSr==0)  //处于左上角原点位置
         {
             ucRunStep=1; //启动
             uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。  
         }           
   
         ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
         break;   
     
  }               
}



void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


void left_to_right()  //从左边移动到右边
{
   ucLed_dr1=1;   // 1代表左右气缸从左边移动到右边

   ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
}
void right_to_left() //从右边返回到左边
{
   ucLed_dr1=0;   // 0代表左右气缸从右边返回到左边

   ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
}
void up_to_down()   //从上边移动到下边
{
   ucLed_dr2=1;   // 1代表上下气缸从上边移动到下边

   ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
}
void down_to_up()    //从下边返回到上边
{
   ucLed_dr2=0;   // 0代表上下气缸从下边返回到上边

   ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
}


void run() //设备自动控制程序
{

switch(ucRunStep)
{
       case 0:    //机械手处于左上角原点的位置,待命状态。此时触发启动按键ucRunStep=1,就触发后续一些列的连续动作。

            break;

       case 1:    //机械手从左边往右边移动
            left_to_right();
            ucRunStep=2;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            break;

       case 2:    //等待机械手移动到最右边,直到触发了最右边的开关感应器。
            if(ucRightSr==0)  //右边感应器被触发
            {
               ucRunStep=3;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;

       case 3:    //机械手从右上边往右下边移动,从上往下。
            up_to_down();
            ucRunStep=4;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            break;

       case 4:    //等待机械手从右上边移动到右下边,直到触发了右下边的开关感应器。
            if(ucDownSr==0)  //右下边感应器被触发
            {
               uiRunTimeCnt=0;  //时间计数器清零,为接下来延时1秒钟做准备
               ucRunStep=5;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;

       case 5:    //机械手在右下边延时1秒
            if(uiRunTimeCnt>const_1s)  //延时1秒
            {
               ucRunStep=6;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;
       case 6:    //原路返回,机械手从右下边往右上边移动。
            down_to_up();
            ucRunStep=7;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            break;

       case 7:    //原路返回,等待机械手移动到最右边的感应开关
            if(ucRightSr==0)
            {
               ucRunStep=8;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;

       case 8:    //原路返回,等待机械手从右边往左边移动
            right_to_left();
            ucRunStep=9;  //这就是鸿哥传说中的怎样灵活控制步骤变量

            break;

       case 9:    //原路返回,等待机械手移动到最左边的感应开关,表示返回到了原点
            if(ucLeftSr==0) //返回到左上角的原点位置
            {
               ucRunStep=0;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;
   }
}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断


  sensor_scan(); //开关感应器软件抗干扰处理函数
  key_scan(); //按键扫描函数

  if(uiRunTimeCnt<0xffff) //不要超过最大int类型范围
  {
     uiRunTimeCnt++; //延时计数器
  }
  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
     beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  }
  else
  {
     ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
     beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=1;  //开中断
}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{
/* 注释二:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  TMOD=0x01;  //设置定时器0为工作方式1


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
前面花了很多节内容在讲按键和跑马灯的关系,但是一直没涉及到人机界面,在大多数的实际项目中,人机界面是必不可少的。人机界面的程序框架该怎么样写?欲知详情,请听下回分解-----在主函数while循环中驱动数码管的动态扫描程序。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
30#
 楼主| 发表于 2014-2-17 02:56:45 | 显示全部楼层
第二十六节:在主函数while循环中驱动数码管的动态扫描程序。

开场白:
上一节通过一个机械手自动控制程序展示了我在工控常用的编程框架,但是一直没涉及到人机界面,在大多数的实际项目中,人机界面是必不可少的,这一节开始讲最常用的人机界面------动态数码管的驱动。

这一节要教会大家两个知识点:
第一点:数码管的动态驱动原理。
第二点:如何通过编程,让数码管显示的内容转移到几个变量接口上,方便以后编写更上一层的窗口程序。

具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。用两片74HC595动态驱动八位共阴数码管。

(2)实现功能:
      开机后显示  8765.4321  的内容,注意,其中有一个小数点。
(3)源代码讲解如下:
#include "REG52.H"


void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
void display_drive(); //显示数码管字模的驱动函数

//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);


sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  

sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  


unsigned char ucDigShow8;  //第8位数码管要显示的内容
unsigned char ucDigShow7;  //第7位数码管要显示的内容
unsigned char ucDigShow6;  //第6位数码管要显示的内容
unsigned char ucDigShow5;  //第5位数码管要显示的内容
unsigned char ucDigShow4;  //第4位数码管要显示的内容
unsigned char ucDigShow3;  //第3位数码管要显示的内容
unsigned char ucDigShow2;  //第2位数码管要显示的内容
unsigned char ucDigShow1;  //第1位数码管要显示的内容


unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


unsigned char ucDisplayUpdate=1; //更新显示标志

//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
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
};

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
      display_drive();  //显示数码管字模的驱动函数
   }

}

/* 注释一:
* 动态驱动数码管的原理是,在八位数码管中,在任何一个瞬间,每次只显示其中一位数码管,另外的七个数码管
* 通过设置其公共位com为高电平来关闭显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管
* 是同时亮的。以下dig_hc595_drive(xx,yy)函数,其中第一个形参xx是驱动数码管段seg的引脚,第二个形参yy是驱动
* 数码管公共位com的引脚。
*/

void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }

   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }

/* 注释二:
* 如果直接是单片机的IO口引脚驱动的数码管,由于驱动的速度太快,此处应该适当增加一点delay延时或者
* 用计数延时的方式来延时,目的是在八位数码管中切换到每位数码管显示的时候,都能停留一会再切换到其它
* 位的数码管界面,这样可以增加显示的效果。但是,由于坚鸿51学习板是间接经过74HC595驱动数码管的,
* 在单片机驱动74HC595的时候,dig_hc595_drive函数本身内部需要执行很多指令,已经相当于delay延时了,
* 因此这里不再需要加delay延时函数或者计数延时。
*/


}


//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;

   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

/* 注释三:
*  注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
*/
         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);

   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;

}


//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}





void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

}
void initial_peripheral() //第二区 初始化外围
{
/* 注释四:
* 让数码管显示的内容转移到以下几个变量接口上,方便以后编写更上一层的窗口程序。
* 只要更改以下对应变量的内容,就可以显示你想显示的数字。初学者应该仔细看看display_drive等函数,
* 了解来龙去脉,就可以知道本驱动程序的框架原理了。
*/
   ucDigShow8=8;  //第8位数码管要显示的内容
   ucDigShow7=7;  //第7位数码管要显示的内容
   ucDigShow6=6;  //第6位数码管要显示的内容
   ucDigShow5=5;  //第5位数码管要显示的内容
   ucDigShow4=4;  //第4位数码管要显示的内容
   ucDigShow3=3;  //第3位数码管要显示的内容
   ucDigShow2=2;  //第2位数码管要显示的内容
   ucDigShow1=1;  //第1位数码管要显示的内容


   ucDigDot8=0;  
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=1;  //显示第5位的小数点
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;
}

总结陈词:
把本程序下载到坚鸿51学习板上,发现显示的效果还是挺不错的。但是,本程序也有一个弱点,在一些项目中 ,主函数循环中的任务越多,就意味着在某一瞬间,每显示一位数码管停留的时间就会越久,一旦超过某个值,会严重影响显示的效果,有没有办法改善它?当然有。欲知详情,请听下回分解-----在定时中断里动态扫描数码管的程序。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
31#
 楼主| 发表于 2014-2-17 02:57:50 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-2-22 22:48 编辑

第二十七节:在定时中断里动态扫描数码管的程序。

开场白:
上一节讲了在主函数循环中动态扫描数码管的程序,但是该程序有一个隐患,在一些项目中 ,主函数循环中的任务越多,就意味着在某一瞬间,每显示一位数码管停留的时间就会越久,一旦超过某个值,会严重影响显示的效果。这一节要教会大家两个知识点:
第一个:如何把动态扫描数码管的程序放在定时中断里,彻底解决上节的显示隐患。
第二个:在定时中断里的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的初始值2000改成了500。

具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。用两片74HC595动态驱动八位共阴数码管。

(2)实现功能:
      开机后显示  8765.4321  的内容,注意,其中有一个小数点。
(3)源代码讲解如下:
#include "REG52.H"


void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
void display_drive(); //显示数码管字模的驱动函数

//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

void T0_time();  //定时中断函数

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  

sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  


unsigned char ucDigShow8;  //第8位数码管要显示的内容
unsigned char ucDigShow7;  //第7位数码管要显示的内容
unsigned char ucDigShow6;  //第6位数码管要显示的内容
unsigned char ucDigShow5;  //第5位数码管要显示的内容
unsigned char ucDigShow4;  //第4位数码管要显示的内容
unsigned char ucDigShow3;  //第3位数码管要显示的内容
unsigned char ucDigShow2;  //第2位数码管要显示的内容
unsigned char ucDigShow1;  //第1位数码管要显示的内容


unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


unsigned char ucDisplayUpdate=1; //更新显示标志

//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
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
};

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
      ;
   }

}

/* 注释一:
* 动态驱动数码管的原理是,在八位数码管中,在任何一个瞬间,每次只显示其中一位数码管,另外的七个数码管
* 通过设置其公共位com为高电平来关闭显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管
* 是同时亮的。以下dig_hc595_drive(xx,yy)函数,其中第一个形参xx是驱动数码管段seg的引脚,第二个形参yy是驱动
* 数码管公共位com的引脚。
*/

void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }

   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }

/* 注释二:
* 如果直接是单片机的IO口引脚驱动的数码管,由于驱动的速度太快,此处应该适当增加一点delay延时或者
* 用计数延时的方式来延时,目的是在八位数码管中切换到每位数码管显示的时候,都能停留一会再切换到其它
* 位的数码管界面,这样可以增加显示的效果。但是,由于坚鸿51学习板是间接经过74HC595驱动数码管的,
* 在单片机驱动74HC595的时候,dig_hc595_drive函数本身内部需要执行很多指令,已经相当于delay延时了,
* 因此这里不再需要加delay延时函数或者计数延时。
*/


}


//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;

   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

/* 注释三:
*  注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
*/
         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);

   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;

}


//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  display_drive();  //数码管字模的驱动函数

/* 注释四:
*  注意,此处的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的2000改成了500。
*/
  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=1;  //开中断
}


void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

  TMOD=0x01;  //设置定时器0为工作方式1

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;

}
void initial_peripheral() //第二区 初始化外围
{
/* 注释五:
* 让数码管显示的内容转移到以下几个变量接口上,方便以后编写更上一层的窗口程序。
* 只要更改以下对应变量的内容,就可以显示你想显示的数字。初学者应该仔细看看display_drive等函数,
* 了解来龙去脉,就可以知道本驱动程序的框架原理了。
*/
   ucDigShow8=8;  //第8位数码管要显示的内容
   ucDigShow7=7;  //第7位数码管要显示的内容
   ucDigShow6=6;  //第6位数码管要显示的内容
   ucDigShow5=5;  //第5位数码管要显示的内容
   ucDigShow4=4;  //第4位数码管要显示的内容
   ucDigShow3=3;  //第3位数码管要显示的内容
   ucDigShow2=2;  //第2位数码管要显示的内容
   ucDigShow1=1;  //第1位数码管要显示的内容


   ucDigDot8=0;  
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=1;  //显示第5位的小数点
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;

   EA=1;     //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断

}

总结陈词:
     有的朋友会质疑,很多教科书上说,定时中断函数里面的内容应该越少越好,你把动态驱动数码管的函数放在中断里面,难道不会影响其它任务的执行吗?我的回答是,大部分的小项目都不会影响,只有少数实时性要求非常高的项目会影响,而对于这类项目,我的做法是从一开始设计硬件电路板的时候,就应该放弃用动态扫描数码管的方案,而是应该选数码管专用驱动芯片来实现静态驱动。因为动态扫描数码管本来就不适合应用在实时性非常高的项目。
      前面这两节都讲了数码管的驱动程序,要在此基础上,做一些项目中经常遇到的界面应用,我们该怎么写程序?欲知详情,请听下回分解----数码管通过切换窗口来设置参数

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
32#
 楼主| 发表于 2014-2-23 01:28:49 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-2-28 14:42 编辑

第二十八节:数码管通过切换窗口来设置参数。

开场白:
上一节讲了数码管的驱动程序,这节在上节的基础上,通过按键切换不同的窗口来设置不同的参数。
这一节要教会大家三个知识点:
第一个:鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
第二个:如何通过一个窗口变量来把按键,数码管,被设置的参数关联起来。
第三个:需要特别注意,在显示被设置参数时,应该先分解出每一位,然后再把分解出来的数据过渡到显示缓冲变量里。

具体内容,请看源代码讲解。

1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换窗口按键对应S9

2)实现功能:
     通过按键设置4个不同的参数。
      一共有4个窗口。每个窗口显示一个参数。
8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
4,3,2,1位数码管显示当前窗口被设置的参数。范围是从09999
有三个按键。一个是加按键,按下此按键会依次增加当前窗口的参数。一个是减按键,按下此按键会依次减少当前窗口的参数。一个是切换窗口按键,按下此按键会依次循环切换不同的窗口。

3)源代码讲解如下:

#include "REG52.H"

#define const_voice_short  40   //蜂鸣器短叫的持续时间
#define const_key_time1  20    //按键去抖动延时的时间
#define const_key_time2  20    //按键去抖动延时的时间
#define const_key_time3  20    //按键去抖动延时的时间
void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
void display_drive(); //显示数码管字模的驱动函数
void display_service(); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time();  //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里

sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停

sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  
sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  
unsigned char ucKeySec=0;   //被触发的按键编号
unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned char ucDigShow8;  //第8位数码管要显示的内容
unsigned char ucDigShow7;  //第7位数码管要显示的内容
unsigned char ucDigShow6;  //第6位数码管要显示的内容
unsigned char ucDigShow5;  //第5位数码管要显示的内容
unsigned char ucDigShow4;  //第4位数码管要显示的内容
unsigned char ucDigShow3;  //第3位数码管要显示的内容
unsigned char ucDigShow2;  //第2位数码管要显示的内容
unsigned char ucDigShow1;  //第1位数码管要显示的内容

unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucWd2Update=0; //窗口2更新显示标志
unsigned char ucWd3Update=0; //窗口3更新显示标志
unsigned char ucWd4Update=0; //窗口4更新显示标志
unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

unsigned char ucTemp1=0;  //中间过渡变量
unsigned char ucTemp2=0;  //中间过渡变量
unsigned char ucTemp3=0;  //中间过渡变量
unsigned char ucTemp4=0;  //中间过渡变量

//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
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
0x40,  //-       序号11
0x73,  //P       序号12
};
void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
     key_service(); //按键服务的应用程序
         display_service(); //显示的窗口菜单服务程序
   }
}
/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/

void display_service() //显示的窗口菜单服务程序
{

   switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示P--1窗口的数据
            if(ucWd1Update==1)  //窗口1要全部更新显示
   {
               ucWd1Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=1;   //第6位数码管显示1
               ucDigShow5=10;  //第5位数码管显示无
/* 注释二:
* 此处为什么要多加4个中间过渡变量ucTemp?是因为uiSetData1分解数据的时候
* 需要进行除法和求余数的运算,就会用到好多条指令,就会耗掉一点时间,类似延时
* 了一会。我们的定时器每隔一段时间都会产生中断,然后在中断里驱动数码管显示,
* 当uiSetData1还没完全分解出4位有效数据时,这个时候来的定时中断,就有可能导致
* 显示的数据瞬间产生不完整,影响显示效果。因此,为了把需要显示的数据过渡最快,
* 所以采取了先分解,再过渡显示的方法。
*/
              //先分解数据
                       ucTemp4=uiSetData1/1000;     
                       ucTemp3=uiSetData1%1000/100;
                       ucTemp2=uiSetData1%100/10;
                       ucTemp1=uiSetData1%10;
  
                          //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
               ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
               ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
               ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
            }
            break;
        case 2:  //显示P--2窗口的数据
            if(ucWd2Update==1)  //窗口2要全部更新显示
   {
               ucWd2Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=2;  //第6位数码管显示2
               ucDigShow5=10;   //第5位数码管显示无
                       ucTemp4=uiSetData2/1000;     //分解数据
                       ucTemp3=uiSetData2%1000/100;
                       ucTemp2=uiSetData2%100/10;
                       ucTemp1=uiSetData2%10;
               ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
               ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
               ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
    }
             break;
        case 3:  //显示P--3窗口的数据
            if(ucWd3Update==1)  //窗口3要全部更新显示
   {
               ucWd3Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=3;  //第6位数码管显示3
               ucDigShow5=10;   //第5位数码管显示无
                       ucTemp4=uiSetData3/1000;     //分解数据
                       ucTemp3=uiSetData3%1000/100;
                       ucTemp2=uiSetData3%100/10;
                       ucTemp1=uiSetData3%10;
               ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
               ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
               ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
   }
            break;
        case 4:  //显示P--4窗口的数据
            if(ucWd4Update==1)  //窗口4要全部更新显示
   {
               ucWd4Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=4;  //第6位数码管显示4
               ucDigShow5=10;   //第5位数码管显示无
                       ucTemp4=uiSetData4/1000;     //分解数据
                       ucTemp3=uiSetData4%1000/100;
                       ucTemp2=uiSetData4%100/10;
                       ucTemp1=uiSetData4%10;
               ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
               ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
               ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
    }
             break;
           }
   

}

void key_scan()//按键扫描函数 放在定时中断里
{  
  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
     uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }
  if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock2=0; //按键自锁标志清零
     uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt2++; //累加定时中断次数
     if(uiKeyTimeCnt2>const_key_time2)
     {
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;  //自锁按键置位,避免一直触发
        ucKeySec=2;    //触发2号键
     }
  }
  if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock3=0; //按键自锁标志清零
     uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt3++; //累加定时中断次数
     if(uiKeyTimeCnt3>const_key_time3)
     {
        uiKeyTimeCnt3=0;
        ucKeyLock3=1;  //自锁按键置位,避免一直触发
        ucKeySec=3;    //触发3号键
     }
  }

}

void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 加按键 对应朱兆祺学习板的S1键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
                  {
                     case 1:
                  uiSetData1++;   
                                  if(uiSetData1>9999) //最大值是9999
                                  {
                                     uiSetData1=9999;
                                  }
                           ucWd1Update=1;  //窗口1更新显示
                              break;
                     case 2:
                  uiSetData2++;
                                  if(uiSetData2>9999) //最大值是9999
                                  {
                                     uiSetData2=9999;
                                  }
                           ucWd2Update=1;  //窗口2更新显示
                              break;
                     case 3:
                  uiSetData3++;
                                  if(uiSetData3>9999) //最大值是9999
                                  {
                                     uiSetData3=9999;
                                  }
                           ucWd3Update=1;  //窗口3更新显示
                              break;
                     case 4:
                  uiSetData4++;
                                  if(uiSetData4>9999) //最大值是9999
                                  {
                                     uiSetData4=9999;
                                  }
                           ucWd4Update=1;  //窗口4更新显示
                              break;
                  }
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 减按键 对应朱兆祺学习板的S5键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
                  {
                     case 1:
                  uiSetData1--;   
/* 注释三:
* 单片机C编译有一个特点,当一个无符号类型的数据0减去1时,就会溢出反而变成这个类型数据的最大值
* 对于int类型的数据,最大值肯定比9999大,因此下面的临界点用if(uiSetData1>9999)来判断。
*/
                                  if(uiSetData1>9999)  
                                  {
                                     uiSetData1=0;  //最小值是0
                                  }
                           ucWd1Update=1;  //窗口1更新显示
                              break;
                     case 2:
                  uiSetData2--;
                                  if(uiSetData2>9999)
                                  {
                                     uiSetData2=0;  //最小值是0
                                  }
                           ucWd2Update=1;  //窗口2更新显示
                              break;
                     case 3:
                  uiSetData3--;
                                  if(uiSetData3>9999)
                                  {
                                     uiSetData3=0;  //最小值是0
                                  }
                           ucWd3Update=1;  //窗口3更新显示
                              break;
                     case 4:
                  uiSetData4--;
                                  if(uiSetData4>9999)
                                  {
                                     uiSetData4=0;  //最小值是0
                                  }
                           ucWd4Update=1;  //窗口4更新显示
                              break;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  
    case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
          ucWd++;  //切换窗口
                  if(ucWd>4)
                  {
                    ucWd=1;
                  }
          switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
                  {
                     case 1:
                           ucWd1Update=1;  //窗口1更新显示
                              break;
                     case 2:
                           ucWd2Update=1;  //窗口2更新显示
                              break;
                     case 3:
                           ucWd3Update=1;  //窗口3更新显示
                              break;
                     case 4:
                           ucWd4Update=1;  //窗口4更新显示
                              break;
                  }
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
         
  }               
}

void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }
   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }

}

//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;
   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);
   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;
}

//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;
   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);
   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;
}

void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断
  key_scan(); //按键扫描函数
  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
     beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
//     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  }
  else
  {
     ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
     beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
//     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  }
  display_drive();  //数码管字模的驱动函数

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=1;  //开中断
}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}

void initial_myself()  //第一区 初始化单片机
{
/* 注释四:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  TMOD=0x01;  //设置定时器0为工作方式1
  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
}
void initial_peripheral() //第二区 初始化外围
{

   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=0;  
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;
   EA=1;     //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断
}


总结陈词:
    这节在第4,3,2,1位显示设置的参数时,还有一点小瑕疵。比如设置参数等于56时,实际显示的是“0056”,也就是高位为0的如果不显示,效果才会更好。我们要把高位为0的去掉不显示,该怎么改程序呢?欲知详情,请听下回分解-----数码管通过切换窗口来设置参数,并且不显示为0的高位。

(未完待续,下节更精彩,不要走开哦)

乐于分享,勇于质疑!
33#
 楼主| 发表于 2014-2-23 01:30:09 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-2-28 14:41 编辑

第二十九节:数码管通过切换窗口来设置参数,并且不显示为0的高位。

开场白:
上一节在第4,3,2,1位显示设置的参数时,还有一点小瑕疵。比如设置参数等于56时,实际显示的是“0056”,也就是高位为0的如果不显示,效果才会更好。
这一节要教会大家两个知识点:
第一个:在上一节display_service()函数里略作修改,把高位为0的去掉不显示。
第二个:加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。


具体内容,请看源代码讲解。

1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换窗口按键对应S9

2)实现功能:
     通过按键设置4个不同的参数。
      一共有4个窗口。每个窗口显示一个参数。
8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
4,3,2,1位数码管显示当前窗口被设置的参数。范围是从09999
有三个按键。一个是加按键,按下此按键会依次增加当前窗口的参数。一个是减按键,按下此按键会依次减少当前窗口的参数。一个是切换窗口按键,按下此按键会依次循环切换不同的窗口。
并且要求被设置的数据不显示为0的高位。比如参数是12时,不能显示“0012”,只能第4,3位不显示,第2,1位显示“12”

3)源代码讲解如下:

#include "REG52.H"

#define const_voice_short  40   //蜂鸣器短叫的持续时间
#define const_key_time1  20    //按键去抖动延时的时间
#define const_key_time2  20    //按键去抖动延时的时间
#define const_key_time3  20    //按键去抖动延时的时间
void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
void display_drive(); //显示数码管字模的驱动函数
void display_service(); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time();  //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里

sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停

sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  
sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  
unsigned char ucKeySec=0;   //被触发的按键编号
unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned char ucDigShow8;  //第8位数码管要显示的内容
unsigned char ucDigShow7;  //第7位数码管要显示的内容
unsigned char ucDigShow6;  //第6位数码管要显示的内容
unsigned char ucDigShow5;  //第5位数码管要显示的内容
unsigned char ucDigShow4;  //第4位数码管要显示的内容
unsigned char ucDigShow3;  //第3位数码管要显示的内容
unsigned char ucDigShow2;  //第2位数码管要显示的内容
unsigned char ucDigShow1;  //第1位数码管要显示的内容

unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucWd2Update=0; //窗口2更新显示标志
unsigned char ucWd3Update=0; //窗口3更新显示标志
unsigned char ucWd4Update=0; //窗口4更新显示标志
unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

unsigned char ucTemp1=0;  //中间过渡变量
unsigned char ucTemp2=0;  //中间过渡变量
unsigned char ucTemp3=0;  //中间过渡变量
unsigned char ucTemp4=0;  //中间过渡变量

//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
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
0x40,  //-       序号11
0x73,  //P       序号12
};
void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
     key_service(); //按键服务的应用程序
         display_service(); //显示的窗口菜单服务程序
   }
}
/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/

void display_service() //显示的窗口菜单服务程序
{

   switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示P--1窗口的数据
            if(ucWd1Update==1)  //窗口1要全部更新显示
   {
               ucWd1Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=1;   //第6位数码管显示1
               ucDigShow5=10;  //第5位数码管显示无

              //先分解数据
                       ucTemp4=uiSetData1/1000;     
                       ucTemp3=uiSetData1%1000/100;
                       ucTemp2=uiSetData1%100/10;
                       ucTemp1=uiSetData1%10;
  
                          //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
/* 注释二:
* 就是在这里略作修改,把高位为0的去掉不显示。
*/
               if(uiSetData1<1000)   
                           {
                              ucDigShow4=10;  //如果小于1000,千位显示无
                           }
               else
                           {
                  ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
                           }
               if(uiSetData1<100)
                           {
                  ucDigShow3=10;  //如果小于100,百位显示无
                           }
                           else
                           {
                  ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
                           }
               if(uiSetData1<10)
                           {
                  ucDigShow2=10;  //如果小于10,十位显示无
                           }
                           else
                           {
                  ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               }
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
            }
            break;
        case 2:  //显示P--2窗口的数据
            if(ucWd2Update==1)  //窗口2要全部更新显示
   {
               ucWd2Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=2;  //第6位数码管显示2
               ucDigShow5=10;   //第5位数码管显示无
                       ucTemp4=uiSetData2/1000;     //分解数据
                       ucTemp3=uiSetData2%1000/100;
                       ucTemp2=uiSetData2%100/10;
                       ucTemp1=uiSetData2%10;

               if(uiSetData2<1000)   
                           {
                              ucDigShow4=10;  //如果小于1000,千位显示无
                           }
               else
                           {
                  ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
                           }
               if(uiSetData2<100)
                           {
                  ucDigShow3=10;  //如果小于100,百位显示无
                           }
                           else
                           {
                  ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
                           }
               if(uiSetData2<10)
                           {
                  ucDigShow2=10;  //如果小于10,十位显示无
                           }
                           else
                           {
                  ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               }
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
    }
             break;
        case 3:  //显示P--3窗口的数据
            if(ucWd3Update==1)  //窗口3要全部更新显示
   {
               ucWd3Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=3;  //第6位数码管显示3
               ucDigShow5=10;   //第5位数码管显示无
                       ucTemp4=uiSetData3/1000;     //分解数据
                       ucTemp3=uiSetData3%1000/100;
                       ucTemp2=uiSetData3%100/10;
                       ucTemp1=uiSetData3%10;
               if(uiSetData3<1000)   
                           {
                              ucDigShow4=10;  //如果小于1000,千位显示无
                           }
               else
                           {
                  ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
                           }
               if(uiSetData3<100)
                           {
                  ucDigShow3=10;  //如果小于100,百位显示无
                           }
                           else
                           {
                  ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
                           }
               if(uiSetData3<10)
                           {
                  ucDigShow2=10;  //如果小于10,十位显示无
                           }
                           else
                           {
                  ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               }
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
   }
            break;
        case 4:  //显示P--4窗口的数据
            if(ucWd4Update==1)  //窗口4要全部更新显示
   {
               ucWd4Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=4;  //第6位数码管显示4
               ucDigShow5=10;   //第5位数码管显示无
                       ucTemp4=uiSetData4/1000;     //分解数据
                       ucTemp3=uiSetData4%1000/100;
                       ucTemp2=uiSetData4%100/10;
                       ucTemp1=uiSetData4%10;

               if(uiSetData4<1000)   
                           {
                              ucDigShow4=10;  //如果小于1000,千位显示无
                           }
               else
                           {
                  ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
                           }
               if(uiSetData4<100)
                           {
                  ucDigShow3=10;  //如果小于100,百位显示无
                           }
                           else
                           {
                  ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
                           }
               if(uiSetData4<10)
                           {
                  ucDigShow2=10;  //如果小于10,十位显示无
                           }
                           else
                           {
                  ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               }
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
    }
             break;
           }
   

}

void key_scan()//按键扫描函数 放在定时中断里
{  
  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
     uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }
  if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock2=0; //按键自锁标志清零
     uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt2++; //累加定时中断次数
     if(uiKeyTimeCnt2>const_key_time2)
     {
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;  //自锁按键置位,避免一直触发
        ucKeySec=2;    //触发2号键
     }
  }
  if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock3=0; //按键自锁标志清零
     uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt3++; //累加定时中断次数
     if(uiKeyTimeCnt3>const_key_time3)
     {
        uiKeyTimeCnt3=0;
        ucKeyLock3=1;  //自锁按键置位,避免一直触发
        ucKeySec=3;    //触发3号键
     }
  }

}

void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 加按键 对应朱兆祺学习板的S1键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
                  {
                     case 1:
                  uiSetData1++;   
                                  if(uiSetData1>9999) //最大值是9999
                                  {
                                     uiSetData1=9999;
                                  }
                           ucWd1Update=1;  //窗口1更新显示
                              break;
                     case 2:
                  uiSetData2++;
                                  if(uiSetData2>9999) //最大值是9999
                                  {
                                     uiSetData2=9999;
                                  }
                           ucWd2Update=1;  //窗口2更新显示
                              break;
                     case 3:
                  uiSetData3++;
                                  if(uiSetData3>9999) //最大值是9999
                                  {
                                     uiSetData3=9999;
                                  }
                           ucWd3Update=1;  //窗口3更新显示
                              break;
                     case 4:
                  uiSetData4++;
                                  if(uiSetData4>9999) //最大值是9999
                                  {
                                     uiSetData4=9999;
                                  }
                           ucWd4Update=1;  //窗口4更新显示
                              break;
                  }
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 减按键 对应朱兆祺学习板的S5键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
                  {
                     case 1:
                  uiSetData1--;   

                                  if(uiSetData1>9999)  
                                  {
                                     uiSetData1=0;  //最小值是0
                                  }
                           ucWd1Update=1;  //窗口1更新显示
                              break;
                     case 2:
                  uiSetData2--;
                                  if(uiSetData2>9999)
                                  {
                                     uiSetData2=0;  //最小值是0
                                  }
                           ucWd2Update=1;  //窗口2更新显示
                              break;
                     case 3:
                  uiSetData3--;
                                  if(uiSetData3>9999)
                                  {
                                     uiSetData3=0;  //最小值是0
                                  }
                           ucWd3Update=1;  //窗口3更新显示
                              break;
                     case 4:
                  uiSetData4--;
                                  if(uiSetData4>9999)
                                  {
                                     uiSetData4=0;  //最小值是0
                                  }
                           ucWd4Update=1;  //窗口4更新显示
                              break;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  
    case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
          ucWd++;  //切换窗口
                  if(ucWd>4)
                  {
                    ucWd=1;
                  }
          switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
                  {
                     case 1:
                           ucWd1Update=1;  //窗口1更新显示
                              break;
                     case 2:
                           ucWd2Update=1;  //窗口2更新显示
                              break;
                     case 3:
                           ucWd3Update=1;  //窗口3更新显示
                              break;
                     case 4:
                           ucWd4Update=1;  //窗口4更新显示
                              break;
                  }
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
         
  }               
}

void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }
   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }

}

//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;
   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);
   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;
}

//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;
   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);
   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;
}

void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断
  key_scan(); //按键扫描函数
  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
     beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
//     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  }
  else
  {
     ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
     beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
//     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  }
  display_drive();  //数码管字模的驱动函数

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=1;  //开中断
}

void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}

void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}

void initial_myself()  //第一区 初始化单片机
{
/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  TMOD=0x01;  //设置定时器0为工作方式1
  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
}
void initial_peripheral() //第二区 初始化外围
{

   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=0;  
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;
   EA=1;     //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断
}


总结陈词:
数码管通过切换窗口来设置参数,这里的窗口类似于一级菜单,在一级菜单下,还可以分解出二级菜单。一级菜单的特点是整屏数码管的显示内容全部都改变,而二级菜单的特点是只改变其中一部分数码管的内容。二级菜单的程序怎么编写?欲知详情,请听下回分解-----数码管通过闪烁来设置数据。

(未完待续,下节更精彩,不要走开哦)

乐于分享,勇于质疑!
34#
 楼主| 发表于 2014-2-28 15:54:33 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-2-28 16:34 编辑

第三十节:数码管通过闪烁来设置数据。

开场白:
   上一节讲了一级菜单,这一节要教会大家两个知识点:
第一个:二级菜单的程序的程序框架。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。


具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标闪烁”按键对应S9键

(2)实现功能:
     通过按键设置4个不同的参数。
    只有1个窗口。这个窗口显示4个参数。
第8,7位数码管显示第1个参数。第6,5位数码管显示第2个参数。第4,3位数码管显示第3个参数。第2,1位数码管显示第4个参数。每个参数的范围是从0到99。
有三个按键。一个是“光标闪烁”按键,依次按下此按键,每两位数码管会依次处于闪烁的状态,哪两位数码管处于闪烁状态时,此时按加键或者减键就可以设置当前选中的参数。依次按下“光标闪烁”按键,数码管会在以下5种状态中循环:只有第8,7位数码管闪烁---只有第6,5位数码管闪烁---只有第4,3位数码管闪烁---只有第2,1位数码管闪烁---所有的数码管都不闪烁。

(3)源代码讲解如下:

#include "REG52.H"


#define const_voice_short  40   //蜂鸣器短叫的持续时间

#define const_key_time1  20    //按键去抖动延时的时间
#define const_key_time2  20    //按键去抖动延时的时间
#define const_key_time3  20    //按键去抖动延时的时间

#define const_dpy_time_half  200  //数码管闪烁时间的半值
#define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
void display_drive(); //显示数码管字模的驱动函数

void display_service(); //显示的窗口菜单服务程序

//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

void T0_time();  //定时中断函数

void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里


sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键

sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  

sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucKeySec=0;   //被触发的按键编号

unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


unsigned char ucDigShow8;  //第8位数码管要显示的内容
unsigned char ucDigShow7;  //第7位数码管要显示的内容
unsigned char ucDigShow6;  //第6位数码管要显示的内容
unsigned char ucDigShow5;  //第5位数码管要显示的内容
unsigned char ucDigShow4;  //第4位数码管要显示的内容
unsigned char ucDigShow3;  //第3位数码管要显示的内容
unsigned char ucDigShow2;  //第2位数码管要显示的内容
unsigned char ucDigShow1;  //第1位数码管要显示的内容


unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucWd1Update=1; //窗口1更新显示标志

unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。

unsigned char ucWd1Part1Update=0;  //在窗口1中,局部1的更新显示标志
unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
unsigned char ucWd1Part3Update=0; //在窗口1中,局部3的更新显示标志
unsigned char ucWd1Part4Update=0; //在窗口1中,局部4的更新显示标志

unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4


unsigned char ucTemp1=0;  //中间过渡变量
unsigned char ucTemp2=0;  //中间过渡变量
unsigned char ucTemp3=0;  //中间过渡变量
unsigned char ucTemp4=0;  //中间过渡变量
unsigned char ucTemp5=0;  //中间过渡变量
unsigned char ucTemp6=0;  //中间过渡变量
unsigned char ucTemp7=0;  //中间过渡变量
unsigned char ucTemp8=0;  //中间过渡变量

unsigned int  uiDpyTimeCnt=0;  //数码管的闪烁计时器,放在定时中断里不断累加

//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
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
0x40,  //-       序号11
0x73,  //P       序号12
};

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
     key_service(); //按键服务的应用程序
         display_service(); //显示的窗口菜单服务程序
   }

}

/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/


void display_service() //显示的窗口菜单服务程序
{


   switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示窗口1的数据

                        if(ucWd1Part1Update==1)  //仅仅参数1局部更新
                        {
                           ucWd1Part1Update=0;   //及时清零标志,避免一直进来扫描

               ucTemp8=uiSetData1/10;  //第1个参数
               ucTemp7=uiSetData1%10;
                           if(uiSetData1<10)
                           {
                              ucDigShow8=10;
                           }
                           else
                           {
                              ucDigShow8=ucTemp8;
                           }
                           ucDigShow7=ucTemp7;
                        }

                        if(ucWd1Part2Update==1)  //仅仅参数2局部更新
                        {
                           ucWd1Part2Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp6=uiSetData2/10;  //第2个参数
               ucTemp5=uiSetData2%10;
                           if(uiSetData2<10)
                           {
                              ucDigShow6=10;
                           }
                           else
                           {
                              ucDigShow6=ucTemp6;
                           }
                           ucDigShow5=ucTemp5;

                        }

                        if(ucWd1Part3Update==1)  //仅仅参数3局部更新
                        {
                           ucWd1Part3Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp4=uiSetData3/10;  //第3个参数
               ucTemp3=uiSetData3%10;
                           if(uiSetData3<10)
                           {
                              ucDigShow4=10;
                           }
                           else
                           {
                              ucDigShow4=ucTemp4;
                           }
                           ucDigShow3=ucTemp3;
                        }

                        if(ucWd1Part4Update==1)  //仅仅参数4局部更新
                        {
                           ucWd1Part4Update=0;   //及时清零标志,避免一直进来扫描

               ucTemp2=uiSetData4/10;  //第4个参数
               ucTemp1=uiSetData4%10;

                           if(uiSetData4<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;
                        }

/* 注释二:
* 必须注意局部更新和全部更新的编写顺序,局部更新应该写在全部更新之前,
* 当局部更新和全部更新同时发生时,这样就能保证到全部更新的优先响应。
*/

            if(ucWd1Update==1)  //窗口1要全部更新显示
                        {
               ucWd1Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp8=uiSetData1/10;  //第1个参数
               ucTemp7=uiSetData1%10;

               ucTemp6=uiSetData2/10;  //第2个参数
               ucTemp5=uiSetData2%10;


               ucTemp4=uiSetData3/10;  //第3个参数
               ucTemp3=uiSetData3%10;

               ucTemp2=uiSetData4/10;  //第4个参数
               ucTemp1=uiSetData4%10;


                           if(uiSetData1<10)
                           {
                              ucDigShow8=10;
                           }
                           else
                           {
                              ucDigShow8=ucTemp8;
                           }
                           ucDigShow7=ucTemp7;


                           if(uiSetData2<10)
                           {
                              ucDigShow6=10;
                           }
                           else
                           {
                              ucDigShow6=ucTemp6;
                           }
                           ucDigShow5=ucTemp5;

                           if(uiSetData3<10)
                           {
                              ucDigShow4=10;
                           }
                           else
                           {
                              ucDigShow4=ucTemp4;
                           }
                           ucDigShow3=ucTemp3;

                           if(uiSetData4<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

            }


                        //数码管闪烁
            switch(ucPart)  //根据局部变量的值,使对应的参数产生闪烁的动态效果。
                        {
                           case 0:  //4个参数都不闪烁

                                break;
                           case 1:  //第1个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData1<10)        //数码管显示内容
                                   {
                                      ucDigShow8=10;
                                   }
                                   else
                                   {
                                      ucDigShow8=ucTemp8;
                                   }
                                   ucDigShow7=ucTemp7;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow8=10;   //数码管显示空,什么都不显示
                                   ucDigShow7=10;

                                        }
                                break;
                           case 2:   //第2个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData2<10)        //数码管显示内容
                                   {
                                      ucDigShow6=10;
                                   }
                                   else
                                   {
                                      ucDigShow6=ucTemp6;
                                   }
                                   ucDigShow5=ucTemp5;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all)  //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow6=10;   //数码管显示空,什么都不显示
                                   ucDigShow5=10;

                                        }
                                break;
                           case 3:  //第3个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData3<10)        //数码管显示内容
                                   {
                                      ucDigShow4=10;
                                   }
                                   else
                                   {
                                      ucDigShow4=ucTemp4;
                                   }
                                   ucDigShow3=ucTemp3;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow4=10;   //数码管显示空,什么都不显示
                                   ucDigShow3=10;

                                        }
                                break;
                           case 4:  //第4个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData4<10)        //数码管显示内容
                                   {
                                      ucDigShow2=10;
                                   }
                                   else
                                   {
                                      ucDigShow2=ucTemp2;
                                   }
                                   ucDigShow1=ucTemp1;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow2=10;   //数码管显示空,什么都不显示
                                   ucDigShow1=10;

                                        }
                                break;
                        }

            break;
   
     }
   


}


void key_scan()//按键扫描函数 放在定时中断里
{  

  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
     uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }

  if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock2=0; //按键自锁标志清零
     uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt2++; //累加定时中断次数
     if(uiKeyTimeCnt2>const_key_time2)
     {
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;  //自锁按键置位,避免一直触发
        ucKeySec=2;    //触发2号键
     }
  }

  if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock3=0; //按键自锁标志清零
     uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt3++; //累加定时中断次数
     if(uiKeyTimeCnt3>const_key_time3)
     {
        uiKeyTimeCnt3=0;
        ucKeyLock3=1;  //自锁按键置位,避免一直触发
        ucKeySec=3;    //触发3号键
     }
  }



}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 加按键 对应朱兆祺学习板的S1键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                   switch(ucPart)  //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
                                   {
                                      case 0:
                                               break;
                                      case 1:
                           uiSetData1++;   
                           if(uiSetData1>99) //最大值是99
                           {
                               uiSetData1=99;
                           }
                                                   ucWd1Part1Update=1; //局部更新显示参数1
                                               break;
                                      case 2:
                           uiSetData2++;   
                           if(uiSetData2>99) //最大值是99
                           {
                               uiSetData2=99;
                           }
                                                   ucWd1Part2Update=1; //局部更新显示参数2
                                               break;
                                      case 3:
                           uiSetData3++;   
                           if(uiSetData3>99) //最大值是99
                           {
                               uiSetData3=99;
                           }
                                                   ucWd1Part3Update=1; //局部更新显示参数3
                                               break;
                                      case 4:
                           uiSetData4++;   
                           if(uiSetData4>99) //最大值是99
                           {
                               uiSetData4=99;
                           }
                                                   ucWd1Part4Update=1; //局部更新显示参数4
                                               break;
                                   }
                   break;
          }     
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 减按键 对应朱兆祺学习板的S5键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                   switch(ucPart)  //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
                                   {
                                      case 0:
                                               break;
                                      case 1:
                           uiSetData1--;   
                           if(uiSetData1>99) //0减去1溢出肯定大于99
                           {
                               uiSetData1=0;
                           }
                                                   ucWd1Part1Update=1; //局部更新显示参数1
                                               break;
                                      case 2:
                           uiSetData2--;   
                           if(uiSetData2>99) //0减去1溢出肯定大于99
                           {
                               uiSetData2=0;
                           }
                                                   ucWd1Part2Update=1; //局部更新显示参数2
                                               break;
                                      case 3:
                           uiSetData3--;   
                           if(uiSetData3>99) //0减去1溢出肯定大于99
                           {
                               uiSetData3=0;
                           }
                                                   ucWd1Part3Update=1; //局部更新显示参数3
                                               break;
                                      case 4:
                           uiSetData4--;  
                           if(uiSetData4>99) //0减去1溢出肯定大于99
                           {
                               uiSetData4=0;
                           }
                                                   ucWd1Part4Update=1; //局部更新显示参数4
                                               break;
                                   }
                   break;
          }  
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  

    case 3:// 切换"光标闪烁"按键 对应朱兆祺学习板的S9键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:  //在窗口1下,切换"光标闪烁"
                   ucPart++;
                                   if(ucPart>4)
                                   {
                                     ucPart=0;
                                   }
                                   ucWd1Update=1;  //窗口1全部更新显示
                   break;
          }  
        
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
         

  }               
}


void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }

   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }



}


//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;

   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);

   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;

}


//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  key_scan(); //按键扫描函数

  uiDpyTimeCnt++;  //数码管的闪烁计时器

  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
     beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
//     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  }
  else
  {
     ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
     beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
//     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  }

  display_drive();  //数码管字模的驱动函数


  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=1;  //开中断
}


void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

  TMOD=0x01;  //设置定时器0为工作方式1

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;

}

void initial_peripheral() //第二区 初始化外围
{


   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=0;  
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;

   EA=1;     //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断

}


总结陈词:
这节讲了数码管通过闪烁来设置数据的基本程序,但是该程序只有一个窗口。实际应用中,有些项目会有几个窗口,而且每个窗口都要设置几个参数,这样的程序该怎么写?欲知详情,请听下回分解-----数码管通过一二级菜单来设置数据的综合程序。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
35#
 楼主| 发表于 2014-2-28 17:07:27 | 显示全部楼层
第三十一节:数码管通过一二级菜单来设置数据的综合程序。

开场白:
   上一节讲了二级菜单,这一节要教会大家两个知识点:
第一个:数码管通过一二级菜单来设置数据的综合程序框架。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。


具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标闪烁”按键对应S9键,切换窗口按键对应S13键。

(2)实现功能:
     通过按键设置4个不同的参数。
    有2个窗口。每个窗口显示2个参数。
   第8,7,6,5位数码管显示”P-1 ”代表第1个窗口,显示”P-2 ”代表第2个窗口。第4,3位数码管显示该窗口下其中一个参数,第2,1位数码管显示该窗口下其中另外一个参数。每个参数的范围是从0到99。
有四个按键。
一个是切换窗口按键,依次按下此按键,会依次切换窗口显示。一个是“光标闪烁”按键,依次按下此按键,每两位数码管会依次处于闪烁的状态,哪两位数码管处于闪烁状态时,此时按加键或者减键就可以设置当前选中的参数。依次按下“光标闪烁”按键,数码管会在以下3种状态中循环:只有第4,3位数码管闪烁---只有第2,1位数码管闪烁---所有的数码管都不闪烁。

(3)源代码讲解如下:

#include "REG52.H"


#define const_voice_short  40   //蜂鸣器短叫的持续时间

#define const_key_time1  20    //按键去抖动延时的时间
#define const_key_time2  20    //按键去抖动延时的时间
#define const_key_time3  20    //按键去抖动延时的时间
#define const_key_time4  20    //按键去抖动延时的时间

#define const_dpy_time_half  200  //数码管闪烁时间的半值
#define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
void display_drive(); //显示数码管字模的驱动函数

void display_service(); //显示的窗口菜单服务程序

//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

void T0_time();  //定时中断函数

void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里


sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  

sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucKeySec=0;   //被触发的按键编号

unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


unsigned char ucDigShow8;  //第8位数码管要显示的内容
unsigned char ucDigShow7;  //第7位数码管要显示的内容
unsigned char ucDigShow6;  //第6位数码管要显示的内容
unsigned char ucDigShow5;  //第5位数码管要显示的内容
unsigned char ucDigShow4;  //第4位数码管要显示的内容
unsigned char ucDigShow3;  //第3位数码管要显示的内容
unsigned char ucDigShow2;  //第2位数码管要显示的内容
unsigned char ucDigShow1;  //第1位数码管要显示的内容


unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucWd2Update=0; //窗口2更新显示标志
unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。

unsigned char ucWd1Part1Update=0;  //在窗口1中,局部1的更新显示标志
unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
unsigned char ucWd2Part1Update=0; //在窗口2中,局部1的更新显示标志
unsigned char ucWd2Part2Update=0; //在窗口2中,局部2的更新显示标志

unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4


unsigned char ucTemp1=0;  //中间过渡变量
unsigned char ucTemp2=0;  //中间过渡变量
unsigned char ucTemp3=0;  //中间过渡变量
unsigned char ucTemp4=0;  //中间过渡变量
unsigned char ucTemp5=0;  //中间过渡变量
unsigned char ucTemp6=0;  //中间过渡变量
unsigned char ucTemp7=0;  //中间过渡变量
unsigned char ucTemp8=0;  //中间过渡变量

unsigned int  uiDpyTimeCnt=0;  //数码管的闪烁计时器,放在定时中断里不断累加

//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
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
0x40,  //-       序号11
0x73,  //P       序号12
};

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
     key_service(); //按键服务的应用程序
         display_service(); //显示的窗口菜单服务程序
   }

}

/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/


void display_service() //显示的窗口菜单服务程序
{


   switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示窗口1的数据

                        if(ucWd1Part1Update==1)  //仅仅参数1局部更新
                        {
                           ucWd1Part1Update=0;   //及时清零标志,避免一直进来扫描

               ucTemp4=uiSetData1/10;  //第1个参数
               ucTemp3=uiSetData1%10;
                           if(uiSetData1<10)
                           {
                              ucDigShow4=10;
                           }
                           else
                           {
                              ucDigShow4=ucTemp4;
                           }
                           ucDigShow3=ucTemp3;
                        }

                        if(ucWd1Part2Update==1)  //仅仅参数2局部更新
                        {
                           ucWd1Part2Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp2=uiSetData2/10;  //第2个参数
               ucTemp1=uiSetData2%10;
                           if(uiSetData2<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                        }

       

/* 注释二:
* 必须注意局部更新和全部更新的编写顺序,局部更新应该写在全部更新之前,
* 当局部更新和全部更新同时发生时,这样就能保证到全部更新的优先响应。
*/

            if(ucWd1Update==1)  //窗口1要全部更新显示
                        {
               ucWd1Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp8=12;  //显示P
               ucTemp7=11;  //显示-
               ucTemp6=1;  //显示1
               ucTemp5=10;  //显示空

               ucTemp4=uiSetData1/10;  //第1个参数
               ucTemp3=uiSetData1%10;

               ucTemp2=uiSetData2/10;  //第2个参数
               ucTemp1=uiSetData2%10;


               ucDigShow8=ucTemp8;  
               ucDigShow7=ucTemp7;  
               ucDigShow6=ucTemp6;  
               ucDigShow5=ucTemp5;

                           if(uiSetData1<10)
                           {
                              ucDigShow4=10;
                           }
                           else
                           {
                              ucDigShow4=ucTemp4;
                           }
                           ucDigShow3=ucTemp3;


                           if(uiSetData2<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                       

            }


                        //数码管闪烁
            switch(ucPart)  //根据局部变量的值,使对应的参数产生闪烁的动态效果。
                        {
                           case 0:  //2个参数都不闪烁

                                break;
                           case 1:  //第1个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData1<10)        //数码管显示内容
                                   {
                                      ucDigShow4=10;
                                   }
                                   else
                                   {
                                      ucDigShow4=ucTemp4;
                                   }
                                   ucDigShow3=ucTemp3;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow4=10;   //数码管显示空,什么都不显示
                                   ucDigShow3=10;

                                        }
                                break;
                           case 2:   //第2个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData2<10)        //数码管显示内容
                                   {
                                      ucDigShow2=10;
                                   }
                                   else
                                   {
                                      ucDigShow2=ucTemp2;
                                   }
                                   ucDigShow1=ucTemp1;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all)  //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow2=10;   //数码管显示空,什么都不显示
                                   ucDigShow1=10;

                                        }
                                break;
       
                        }

            break;
       case 2:   //显示窗口2的数据

                        if(ucWd2Part1Update==1)  //在窗口2中,仅仅参数1局部更新
                        {
                           ucWd2Part1Update=0;   //及时清零标志,避免一直进来扫描

               ucTemp4=uiSetData3/10;  //第3个参数
               ucTemp3=uiSetData3%10;
                           if(uiSetData3<10)
                           {
                              ucDigShow4=10;
                           }
                           else
                           {
                              ucDigShow4=ucTemp4;
                           }
                           ucDigShow3=ucTemp3;
                        }

                        if(ucWd2Part2Update==1)  //在窗口2中,仅仅参数2局部更新
                        {
                           ucWd2Part2Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp2=uiSetData4/10;  //第4个参数
               ucTemp1=uiSetData4%10;
                           if(uiSetData4<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                        }

            if(ucWd2Update==1)  //窗口2要全部更新显示
                        {
               ucWd2Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp8=12;  //显示P
               ucTemp7=11;  //显示-
               ucTemp6=2;  //显示2
               ucTemp5=10;  //显示空

               ucTemp4=uiSetData3/10;  //第3个参数
               ucTemp3=uiSetData3%10;

               ucTemp2=uiSetData4/10;  //第4个参数
               ucTemp1=uiSetData4%10;


               ucDigShow8=ucTemp8;  
               ucDigShow7=ucTemp7;  
               ucDigShow6=ucTemp6;  
               ucDigShow5=ucTemp5;

                           if(uiSetData3<10)
                           {
                              ucDigShow4=10;
                           }
                           else
                           {
                              ucDigShow4=ucTemp4;
                           }
                           ucDigShow3=ucTemp3;


                           if(uiSetData4<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                       

            }


                        //数码管闪烁
            switch(ucPart)  //根据局部变量的值,使对应的参数产生闪烁的动态效果。
                        {
                           case 0:  //2个参数都不闪烁

                                break;
                           case 1:  //第3个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData3<10)        //数码管显示内容
                                   {
                                      ucDigShow4=10;
                                   }
                                   else
                                   {
                                      ucDigShow4=ucTemp4;
                                   }
                                   ucDigShow3=ucTemp3;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow4=10;   //数码管显示空,什么都不显示
                                   ucDigShow3=10;

                                        }
                                break;
                           case 2:   //第4个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData4<10)        //数码管显示内容
                                   {
                                      ucDigShow2=10;
                                   }
                                   else
                                   {
                                      ucDigShow2=ucTemp2;
                                   }
                                   ucDigShow1=ucTemp1;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all)  //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow2=10;   //数码管显示空,什么都不显示
                                   ucDigShow1=10;

                                        }
                                break;
       
                        }

            break;   
     }
   


}


void key_scan()//按键扫描函数 放在定时中断里
{  

  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
     uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }

  if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock2=0; //按键自锁标志清零
     uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt2++; //累加定时中断次数
     if(uiKeyTimeCnt2>const_key_time2)
     {
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;  //自锁按键置位,避免一直触发
        ucKeySec=2;    //触发2号键
     }
  }

  if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock3=0; //按键自锁标志清零
     uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt3++; //累加定时中断次数
     if(uiKeyTimeCnt3>const_key_time3)
     {
        uiKeyTimeCnt3=0;
        ucKeyLock3=1;  //自锁按键置位,避免一直触发
        ucKeySec=3;    //触发3号键
     }
  }

  if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock4=0; //按键自锁标志清零
     uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt4++; //累加定时中断次数
     if(uiKeyTimeCnt4>const_key_time4)
     {
        uiKeyTimeCnt4=0;
        ucKeyLock4=1;  //自锁按键置位,避免一直触发
        ucKeySec=4;    //触发4号键
     }
  }

}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 加按键 对应朱兆祺学习板的S1键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                   switch(ucPart)  //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
                                   {
                                      case 0:
                                               break;
                                      case 1:
                           uiSetData1++;   
                           if(uiSetData1>99) //最大值是99
                           {
                               uiSetData1=99;
                           }
                                                   ucWd1Part1Update=1; //局部更新显示参数1
                                               break;
                                      case 2:
                           uiSetData2++;   
                           if(uiSetData2>99) //最大值是99
                           {
                               uiSetData2=99;
                           }
                                                   ucWd1Part2Update=1; //局部更新显示参数2
                                               break;
                                   }
                   break;
              case 2:
                   switch(ucPart)  //在窗口2下,根据不同的局部闪烁位置来设置不同的参数
                                   {
                                      case 0:
                                               break;
                                      case 1:
                           uiSetData3++;   
                           if(uiSetData3>99) //最大值是99
                           {
                               uiSetData3=99;
                           }
                                                   ucWd2Part1Update=1; //局部更新显示参数1
                                               break;
                                      case 2:
                           uiSetData4++;   
                           if(uiSetData4>99) //最大值是99
                           {
                               uiSetData4=99;
                           }
                                                   ucWd2Part2Update=1; //局部更新显示参数2
                                               break;
                                   }
                   break;
          }     
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 减按键 对应朱兆祺学习板的S5键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                   switch(ucPart)  //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
                                   {
                                      case 0:
                                               break;
                                      case 1:
                           uiSetData1--;   
                           if(uiSetData1>99) //0减去1溢出肯定大于99
                           {
                               uiSetData1=0;
                           }
                                                   ucWd1Part1Update=1; //局部更新显示参数1
                                               break;
                                      case 2:
                           uiSetData2--;   
                           if(uiSetData2>99) //0减去1溢出肯定大于99
                           {
                               uiSetData2=0;
                           }
                                                   ucWd1Part2Update=1; //局部更新显示参数2
                                               break;
                                   }
                   break;
              case 2:
                   switch(ucPart)  //在窗口2下,根据不同的局部闪烁位置来设置不同的参数
                                   {
                                      case 0:
                                               break;
                                      case 1:
                           uiSetData3--;   
                           if(uiSetData3>99) //0减去1溢出肯定大于99
                           {
                               uiSetData3=0;
                           }
                                                   ucWd2Part1Update=1; //局部更新显示参数1
                                               break;
                                      case 2:
                           uiSetData4--;   
                           if(uiSetData4>99) //0减去1溢出肯定大于99
                           {
                               uiSetData4=0;
                           }
                                                   ucWd2Part2Update=1; //局部更新显示参数2
                                               break;
                                   }
                   break;
          }  
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  

    case 3:// 切换"光标闪烁"按键 对应朱兆祺学习板的S9键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:  //在窗口1下,切换"光标闪烁"
                   ucPart++;
                                   if(ucPart>2)
                                   {
                                     ucPart=0;
                                   }
                                   ucWd1Update=1;  //窗口1全部更新显示
                   break;
              case 2:  //在窗口2下,切换"光标闪烁"
                   ucPart++;
                                   if(ucPart>2)
                                   {
                                     ucPart=0;
                                   }
                                   ucWd2Update=1;  //窗口2全部更新显示
                   break;
          }  
        
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;      
   
    case 4:// 切换窗口按键 对应朱兆祺学习板的S13键
              ucWd++;
                  if(ucWd>2)
                  {
                     ucWd=1;
                  }

                  ucPart=0; //强行把局部变量复位,让新切换的窗口不闪烁

          switch(ucWd)  //在不同的窗口下,更新显示不同的窗口
          {
              case 1:  //在窗口1下
                                   ucWd1Update=1;  //窗口1全部更新显示
                   break;
              case 2:  //在窗口2下
                                   ucWd2Update=1;  //窗口2全部更新显示
                   break;
          }  
        
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         

  }               
}


void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }

   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }



}


//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;

   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);

   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;

}


//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  key_scan(); //按键扫描函数

  uiDpyTimeCnt++;  //数码管的闪烁计时器

  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
     beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
//     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  }
  else
  {
     ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
     beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
//     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  }

  display_drive();  //数码管字模的驱动函数


  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=1;  //开中断
}


void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

  TMOD=0x01;  //设置定时器0为工作方式1

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;

}

void initial_peripheral() //第二区 初始化外围
{


   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=0;  
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;

   EA=1;     //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断

}


总结陈词:
这节讲了数码管通过一二级菜单来设置数据的综合程序,鸿哥的人机界面程序框架基本上都涉及到了,为了继续加深熟悉鸿哥的“一二级菜单显示理论”,下一节会继续讲一个常用的数码管项目小程序,这个项目小程序鸿哥是怎么写的?欲知详情,请听下回分解-----数码管中的倒计时程序。
(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
36#
 楼主| 发表于 2014-3-9 20:21:23 | 显示全部楼层
第三十二节:数码管中的倒计时程序。

开场白:
   上一节讲了一二级菜单的综合程序,这一节要教会大家三个知识点:
第一个:通过本程序,继续加深理解按键与数码管的关联方法。
第二个:复习一下我在第五节教给大家的时间校正法。
第三个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。


具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。启动和暂停键对应S1键,复位键对应S5键。

(2)实现功能:按下启动暂停按键时,倒计时开始工作,再按一次启动暂停按键时,则暂停倒计时。在任何时候,按下复位按键,倒计时将暂停工作,并且恢复倒计时当前默认值99。
     
(3)源代码讲解如下:

#include "REG52.H"


#define const_voice_short  40   //蜂鸣器短叫的持续时间
#define const_voice_long   200    //蜂鸣器长叫的持续时间

#define const_key_time1  20    //按键去抖动延时的时间
#define const_key_time2  20    //按键去抖动延时的时间


#define const_dpy_time_half  200  //数码管闪烁时间的半值
#define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大

/* 注释一:
* 如何知道1秒钟需要多少个定时中断?
* 这个需要编写一段小程序测试,得到测试的结果后再按比例修正。
* 步骤:
* 第一步:在程序代码上先写入1秒钟大概需要200个定时中断。
* 第二步:把程序烧录进单片机后,上电开始测试,手上同步打开手机里的秒表。
*         如果单片机倒计时跑完了99秒,而手机上的秒表才走了45秒。
* 第三步:那么最终得出1秒钟需要的定时中断次数是:const_1s=(200*99)/45=440
*/


#define const_1s  440   //大概一秒钟所需要的定时中断次数

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
void display_drive(); //显示数码管字模的驱动函数
void display_service(); //显示的窗口菜单服务程序

//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

void T0_time();  //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里


sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键

sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  

sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucKeySec=0;   //被触发的按键编号

unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志


unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


unsigned char ucDigShow8;  //第8位数码管要显示的内容
unsigned char ucDigShow7;  //第7位数码管要显示的内容
unsigned char ucDigShow6;  //第6位数码管要显示的内容
unsigned char ucDigShow5;  //第5位数码管要显示的内容
unsigned char ucDigShow4;  //第4位数码管要显示的内容
unsigned char ucDigShow3;  //第3位数码管要显示的内容
unsigned char ucDigShow2;  //第2位数码管要显示的内容
unsigned char ucDigShow1;  //第1位数码管要显示的内容


unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucWd1Update=1; //窗口1更新显示标志


unsigned char ucCountDown=99;  //倒计时的当前值
unsigned char ucStartFlag=0;  //暂停与启动的标志位
unsigned int  uiTimeCnt=0;  //倒计时的时间计时器

unsigned char ucTemp1=0;  //中间过渡变量
unsigned char ucTemp2=0;  //中间过渡变量
unsigned char ucTemp3=0;  //中间过渡变量
unsigned char ucTemp4=0;  //中间过渡变量
unsigned char ucTemp5=0;  //中间过渡变量
unsigned char ucTemp6=0;  //中间过渡变量
unsigned char ucTemp7=0;  //中间过渡变量
unsigned char ucTemp8=0;  //中间过渡变量


//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
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
0x40,  //-       序号11
0x73,  //P       序号12
};

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_service(); //按键服务的应用程序
       display_service(); //显示的窗口菜单服务程序
   }

}


/* 注释二:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/


void display_service() //显示的窗口菜单服务程序
{



  //由于本程序只有一个窗口,读者在做实际项目的时候,可以省略switch(ucWd)
   switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示窗口1的数据
            if(ucWd1Update==1)  //窗口1要全部更新显示
                        {
               ucWd1Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp8=10;  //显示空
               ucTemp7=10;  //显示空
               ucTemp6=10;  //显示空
               ucTemp5=10;  //显示空
               ucTemp4=10;  //显示空
               ucTemp3=10;  //显示空

               ucTemp2=ucCountDown/10;  //倒计时的当前值
               ucTemp1=ucCountDown%10;


               ucDigShow8=ucTemp8;  
               ucDigShow7=ucTemp7;  
               ucDigShow6=ucTemp6;  
               ucDigShow5=ucTemp5;
               ucDigShow4=ucTemp4;  
               ucDigShow3=ucTemp3;


                           if(ucCountDown<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                       

            }
            break;
   
     }
   


}


void key_scan()//按键扫描函数 放在定时中断里
{  

  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
     uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }

  if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock2=0; //按键自锁标志清零
     uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt2++; //累加定时中断次数
     if(uiKeyTimeCnt2>const_key_time2)
     {
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;  //自锁按键置位,避免一直触发
        ucKeySec=2;    //触发2号键
     }
  }

}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 启动和暂停按键 对应朱兆祺学习板的S1键

       
         //由于本程序只有一个窗口,读者在做实际项目的时候,可以省略switch(ucWd)
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                   if(ucStartFlag==0)  //如果原来处于暂停的状态,则启动
                                   {
                      ucStartFlag=1; //启动
                                   }
                                   else     //如果原来处于启动的状态,则暂停
                                   {
                                      ucStartFlag=0;  //暂停
                                   }
                   break;
           
          }     
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 复位按键 对应朱兆祺学习板的S5键

         //由于本程序只有一个窗口,读者在做实际项目的时候,可以省略switch(ucWd)
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                                   ucStartFlag=0;  //暂停
                   ucCountDown=99;  //恢复倒计时的默认值99
                   uiTimeCnt=0;  //倒计时的时间计时器清零
                                   ucWd1Update=1; //窗口1更新显示标志  只要ucCountDown变化了,就要更新显示一次
                   break;
         
          }  
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  

  }               
}


void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }

   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }



}


//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;

   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);

   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;

}


//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  key_scan(); //按键扫描函数


  if(ucStartFlag==1)  //启动倒计时的计时器
  {
     uiTimeCnt++;
     if(uiTimeCnt>=const_1s)    //1秒钟的时间到
     {
            if(ucCountDown!=0) //加这个判断,就是避免在0的情况下减1
            {
               ucCountDown--;  //倒计时当前显示值减1
            }

        if(ucCountDown==0)  //倒计时结束
            {
               ucStartFlag=0;  //暂停
           uiVoiceCnt=const_voice_long; //蜂鸣器触发提醒,滴一声就停。
            }

        ucWd1Update=1; //窗口1更新显示标志
        uiTimeCnt=0;   //计时器清零,准备从新开始计时
     }
  }



  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
     beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
//     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  }
  else
  {
     ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
     beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
//     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  }

  display_drive();  //数码管字模的驱动函数


  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=1;  //开中断
}


void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

  TMOD=0x01;  //设置定时器0为工作方式1

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;

}

void initial_peripheral() //第二区 初始化外围
{


   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=0;  
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;

   EA=1;     //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断

}


总结陈词:
这节讲了数码管中的倒计时程序。如果要在此程序上多增加两个按键,用来控制数码管倒计时的速度档位,并且需要在数码管中闪烁显示被设置的速度档位,该怎么编写这个程序?欲知详情,请听下回分解-----能设置速度档位的数码管倒计时程序。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
37#
 楼主| 发表于 2014-3-9 20:22:53 | 显示全部楼层
第三十三节:能设置速度档位的数码管倒计时程序。

开场白:
   上一节讲了数码管中的倒计时程序。这节要在此程序上多增加两个按键,用来控制数码管倒计时的速度档位,并且需要在数码管中闪烁显示被设置的速度档位。这一节要教会大家三个知识点:
第一个:把一个按键的短按与长按复合应用在项目中的程序结构。
第二个:通过本程序,继续加深理解按键与数码管的关联方法。
第三个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。

具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。启动和暂停键对应S1键,复位键对应S5键。加键对应S9键,减键对应S13键。

(2)实现功能:按下启动暂停按键时,倒计时开始工作,再按一次启动暂停按键时,则暂停倒计时。在任何时候,按下复位按键,倒计时将暂停工作,并且恢复倒计时当前默认值99。如果长按复位按键,在数码管会切换到第2个闪烁窗口,用来设置速度档位,修改完速度档位后,再一次按下复位按键,或者直接按启动暂停按键,会切换回窗口1显示倒计时的当前数据。
     
(3)源代码讲解如下:

#include "REG52.H"


#define const_voice_short  40   //蜂鸣器短叫的持续时间
#define const_voice_long   200    //蜂鸣器长叫的持续时间

#define const_key_time1  20    //按键去抖动延时的时间
#define const_key_time2  20    //按键去抖动延时的时间
#define const_key_time3  20    //按键去抖动延时的时间
#define const_key_time4  20    //按键去抖动延时的时间

#define const_key_long_time 200  //长按复位按键的时间


#define const_dpy_time_half  200  //数码管闪烁时间的半值
#define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大


void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
void display_drive(); //显示数码管字模的驱动函数

void display_service(); //显示的窗口菜单服务程序

//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

void T0_time();  //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里


sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键


sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  

sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucKeySec=0;   //被触发的按键编号

unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志




unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


unsigned char ucDigShow8;  //第8位数码管要显示的内容
unsigned char ucDigShow7;  //第7位数码管要显示的内容
unsigned char ucDigShow6;  //第6位数码管要显示的内容
unsigned char ucDigShow5;  //第5位数码管要显示的内容
unsigned char ucDigShow4;  //第4位数码管要显示的内容
unsigned char ucDigShow3;  //第3位数码管要显示的内容
unsigned char ucDigShow2;  //第2位数码管要显示的内容
unsigned char ucDigShow1;  //第1位数码管要显示的内容


unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucWd2Update=1; //窗口2更新显示标志

unsigned char ucCountDown=99;  //倒计时的当前值
unsigned char ucStartFlag=0;  //暂停与启动的标志位
unsigned int  uiTimeCnt=0;  //倒计时的时间计时器

unsigned int  uiDpyTimeCnt=0;  //数码管的闪烁计时器,放在定时中断里不断累加

unsigned int  uiSetData1=50;  //速度档位
unsigned int  uiSpeedCnt=400;  //影响速度变量,它跟速度档位uiSetData1有关联

unsigned char ucTemp1=0;  //中间过渡变量
unsigned char ucTemp2=0;  //中间过渡变量
unsigned char ucTemp3=0;  //中间过渡变量
unsigned char ucTemp4=0;  //中间过渡变量
unsigned char ucTemp5=0;  //中间过渡变量
unsigned char ucTemp6=0;  //中间过渡变量
unsigned char ucTemp7=0;  //中间过渡变量
unsigned char ucTemp8=0;  //中间过渡变量


//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
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
0x40,  //-       序号11
0x73,  //P       序号12
};

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_service(); //按键服务的应用程序
       display_service(); //显示的窗口菜单服务程序
   }

}



/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/


void display_service() //显示的窗口菜单服务程序
{


   switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示窗口1的数据
            if(ucWd1Update==1)  //窗口1要全部更新显示
                        {
               ucWd1Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp8=10;  //显示空
               ucTemp7=10;  //显示空
               ucTemp6=10;  //显示空
               ucTemp5=10;  //显示空
               ucTemp4=10;  //显示空
               ucTemp3=10;  //显示空

               ucTemp2=ucCountDown/10;  //倒计时的当前值
               ucTemp1=ucCountDown%10;


               ucDigShow8=ucTemp8;  
               ucDigShow7=ucTemp7;  
               ucDigShow6=ucTemp6;  
               ucDigShow5=ucTemp5;
               ucDigShow4=ucTemp4;  
               ucDigShow3=ucTemp3;


                           if(ucCountDown<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                       

            }
            break;
       case 2:   //显示窗口2的数据
            if(ucWd2Update==1)  //窗口2要全部更新显示
            {
               ucWd2Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp8=10;  //显示空
               ucTemp7=10;  //显示空
               ucTemp6=10;  //显示空
               ucTemp5=10;  //显示空
               ucTemp4=10;  //显示空
               ucTemp3=10;  //显示空

               ucTemp2=uiSetData1/10;  //倒计时的速度档位
               ucTemp1=uiSetData1%10;


               ucDigShow8=ucTemp8;  
               ucDigShow7=ucTemp7;  
               ucDigShow6=ucTemp6;  
               ucDigShow5=ucTemp5;
               ucDigShow4=ucTemp4;  
               ucDigShow3=ucTemp3;


                           if(uiSetData1<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                        

            }

            //数码管闪烁
            if(uiDpyTimeCnt==const_dpy_time_half)
            {
               if(uiSetData1<10)        //数码管显示内容
               {
                    ucDigShow2=10;
               }
               else
               {
                    ucDigShow2=ucTemp2;
               }
               ucDigShow1=ucTemp1;
            }
            else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
            {                       
               uiDpyTimeCnt=0;   //及时把闪烁记时器清零                          

               ucDigShow2=10;   //数码管显示空,什么都不显示
               ucDigShow1=10;

            }

            break;

   
     }
   


}


void key_scan()//按键扫描函数 放在定时中断里
{  

  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
     uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }

/* 注释二:
* 请注意以下长按复位按键与短按复位按键的写法。在本程序中,每次长按复位按键必然
* 触发一次短按复位按键。
*/

  if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock2=0; //按键自锁标志清零
     uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt2++; //累加定时中断次数
     if(uiKeyTimeCnt2>const_key_time2)
     {
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;  //自锁按键置位,避免一直触发
        ucKeySec=2;    //触发2号键
     }
  }
  else if(uiKeyTimeCnt2<const_key_long_time)   //长按复位按键
  {
      uiKeyTimeCnt2++;
          if(uiKeyTimeCnt2==const_key_long_time)
          {
             ucKeySec=17;    //触发17号长按复位键
          }
  }

if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock3=0; //按键自锁标志清零
     uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt3++; //累加定时中断次数
     if(uiKeyTimeCnt3>const_key_time3)
     {
        uiKeyTimeCnt3=0;
        ucKeyLock3=1;  //自锁按键置位,避免一直触发
        ucKeySec=3;    //触发3号键
     }
  }

  if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock4=0; //按键自锁标志清零
     uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt4++; //累加定时中断次数
     if(uiKeyTimeCnt4>const_key_time4)
     {
        uiKeyTimeCnt4=0;
        ucKeyLock4=1;  //自锁按键置位,避免一直触发
        ucKeySec=4;    //触发4号键
     }
  }




}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 启动和暂停按键 对应朱兆祺学习板的S1键

          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                   if(ucStartFlag==0)  //如果原来处于暂停的状态,则启动
                                   {
                      ucStartFlag=1; //启动
                                   }
                                   else     //如果原来处于启动的状态,则暂停
                                   {
                                      ucStartFlag=0;  //暂停
                                   }
                   break;


           
          }   

          ucWd=1;  //不管在哪个窗口,强行切换回窗口1
                  ucWd1Update=1; //窗口1更新显示标志

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 复位按键 对应朱兆祺学习板的S5键

          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:    //在窗口1中
                                   ucStartFlag=0;  //暂停
                   ucCountDown=99;  //恢复倒计时的默认值99
                   uiTimeCnt=0;  //倒计时的时间计时器清零
                                   ucWd1Update=1; //窗口1更新显示标志  只要ucCountDown变化了,就要更新显示一次
                   break;
              case 2:    //在窗口2中
                   ucWd=1;  //切换回窗口1
                                   ucWd1Update=1; //窗口1更新显示标志
                   break;
          }  
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  

    case 3:// 加按键 对应朱兆祺学习板的S9键


          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 2:   //在窗口2中
                   uiSetData1++;       //速度档位累加,档位越大,速度越快.
                                   if(uiSetData1>99)
                                   {
                                      uiSetData1=99;
                                   }
                   uiSpeedCnt=440-(uiSetData1*2);  //速度档位越大,累计中断数uiSpeedCnt越小,从而倒计时的时间越快

                                   ucWd2Update=1; //窗口2更新显示
                   break;
          }     
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 4:// 减按键 对应朱兆祺学习板的S13键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 2:   //在窗口2中
                               if(uiSetData1>0)  //加此条件判断,避免0减1
                                   {
                      uiSetData1--;       //速度档位累减,档位越小,速度越慢.
                                   }

                   uiSpeedCnt=440-(uiSetData1*2);  //速度档位越小,累计中断数uiSpeedCnt越大,从而倒计时的时间越慢

                                   ucWd2Update=1; //窗口2更新显示
                   break;
          }
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;

    case 17:// 长按复位按键 对应朱兆祺学习板的S5键

          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:  //窗口1下
                   ucWd=2;  //切换到闪烁窗口2  进行设置速度档位显示
                                   ucWd2Update=1; //窗口2更新显示标志
                   break;
         
          }  
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;

  }               
}


void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }

   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }



}


//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;

   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);

   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;

}


//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  key_scan(); //按键扫描函数


  if(ucStartFlag==1)  //启动倒计时的计时器
  {
     uiTimeCnt++;
     if(uiTimeCnt>=uiSpeedCnt)    //时间到
     {
            if(ucCountDown!=0) //加这个判断,就是避免在0的情况下减1
            {
               ucCountDown--;  //倒计时当前显示值减1
            }

        if(ucCountDown==0)  //倒计时结束
            {
               ucStartFlag=0;  //暂停
           uiVoiceCnt=const_voice_long; //蜂鸣器触发提醒,滴一声就停。
            }

        ucWd1Update=1; //窗口1更新显示标志
        uiTimeCnt=0;   //计时器清零,准备从新开始计时
     }
  }


  uiDpyTimeCnt++;  //数码管的闪烁计时器


  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
     beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
//     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  }
  else
  {
     ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
     beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
//     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  }

  display_drive();  //数码管字模的驱动函数


  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=1;  //开中断
}


void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

  TMOD=0x01;  //设置定时器0为工作方式1

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;

}

void initial_peripheral() //第二区 初始化外围
{


   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=0;  
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;

   uiSpeedCnt=440-(uiSetData1*2);  //速度档位越大,累计中断数uiSpeedCnt越小,从而倒计时的时间越快

   EA=1;     //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断

}


总结陈词:
这节讲了能设置速度档位的数码管倒计时程序。现在很多人用iphone4S的手机,这个手机每次开机显示的时候,都要通过4个密码开锁,如果我们要用4位数码管来实现这个密码锁功能,该怎么编写这个程序?欲知详情,请听下回分解-----在数码管中实现iphone4S开机密码锁的程序。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
38#
 楼主| 发表于 2014-3-15 10:11:52 | 显示全部楼层
第三十四节:在数码管中实现iphone4S开机密码锁的程序。

开场白:
    这一节要教会大家四个知识点:
第一个:类似手机上10秒钟内无按键操作将自动进入锁屏的程序。
第二个:如何用一个数组来接收按键的一串数字输入。
第三个:矩阵键盘中,数字按键的输入,由于这部分按键的代码相似度非常高,因此把它封装在一个函数里可以非常简洁方便。
第四个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。

具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键,数字0键对应S10键。其他的按键不用。

(2)实现功能:
本程序有3个窗口。
开机显示第1个密码登录框窗口“----”,在这个窗口下输入密码,如果密码等于”9922”表示密码正确,将会切换到第2个显示按键值的窗口。在窗口2下,按不同的按键会显示不同的按键值,如果10秒内没有按键操作,将会自动切换到第1个密码登录窗口,类似手机上的自动锁屏操作。在密码登录窗口1下,如果密码不正确,会自动清除密码的数字,继续在窗口1下显示”----”。  
窗口3是用来停留0.5秒显示全部密码的信息,然后根据密码的正确与否自动切换到对应的窗口。

3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_no_key_push 4400   //大概10秒内无按键按下的时间
  3. #define const_0_1s  220            //大概0.5秒的时间

  4. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  5. #define const_key_time  20    //按键去抖动延时的时间


  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);
  10. //驱动数码管的74HC595
  11. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  12. void display_drive(); //显示数码管字模的驱动函数
  13. void display_service(); //显示的窗口菜单服务程序
  14. //驱动LED的74HC595
  15. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  16. void T0_time();  //定时中断函数

  17. void number_key_input(unsigned char ucWhichKey);  //由于数字按键的代码相似度高,因此封装在这个函数里
  18. void key_service(); //按键服务的应用程序
  19. void key_scan();//按键扫描函数 放在定时中断里

  20. sbit key_sr1=P0^0; //第一行输入
  21. sbit key_sr2=P0^1; //第二行输入
  22. sbit key_sr3=P0^2; //第三行输入
  23. sbit key_sr4=P0^3; //第四行输入

  24. sbit key_dr1=P0^4; //第一列输出
  25. sbit key_dr2=P0^5; //第二列输出
  26. sbit key_dr3=P0^6; //第三列输出
  27. sbit key_dr4=P0^7; //第四列输出


  28. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  29. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停

  30. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  31. sbit dig_hc595_st_dr=P2^1;  
  32. sbit dig_hc595_ds_dr=P2^2;  
  33. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  34. sbit hc595_st_dr=P2^4;  
  35. sbit hc595_ds_dr=P2^5;  

  36. unsigned char ucKeyStep=1;  //按键扫描步骤变量

  37. unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器
  38. unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

  39. unsigned char ucRowRecord=1; //记录当前扫描到第几列了


  40. unsigned char ucKeySec=0;   //被触发的按键编号
  41. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  42. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  43. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  44. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  45. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  46. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  47. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  48. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  49. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  50. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  51. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  52. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  53. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  54. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  55. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  56. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  57. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  58. unsigned char ucDigShowTemp=0; //临时中间变量
  59. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  60. unsigned char ucWd1Update=1; //窗口1更新显示标志
  61. unsigned char ucWd2Update=0; //窗口2更新显示标志
  62. unsigned char ucWd3Update=0; //窗口3更新显示标志
  63. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。


  64. unsigned char ucInputPassword[4];  //在第1个窗口下,显示输入的4个密码
  65. unsigned char ucPasswordCnt=0; //记录当前已经输入到哪一位密码了
  66. unsigned char ucKeyNumber=1;  //在第2个窗口下,显示当前被按下的按键

  67. unsigned int  uiNoKeyPushTimer=const_no_key_push;  //10秒内无按键按下的计时器
  68. unsigned int  uiPasswordTimer=const_0_1s;  //显示0.5秒钟全部密码的计时器,让窗口3停留显示0.5秒钟之后自动消失

  69. unsigned char ucTemp1=0;  //中间过渡变量
  70. unsigned char ucTemp2=0;  //中间过渡变量
  71. unsigned char ucTemp3=0;  //中间过渡变量
  72. unsigned char ucTemp4=0;  //中间过渡变量

  73. //根据原理图得出的共阴数码管字模表
  74. code unsigned char dig_table[]=
  75. {
  76. 0x3f,  //0       序号0
  77. 0x06,  //1       序号1
  78. 0x5b,  //2       序号2
  79. 0x4f,  //3       序号3
  80. 0x66,  //4       序号4
  81. 0x6d,  //5       序号5
  82. 0x7d,  //6       序号6
  83. 0x07,  //7       序号7
  84. 0x7f,  //8       序号8
  85. 0x6f,  //9       序号9
  86. 0x00,  //无      序号10
  87. 0x40,  //-       序号11
  88. 0x73,  //P       序号12
  89. };
  90. void main()
  91.   {
  92.    initial_myself();  
  93.    delay_long(100);   
  94.    initial_peripheral();
  95.    while(1)  
  96.    {
  97.      key_service(); //按键服务的应用程序
  98.          display_service(); //显示的窗口菜单服务程序
  99.    }
  100. }
  101. /* 注释一:
  102. *鸿哥首次提出的"一二级菜单显示理论":
  103. *凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
  104. *每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
  105. *局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
  106. *表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
  107. */

  108. void display_service() //显示的窗口菜单服务程序
  109. {

  110.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  111.    {
  112.        case 1:   //显示输入密码的登录框
  113.             if(ucWd1Update==1)  //窗口1要全部更新显示
  114.             {
  115.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描

  116.                ucDigShow8=10;  //第8位数码管显示无
  117.                ucDigShow7=10;  //第7位数码管显示无
  118.                ucDigShow6=10;  //第6位数码管显示无
  119.                ucDigShow5=10;  //第5位数码管显示无

  120.                ucDigShow4=ucInputPassword[0];  //第4位数码管显示输入的密码
  121.                ucDigShow3=ucInputPassword[1];  //第3位数码管显示输入的密码
  122.                ucDigShow2=ucInputPassword[2];  //第2位数码管显示输入的密码
  123.                ucDigShow1=ucInputPassword[3];  //第1位数码管显示输入的密码

  124.             }
  125.             break;
  126.         case 2:  //显示被按下的键值
  127.             if(ucWd2Update==1)  //窗口2要全部更新显示
  128.             {
  129.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描

  130.                ucDigShow8=10;  //第8位数码管显示无
  131.                ucDigShow7=10;  //第7位数码管显示无
  132.                ucDigShow6=10;  //第6位数码管显示无
  133.                ucDigShow5=10;  //第5位数码管显示无

  134.                ucDigShow4=10;  //第4位数码管显示无
  135.                ucDigShow3=10;  //第3位数码管显示无
  136.                ucDigShow2=10;  //第2位数码管显示无
  137.                ucDigShow1=ucKeyNumber; //第1位数码管显示被按下的键值
  138.             }
  139.             break;
  140.        case 3:   //当输入完4个密码后,显示1秒钟的密码登录框,
  141.             if(ucWd3Update==1)  //窗口3要全部更新显示
  142.             {
  143.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描

  144.                ucDigShow8=10;  //第8位数码管显示无
  145.                ucDigShow7=10;  //第7位数码管显示无
  146.                ucDigShow6=10;  //第6位数码管显示无
  147.                ucDigShow5=10;  //第5位数码管显示无

  148.                ucDigShow4=ucInputPassword[0];  //第4位数码管显示输入的密码
  149.                ucDigShow3=ucInputPassword[1];  //第3位数码管显示输入的密码
  150.                ucDigShow2=ucInputPassword[2];  //第2位数码管显示输入的密码
  151.                ucDigShow1=ucInputPassword[3];  //第1位数码管显示输入的密码

  152.             }
  153.             break;     
  154.     }
  155.    

  156. }
  157. void key_scan()//按键扫描函数 放在定时中断里
  158. {  


  159.   switch(ucKeyStep)
  160.   {
  161.      case 1:   //按键扫描输出第ucRowRecord列低电平
  162.               if(ucRowRecord==1)  //第一列输出低电平
  163.                   {
  164.              key_dr1=0;      
  165.              key_dr2=1;
  166.              key_dr3=1;   
  167.              key_dr4=1;
  168.                   }
  169.               else if(ucRowRecord==2)  //第二列输出低电平
  170.                   {
  171.              key_dr1=1;      
  172.              key_dr2=0;
  173.              key_dr3=1;   
  174.              key_dr4=1;
  175.                   }
  176.               else if(ucRowRecord==3)  //第三列输出低电平
  177.                   {
  178.              key_dr1=1;      
  179.              key_dr2=1;
  180.              key_dr3=0;   
  181.              key_dr4=1;
  182.                   }
  183.               else   //第四列输出低电平
  184.                   {
  185.              key_dr1=1;      
  186.              key_dr2=1;
  187.              key_dr3=1;   
  188.              key_dr4=0;
  189.                   }

  190.           uiKeyTimeCnt=0;  //延时计数器清零
  191.           ucKeyStep++;     //切换到下一个运行步骤
  192.               break;

  193.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  194.           uiKeyTimeCnt++;
  195.                   if(uiKeyTimeCnt>1)
  196.                   {
  197.                      uiKeyTimeCnt=0;
  198.              ucKeyStep++;     //切换到下一个运行步骤
  199.                   }
  200.               break;

  201.      case 3:
  202.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  203.           {  
  204.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  205.              ucKeyLock=0;  //按键自锁标志清零
  206.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  207.    
  208.                          ucRowRecord++;  //输出下一列
  209.                          if(ucRowRecord>4)  
  210.                          {
  211.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  212.                          }

  213.           }
  214.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  215.                   {
  216.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  217.                          {
  218.                             uiKeyTimeCnt++;  //去抖动延时计数器
  219.                                 if(uiKeyTimeCnt>const_key_time)
  220.                                 {
  221.                                    uiKeyTimeCnt=0;
  222.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  223.                        if(ucRowRecord==1)  //第一列输出低电平
  224.                            {
  225.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  226.                            }
  227.                        else if(ucRowRecord==2)  //第二列输出低电平
  228.                            {
  229.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  230.                            }
  231.                        else if(ucRowRecord==3)  //第三列输出低电平
  232.                            {
  233.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  234.                            }
  235.                        else   //第四列输出低电平
  236.                            {
  237.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  238.                            }

  239.                                 }
  240.                         
  241.                          }
  242.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  243.                          {
  244.                             uiKeyTimeCnt++;  //去抖动延时计数器
  245.                                 if(uiKeyTimeCnt>const_key_time)
  246.                                 {
  247.                                    uiKeyTimeCnt=0;
  248.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  249.                        if(ucRowRecord==1)  //第一列输出低电平
  250.                            {
  251.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  252.                            }
  253.                        else if(ucRowRecord==2)  //第二列输出低电平
  254.                            {
  255.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  256.                            }
  257.                        else if(ucRowRecord==3)  //第三列输出低电平
  258.                            {
  259.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  260.                            }
  261.                        else   //第四列输出低电平
  262.                            {
  263.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  264.                            }
  265.                                 }
  266.                         
  267.                          }
  268.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  269.                          {
  270.                             uiKeyTimeCnt++;  //去抖动延时计数器
  271.                                 if(uiKeyTimeCnt>const_key_time)
  272.                                 {
  273.                                    uiKeyTimeCnt=0;
  274.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  275.                        if(ucRowRecord==1)  //第一列输出低电平
  276.                            {
  277.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  278.                            }
  279.                        else if(ucRowRecord==2)  //第二列输出低电平
  280.                            {
  281.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  282.                            }
  283.                        else if(ucRowRecord==3)  //第三列输出低电平
  284.                            {
  285.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  286.                            }
  287.                        else   //第四列输出低电平
  288.                            {
  289.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  290.                            }
  291.                                 }
  292.                         
  293.                          }
  294.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  295.                          {
  296.                             uiKeyTimeCnt++;  //去抖动延时计数器
  297.                                 if(uiKeyTimeCnt>const_key_time)
  298.                                 {
  299.                                    uiKeyTimeCnt=0;
  300.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  301.                        if(ucRowRecord==1)  //第一列输出低电平
  302.                            {
  303.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  304.                            }
  305.                        else if(ucRowRecord==2)  //第二列输出低电平
  306.                            {
  307.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  308.                            }
  309.                        else if(ucRowRecord==3)  //第三列输出低电平
  310.                            {
  311.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  312.                            }
  313.                        else   //第四列输出低电平
  314.                            {
  315.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  316.                            }
  317.                                 }
  318.                         
  319.                          }
  320.                   
  321.                   }
  322.               break;

  323.   }


  324. }


  325. void key_service() //第三区 按键服务的应用程序
  326. {
  327.   switch(ucKeySec) //按键服务状态切换
  328.   {
  329.     case 1:// 1号键 对应朱兆祺学习板的S1键
  330.           number_key_input(1);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  331.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  332.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  333.           break;        
  334.     case 2:// 2号键 对应朱兆祺学习板的S2键
  335.           number_key_input(2);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  336.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  337.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  338.           break;     
  339.     case 3:// 3号键 对应朱兆祺学习板的S3键
  340.           number_key_input(3);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  341.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  342.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  343.           break;         
  344.     case 4:// 4号键 对应朱兆祺学习板的S4键
  345.           number_key_input(4);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  346.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  347.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  348.           break;   
  349.     case 5:// 5号键 对应朱兆祺学习板的S5键
  350.           number_key_input(5);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  351.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  352.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  353.           break;   
  354.     case 6:// 6号键 对应朱兆祺学习板的S6键
  355.           number_key_input(6);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  356.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  357.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  358.           break;   
  359.     case 7:// 7号键 对应朱兆祺学习板的S7键
  360.           number_key_input(7);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  361.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  362.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  363.           break;   
  364.     case 8:// 8号键 对应朱兆祺学习板的S8键
  365.           number_key_input(8);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  366.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  367.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  368.           break;   
  369.     case 9:// 9号键 对应朱兆祺学习板的S9键
  370.           number_key_input(9);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  371.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  372.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  373.           break;   
  374.     case 10:// 把这个按键专门用来输入数字0    对应朱兆祺学习板的S10键
  375.           number_key_input(0);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  376.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  377.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  378.           break;   
  379.     case 11:// 11号键 对应朱兆祺学习板的S11键

  380.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  381.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  382.           break;   
  383.     case 12:// 12号键 对应朱兆祺学习板的S12键

  384.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  385.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  386.           break;   
  387.     case 13:// 13号键 对应朱兆祺学习板的S13键

  388.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  389.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  390.           break;   
  391.     case 14:// 14号键 对应朱兆祺学习板的S14键

  392.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  393.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  394.           break;   
  395.     case 15:// 15号键 对应朱兆祺学习板的S15键

  396.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  397.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  398.           break;   
  399.     case 16:// 16号键 对应朱兆祺学习板的S16键

  400.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  401.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  402.           break;   
  403.   }               
  404. }

  405. void number_key_input(unsigned char ucWhichKey)  //由于数字按键的代码相似度高,因此封装在这个函数里
  406. {


  407.     switch(ucWd)
  408.            {
  409.            case 1:   //在显示密码登录框的窗口下
  410.             ucInputPassword[ucPasswordCnt]=ucWhichKey;   //输入的密码值显示
  411.             ucPasswordCnt++;
  412.                         if(ucPasswordCnt>=4)
  413.                         {
  414.                             ucPasswordCnt=0;
  415.                                 ucWd=3;//切换到第3个的窗口,停留显示1秒钟全部密码
  416.                             ucWd3Update=1;  //更新显示窗口3
  417.                 uiPasswordTimer=const_0_1s;  //显示0.5秒钟全部密码的计时器,让窗口3停留显示0.5秒钟之后自动消失
  418.                         }

  419.                         ucWd1Update=1; //更新显示窗口1

  420.                         uiNoKeyPushTimer=const_no_key_push;  //10秒内无按键按下的计时器赋新值
  421.                         break;
  422.            case 2:   //在显示按键值的窗口下
  423.                 ucKeyNumber=ucWhichKey; //输入的按键数值显示
  424.                     ucWd2Update=1;  //更新显示窗口2

  425.                         uiNoKeyPushTimer=const_no_key_push;  //10秒内无按键按下的计时器赋新值
  426.                     break;
  427.     }

  428. }


  429. void display_drive()  
  430. {
  431.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  432.    switch(ucDisplayDriveStep)
  433.    {
  434.       case 1:  //显示第1位
  435.            ucDigShowTemp=dig_table[ucDigShow1];
  436.                    if(ucDigDot1==1)
  437.                    {
  438.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  439.                    }
  440.            dig_hc595_drive(ucDigShowTemp,0xfe);
  441.                break;
  442.       case 2:  //显示第2位
  443.            ucDigShowTemp=dig_table[ucDigShow2];
  444.                    if(ucDigDot2==1)
  445.                    {
  446.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  447.                    }
  448.            dig_hc595_drive(ucDigShowTemp,0xfd);
  449.                break;
  450.       case 3:  //显示第3位
  451.            ucDigShowTemp=dig_table[ucDigShow3];
  452.                    if(ucDigDot3==1)
  453.                    {
  454.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  455.                    }
  456.            dig_hc595_drive(ucDigShowTemp,0xfb);
  457.                break;
  458.       case 4:  //显示第4位
  459.            ucDigShowTemp=dig_table[ucDigShow4];
  460.                    if(ucDigDot4==1)
  461.                    {
  462.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  463.                    }
  464.            dig_hc595_drive(ucDigShowTemp,0xf7);
  465.                break;
  466.       case 5:  //显示第5位
  467.            ucDigShowTemp=dig_table[ucDigShow5];
  468.                    if(ucDigDot5==1)
  469.                    {
  470.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  471.                    }
  472.            dig_hc595_drive(ucDigShowTemp,0xef);
  473.                break;
  474.       case 6:  //显示第6位
  475.            ucDigShowTemp=dig_table[ucDigShow6];
  476.                    if(ucDigDot6==1)
  477.                    {
  478.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  479.                    }
  480.            dig_hc595_drive(ucDigShowTemp,0xdf);
  481.                break;
  482.       case 7:  //显示第7位
  483.            ucDigShowTemp=dig_table[ucDigShow7];
  484.                    if(ucDigDot7==1)
  485.                    {
  486.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  487.            }
  488.            dig_hc595_drive(ucDigShowTemp,0xbf);
  489.                break;
  490.       case 8:  //显示第8位
  491.            ucDigShowTemp=dig_table[ucDigShow8];
  492.                    if(ucDigDot8==1)
  493.                    {
  494.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  495.                    }
  496.            dig_hc595_drive(ucDigShowTemp,0x7f);
  497.                break;
  498.    }
  499.    ucDisplayDriveStep++;
  500.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  501.    {
  502.      ucDisplayDriveStep=1;
  503.    }

  504. }

  505. //数码管的74HC595驱动函数
  506. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  507. {
  508.    unsigned char i;
  509.    unsigned char ucTempData;
  510.    dig_hc595_sh_dr=0;
  511.    dig_hc595_st_dr=0;
  512.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  513.    for(i=0;i<8;i++)
  514.    {
  515.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  516.          else dig_hc595_ds_dr=0;
  517.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  518.          delay_short(1);
  519.          dig_hc595_sh_dr=1;
  520.          delay_short(1);
  521.          ucTempData=ucTempData<<1;
  522.    }
  523.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  524.    for(i=0;i<8;i++)
  525.    {
  526.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  527.          else dig_hc595_ds_dr=0;
  528.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  529.          delay_short(1);
  530.          dig_hc595_sh_dr=1;
  531.          delay_short(1);
  532.          ucTempData=ucTempData<<1;
  533.    }
  534.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  535.    delay_short(1);
  536.    dig_hc595_st_dr=1;
  537.    delay_short(1);
  538.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  539.    dig_hc595_st_dr=0;
  540.    dig_hc595_ds_dr=0;
  541. }

  542. //LED灯的74HC595驱动函数
  543. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  544. {
  545.    unsigned char i;
  546.    unsigned char ucTempData;
  547.    hc595_sh_dr=0;
  548.    hc595_st_dr=0;
  549.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  550.    for(i=0;i<8;i++)
  551.    {
  552.          if(ucTempData>=0x80)hc595_ds_dr=1;
  553.          else hc595_ds_dr=0;
  554.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  555.          delay_short(1);
  556.          hc595_sh_dr=1;
  557.          delay_short(1);
  558.          ucTempData=ucTempData<<1;
  559.    }
  560.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  561.    for(i=0;i<8;i++)
  562.    {
  563.          if(ucTempData>=0x80)hc595_ds_dr=1;
  564.          else hc595_ds_dr=0;
  565.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  566.          delay_short(1);
  567.          hc595_sh_dr=1;
  568.          delay_short(1);
  569.          ucTempData=ucTempData<<1;
  570.    }
  571.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  572.    delay_short(1);
  573.    hc595_st_dr=1;
  574.    delay_short(1);
  575.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  576.    hc595_st_dr=0;
  577.    hc595_ds_dr=0;
  578. }

  579. void T0_time() interrupt 1
  580. {
  581.   unsigned int i;
  582.   TF0=0;  //清除中断标志
  583.   TR0=0; //关中断

  584.   if(ucWd==3)  //在窗口3下
  585.   {
  586.      if(uiPasswordTimer>0)   
  587.          {
  588.             uiPasswordTimer--;  
  589.          }
  590.      if(uiPasswordTimer==0)
  591.          {
  592.                 if(ucInputPassword[0]==9&&ucInputPassword[1]==9&&ucInputPassword[2]==2&&ucInputPassword[3]==2)  
  593.             {     //如果密码等于9922,则正确
  594.                       ucWd=2;//切换到第2个显示按键的窗口
  595.                       ucWd2Update=1;  //更新显示窗口2
  596.             }
  597.                 else //如果密码不正确,则继续显示----
  598.             {
  599.               for(i=0;i<4;i++)
  600.               {
  601.                  ucInputPassword[i]=11;  //开机默认密码全部显示"----"
  602.               }
  603.                           ucWd=1;
  604.                   ucWd1Update=1; //更新显示窗口1
  605.                 }

  606.          }
  607.   }


  608.   if(ucWd==2)  //在窗口2下
  609.   {
  610.      if(uiNoKeyPushTimer>0)   
  611.          {
  612.             uiNoKeyPushTimer--;  
  613.          }
  614.      if(uiNoKeyPushTimer==0)//如果10秒内无按键按下,则自动切换到显示密码登录框的界面
  615.          {
  616.        for(i=0;i<4;i++)
  617.        {
  618.           ucInputPassword[i]=11;  //开机默认密码全部显示"----"
  619.        }
  620.            ucWd=1;
  621.            ucWd1Update=1; //更新显示窗口1
  622.          }
  623.   }


  624.   key_scan(); //按键扫描函数
  625.   if(uiVoiceCnt!=0)
  626.   {
  627.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  628.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  629. //     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  630.   }
  631.   else
  632.   {
  633.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  634.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  635. //     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  636.   }
  637.   display_drive();  //数码管字模的驱动函数

  638.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  639.   TL0=0x0b;
  640.   TR0=1;  //开中断
  641. }

  642. void delay_short(unsigned int uiDelayShort)
  643. {
  644.    unsigned int i;  
  645.    for(i=0;i<uiDelayShort;i++)
  646.    {
  647.      ;   //一个分号相当于执行一条空语句
  648.    }
  649. }

  650. void delay_long(unsigned int uiDelayLong)
  651. {
  652.    unsigned int i;
  653.    unsigned int j;
  654.    for(i=0;i<uiDelayLong;i++)
  655.    {
  656.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  657.           {
  658.              ; //一个分号相当于执行一条空语句
  659.           }
  660.    }
  661. }

  662. void initial_myself()  //第一区 初始化单片机
  663. {

  664.   led_dr=0;  //关闭独立LED灯
  665.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  666.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  667.   TMOD=0x01;  //设置定时器0为工作方式1
  668.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  669.   TL0=0x0b;
  670. }
  671. void initial_peripheral() //第二区 初始化外围
  672. {
  673.    unsigned int i; //个人的变量命名习惯,i,j,k等单个字母的变量名只用在for循环里
  674.    for(i=0;i<4;i++)
  675.    {
  676.      ucInputPassword[i]=11;  //开机默认密码全部显示"----"
  677.    }

  678.    ucDigDot8=0;   //小数点全部不显示
  679.    ucDigDot7=0;  
  680.    ucDigDot6=0;
  681.    ucDigDot5=0;  
  682.    ucDigDot4=0;
  683.    ucDigDot3=0;  
  684.    ucDigDot2=0;
  685.    ucDigDot1=0;
  686.    EA=1;     //开总中断
  687.    ET0=1;    //允许定时中断
  688.    TR0=1;    //启动定时中断
  689. }
复制代码


总结陈词:
这节讲了iphone4S开机密码锁的程序。2014年春节的时候,一帮朋友举行小规模的象棋比赛,有一些朋友下棋的速度实在是太慢了,为了限制比赛时间,我专门用朱兆祺的51学习板做了一个棋类比赛专用计时器给他们用,这个程序该怎么编写?欲知详情,请听下回分解-----带数码管显示的象棋比赛专用计时器。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
39#
 楼主| 发表于 2014-3-21 01:34:48 | 显示全部楼层
第三十五节:带数码管显示的象棋比赛专用计时器。

开场白:
2014年春节的时候,一帮朋友举行小规模的象棋比赛,有一些朋友下棋的速度实在是太慢了,为了限制比赛时间,我专门用朱兆祺的51学习板做了一个棋类比赛专用计时器给他们用。这一节要教会大家两个知识点:
第一个:按键服务程序操作的精髓在于根据当前系统处于什么窗口状态下就执行什么操作。紧紧围绕着不同的窗口ucWd来执行不同的操作。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。

具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。
刚上电开机时,红棋加时键对应S1键,红棋减时键对应S2键.。
刚上电开机时,黑棋加时键对应S3键,黑棋减时键对应S4键.。
比赛中途暂停双方计时的暂停按键对应S6键。刚上电时,复位双方默认20分时间的复位按键对应S7按键。
红棋的抢时按键对应S13键,黑棋的抢时按键对应S16按键。

(2)实现功能:
棋类计时器有点像抢答器,本质上有两个计时器。比赛的时候对弈的两个棋友各用一个不同的按键抢时间,红棋走一步棋后,就按一下自己的抢时按键,这个时候红棋的计时器停止计时,而黑棋的计时器开始计时,黑棋走了一步棋后,按一下自己的计时器,黑棋停止计时,红棋继续计时,依次循环,谁的时间最先用完谁就输,蜂鸣器也会发出长鸣的声音提示时间到。
上电开机默认双方各有20分钟的时间,左边显示的是红棋的时间,右边显示的是黑棋的时间。此时可以通过S1,S2.,S3,S4的加减按键来设置各自的最大倒计时时间。此时如果按下复位按键S7,会自动把双方的时间设置为默认的20分钟。
设置好最大倒计时的时间后,此时任意一方按下各自的抢时按键(S13或者S16),则自己的计时器停止计时,而对方开始倒计时。此时数码管显示的是对方的时间,而自己的时间屏蔽不显示。
在开始倒计时的时候,如果中途有棋友要接听电话或者忙别的事情,需要暂时暂停一下双方的时间,这个时候可以按S6暂停按键来暂停双方的计时,忙完后再次按下暂停按键会继续倒计时。任何一方的时间走完,都会蜂鸣器长鸣提示。
(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_voice_long   900   //蜂鸣器长叫的持续时间

  4. #define const_key_time  10    //按键去抖动延时的时间

  5. #define const_1s     422   //产生一秒钟的时间基准

  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);
  10. void T0_time();  //定时中断函数
  11. void key_service();
  12. void key_scan(); //按键扫描函数 放在定时中断里

  13. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
  14. void display_drive();  //放在定时中断里的数码管驱动函数
  15. void time_service();  //放在定时中断里的时间应用程序
  16. void display_service();  


  17. sbit key_sr1=P0^0; //第一行输入
  18. sbit key_sr2=P0^1; //第二行输入
  19. sbit key_sr3=P0^2; //第三行输入
  20. sbit key_sr4=P0^3; //第四行输入

  21. sbit key_dr1=P0^4; //第一列输出
  22. sbit key_dr2=P0^5; //第二列输出
  23. sbit key_dr3=P0^6; //第三列输出
  24. sbit key_dr4=P0^7; //第四列输出

  25. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口


  26. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


  27. sbit dig_hc595_sh_dr=P2^0;     //数码管 的74HC595程序
  28. sbit dig_hc595_st_dr=P2^1;  
  29. sbit dig_hc595_ds_dr=P2^2;  

  30. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  31. sbit hc595_st_dr=P2^4;  
  32. sbit hc595_ds_dr=P2^5;  


  33. unsigned char ucKeyStep=1;  //按键扫描步骤变量

  34. unsigned char ucKeySec=0;   //被触发的按键编号
  35. unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器
  36. unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

  37. unsigned char ucRowRecord=1; //记录当前扫描到第几列了

  38. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  39. unsigned char ucDigShow8=0;  //第8位数码管要显示的内容
  40. unsigned char ucDigShow7=0;  //第7位数码管要显示的内容
  41. unsigned char ucDigShow6=0;  //第6位数码管要显示的内容
  42. unsigned char ucDigShow5=0;  //第5位数码管要显示的内容
  43. unsigned char ucDigShow4=0;  //第4位数码管要显示的内容
  44. unsigned char ucDigShow3=0;  //第3位数码管要显示的内容
  45. unsigned char ucDigShow2=0;  //第2位数码管要显示的内容
  46. unsigned char ucDigShow1=0;  //第1位数码管要显示的内容
  47. unsigned char ucDigDot3=1;  //数码管3的小数点是否显示的标志
  48. unsigned char ucDigDot7=1;  //数码管7的小数点是否显示的标志

  49. unsigned char ucDigShowTemp=0; //临时中间变量

  50. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  51. unsigned int uiRedTimeCnt=0;    //红棋产生秒基准的时间计时器
  52. unsigned int uiBlackTimeCnt=0;  //黑棋产生秒基准的时间计时器

  53. unsigned int uiRedTotal=1200;    //红棋的总时间
  54. unsigned int uiBlackTotal=1200;  //黑棋的总时间

  55. unsigned char ucRedFlag=0;  //红棋是否开始计时的标志
  56. unsigned char ucBlackFlag=0;  //黑棋是否开始计时的标志

  57. unsigned char ucDisplayUpdate=1; //更新显示标志

  58. /* 注释一:
  59. *  ucWd变量是本程序最核心的变量,代表显示哪一个窗口和系统处于当前哪种状态
  60. */
  61. unsigned char ucWd=1;

  62. code unsigned char dig_table[]=
  63. {
  64. 0x3f,  //0       序号0
  65. 0x06,  //1       序号1
  66. 0x5b,  //2       序号2
  67. 0x4f,  //3       序号3
  68. 0x66,  //4       序号4
  69. 0x6d,  //5       序号5
  70. 0x7d,  //6       序号6
  71. 0x07,  //7       序号7
  72. 0x7f,  //8       序号8
  73. 0x6f,  //9       序号9
  74. 0x00,  //不显示  序号10
  75. };

  76. void main()
  77.   {
  78.    initial_myself();  
  79.    delay_long(100);   
  80.    initial_peripheral();
  81.    while(1)  
  82.    {
  83.        key_service();
  84.        display_service();  
  85.    }

  86. }


  87. void time_service()  //放在定时中断里的时间应用程序
  88. {
  89.   if(ucRedFlag==1)  //1代表红棋在运行中
  90.   {
  91.      uiRedTimeCnt++;
  92.          if(uiRedTimeCnt>const_1s)
  93.          {
  94.         uiRedTimeCnt=0;
  95.         if(uiRedTotal>0)
  96.                 {
  97.                    uiRedTotal--;
  98.                 }
  99.                 else  //时间到
  100.                 {
  101.                     ucRedFlag=0;    //红棋和黑棋同时停止计时
  102.                    ucBlackFlag=0;
  103.                    ucWd=1;  //切换到第一个窗口的状态
  104.                    uiVoiceCnt=const_voice_long; //报警声音触发
  105.                 }
  106.                

  107.         ucDisplayUpdate=1;  //更新显示
  108.          }
  109.   }


  110.   if(ucBlackFlag==1)  //1代表黑棋在运行中
  111.   {
  112.      uiBlackTimeCnt++;
  113.          if(uiBlackTimeCnt>const_1s)
  114.          {
  115.         uiBlackTimeCnt=0;
  116.         if(uiBlackTotal>0)
  117.                 {
  118.                    uiBlackTotal--;
  119.                 }
  120.                 else  //时间到
  121.                 {
  122.                     ucRedFlag=0;  //红棋和黑棋同时停止计时
  123.                    ucBlackFlag=0;
  124.                    ucWd=1;  //切换到第一个窗口的状态
  125.                    uiVoiceCnt=const_voice_long; //报警声音触发
  126.                 }
  127.                

  128.         ucDisplayUpdate=1;  //更新显示
  129.          }
  130.   }
  131. }

  132. void display_service()  //放在定时中断里的显示应用程序
  133. {
  134.   if(ucDisplayUpdate==1)  //有数据更新显示
  135.   {
  136.      ucDisplayUpdate=0;
  137.          switch(ucWd)     //本程序最核心的变量ucWd
  138.          {
  139.            case 1:  //窗口1,代表刚上电或者复位后的状态
  140.                       //红棋分解出分
  141.                        ucDigShowTemp=uiRedTotal/60;
  142.             ucDigShow8=ucDigShowTemp/10;
  143.             ucDigShow7=ucDigShowTemp%10;

  144.                        //红棋分解出秒
  145.                     ucDigShowTemp=uiRedTotal%60;
  146.             ucDigShow6=ucDigShowTemp/10;
  147.             ucDigShow5=ucDigShowTemp%10;
  148.                         ucDigDot7=1;  //数码管7的小数点显示

  149.                       //黑棋分解出分
  150.                        ucDigShowTemp=uiBlackTotal/60;
  151.             ucDigShow4=ucDigShowTemp/10;
  152.             ucDigShow3=ucDigShowTemp%10;

  153.                        //黑棋分解出秒
  154.                     ucDigShowTemp=uiBlackTotal%60;
  155.             ucDigShow2=ucDigShowTemp/10;
  156.             ucDigShow1=ucDigShowTemp%10;
  157.                         ucDigDot3=1;  //数码管3的小数点显示

  158.             led_dr=1;  //计时器处于停止状态,LED亮

  159.                 break;
  160.            case 2:  //窗口2,代表黑棋正在运行中的状态

  161.                       //红棋全部不显示
  162.             ucDigShow8=10;
  163.             ucDigShow7=10;
  164.             ucDigShow6=10;
  165.             ucDigShow5=10;
  166.                         ucDigDot7=0;  //数码管7的小数点不显示

  167.                       //黑棋分解出分
  168.                        ucDigShowTemp=uiBlackTotal/60;
  169.             ucDigShow4=ucDigShowTemp/10;
  170.             ucDigShow3=ucDigShowTemp%10;

  171.                        //黑棋分解出秒
  172.                     ucDigShowTemp=uiBlackTotal%60;
  173.             ucDigShow2=ucDigShowTemp/10;
  174.             ucDigShow1=ucDigShowTemp%10;
  175.                         ucDigDot3=1;  //数码管3的小数点显示

  176.             led_dr=0;  //计时器处于计时状态,LED灭

  177.                 break;

  178.            case 3:  //窗口3,代表黑棋在中途暂停的状态

  179.                       //红棋全部不显示
  180.             ucDigShow8=10;
  181.             ucDigShow7=10;
  182.             ucDigShow6=10;
  183.             ucDigShow5=10;
  184.                         ucDigDot7=0;  //数码管7的小数点不显示

  185.                       //黑棋分解出分
  186.                        ucDigShowTemp=uiBlackTotal/60;
  187.             ucDigShow4=ucDigShowTemp/10;
  188.             ucDigShow3=ucDigShowTemp%10;

  189.                        //黑棋分解出秒
  190.                     ucDigShowTemp=uiBlackTotal%60;
  191.             ucDigShow2=ucDigShowTemp/10;
  192.             ucDigShow1=ucDigShowTemp%10;
  193.                         ucDigDot3=1;  //数码管3的小数点显示


  194.             led_dr=1;  //计时器处于暂停状态,LED亮

  195.                 break;
  196.            case 4:  //窗口4,代表红棋正在运行中的状态
  197.                       //红棋分解出分
  198.                        ucDigShowTemp=uiRedTotal/60;
  199.             ucDigShow8=ucDigShowTemp/10;
  200.             ucDigShow7=ucDigShowTemp%10;

  201.                        //红棋分解出秒
  202.                     ucDigShowTemp=uiRedTotal%60;
  203.             ucDigShow6=ucDigShowTemp/10;
  204.             ucDigShow5=ucDigShowTemp%10;
  205.                         ucDigDot7=1;  //数码管7的小数点显示


  206.                       //黑棋全部不显示
  207.             ucDigShow4=10;
  208.             ucDigShow3=10;
  209.             ucDigShow2=10;
  210.             ucDigShow1=10;
  211.                         ucDigDot3=0;  //数码管3的小数点不显示

  212.             led_dr=0;  //计时器处于倒计时状态,LED灭

  213.                 break;

  214.            case 5:  //窗口5,代表红棋在中途暂停的状态
  215.                       //红棋分解出分
  216.                        ucDigShowTemp=uiRedTotal/60;
  217.             ucDigShow8=ucDigShowTemp/10;
  218.             ucDigShow7=ucDigShowTemp%10;

  219.                        //红棋分解出秒
  220.                     ucDigShowTemp=uiRedTotal%60;
  221.             ucDigShow6=ucDigShowTemp/10;
  222.             ucDigShow5=ucDigShowTemp%10;
  223.                         ucDigDot7=1;  //数码管7的小数点显示


  224.                       //黑棋全部不显示
  225.             ucDigShow4=10;
  226.             ucDigShow3=10;
  227.             ucDigShow2=10;
  228.             ucDigShow1=10;
  229.                         ucDigDot3=0;  //数码管3的小数点不显示

  230.             led_dr=1;  //计时器处于暂停状态,LED亮

  231.                 break;
  232.          }
  233.   }
  234. }

  235. void display_drive()  //放在定时中断里的数码管驱动函数
  236. {
  237.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  238.    switch(ucDisplayDriveStep)
  239.    {
  240.       case 1:  //显示第1位
  241.            ucDigShowTemp=dig_table[ucDigShow1];
  242.            dig_hc595_drive(ucDigShowTemp,0xfe);
  243.                break;
  244.       case 2:  //显示第2位
  245.            ucDigShowTemp=dig_table[ucDigShow2];
  246.            dig_hc595_drive(ucDigShowTemp,0xfd);
  247.                break;
  248.       case 3:  //显示第3位
  249.            ucDigShowTemp=dig_table[ucDigShow3];
  250.                    if(ucDigDot3==1)
  251.                    {
  252.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  253.                    }
  254.            dig_hc595_drive(ucDigShowTemp,0xfb);
  255.                break;
  256.       case 4:  //显示第4位
  257.            ucDigShowTemp=dig_table[ucDigShow4];
  258.            dig_hc595_drive(ucDigShowTemp,0xf7);
  259.                break;
  260.       case 5:  //显示第5位
  261.            ucDigShowTemp=dig_table[ucDigShow5];
  262.            dig_hc595_drive(ucDigShowTemp,0xef);
  263.                break;
  264.       case 6:  //显示第6位
  265.            ucDigShowTemp=dig_table[ucDigShow6];
  266.            dig_hc595_drive(ucDigShowTemp,0xdf);
  267.                break;
  268.       case 7:  //显示第7位
  269.            ucDigShowTemp=dig_table[ucDigShow7];
  270.                    if(ucDigDot7==1)
  271.                    {
  272.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  273.            }
  274.            dig_hc595_drive(ucDigShowTemp,0xbf);
  275.                break;
  276.       case 8:  //显示第8位
  277.            ucDigShowTemp=dig_table[ucDigShow8];
  278.            dig_hc595_drive(ucDigShowTemp,0x7f);
  279.                break;
  280.    }

  281.    ucDisplayDriveStep++;
  282.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  283.    {
  284.      ucDisplayDriveStep=1;
  285.    }
  286. }


  287. //数码管的74HC595驱动函数
  288. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  289. {
  290.    unsigned char i;
  291.    unsigned char ucTempData;
  292.    dig_hc595_sh_dr=0;
  293.    dig_hc595_st_dr=0;

  294.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  295.    for(i=0;i<8;i++)
  296.    {
  297.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  298.          else dig_hc595_ds_dr=0;

  299. /* 注释二:
  300. *  注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
  301. */
  302.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  303.          delay_short(1);
  304.          dig_hc595_sh_dr=1;
  305.          delay_short(1);

  306.          ucTempData=ucTempData<<1;
  307.    }

  308.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  309.    for(i=0;i<8;i++)
  310.    {
  311.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  312.          else dig_hc595_ds_dr=0;

  313.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  314.          delay_short(1);
  315.          dig_hc595_sh_dr=1;
  316.          delay_short(1);

  317.          ucTempData=ucTempData<<1;
  318.    }

  319.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  320.    delay_short(1);
  321.    dig_hc595_st_dr=1;
  322.    delay_short(1);

  323.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  324.    dig_hc595_st_dr=0;
  325.    dig_hc595_ds_dr=0;

  326. }


  327. //LED灯的74HC595驱动函数
  328. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  329. {
  330.    unsigned char i;
  331.    unsigned char ucTempData;
  332.    hc595_sh_dr=0;
  333.    hc595_st_dr=0;

  334.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  335.    for(i=0;i<8;i++)
  336.    {
  337.          if(ucTempData>=0x80)hc595_ds_dr=1;
  338.          else hc595_ds_dr=0;

  339.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  340.          delay_short(1);
  341.          hc595_sh_dr=1;
  342.          delay_short(1);

  343.          ucTempData=ucTempData<<1;
  344.    }

  345.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  346.    for(i=0;i<8;i++)
  347.    {
  348.          if(ucTempData>=0x80)hc595_ds_dr=1;
  349.          else hc595_ds_dr=0;

  350.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  351.          delay_short(1);
  352.          hc595_sh_dr=1;
  353.          delay_short(1);

  354.          ucTempData=ucTempData<<1;
  355.    }

  356.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  357.    delay_short(1);
  358.    hc595_st_dr=1;
  359.    delay_short(1);

  360.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  361.    hc595_st_dr=0;
  362.    hc595_ds_dr=0;

  363. }


  364. void key_scan()//按键扫描函数 放在定时中断里
  365. {  

  366.   switch(ucKeyStep)
  367.   {
  368.      case 1:   //按键扫描输出第ucRowRecord列低电平
  369.               if(ucRowRecord==1)  //第一列输出低电平
  370.                   {
  371.              key_dr1=0;      
  372.              key_dr2=1;
  373.              key_dr3=1;   
  374.              key_dr4=1;
  375.                   }
  376.               else if(ucRowRecord==2)  //第二列输出低电平
  377.                   {
  378.              key_dr1=1;      
  379.              key_dr2=0;
  380.              key_dr3=1;   
  381.              key_dr4=1;
  382.                   }
  383.               else if(ucRowRecord==3)  //第三列输出低电平
  384.                   {
  385.              key_dr1=1;      
  386.              key_dr2=1;
  387.              key_dr3=0;   
  388.              key_dr4=1;
  389.                   }
  390.               else   //第四列输出低电平
  391.                   {
  392.              key_dr1=1;      
  393.              key_dr2=1;
  394.              key_dr3=1;   
  395.              key_dr4=0;
  396.                   }

  397.           uiKeyTimeCnt=0;  //延时计数器清零
  398.           ucKeyStep++;     //切换到下一个运行步骤
  399.               break;

  400.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  401.           uiKeyTimeCnt++;
  402.                   if(uiKeyTimeCnt>1)
  403.                   {
  404.                      uiKeyTimeCnt=0;
  405.              ucKeyStep++;     //切换到下一个运行步骤
  406.                   }
  407.               break;

  408.      case 3:
  409.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  410.           {  
  411.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  412.              ucKeyLock=0;  //按键自锁标志清零
  413.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  414.    
  415.                          ucRowRecord++;  //输出下一列
  416.                          if(ucRowRecord>4)  
  417.                          {
  418.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  419.                          }

  420.           }
  421.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  422.                   {
  423.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  424.                          {
  425.                             uiKeyTimeCnt++;  //去抖动延时计数器
  426.                                 if(uiKeyTimeCnt>const_key_time)
  427.                                 {
  428.                                    uiKeyTimeCnt=0;
  429.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  430.                        if(ucRowRecord==1)  //第一列输出低电平
  431.                            {
  432.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  433.                            }
  434.                        else if(ucRowRecord==2)  //第二列输出低电平
  435.                            {
  436.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  437.                            }
  438.                        else if(ucRowRecord==3)  //第三列输出低电平
  439.                            {
  440.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  441.                            }
  442.                        else   //第四列输出低电平
  443.                            {
  444.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  445.                            }

  446.                                 }
  447.                         
  448.                          }
  449.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  450.                          {
  451.                             uiKeyTimeCnt++;  //去抖动延时计数器
  452.                                 if(uiKeyTimeCnt>const_key_time)
  453.                                 {
  454.                                    uiKeyTimeCnt=0;
  455.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  456.                        if(ucRowRecord==1)  //第一列输出低电平
  457.                            {
  458.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  459.                            }
  460.                        else if(ucRowRecord==2)  //第二列输出低电平
  461.                            {
  462.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  463.                            }
  464.                        else if(ucRowRecord==3)  //第三列输出低电平
  465.                            {
  466.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  467.                            }
  468.                        else   //第四列输出低电平
  469.                            {
  470.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  471.                            }
  472.                                 }
  473.                         
  474.                          }
  475.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  476.                          {
  477.                             uiKeyTimeCnt++;  //去抖动延时计数器
  478.                                 if(uiKeyTimeCnt>const_key_time)
  479.                                 {
  480.                                    uiKeyTimeCnt=0;
  481.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  482.                        if(ucRowRecord==1)  //第一列输出低电平
  483.                            {
  484.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  485.                            }
  486.                        else if(ucRowRecord==2)  //第二列输出低电平
  487.                            {
  488.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  489.                            }
  490.                        else if(ucRowRecord==3)  //第三列输出低电平
  491.                            {
  492.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  493.                            }
  494.                        else   //第四列输出低电平
  495.                            {
  496.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  497.                            }
  498.                                 }
  499.                         
  500.                          }
  501.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  502.                          {
  503.                             uiKeyTimeCnt++;  //去抖动延时计数器
  504.                                 if(uiKeyTimeCnt>const_key_time)
  505.                                 {
  506.                                    uiKeyTimeCnt=0;
  507.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  508.                        if(ucRowRecord==1)  //第一列输出低电平
  509.                            {
  510.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  511.                            }
  512.                        else if(ucRowRecord==2)  //第二列输出低电平
  513.                            {
  514.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  515.                            }
  516.                        else if(ucRowRecord==3)  //第三列输出低电平
  517.                            {
  518.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  519.                            }
  520.                        else   //第四列输出低电平
  521.                            {
  522.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  523.                            }
  524.                                 }
  525.                         
  526.                          }
  527.                   
  528.                   }
  529.               break;

  530.   }


  531. }

  532. /* 注释三:
  533. *  按键服务程序操作的精髓在于根据当前系统处于什么窗口下就执行什么操作。
  534. *  紧紧围绕着不同的窗口ucWd来执行不同的操作。
  535. */
  536. void key_service() //第三区 放在定时中断里的按键服务应用程序
  537. {
  538.   switch(ucKeySec) //按键服务状态切换
  539.   {
  540.     case 1:// 1号键 对应朱兆祺学习板的S1键  红棋加分 按键
  541.               switch(ucWd)  //本程序最核心的变量ucWd
  542.               {
  543.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  544.                               uiRedTotal=uiRedTotal+60;  //加红棋分的时间,此处60秒代表一分
  545.                                   if(uiRedTotal>5940)
  546.                                   {
  547.                                      uiRedTotal=5940;
  548.                                   }
  549.                   uiRedTotal=uiRedTotal-(uiRedTotal%60);  //去秒取整分

  550.                                   ucDisplayUpdate=1;  //更新显示
  551.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  552.                       break;

  553.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  554.                       break;

  555.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  556.                       break;

  557.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  558.                       break;

  559.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  560.                       break;

  561.           }
  562.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  563.           break;        

  564.     case 2:// 2号键 对应朱兆祺学习板的S2键  红棋减分 按键
  565.               switch(ucWd)  //本程序最核心的变量ucWd
  566.               {
  567.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  568.                               if(uiRedTotal>=60)
  569.                                   {
  570.                                  uiRedTotal=uiRedTotal-60;  //减红棋分的时间,此处60秒代表一分
  571.                                   }
  572.                   uiRedTotal=uiRedTotal-(uiRedTotal%60);  //去秒取整分

  573.                                   ucDisplayUpdate=1;  //更新显示
  574.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  575.                       break;

  576.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  577.                       break;

  578.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  579.                       break;

  580.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  581.                       break;

  582.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  583.                       break;

  584.           }
  585.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  586.           break;     

  587.     case 3:// 3号键 对应朱兆祺学习板的S3键  黑棋加分 按键
  588.               switch(ucWd)  //本程序最核心的变量ucWd
  589.               {
  590.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  591.                               uiBlackTotal=uiBlackTotal+60;  //加黑棋分的时间,此处60秒代表一分
  592.                                   if(uiBlackTotal>5940)
  593.                                   {
  594.                                      uiBlackTotal=5940;
  595.                                   }
  596.                   uiBlackTotal=uiBlackTotal-(uiBlackTotal%60);  //去秒取整分

  597.                                   ucDisplayUpdate=1;  //更新显示
  598.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  599.                       break;

  600.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  601.                       break;

  602.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  603.                       break;

  604.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  605.                       break;

  606.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  607.                       break;

  608.           }
  609.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  610.           break;         

  611.     case 4:// 4号键 对应朱兆祺学习板的S4键  黑棋减分 按键
  612.               switch(ucWd)  //本程序最核心的变量ucWd
  613.               {
  614.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  615.                               if(uiBlackTotal>=60)
  616.                                   {
  617.                                  uiBlackTotal=uiBlackTotal-60;  //减黑棋分的时间,此处60秒代表一分
  618.                                   }
  619.                   uiBlackTotal=uiBlackTotal-(uiBlackTotal%60);  //去秒取整分

  620.                                   ucDisplayUpdate=1;  //更新显示
  621.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  622.                       break;

  623.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  624.                       break;

  625.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  626.                       break;

  627.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  628.                       break;

  629.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  630.                       break;

  631.           }
  632.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  633.           break;   

  634.     case 5:// 5号键 对应朱兆祺学习板的S5键


  635.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  636.           break;   

  637.     case 6:// 6号键 对应朱兆祺学习板的S6键  中途暂停和启动按键
  638.               switch(ucWd)  //本程序最核心的变量ucWd
  639.               {
  640.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          

  641.                       break;

  642.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  643.                   ucRedFlag=0;    //暂停计时
  644.                   ucBlackFlag=0;//暂停计时
  645.                                   ucWd=3; //切换到黑棋中途暂停的状态

  646.                                   ucDisplayUpdate=1;  //更新显示
  647.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  648.                       break;

  649.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  650.                   ucRedFlag=0;   //红棋暂停计时
  651.                   ucBlackFlag=1; //黑棋继续计时
  652.                                   ucWd=2;       //切换到黑棋正在运行中的状态

  653.                                   ucDisplayUpdate=1;  //更新显示
  654.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  655.                       break;

  656.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  657.                   ucRedFlag=0;    //暂停计时
  658.                   ucBlackFlag=0;//暂停计时
  659.                                   ucWd=5;       //切换到红棋中途暂停的状态

  660.                                   ucDisplayUpdate=1;  //更新显示
  661.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  662.                       break;

  663.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  664.                   ucRedFlag=1;   //红棋继续计时
  665.                   ucBlackFlag=0; //黑棋暂停计时
  666.                                   ucWd=4;       //切换到红棋正在运行中的状态

  667.                                   ucDisplayUpdate=1;  //更新显示
  668.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。

  669.                       break;

  670.           }
  671.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  672.           break;   

  673.     case 7:// 7号键 对应朱兆祺学习板的S7键  在第一个窗口下,把计时器的值恢复为开机时的默认值20分钟
  674.               switch(ucWd)  //本程序最核心的变量ucWd
  675.               {
  676.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  677.                   uiRedTotal=1200;    //红棋的总时间
  678.                   uiBlackTotal=1200;  //黑棋的总时间

  679.                                   ucDisplayUpdate=1;  //更新显示
  680.                                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  681.                       break;

  682.                  case 2:  //窗口2,代表黑棋正在运行中的状态       

  683.                       break;

  684.                  case 3:  //窗口3,代表黑棋在中途暂停的状态

  685.                       break;

  686.                  case 4:  //窗口4,代表红棋正在运行中的状态          

  687.                       break;

  688.                  case 5:  //窗口5,代表红棋在中途暂停的状态

  689.                       break;

  690.           }
  691.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  692.           break;   
  693.     case 8:// 8号键 对应朱兆祺学习板的S8键

  694.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  695.           break;   
  696.     case 9:// 9号键 对应朱兆祺学习板的S9键

  697.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  698.           break;   
  699.     case 10:// 10号键 对应朱兆祺学习板的S10键

  700.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  701.           break;   
  702.     case 11:// 11号键 对应朱兆祺学习板的S11键

  703.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  704.           break;   
  705.     case 12:// 12号键 对应朱兆祺学习板的S12键

  706.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  707.           break;   
  708.     case 13:// 13号键 对应朱兆祺学习板的S13键  红棋按下
  709.               switch(ucWd)  //本程序最核心的变量ucWd
  710.               {
  711.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  712.                   ucRedFlag=0;    //红棋暂停计时
  713.                   ucBlackFlag=1;  //黑棋继续计时
  714.                                   ucWd=2; //切换到黑棋正在运行中的状态

  715.                                   ucDisplayUpdate=1;  //更新显示
  716.                       break;

  717.                  case 2:  //窗口2,代表黑棋正在运行中的状态       

  718.                       break;

  719.                  case 3:  //窗口3,代表黑棋在中途暂停的状态

  720.                       break;

  721.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  722.                   ucRedFlag=0;    //红棋暂停计时
  723.                   ucBlackFlag=1;  //黑棋继续计时
  724.                                   ucWd=2; //切换到黑棋正在运行中的状态

  725.                                   ucDisplayUpdate=1;  //更新显示
  726.                       break;

  727.                  case 5:  //窗口5,代表红棋在中途暂停的状态

  728.                       break;

  729.           }

  730.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  731.           break;   
  732.     case 14:// 14号键 对应朱兆祺学习板的S14键

  733.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  734.           break;   
  735.     case 15:// 15号键 对应朱兆祺学习板的S15键

  736.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  737.           break;   

  738.     case 16:// 16号键 对应朱兆祺学习板的S16键    黑棋按下
  739.               switch(ucWd)  //本程序最核心的变量ucWd
  740.               {
  741.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  742.                   ucRedFlag=1;    //红棋继续计时
  743.                   ucBlackFlag=0;  //黑棋暂停计时
  744.                                   ucWd=4; //切换到红棋正在运行中的状态

  745.                                   ucDisplayUpdate=1;  //更新显示
  746.                       break;

  747.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  748.                   ucRedFlag=1;    //红棋继续计时
  749.                   ucBlackFlag=0;  //黑棋暂停计时
  750.                                   ucWd=4; //切换到红棋正在运行中的状态

  751.                                   ucDisplayUpdate=1;  //更新显示
  752.                       break;

  753.                  case 3:  //窗口3,代表黑棋在中途暂停的状态

  754.                       break;

  755.                  case 4:  //窗口4,代表红棋正在运行中的状态          

  756.                       break;

  757.                  case 5:  //窗口5,代表红棋在中途暂停的状态

  758.                       break;

  759.           }
  760.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  761.           break;   
  762.   }               
  763. }



  764. void T0_time() interrupt 1
  765. {
  766.   TF0=0;  //清除中断标志
  767.   TR0=0; //关中断
  768.   key_scan(); //放在定时中断里的按键扫描函数
  769.   time_service();  //放在定时中断里的时间应用程序

  770.   if(uiVoiceCnt!=0)
  771.   {
  772.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  773.          beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  774.   }
  775.   else
  776.   {
  777.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  778.            beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  779.   }

  780.   display_drive();  //放在定时中断里的数码管驱动函数

  781. /* 注释四:
  782. *  注意,此处的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的2000改成了500。
  783. */
  784.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  785.   TL0=0x0b;
  786.   TR0=1;  //开中断
  787. }

  788. void delay_short(unsigned int uiDelayShort)
  789. {
  790.    unsigned int i;  
  791.    for(i=0;i<uiDelayShort;i++)
  792.    {
  793.      ;   //一个分号相当于执行一条空语句
  794.    }
  795. }


  796. void delay_long(unsigned int uiDelayLong)
  797. {
  798.    unsigned int i;
  799.    unsigned int j;
  800.    for(i=0;i<uiDelayLong;i++)
  801.    {
  802.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  803.           {
  804.              ; //一个分号相当于执行一条空语句
  805.           }
  806.    }
  807. }


  808. void initial_myself()  //第一区 初始化单片机
  809. {

  810.   led_dr=1;
  811.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  812.   hc595_drive(0x00,0x00);
  813.   TMOD=0x01;  //设置定时器0为工作方式1

  814.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  815.   TL0=0x0b;
  816. }
  817. void initial_peripheral() //第二区 初始化外围
  818. {


  819.   EA=1;     //开总中断
  820.   ET0=1;    //允许定时中断
  821.   TR0=1;    //启动定时中断



  822. }
复制代码


总结陈词:
这节讲了象棋比赛专用计时器的项目程序。为了继续加深读者理解按键和显示是如何有规律关联起来的,下节会继续讲一个相关的小项目程序。欲知详情,请听下回分解-----带数码管显示的加法简易计算器。

(未完待续,下节更精彩,不要走开哦)

乐于分享,勇于质疑!
40#
 楼主| 发表于 2014-3-27 12:54:10 | 显示全部楼层
第三十六节:带数码管显示的加法简易计算器。

开场白:
    这一节要做一个简单的计算器。这个计算器不带小数点,只能进行不超过8位数据的加法运算,它麻雀虽小但是五脏俱全,它能清晰地勾勒出商业计算器的程序框架和思路。读者只要看懂本节程序框架的规律,以后自己想做一个复杂一点的计算器应该是没问题的。复杂的计算器在算法上要用数组进行特殊处理,不能简单地直接用C语言的+,-,*,/运算符,这方面的内容我会在以后的章节中跟大家分享。
这一节要教会大家两个知识点:
第一个:数字按键的输入和十进制数值的移位方法。
第二个:继续加深理解按键与数码管的关联程序框架。

具体内容,请看源代码讲解。

(1)硬件平台:
基于坚鸿51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。加号键对应S13,等于号键对应S14,清除复位按键对应S16。其它按键不用。

(2)实现功能:
常用的加法计算器功能。有连加功能。
本程序有2个窗口。
第1个窗口:原始数据和运算结果窗口。  比如加法运算中的被加数
第2个窗口:第二个参与运行的数据窗口。比如加法运算中的加数

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_voice_long   900   //蜂鸣器长叫的持续时间

  4. #define const_key_time  10    //按键去抖动延时的时间

  5. #define const_1s     422   //产生一秒钟的时间基准

  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);
  10. void T0_time();  //定时中断函数
  11. void key_service();
  12. void key_scan(); //按键扫描函数 放在定时中断里

  13. void number_key_input(unsigned long ucWhichKey);  //由于数字按键的代码相似度高,因此封装在这个函数里

  14. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
  15. void display_drive();  //放在定时中断里的数码管驱动函数
  16. void display_service();  


  17. sbit key_sr1=P0^0; //第一行输入
  18. sbit key_sr2=P0^1; //第二行输入
  19. sbit key_sr3=P0^2; //第三行输入
  20. sbit key_sr4=P0^3; //第四行输入

  21. sbit key_dr1=P0^4; //第一列输出
  22. sbit key_dr2=P0^5; //第二列输出
  23. sbit key_dr3=P0^6; //第三列输出
  24. sbit key_dr4=P0^7; //第四列输出

  25. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口


  26. sbit led_dr=P3^5; //LED指示灯


  27. sbit dig_hc595_sh_dr=P2^0;     //数码管 的74HC595程序
  28. sbit dig_hc595_st_dr=P2^1;  
  29. sbit dig_hc595_ds_dr=P2^2;  

  30. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  31. sbit hc595_st_dr=P2^4;  
  32. sbit hc595_ds_dr=P2^5;  


  33. unsigned char ucKeyStep=1;  //按键扫描步骤变量

  34. unsigned char ucKeySec=0;   //被触发的按键编号
  35. unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器
  36. unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

  37. unsigned char ucRowRecord=1; //记录当前扫描到第几列了

  38. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  39. unsigned char ucDigShow8=0;  //第8位数码管要显示的内容
  40. unsigned char ucDigShow7=0;  //第7位数码管要显示的内容
  41. unsigned char ucDigShow6=0;  //第6位数码管要显示的内容
  42. unsigned char ucDigShow5=0;  //第5位数码管要显示的内容
  43. unsigned char ucDigShow4=0;  //第4位数码管要显示的内容
  44. unsigned char ucDigShow3=0;  //第3位数码管要显示的内容
  45. unsigned char ucDigShow2=0;  //第2位数码管要显示的内容
  46. unsigned char ucDigShow1=0;  //第1位数码管要显示的内容


  47. unsigned char ucDigShowTemp=0; //临时中间变量

  48. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  49. unsigned char ucDisplayUpdate=1; //更新显示标志

  50. unsigned long ulSource=0;  //原始数据    比如在加法运算中的被加数
  51. unsigned long ulOther=0; //另外一个参与运算的数据  比如在加法运算中的加数
  52. unsigned long ulResult=0; //运算结果
  53. unsigned char ucOperator=0; //运行符号。0代表当前没有选择运行符号。1代表当前的运算符是加法。

  54. /* 注释一:
  55. *  ucWd变量是本程序最核心的变量,代表数码管显示哪一个窗口
  56. *  本程序只有两个窗口,他们分别是:
  57. *  第一个窗口:原始数据和运算结果窗口。  比如加法运算中的被加数
  58. *  第二个窗口:第二个参与运行的数据窗口。比如加法运算中的加数
  59. */
  60. unsigned char ucWd=1;

  61. code unsigned char dig_table[]=
  62. {
  63. 0x3f,  //0       序号0
  64. 0x06,  //1       序号1
  65. 0x5b,  //2       序号2
  66. 0x4f,  //3       序号3
  67. 0x66,  //4       序号4
  68. 0x6d,  //5       序号5
  69. 0x7d,  //6       序号6
  70. 0x07,  //7       序号7
  71. 0x7f,  //8       序号8
  72. 0x6f,  //9       序号9
  73. 0x00,  //不显示  序号10
  74. };

  75. void main()
  76.   {
  77.    initial_myself();  
  78.    delay_long(100);   
  79.    initial_peripheral();
  80.    while(1)  
  81.    {
  82.        key_service();
  83.        display_service();  
  84.    }

  85. }



  86. void display_service()  //放在定时中断里的显示应用程序
  87. {
  88.   if(ucDisplayUpdate==1)  //有数据更新显示
  89.   {
  90.      ucDisplayUpdate=0;
  91.          switch(ucWd)     //本程序最核心的变量ucWd
  92.          {
  93.            case 1:  //窗口1  原始数据和运算结果窗口
  94.                 if(ulSource>=10000000)
  95.                                 {
  96.                                    ucDigShow8=ulSource/10000000;
  97.                                 }
  98.                                 else
  99.                                 {
  100.                                ucDigShow8=10;//数据显示空
  101.                                 }


  102.                 if(ulSource>=1000000)
  103.                                 {
  104.                                    ucDigShow7=ulSource%10000000/1000000;
  105.                                 }
  106.                                 else
  107.                                 {
  108.                                ucDigShow7=10;//数据显示空
  109.                                 }


  110.                 if(ulSource>=100000)
  111.                                 {
  112.                                    ucDigShow6=ulSource%1000000/100000;
  113.                                 }
  114.                                 else
  115.                                 {
  116.                                ucDigShow6=10;//数据显示空
  117.                                 }

  118.                 if(ulSource>=10000)
  119.                                 {
  120.                                    ucDigShow5=ulSource%100000/10000;
  121.                                 }
  122.                                 else
  123.                                 {
  124.                                ucDigShow5=10;//数据显示空
  125.                                 }

  126.                 if(ulSource>=1000)
  127.                                 {
  128.                                    ucDigShow4=ulSource%10000/1000;
  129.                                 }
  130.                                 else
  131.                                 {
  132.                                ucDigShow4=10;//数据显示空
  133.                                 }

  134.                 if(ulSource>=100)
  135.                                 {
  136.                                    ucDigShow3=ulSource%1000/100;
  137.                                 }
  138.                                 else
  139.                                 {
  140.                                ucDigShow3=10;//数据显示空
  141.                                 }

  142.                 if(ulSource>=10)
  143.                                 {
  144.                                    ucDigShow2=ulSource%100/10;
  145.                                 }
  146.                                 else
  147.                                 {
  148.                                ucDigShow2=10;//数据显示空
  149.                                 }

  150.                                 ucDigShow1=ulSource%10;

  151.                 break;
  152.            case 2:  //窗口2  第二个参与运算数据的窗口  比如加法运算中的加数
  153.                 if(ulOther>=10000000)
  154.                                 {
  155.                                    ucDigShow8=ulOther/10000000;
  156.                                 }
  157.                                 else
  158.                                 {
  159.                                ucDigShow8=10;//数据显示空
  160.                                 }


  161.                 if(ulOther>=1000000)
  162.                                 {
  163.                                    ucDigShow7=ulOther%10000000/1000000;
  164.                                 }
  165.                                 else
  166.                                 {
  167.                                ucDigShow7=10;//数据显示空
  168.                                 }


  169.                 if(ulOther>=100000)
  170.                                 {
  171.                                    ucDigShow6=ulOther%1000000/100000;
  172.                                 }
  173.                                 else
  174.                                 {
  175.                                ucDigShow6=10;//数据显示空
  176.                                 }

  177.                 if(ulOther>=10000)
  178.                                 {
  179.                                    ucDigShow5=ulOther%100000/10000;
  180.                                 }
  181.                                 else
  182.                                 {
  183.                                ucDigShow5=10;//数据显示空
  184.                                 }

  185.                 if(ulOther>=1000)
  186.                                 {
  187.                                    ucDigShow4=ulOther%10000/1000;
  188.                                 }
  189.                                 else
  190.                                 {
  191.                                ucDigShow4=10;//数据显示空
  192.                                 }

  193.                 if(ulOther>=100)
  194.                                 {
  195.                                    ucDigShow3=ulOther%1000/100;
  196.                                 }
  197.                                 else
  198.                                 {
  199.                                ucDigShow3=10;//数据显示空
  200.                                 }

  201.                 if(ulOther>=10)
  202.                                 {
  203.                                    ucDigShow2=ulOther%100/10;
  204.                                 }
  205.                                 else
  206.                                 {
  207.                                ucDigShow2=10;//数据显示空
  208.                                 }

  209.                                 ucDigShow1=ulOther%10;

  210.                 break;
  211.          }
  212.   }
  213. }

  214. void display_drive()  //放在定时中断里的数码管驱动函数
  215. {
  216.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  217.    switch(ucDisplayDriveStep)
  218.    {
  219.       case 1:  //显示第1位
  220.            ucDigShowTemp=dig_table[ucDigShow1];
  221.            dig_hc595_drive(ucDigShowTemp,0xfe);
  222.                break;
  223.       case 2:  //显示第2位
  224.            ucDigShowTemp=dig_table[ucDigShow2];
  225.            dig_hc595_drive(ucDigShowTemp,0xfd);
  226.                break;
  227.       case 3:  //显示第3位
  228.            ucDigShowTemp=dig_table[ucDigShow3];
  229.            dig_hc595_drive(ucDigShowTemp,0xfb);
  230.                break;
  231.       case 4:  //显示第4位
  232.            ucDigShowTemp=dig_table[ucDigShow4];
  233.            dig_hc595_drive(ucDigShowTemp,0xf7);
  234.                break;
  235.       case 5:  //显示第5位
  236.            ucDigShowTemp=dig_table[ucDigShow5];
  237.            dig_hc595_drive(ucDigShowTemp,0xef);
  238.                break;
  239.       case 6:  //显示第6位
  240.            ucDigShowTemp=dig_table[ucDigShow6];
  241.            dig_hc595_drive(ucDigShowTemp,0xdf);
  242.                break;
  243.       case 7:  //显示第7位
  244.            ucDigShowTemp=dig_table[ucDigShow7];
  245.            dig_hc595_drive(ucDigShowTemp,0xbf);
  246.                break;
  247.       case 8:  //显示第8位
  248.            ucDigShowTemp=dig_table[ucDigShow8];
  249.            dig_hc595_drive(ucDigShowTemp,0x7f);
  250.                break;
  251.    }

  252.    ucDisplayDriveStep++;
  253.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  254.    {
  255.      ucDisplayDriveStep=1;
  256.    }
  257. }


  258. //数码管的74HC595驱动函数
  259. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  260. {
  261.    unsigned char i;
  262.    unsigned char ucTempData;
  263.    dig_hc595_sh_dr=0;
  264.    dig_hc595_st_dr=0;

  265.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  266.    for(i=0;i<8;i++)
  267.    {
  268.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  269.          else dig_hc595_ds_dr=0;

  270. /* 注释二:
  271. *  注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
  272. */
  273.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  274.          delay_short(1);
  275.          dig_hc595_sh_dr=1;
  276.          delay_short(1);

  277.          ucTempData=ucTempData<<1;
  278.    }

  279.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  280.    for(i=0;i<8;i++)
  281.    {
  282.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  283.          else dig_hc595_ds_dr=0;

  284.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  285.          delay_short(1);
  286.          dig_hc595_sh_dr=1;
  287.          delay_short(1);

  288.          ucTempData=ucTempData<<1;
  289.    }

  290.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  291.    delay_short(1);
  292.    dig_hc595_st_dr=1;
  293.    delay_short(1);

  294.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  295.    dig_hc595_st_dr=0;
  296.    dig_hc595_ds_dr=0;

  297. }


  298. //LED灯的74HC595驱动函数
  299. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  300. {
  301.    unsigned char i;
  302.    unsigned char ucTempData;
  303.    hc595_sh_dr=0;
  304.    hc595_st_dr=0;

  305.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  306.    for(i=0;i<8;i++)
  307.    {
  308.          if(ucTempData>=0x80)hc595_ds_dr=1;
  309.          else hc595_ds_dr=0;

  310.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  311.          delay_short(1);
  312.          hc595_sh_dr=1;
  313.          delay_short(1);

  314.          ucTempData=ucTempData<<1;
  315.    }

  316.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  317.    for(i=0;i<8;i++)
  318.    {
  319.          if(ucTempData>=0x80)hc595_ds_dr=1;
  320.          else hc595_ds_dr=0;

  321.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  322.          delay_short(1);
  323.          hc595_sh_dr=1;
  324.          delay_short(1);

  325.          ucTempData=ucTempData<<1;
  326.    }

  327.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  328.    delay_short(1);
  329.    hc595_st_dr=1;
  330.    delay_short(1);

  331.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  332.    hc595_st_dr=0;
  333.    hc595_ds_dr=0;

  334. }


  335. void key_scan()//按键扫描函数 放在定时中断里
  336. {  

  337.   switch(ucKeyStep)
  338.   {
  339.      case 1:   //按键扫描输出第ucRowRecord列低电平
  340.               if(ucRowRecord==1)  //第一列输出低电平
  341.                   {
  342.              key_dr1=0;      
  343.              key_dr2=1;
  344.              key_dr3=1;   
  345.              key_dr4=1;
  346.                   }
  347.               else if(ucRowRecord==2)  //第二列输出低电平
  348.                   {
  349.              key_dr1=1;      
  350.              key_dr2=0;
  351.              key_dr3=1;   
  352.              key_dr4=1;
  353.                   }
  354.               else if(ucRowRecord==3)  //第三列输出低电平
  355.                   {
  356.              key_dr1=1;      
  357.              key_dr2=1;
  358.              key_dr3=0;   
  359.              key_dr4=1;
  360.                   }
  361.               else   //第四列输出低电平
  362.                   {
  363.              key_dr1=1;      
  364.              key_dr2=1;
  365.              key_dr3=1;   
  366.              key_dr4=0;
  367.                   }

  368.           uiKeyTimeCnt=0;  //延时计数器清零
  369.           ucKeyStep++;     //切换到下一个运行步骤
  370.               break;

  371.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  372.           uiKeyTimeCnt++;
  373.                   if(uiKeyTimeCnt>1)
  374.                   {
  375.                      uiKeyTimeCnt=0;
  376.              ucKeyStep++;     //切换到下一个运行步骤
  377.                   }
  378.               break;

  379.      case 3:
  380.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  381.           {  
  382.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  383.              ucKeyLock=0;  //按键自锁标志清零
  384.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  385.    
  386.                          ucRowRecord++;  //输出下一列
  387.                          if(ucRowRecord>4)  
  388.                          {
  389.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  390.                          }

  391.           }
  392.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  393.                   {
  394.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  395.                          {
  396.                             uiKeyTimeCnt++;  //去抖动延时计数器
  397.                                 if(uiKeyTimeCnt>const_key_time)
  398.                                 {
  399.                                    uiKeyTimeCnt=0;
  400.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  401.                        if(ucRowRecord==1)  //第一列输出低电平
  402.                            {
  403.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  404.                            }
  405.                        else if(ucRowRecord==2)  //第二列输出低电平
  406.                            {
  407.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  408.                            }
  409.                        else if(ucRowRecord==3)  //第三列输出低电平
  410.                            {
  411.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  412.                            }
  413.                        else   //第四列输出低电平
  414.                            {
  415.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  416.                            }

  417.                                 }
  418.                         
  419.                          }
  420.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  421.                          {
  422.                             uiKeyTimeCnt++;  //去抖动延时计数器
  423.                                 if(uiKeyTimeCnt>const_key_time)
  424.                                 {
  425.                                    uiKeyTimeCnt=0;
  426.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  427.                        if(ucRowRecord==1)  //第一列输出低电平
  428.                            {
  429.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  430.                            }
  431.                        else if(ucRowRecord==2)  //第二列输出低电平
  432.                            {
  433.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  434.                            }
  435.                        else if(ucRowRecord==3)  //第三列输出低电平
  436.                            {
  437.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  438.                            }
  439.                        else   //第四列输出低电平
  440.                            {
  441.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  442.                            }
  443.                                 }
  444.                         
  445.                          }
  446.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  447.                          {
  448.                             uiKeyTimeCnt++;  //去抖动延时计数器
  449.                                 if(uiKeyTimeCnt>const_key_time)
  450.                                 {
  451.                                    uiKeyTimeCnt=0;
  452.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  453.                        if(ucRowRecord==1)  //第一列输出低电平
  454.                            {
  455.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  456.                            }
  457.                        else if(ucRowRecord==2)  //第二列输出低电平
  458.                            {
  459.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  460.                            }
  461.                        else if(ucRowRecord==3)  //第三列输出低电平
  462.                            {
  463.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  464.                            }
  465.                        else   //第四列输出低电平
  466.                            {
  467.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  468.                            }
  469.                                 }
  470.                         
  471.                          }
  472.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  473.                          {
  474.                             uiKeyTimeCnt++;  //去抖动延时计数器
  475.                                 if(uiKeyTimeCnt>const_key_time)
  476.                                 {
  477.                                    uiKeyTimeCnt=0;
  478.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  479.                        if(ucRowRecord==1)  //第一列输出低电平
  480.                            {
  481.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  482.                            }
  483.                        else if(ucRowRecord==2)  //第二列输出低电平
  484.                            {
  485.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  486.                            }
  487.                        else if(ucRowRecord==3)  //第三列输出低电平
  488.                            {
  489.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  490.                            }
  491.                        else   //第四列输出低电平
  492.                            {
  493.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  494.                            }
  495.                                 }
  496.                         
  497.                          }
  498.                   
  499.                   }
  500.               break;

  501.   }


  502. }

  503. /* 注释三:
  504. *  按键服务程序操作的精髓在于根据当前系统处于什么窗口下,在此窗口下的运算符处于
  505. *  什么状态,然后紧紧围绕着不同的窗口ucWd,不同的ucOperator来执行不同的操作。
  506. */
  507. void key_service() //第三区 按键服务的应用程序
  508. {
  509.   switch(ucKeySec) //按键服务状态切换
  510.   {
  511.     case 1:// 1号键 对应朱兆祺学习板的S1键
  512.           number_key_input(1);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  513.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  514.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  515.           break;        
  516.     case 2:// 2号键 对应朱兆祺学习板的S2键
  517.           number_key_input(2);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  518.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  519.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  520.           break;     
  521.     case 3:// 3号键 对应朱兆祺学习板的S3键
  522.           number_key_input(3);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  523.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  524.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  525.           break;         
  526.     case 4:// 4号键 对应朱兆祺学习板的S4键
  527.           number_key_input(4);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  528.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  529.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  530.           break;   
  531.     case 5:// 5号键 对应朱兆祺学习板的S5键
  532.           number_key_input(5);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  533.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  534.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  535.           break;   
  536.     case 6:// 6号键 对应朱兆祺学习板的S6键
  537.           number_key_input(6);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  538.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  539.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  540.           break;   
  541.     case 7:// 7号键 对应朱兆祺学习板的S7键
  542.           number_key_input(7);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  543.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  544.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  545.           break;   
  546.     case 8:// 8号键 对应朱兆祺学习板的S8键
  547.           number_key_input(8);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  548.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  549.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  550.           break;   
  551.     case 9:// 9号键 对应朱兆祺学习板的S9键
  552.           number_key_input(9);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  553.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  554.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  555.           break;   
  556.     case 10:// 把这个按键专门用来输入数字0    对应朱兆祺学习板的S10键
  557.           number_key_input(0);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  558.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  559.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  560.           break;   
  561.     case 11:// 11号键 对应朱兆祺学习板的S11键

  562.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  563.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  564.           break;   
  565.     case 12:// 12号键 对应朱兆祺学习板的S12键

  566.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  567.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  568.           break;   
  569.     case 13:// 13号键 加号按键  对应朱兆祺学习板的S13键
  570.           switch(ucWd)
  571.                  {
  572.                   case 1:   //在原始数据和运算结果的窗口下
  573.                    ucOperator=1; //加法
  574.                    ulOther=ulSource;  //第二个运算数默认等于原始数
  575.                    ucDisplayUpdate=1;  //刷新显示窗口
  576.                                  break;
  577.                   case 2:   //在第二个参与运算数据的窗口下
  578.                    ulResult=ulSource+ulOther;//连加
  579.                    ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
  580.                    ucWd=1;        //切换到第一个窗口
  581.                    ucDisplayUpdate=1;  //刷新显示窗口
  582.                            break;
  583.           }

  584.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  585.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  586.           break;   
  587.     case 14:// 14号键 等于号按键  对应朱兆祺学习板的S14键  
  588.           switch(ucWd)
  589.                  {
  590.                   case 1:   //在原始数据和运算结果的窗口下
  591.                            switch(ucOperator)  //根据不同的运算符号进行不同的操作
  592.                            {
  593.                                    case 0:  //无运算符号

  594.                                         break;
  595.                                    case 1:  //加法
  596.                             ulResult=ulSource+ulOther;//连加
  597.                             ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
  598.                             ucDisplayUpdate=1;  //刷新显示窗口
  599.                                         break;
  600.                                    case 2:  //减法  本程序没有减法功能,如果读者想增加减法程序,可以按键这个框架添加下去

  601.                                         break;
  602.                        
  603.                            }
  604.                                  break;
  605.                   case 2:   //在第二个参与运算数据的窗口下
  606.                            switch(ucOperator)  //根据不同的运算符号进行不同的操作
  607.                            {
  608.                                    case 1:  //加法
  609.                             ulResult=ulSource+ulOther;//连加
  610.                             ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
  611.                             ucWd=1;        //切换到第一个窗口
  612.                             ucDisplayUpdate=1;  //刷新显示窗口
  613.                                         break;
  614.                                    case 2:  //减法  本程序没有减法功能,如果读者想增加减法程序,可以按键这个框架添加下去

  615.                                         break;
  616.                        
  617.                            }
  618.                            break;
  619.           }

  620.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  621.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  622.           break;   
  623.     case 15:// 15号键 对应朱兆祺学习板的S15键

  624.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  625.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  626.           break;   
  627.     case 16:// 16号键 清除按键 相当于复位的功能。重新输入数据  对应朱兆祺学习板的S16键
  628.               ulSource=0;
  629.                   ulOther=0;
  630.           ulResult=0;
  631.                   ucOperator=0;
  632.           ucWd=1;        //切换到第一个窗口
  633.           ucDisplayUpdate=1;  //刷新显示窗口
  634.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  635.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  636.           break;   
  637.   }               
  638. }


  639. /* 注释四:
  640. * 此处参与运算的输入数字ucWhichKey记得用最大变量类型unsigned long,可以避免数据溢出等错误
  641. */
  642. void number_key_input(unsigned long ucWhichKey)  //由于数字按键的代码相似度高,因此封装在这个函数里
  643. {


  644.     switch(ucWd)
  645.            {
  646.            case 1:   //在原始数据和运算结果的窗口下
  647.             switch(ucOperator)  //根据不同的运算符号进行不同的操作
  648.                         {
  649.                            case 0:  //无运算符号  按键输入原始数据,比如被加输
  650.                                 if(ulSource<=9999999) //最大只能输入8位数
  651.                                         {
  652.                        ulSource=ulSource*10+ucWhichKey;  //十进制的数值移位方法。
  653.                                         }
  654.                                 break;
  655.                            default:  //在已经按下了运算符号的情况下
  656.                     ulOther=0;  //第二个运算数先清零,再输入新的数据,然后马上切换到第2个窗口下
  657.                     ulOther=ucWhichKey;
  658.                     ucWd=2; //马上切换到第二个窗口下
  659.                                 break;
  660.                        
  661.                         }

  662.             ucDisplayUpdate=1;  //刷新显示窗口
  663.                         break;
  664.            case 2:   //在第二个参与运算数据的窗口下   按键输入第二个参与运算的数据
  665.                         if(ulOther<=9999999) //最大只能输入8位数
  666.                         {
  667.                ulOther=ulOther*10+ucWhichKey;  //十进制的数值移位方法。
  668.                     }

  669.             ucDisplayUpdate=1;  //刷新显示窗口
  670.                     break;
  671.     }

  672. }


  673. void T0_time() interrupt 1
  674. {
  675.   TF0=0;  //清除中断标志
  676.   TR0=0; //关中断
  677.   key_scan(); //放在定时中断里的按键扫描函数
  678.   if(uiVoiceCnt!=0)
  679.   {
  680.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  681.          beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  682.   }
  683.   else
  684.   {
  685.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  686.            beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  687.   }

  688.   display_drive();  //放在定时中断里的数码管驱动函数

  689. /* 注释五:
  690. *  注意,此处的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的2000改成了500。
  691. */
  692.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  693.   TL0=0x0b;
  694.   TR0=1;  //开中断
  695. }

  696. void delay_short(unsigned int uiDelayShort)
  697. {
  698.    unsigned int i;  
  699.    for(i=0;i<uiDelayShort;i++)
  700.    {
  701.      ;   //一个分号相当于执行一条空语句
  702.    }
  703. }


  704. void delay_long(unsigned int uiDelayLong)
  705. {
  706.    unsigned int i;
  707.    unsigned int j;
  708.    for(i=0;i<uiDelayLong;i++)
  709.    {
  710.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  711.           {
  712.              ; //一个分号相当于执行一条空语句
  713.           }
  714.    }
  715. }


  716. void initial_myself()  //第一区 初始化单片机
  717. {

  718.   led_dr=0;
  719.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  720.   hc595_drive(0x00,0x00);
  721.   TMOD=0x01;  //设置定时器0为工作方式1

  722.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  723.   TL0=0x0b;
  724. }
  725. void initial_peripheral() //第二区 初始化外围
  726. {


  727.   EA=1;     //开总中断
  728.   ET0=1;    //允许定时中断
  729.   TR0=1;    //启动定时中断



  730. }
复制代码


总结陈词:
这节讲了加法简易计算器的程序项目。为了让读者理解运动,按键,显示是如何有规律关联起来的,下节会继续讲一个相关的小项目程序。欲知详情,请听下回分解-----数码管作为仪表盘显示跑马灯的方向,速度和运行状态。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|独闷闷网 ( 粤ICP备12007667号-2 )

GMT+8, 2024-5-6 07:54 , Processed in 0.247246 second(s), 16 queries .

快速回复 返回顶部 返回列表