搜索
bottom↓
楼主: 吴坚鸿

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

  [复制链接]

出0入0汤圆

发表于 2014-3-10 19:28:37 | 显示全部楼层
支持一下,记得我转载过楼主的一篇文章.

出0入0汤圆

发表于 2014-3-10 19:43:43 | 显示全部楼层
纯膜拜下lz这在mcu行业耕耘了10年以上的牛人....

出0入0汤圆

发表于 2014-3-10 20:13:55 | 显示全部楼层
超好的帖子,收藏并学习,感谢楼主的无私分享,祝好!

出0入0汤圆

 楼主| 发表于 2014-3-10 20:25:42 | 显示全部楼层
第三十一节:数码管通过一二级菜单来设置数据的综合程序。

开场白:
   上一节讲了二级菜单,这一节要教会大家两个知识点:
第一个:数码管通过一二级菜单来设置数据的综合程序框架。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用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;    //启动定时中断

}


总结陈词:
这节讲了数码管通过一二级菜单来设置数据的综合程序,鸿哥的人机界面程序框架基本上都涉及到了,为了继续加深熟悉鸿哥的“一二级菜单显示理论”,下一节会继续讲一个常用的数码管项目小程序,这个项目小程序鸿哥是怎么写的?欲知详情,请听下回分解-----数码管中的倒计时程序。

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

出0入0汤圆

 楼主| 发表于 2014-3-10 20:27:32 | 显示全部楼层
第三十二节:数码管中的倒计时程序。

开场白:
   上一节讲了一二级菜单的综合程序,这一节要教会大家三个知识点:
第一个:通过本程序,继续加深理解按键与数码管的关联方法。
第二个:复习一下我在第五节教给大家的时间校正法。
第三个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用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;    //启动定时中断

}


总结陈词:
这节讲了数码管中的倒计时程序。如果要在此程序上多增加两个按键,用来控制数码管倒计时的速度档位,并且需要在数码管中闪烁显示被设置的速度档位,该怎么编写这个程序?欲知详情,请听下回分解-----能设置速度档位的数码管倒计时程序。

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

出0入0汤圆

 楼主| 发表于 2014-3-10 20:29:12 | 显示全部楼层
第三十三节:能设置速度档位的数码管倒计时程序。

开场白:
   上一节讲了数码管中的倒计时程序。这节要在此程序上多增加两个按键,用来控制数码管倒计时的速度档位,并且需要在数码管中闪烁显示被设置的速度档位。这一节要教会大家三个知识点:
第一个:把一个按键的短按与长按复合应用在项目中的程序结构。
第二个:通过本程序,继续加深理解按键与数码管的关联方法。
第三个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用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开机密码锁的程序。

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

出0入0汤圆

发表于 2014-3-10 20:31:00 | 显示全部楼层
发烧友论坛就一直追你的帖子   欢迎来到莫大的论坛     读了你的帖子  收获真的不少   十分感谢啊

出0入4汤圆

发表于 2014-3-10 20:36:34 | 显示全部楼层
楼主在实际项目中的按键部分代码也是这样:长、短按,组合键分开写的吗?

出0入0汤圆

 楼主| 发表于 2014-3-10 20:38:38 | 显示全部楼层
sunliezhi 发表于 2014-3-10 20:36
楼主在实际项目中的按键部分代码也是这样:长、短按,组合键分开写的吗? ...

根据客户的需要吧。基本的按键程序框架就是这样写的。

出0入0汤圆

 楼主| 发表于 2014-3-10 20:39:17 | 显示全部楼层
fshunj 发表于 2014-3-10 19:28
支持一下,记得我转载过楼主的一篇文章.

谢谢你的支持。

出0入0汤圆

发表于 2014-3-10 20:39:20 | 显示全部楼层
感谢分享、

出0入0汤圆

发表于 2014-3-10 20:45:29 | 显示全部楼层
楼主是实用派人士。能毫无保留的分享自己的想法很不错。

出0入0汤圆

发表于 2014-3-10 20:45:50 | 显示全部楼层
好牛哦!鸿哥,看你的

出0入4汤圆

发表于 2014-3-10 20:53:09 | 显示全部楼层
楼主的家境应该不错

出0入0汤圆

发表于 2014-3-10 20:58:09 | 显示全部楼层
今天看到ZLG出的258元的开发板,突然想到,今后若出现100元的ARM9核心板带Linux/Wince操作系统后,开发模式会巨变,届时嵌入式系统也有128MB内存,不用考虑资源,不用考虑硬件驱动,而且考虑开发应用程序的速度,考虑产品上市的速度。

开发“单片机”程序将和开发PC机程序一样,设计模式、多线程程序设计将从PC领域直接进入嵌入式领域并成为主流,并统治高附加值的设备开发领域

单片机框架将成为历史...或被挤入极低附加值的且成本敏感的领域...

出0入0汤圆

发表于 2014-3-10 21:15:31 | 显示全部楼层
FD8000 发表于 2014-3-10 20:58
今天看到ZLG出的258元的开发板,突然想到,今后若出现100元的ARM9核心板带Linux/Wince操作系统后,开发模式 ...

绝对会实现的

出50入0汤圆

发表于 2014-3-10 21:23:07 | 显示全部楼层
支持楼主,楼主辛苦

出0入0汤圆

发表于 2014-3-10 21:29:37 | 显示全部楼层
支持一下

出0入0汤圆

发表于 2014-3-10 21:31:14 | 显示全部楼层

火钳流明

出0入4汤圆

发表于 2014-3-10 21:32:51 | 显示全部楼层
不错不错

出0入0汤圆

 楼主| 发表于 2014-3-10 22:08:28 | 显示全部楼层
FD8000 发表于 2014-3-10 20:58
今天看到ZLG出的258元的开发板,突然想到,今后若出现100元的ARM9核心板带Linux/Wince操作系统后,开发模式 ...

我也经常用我现在分享的单片机程序框架来帮一些客户开发PC端的上位机软件,虽然我是用VC帮他们开发上位机软件,但是我觉得我也用了很多单片机程序框架的思路。所以说,编程思想是灵魂,它永远都不会过时,不管你是开发单片机,还是开发嵌入式,还是开发PC端,它无处不在,放之四海而皆准。

出0入0汤圆

发表于 2014-3-10 22:12:27 | 显示全部楼层
搬个板凳,跟着学。

出0入0汤圆

发表于 2014-3-10 22:39:18 | 显示全部楼层
必须欢迎,支持一下!

出0入0汤圆

发表于 2014-3-10 22:54:00 | 显示全部楼层
看来楼主是想把单片机累死!!哈哈哈

出0入0汤圆

发表于 2014-3-10 23:40:17 | 显示全部楼层

能讲讲关于“按键最成熟的就是用FIFO环形缓冲”的思想么?
有链接也行; 度娘没搜到

出0入0汤圆

发表于 2014-3-10 23:48:58 | 显示全部楼层
标记一个,顶鸿哥

出0入0汤圆

发表于 2014-3-11 00:21:09 | 显示全部楼层
希望LZ能整理个PDF出来

出0入0汤圆

发表于 2014-3-11 00:24:27 来自手机 | 显示全部楼层
顶一下!!!!

出0入0汤圆

发表于 2014-3-11 00:45:39 | 显示全部楼层
本帖最后由 goolloo 于 2014-3-11 00:48 编辑

lz,教书教的好,不仅知识很重要,讲课方法也很重要。
论坛发帖要发号,不仅内容重要,排版也很重要。






大段的代码这样子看是不是清爽很多呢?

  1. void main()
  2. {
  3.         initial_myself();  
  4.         delay_long(100);   
  5.         initial_peripheral();
  6.         while(1)  
  7.         {
  8.                 key_service(); //按键服务的应用程序
  9.                 display_service(); //显示的窗口菜单服务程序
  10.         }

  11. }
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出5入42汤圆

发表于 2014-3-11 01:55:49 来自手机 | 显示全部楼层
看了两节,很实际也很有参考价值。给楼主提个建议,希望能把实例代码以附件形式上传,排好版,要不然一股脑的帖在帖子里面太影响阅读了。呵呵,要求可能有点过分,希望楼主能满足。多谢楼主分享,对广大电工真是好福利。

出0入264汤圆

发表于 2014-3-11 08:38:59 | 显示全部楼层
Free_Bird 发表于 2014-3-10 23:40
能讲讲关于“按键最成熟的就是用FIFO环形缓冲”的思想么?
有链接也行; 度娘没搜到 ...

仔细看完这个帖子,包括所有的回帖。
http://www.amobbs.com/thread-5542774-1-1.html

出0入0汤圆

发表于 2014-3-11 08:55:16 | 显示全部楼层
支持原创,你辛苦了,你无私的奉献精神,鼓舞着我们

出0入0汤圆

发表于 2014-3-11 09:03:17 | 显示全部楼层
cool~~~~~~

出0入0汤圆

 楼主| 发表于 2014-3-11 09:51:10 | 显示全部楼层
goolloo 发表于 2014-3-11 00:45
lz,教书教的好,不仅知识很重要,讲课方法也很重要。
论坛发帖要发号,不仅内容重要,排版也很重要。

谢谢你的建议,下一节我会注意的。

出0入0汤圆

 楼主| 发表于 2014-3-11 09:51:46 | 显示全部楼层
kevin_me 发表于 2014-3-11 01:55
看了两节,很实际也很有参考价值。给楼主提个建议,希望能把实例代码以附件形式上传,排好版,要不然一股脑 ...

谢谢你的建议,下一节我会注意的。我发现大家好热心,再次表示感谢。

出0入0汤圆

发表于 2014-3-11 10:27:17 | 显示全部楼层
太多实用性的东西,谢谢楼主的无私奉献

出0入0汤圆

发表于 2014-3-11 11:10:40 来自手机 | 显示全部楼层
支持LZ 初学者有福了

出0入0汤圆

发表于 2014-3-11 11:12:23 | 显示全部楼层
站位!学习!

出0入0汤圆

发表于 2014-3-11 13:24:21 | 显示全部楼层
阿莫我也是久闻大名,感觉还不错

出0入0汤圆

发表于 2014-3-11 13:26:55 | 显示全部楼层
好好学习学习,MARK!

出0入0汤圆

发表于 2014-3-11 13:32:15 | 显示全部楼层
LZ牛人 有机会向LZ学习.

出0入0汤圆

发表于 2014-3-11 13:43:27 | 显示全部楼层
太好了,感谢lz

出0入0汤圆

发表于 2014-3-11 13:53:34 | 显示全部楼层
支持一下

出0入4汤圆

发表于 2014-3-11 14:13:24 | 显示全部楼层
mcu_lover 发表于 2014-3-11 08:38
仔细看完这个帖子,包括所有的回帖。
http://www.amobbs.com/thread-5542774-1-1.html

忠言逆耳啊,看来听得进的人不多呀

出0入0汤圆

发表于 2014-3-11 22:35:41 | 显示全部楼层
Free_Bird 发表于 2014-3-10 23:40
能讲讲关于“按键最成熟的就是用FIFO环形缓冲”的思想么?
有链接也行; 度娘没搜到 ...

同求
要用到

出0入0汤圆

发表于 2014-3-11 22:45:33 | 显示全部楼层
看着书学51都是单任务 这种多任务操作还很少接触 楼主的讲义非常实用 谢谢了!!
期待后期更多好文出来!

出0入0汤圆

发表于 2014-3-12 12:44:36 | 显示全部楼层
写的很详细,膜拜中

出0入0汤圆

发表于 2014-3-12 13:04:05 | 显示全部楼层
太牛了  一定要支持

出0入0汤圆

发表于 2014-3-12 13:14:26 | 显示全部楼层
膜拜 支持一下

出0入0汤圆

发表于 2014-3-12 13:19:50 | 显示全部楼层
新手的福音!!!!!!!!!

出0入0汤圆

发表于 2014-3-12 15:25:59 | 显示全部楼层
楼主是个热情和热于分享的人,在别的论坛都有见到过,欢迎并感谢~

出0入0汤圆

发表于 2014-3-12 15:49:30 | 显示全部楼层
支持,先mark一下,有时间慢慢研究!

出0入0汤圆

发表于 2014-3-12 15:57:06 | 显示全部楼层
本帖最后由 carollim 于 2014-3-12 16:13 编辑

支持了    在FS那边看过楼主   大牛一只

出0入0汤圆

发表于 2014-3-12 16:48:39 | 显示全部楼层
搬凳子,准备听课!先谢谢啦~

出0入0汤圆

发表于 2014-3-12 16:52:04 | 显示全部楼层
谢谢分享

出0入0汤圆

发表于 2014-3-12 20:15:54 | 显示全部楼层

仔细看完这个帖子,包括所有的回帖。
http://www.amobbs.com/thread-5542774-1-1.html

出0入0汤圆

发表于 2014-3-12 20:37:49 来自手机 | 显示全部楼层
mark,谢谢分享

出0入0汤圆

发表于 2014-3-12 23:00:31 | 显示全部楼层
MARK                       

出0入0汤圆

发表于 2014-3-12 23:04:19 来自手机 | 显示全部楼层
支持一哈!!

出0入0汤圆

 楼主| 发表于 2014-3-12 23:07:09 | 显示全部楼层
yyinfo263 发表于 2014-3-11 22:45
看着书学51都是单任务 这种多任务操作还很少接触 楼主的讲义非常实用 谢谢了!!
期待后期更多好 ...

我一路走来,都是靠自己不断积累不断摸索的,积累到一定程度才形成现在的理论。我懂初学者缺什么,我知道他们需要什么。有好多朋友愿意花几千元让我教他两个星期怎么做项目,只是我平时项目缠身,实在抽不出太多时间来手把手教,因此我决定把它写出来,让它像病毒一样到处传播,让无数单片机初学者都获益。

出0入0汤圆

发表于 2014-3-12 23:25:23 | 显示全部楼层
这个一定要顶,扫盲一下.

出0入4汤圆

发表于 2014-3-13 08:13:09 | 显示全部楼层
标记一下,学习。

出0入0汤圆

发表于 2014-3-13 13:27:31 | 显示全部楼层
支持一下!收藏了

出0入0汤圆

发表于 2014-3-13 14:13:58 | 显示全部楼层
mark sp

出0入26汤圆

发表于 2014-3-13 14:16:08 | 显示全部楼层
感谢无私奉献。先收藏了,有空好好看看,总有值得学习的地方。

出0入0汤圆

发表于 2014-3-13 17:44:11 | 显示全部楼层
希望楼主能够更新1602液晶部分的讲解。

出110入93汤圆

发表于 2014-3-13 17:57:59 | 显示全部楼层
68336016 发表于 2014-3-10 13:09
半路出家人士来学习一下

偶也是,都金牌了,膜拜。

出0入0汤圆

发表于 2014-3-13 19:09:57 | 显示全部楼层
吴坚鸿 发表于 2014-3-12 23:07
我一路走来,都是靠自己不断积累不断摸索的,积累到一定程度才形成现在的理论。我懂初学者缺什么,我知道 ...

我是一个电工,这两天才买的马潮AVR学习板,你的代码我都看不懂,可是看你的例子的名字就很兴奋,谢谢楼主,先收藏起来以后再学习吧

出0入0汤圆

发表于 2014-3-13 20:16:44 来自手机 | 显示全部楼层
学习学习,谢谢

出0入0汤圆

发表于 2014-3-13 22:14:10 | 显示全部楼层
马克一下先

出40入0汤圆

发表于 2014-3-13 22:17:32 | 显示全部楼层
好贴,学习中。

出0入0汤圆

发表于 2014-3-13 23:23:25 | 显示全部楼层
好东西。
楼主做了这么多年,硬件方面是否也有类似心得?

出0入0汤圆

发表于 2014-3-13 23:55:33 来自手机 | 显示全部楼层
收藏了…………

出0入0汤圆

发表于 2014-3-13 23:56:49 | 显示全部楼层
直接顶起。

出0入0汤圆

发表于 2014-3-14 00:05:48 | 显示全部楼层
感谢分享!

出0入0汤圆

发表于 2014-3-14 00:32:49 | 显示全部楼层
epwwm 发表于 2014-3-10 12:01
“(2)很难记住繁杂的汇编语言指令?除非是在校学生要应付考试或者少数工作中绕不开汇编,否则学汇编就是 ...

顶你这句

出0入0汤圆

发表于 2014-3-14 08:52:14 | 显示全部楼层
佩服楼主这么博大的胸怀,将自己的宝贵经验倾囊相授,非常期待楼主的创作,我会经常来看看!

出0入0汤圆

发表于 2014-3-14 10:42:23 | 显示全部楼层
非常感谢,崇拜呀

出0入0汤圆

 楼主| 发表于 2014-3-14 12:59:10 | 显示全部楼层
newidea 发表于 2014-3-13 23:23
好东西。
楼主做了这么多年,硬件方面是否也有类似心得?

硬件方面也有一些。可能以后会另外开贴分享。

出0入0汤圆

 楼主| 发表于 2014-3-14 13:09:36 | 显示全部楼层
nsj21n 发表于 2014-3-14 08:52
佩服楼主这么博大的胸怀,将自己的宝贵经验倾囊相授,非常期待楼主的创作,我会经常来看看! ...

其实以前我年轻的时候也不愿意分享技术的,我想以后我的技术传男不传女,世世代代传下去。
后来我想开了,人生在世短短数十年,不管是技术还是金钱,生不带来死不带去,你只有把它分享出来才会对社会有价值有意义,
从而让你获得更大的心理满足。

出0入0汤圆

发表于 2014-3-14 13:10:06 | 显示全部楼层
很好很强大  向前辈学习

出0入0汤圆

发表于 2014-3-14 13:11:39 | 显示全部楼层
学习一下!

出0入0汤圆

发表于 2014-3-14 16:37:29 | 显示全部楼层
值得学习,谢谢楼主!

出0入0汤圆

发表于 2014-3-14 18:26:49 | 显示全部楼层
吴坚鸿 发表于 2014-3-14 13:09
其实以前我年轻的时候也不愿意分享技术的,我想以后我的技术传男不传女,世世代代传下去。
后来我想开了 ...

嗯,楼主说的在理。我工作了5年了,不管是兴趣还是为了工作我都一直愿意去学习一些东西,虽然可能没那么逻辑思维强,不管怎么说我还是喜欢单片机,喜欢调试成功那种成就感,部位别的,只为通过自己的努力成功。楼主的经验我要认真学习下,编程的精髓要掌握,活到老学到老!

出0入0汤圆

发表于 2014-3-14 18:34:07 | 显示全部楼层
辛苦  好好学习一下

出0入0汤圆

发表于 2014-3-14 19:17:34 | 显示全部楼层
LZ,我在电子发烧友论坛看过你的文章,写的别具一格,很不错,向LZ学习。

出0入0汤圆

发表于 2014-3-14 21:22:56 | 显示全部楼层
鸿哥,看好你

出0入0汤圆

发表于 2014-3-14 21:45:57 | 显示全部楼层
本帖最后由 4317mjh 于 2014-3-14 21:48 编辑

很难记住繁杂的汇编语言指令?除非是在校学生要应付考试或者少数工作中绕不开汇编,否则学汇编就是浪费时间。我从来就没有用汇编帮客户做过一个项目   这话,感觉不对。。。。。谁能说说汇编和C语言对于单片机的关系??

出0入0汤圆

发表于 2014-3-14 21:46:26 | 显示全部楼层
本帖最后由 4317mjh 于 2014-3-14 21:48 编辑

。。。。。。。。。。。。。。

出0入0汤圆

发表于 2014-3-15 00:49:53 | 显示全部楼层
我业余爱好,写程序都是瞎编乱写,看了这个帖子受益匪浅,非常感谢分享这些经验和思维,辛苦了。

出0入0汤圆

发表于 2014-3-15 09:24:59 | 显示全部楼层
支持 支持

出0入0汤圆

发表于 2014-3-15 09:31:07 | 显示全部楼层
很全面的资料!

出0入0汤圆

 楼主| 发表于 2014-3-15 09:52:18 | 显示全部楼层
本帖最后由 吴坚鸿 于 2014-3-15 09:58 编辑

第三十四节:在数码管中实现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学习板做了一个棋类比赛专用计时器给他们用,这个程序该怎么编写?欲知详情,请听下回分解-----带数码管显示的象棋比赛专用计时器。

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

出0入0汤圆

发表于 2014-3-15 10:08:49 | 显示全部楼层
学习!!!

出0入0汤圆

发表于 2014-3-15 10:26:41 | 显示全部楼层
哇哦 ~~~~  哇噢~~~~~ 赞一个  这个绝对好帖 绝对火!!!! 顶一个!!!!

出0入0汤圆

发表于 2014-3-15 10:53:25 | 显示全部楼层
玩转了,必须顶!

出0入0汤圆

发表于 2014-3-15 10:58:42 | 显示全部楼层
这是一笔很大的财富!

出0入0汤圆

发表于 2014-3-15 11:02:16 | 显示全部楼层
先mark下,再顶一下,欢迎来阿莫

出0入0汤圆

发表于 2014-3-15 11:28:49 | 显示全部楼层
从业将近十年!手把手教你单片机程序框架(连载)
谢谢!学习

出0入0汤圆

发表于 2014-3-15 17:26:25 | 显示全部楼层
看看大侠的风格是怎么样的?
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-25 15:13

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表