xiaodou 发表于 2014-3-15 17:38:29

这帖子很是火啊

desireyao 发表于 2014-3-15 18:15:19

过来看看

ycheng2004 发表于 2014-3-15 18:52:52

太好,谢谢,Mark,                              

大漠游民 发表于 2014-3-15 21:50:52

太好了,我要好好看看,谢谢

1170390 发表于 2014-3-15 21:56:11

标题。。。。。。。。。

曾家0762 发表于 2014-3-15 22:23:05

{:funk:}好详细的资料,绝对的适合新手,楼主真的辛苦啦

sunday151640 发表于 2014-3-15 22:29:24

顶楼主。。

sept 发表于 2014-3-15 22:34:25

mark                                                   

poet_lee 发表于 2014-3-15 22:35:08

很丰富的资料 感谢分享

caixiong 发表于 2014-3-16 17:39:11

楼主好人,感谢无私奉献,

meikai 发表于 2014-3-16 18:24:04

支持吴坚鸿!!!!

dong889 发表于 2014-3-16 19:40:43

mark 经验分享!

青春从未醒 发表于 2014-3-20 13:48:09

cool,支持一下,以前在dzfsy论坛就·看过作者的文章,对写的很好

scadu 发表于 2014-3-20 15:04:36


火前流明。。。。。。      顶完再看

afeiyang 发表于 2014-3-20 15:16:03

这个得支持……{:smile:}

冷月无声 发表于 2014-3-20 15:42:11

mark一下

kkey 发表于 2014-3-20 17:55:18

这个不支持不行啊……

lm4766 发表于 2014-3-20 17:58:59

马克,学习了……。

wjq1n1n 发表于 2014-3-20 18:17:44

支持一下!

xinwu 发表于 2014-3-20 19:35:03

以前看的都是很简单的入门。一看楼主分享的,感觉就是有很多项目实战经验的。赞叹楼主的无私奉献!

agilentvee 发表于 2014-3-20 19:47:31

很好,很好保存

wind2100 发表于 2014-3-20 20:00:40

这些讲完 算是可以入门了。

waymcu 发表于 2014-3-20 20:18:01

手把手教你单片机程序框架 mark

吴坚鸿 发表于 2014-3-21 01:25:11

第三十五节:带数码管显示的象棋比赛专用计时器。

开场白:
2014年春节的时候,一帮朋友举行小规模的象棋比赛,有一些朋友下棋的速度实在是太慢了,为了限制比赛时间,我专门用朱兆祺的51学习板做了一个棋类比赛专用计时器给他们用。这一节要教会大家两个知识点:
第一个:按键服务程序操作的精髓在于根据当前系统处于什么窗口状态下就执行什么操作。紧紧围绕着不同的窗口ucWd来执行不同的操作。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。

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

(1)硬件平台:基于朱兆祺51单片机学习板。
刚上电开机时,红棋加时键对应S1键,红棋减时键对应S2键.。
刚上电开机时,黑棋加时键对应S3键,黑棋减时键对应S4键.。
比赛中途暂停双方计时的暂停按键对应S6键。刚上电时,复位双方默认20分时间的复位按键对应S7按键。
红棋的抢时按键对应S13键,黑棋的抢时按键对应S16按键。

(2)实现功能:
棋类计时器有点像抢答器,本质上有两个计时器。比赛的时候对弈的两个棋友各用一个不同的按键抢时间,红棋走一步棋后,就按一下自己的抢时按键,这个时候红棋的计时器停止计时,而黑棋的计时器开始计时,黑棋走了一步棋后,按一下自己的计时器,黑棋停止计时,红棋继续计时,依次循环,谁的时间最先用完谁就输,蜂鸣器也会发出长鸣的声音提示时间到。
上电开机默认双方各有20分钟的时间,左边显示的是红棋的时间,右边显示的是黑棋的时间。此时可以通过S1,S2.,S3,S4的加减按键来设置各自的最大倒计时时间。此时如果按下复位按键S7,会自动把双方的时间设置为默认的20分钟。
设置好最大倒计时的时间后,此时任意一方按下各自的抢时按键(S13或者S16),则自己的计时器停止计时,而对方开始倒计时。此时数码管显示的是对方的时间,而自己的时间屏蔽不显示。
在开始倒计时的时候,如果中途有棋友要接听电话或者忙别的事情,需要暂时暂停一下双方的时间,这个时候可以按S6暂停按键来暂停双方的计时,忙完后再次按下暂停按键会继续倒计时。任何一方的时间走完,都会蜂鸣器长鸣提示。
(3)源代码讲解如下:
#include "REG52.H"

#define const_voice_short40   //蜂鸣器短叫的持续时间
#define const_voice_long   900   //蜂鸣器长叫的持续时间

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

#define const_1s   422   //产生一秒钟的时间基准

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void T0_time();//定时中断函数
void key_service();
void key_scan(); //按键扫描函数 放在定时中断里

void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive();//放在定时中断里的数码管驱动函数
void time_service();//放在定时中断里的时间应用程序
void display_service();


sbit key_sr1=P0^0; //第一行输入
sbit key_sr2=P0^1; //第二行输入
sbit key_sr3=P0^2; //第三行输入
sbit key_sr4=P0^3; //第四行输入

sbit key_dr1=P0^4; //第一列输出
sbit key_dr2=P0^5; //第二列输出
sbit key_dr3=P0^6; //第三列输出
sbit key_dr4=P0^7; //第四列输出

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 ucKeyStep=1;//按键扫描步骤变量

unsigned char ucKeySec=0;   //被触发的按键编号
unsigned intuiKeyTimeCnt=0; //按键去抖动延时计数器
unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

unsigned char ucRowRecord=1; //记录当前扫描到第几列了

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

unsigned char ucDigShow8=0;//第8位数码管要显示的内容
unsigned char ucDigShow7=0;//第7位数码管要显示的内容
unsigned char ucDigShow6=0;//第6位数码管要显示的内容
unsigned char ucDigShow5=0;//第5位数码管要显示的内容
unsigned char ucDigShow4=0;//第4位数码管要显示的内容
unsigned char ucDigShow3=0;//第3位数码管要显示的内容
unsigned char ucDigShow2=0;//第2位数码管要显示的内容
unsigned char ucDigShow1=0;//第1位数码管要显示的内容
unsigned char ucDigDot3=1;//数码管3的小数点是否显示的标志
unsigned char ucDigDot7=1;//数码管7的小数点是否显示的标志

unsigned char ucDigShowTemp=0; //临时中间变量

unsigned char ucDisplayDriveStep=1;//动态扫描数码管的步骤变量

unsigned int uiRedTimeCnt=0;    //红棋产生秒基准的时间计时器
unsigned int uiBlackTimeCnt=0;//黑棋产生秒基准的时间计时器

unsigned int uiRedTotal=1200;    //红棋的总时间
unsigned int uiBlackTotal=1200;//黑棋的总时间

unsigned char ucRedFlag=0;//红棋是否开始计时的标志
unsigned char ucBlackFlag=0;//黑棋是否开始计时的标志

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

/* 注释一:
*ucWd变量是本程序最核心的变量,代表显示哪一个窗口和系统处于当前哪种状态
*/
unsigned char ucWd=1;

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

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

}


void time_service()//放在定时中断里的时间应用程序
{
if(ucRedFlag==1)//1代表红棋在运行中
{
   uiRedTimeCnt++;
       if(uiRedTimeCnt>const_1s)
       {
      uiRedTimeCnt=0;
      if(uiRedTotal>0)
                {
                   uiRedTotal--;
                }
                else//时间到
                {
                  ucRedFlag=0;    //红棋和黑棋同时停止计时
                   ucBlackFlag=0;
                   ucWd=1;//切换到第一个窗口的状态
                   uiVoiceCnt=const_voice_long; //报警声音触发
                }
               

      ucDisplayUpdate=1;//更新显示
       }
}


if(ucBlackFlag==1)//1代表黑棋在运行中
{
   uiBlackTimeCnt++;
       if(uiBlackTimeCnt>const_1s)
       {
      uiBlackTimeCnt=0;
      if(uiBlackTotal>0)
                {
                   uiBlackTotal--;
                }
                else//时间到
                {
                  ucRedFlag=0;//红棋和黑棋同时停止计时
                   ucBlackFlag=0;
                   ucWd=1;//切换到第一个窗口的状态
                   uiVoiceCnt=const_voice_long; //报警声音触发
                }
               

      ucDisplayUpdate=1;//更新显示
       }
}
}

void display_service()//放在定时中断里的显示应用程序
{
if(ucDisplayUpdate==1)//有数据更新显示
{
   ucDisplayUpdate=0;
       switch(ucWd)   //本程序最核心的变量ucWd
       {
           case 1://窗口1,代表刚上电或者复位后的状态
                    //红棋分解出分
                     ucDigShowTemp=uiRedTotal/60;
            ucDigShow8=ucDigShowTemp/10;
            ucDigShow7=ucDigShowTemp%10;

                     //红棋分解出秒
                  ucDigShowTemp=uiRedTotal%60;
            ucDigShow6=ucDigShowTemp/10;
            ucDigShow5=ucDigShowTemp%10;
                        ucDigDot7=1;//数码管7的小数点显示

                    //黑棋分解出分
                     ucDigShowTemp=uiBlackTotal/60;
            ucDigShow4=ucDigShowTemp/10;
            ucDigShow3=ucDigShowTemp%10;

                     //黑棋分解出秒
                  ucDigShowTemp=uiBlackTotal%60;
            ucDigShow2=ucDigShowTemp/10;
            ucDigShow1=ucDigShowTemp%10;
                        ucDigDot3=1;//数码管3的小数点显示

            led_dr=1;//计时器处于停止状态,LED亮

                break;
           case 2://窗口2,代表黑棋正在运行中的状态

                    //红棋全部不显示
            ucDigShow8=10;
            ucDigShow7=10;
            ucDigShow6=10;
            ucDigShow5=10;
                        ucDigDot7=0;//数码管7的小数点不显示

                    //黑棋分解出分
                     ucDigShowTemp=uiBlackTotal/60;
            ucDigShow4=ucDigShowTemp/10;
            ucDigShow3=ucDigShowTemp%10;

                     //黑棋分解出秒
                  ucDigShowTemp=uiBlackTotal%60;
            ucDigShow2=ucDigShowTemp/10;
            ucDigShow1=ucDigShowTemp%10;
                        ucDigDot3=1;//数码管3的小数点显示

            led_dr=0;//计时器处于计时状态,LED灭

                break;

           case 3://窗口3,代表黑棋在中途暂停的状态

                    //红棋全部不显示
            ucDigShow8=10;
            ucDigShow7=10;
            ucDigShow6=10;
            ucDigShow5=10;
                        ucDigDot7=0;//数码管7的小数点不显示

                    //黑棋分解出分
                     ucDigShowTemp=uiBlackTotal/60;
            ucDigShow4=ucDigShowTemp/10;
            ucDigShow3=ucDigShowTemp%10;

                     //黑棋分解出秒
                  ucDigShowTemp=uiBlackTotal%60;
            ucDigShow2=ucDigShowTemp/10;
            ucDigShow1=ucDigShowTemp%10;
                        ucDigDot3=1;//数码管3的小数点显示


            led_dr=1;//计时器处于暂停状态,LED亮

                break;
           case 4://窗口4,代表红棋正在运行中的状态
                    //红棋分解出分
                     ucDigShowTemp=uiRedTotal/60;
            ucDigShow8=ucDigShowTemp/10;
            ucDigShow7=ucDigShowTemp%10;

                     //红棋分解出秒
                  ucDigShowTemp=uiRedTotal%60;
            ucDigShow6=ucDigShowTemp/10;
            ucDigShow5=ucDigShowTemp%10;
                        ucDigDot7=1;//数码管7的小数点显示


                    //黑棋全部不显示
            ucDigShow4=10;
            ucDigShow3=10;
            ucDigShow2=10;
            ucDigShow1=10;
                        ucDigDot3=0;//数码管3的小数点不显示

            led_dr=0;//计时器处于倒计时状态,LED灭

                break;

           case 5://窗口5,代表红棋在中途暂停的状态
                    //红棋分解出分
                     ucDigShowTemp=uiRedTotal/60;
            ucDigShow8=ucDigShowTemp/10;
            ucDigShow7=ucDigShowTemp%10;

                     //红棋分解出秒
                  ucDigShowTemp=uiRedTotal%60;
            ucDigShow6=ucDigShowTemp/10;
            ucDigShow5=ucDigShowTemp%10;
                        ucDigDot7=1;//数码管7的小数点显示


                    //黑棋全部不显示
            ucDigShow4=10;
            ucDigShow3=10;
            ucDigShow2=10;
            ucDigShow1=10;
                        ucDigDot3=0;//数码管3的小数点不显示

            led_dr=1;//计时器处于暂停状态,LED亮

                break;
       }
}
}

void display_drive()//放在定时中断里的数码管驱动函数
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1://显示第1位
         ucDigShowTemp=dig_table;
         dig_hc595_drive(ucDigShowTemp,0xfe);
             break;
      case 2://显示第2位
         ucDigShowTemp=dig_table;
         dig_hc595_drive(ucDigShowTemp,0xfd);
             break;
      case 3://显示第3位
         ucDigShowTemp=dig_table;
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
                   }
         dig_hc595_drive(ucDigShowTemp,0xfb);
             break;
      case 4://显示第4位
         ucDigShowTemp=dig_table;
         dig_hc595_drive(ucDigShowTemp,0xf7);
             break;
      case 5://显示第5位
         ucDigShowTemp=dig_table;
         dig_hc595_drive(ucDigShowTemp,0xef);
             break;
      case 6://显示第6位
         ucDigShowTemp=dig_table;
         dig_hc595_drive(ucDigShowTemp,0xdf);
             break;
      case 7://显示第7位
         ucDigShowTemp=dig_table;
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
         }
         dig_hc595_drive(ucDigShowTemp,0xbf);
             break;
      case 8://显示第8位
         ucDigShowTemp=dig_table;
         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;

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

         ucTempData=ucTempData<<1;
   }

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

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

         ucTempData=ucTempData<<1;
   }

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

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

}


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

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

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

         ucTempData=ucTempData<<1;
   }

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

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

         ucTempData=ucTempData<<1;
   }

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

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

}


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

switch(ucKeyStep)
{
   case 1:   //按键扫描输出第ucRowRecord列低电平
            if(ucRowRecord==1)//第一列输出低电平
                  {
             key_dr1=0;      
             key_dr2=1;
             key_dr3=1;   
             key_dr4=1;
                  }
            else if(ucRowRecord==2)//第二列输出低电平
                  {
             key_dr1=1;      
             key_dr2=0;
             key_dr3=1;   
             key_dr4=1;
                  }
            else if(ucRowRecord==3)//第三列输出低电平
                  {
             key_dr1=1;      
             key_dr2=1;
             key_dr3=0;   
             key_dr4=1;
                  }
            else   //第四列输出低电平
                  {
             key_dr1=1;      
             key_dr2=1;
             key_dr3=1;   
             key_dr4=0;
                  }

          uiKeyTimeCnt=0;//延时计数器清零
          ucKeyStep++;   //切换到下一个运行步骤
            break;

   case 2:   //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
          uiKeyTimeCnt++;
                  if(uiKeyTimeCnt>1)
                  {
                     uiKeyTimeCnt=0;
             ucKeyStep++;   //切换到下一个运行步骤
                  }
            break;

   case 3:
          if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
          {
             ucKeyStep=1;//如果没有按键按下,返回到第一个运行步骤重新开始扫描
             ucKeyLock=0;//按键自锁标志清零
             uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙   
   
                         ucRowRecord++;//输出下一列
                         if(ucRowRecord>4)
                         {
                            ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
                         }

          }
                  else if(ucKeyLock==0)//有按键按下,且是第一次触发
                  {
                     if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=1;//触发1号键 对应朱兆祺学习板的S1键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=2;//触发2号键 对应朱兆祺学习板的S2键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=3;//触发3号键 对应朱兆祺学习板的S3键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=4;//触发4号键 对应朱兆祺学习板的S4键
                           }

                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=5;//触发5号键 对应朱兆祺学习板的S5键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=6;//触发6号键 对应朱兆祺学习板的S6键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=7;//触发7号键 对应朱兆祺学习板的S7键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=8;//触发8号键 对应朱兆祺学习板的S8键
                           }
                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=9;//触发9号键 对应朱兆祺学习板的S9键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=10;//触发10号键 对应朱兆祺学习板的S10键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=11;//触发11号键 对应朱兆祺学习板的S11键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=12;//触发12号键 对应朱兆祺学习板的S12键
                           }
                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=13;//触发13号键 对应朱兆祺学习板的S13键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=14;//触发14号键 对应朱兆祺学习板的S14键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=15;//触发15号键 对应朱兆祺学习板的S15键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=16;//触发16号键 对应朱兆祺学习板的S16键
                           }
                              }
                        
                         }
                  
                  }
            break;

}


}

/* 注释三:
*按键服务程序操作的精髓在于根据当前系统处于什么窗口下就执行什么操作。
*紧紧围绕着不同的窗口ucWd来执行不同的操作。
*/
void key_service() //第三区 放在定时中断里的按键服务应用程序
{
switch(ucKeySec) //按键服务状态切换
{
    case 1:// 1号键 对应朱兆祺学习板的S1键红棋加分 按键
              switch(ucWd)//本程序最核心的变量ucWd
              {
                 case 1://窗口1,代表刚上电,完成或者复位后的状态          
                              uiRedTotal=uiRedTotal+60;//加红棋分的时间,此处60秒代表一分
                                  if(uiRedTotal>5940)
                                  {
                                     uiRedTotal=5940;
                                  }
                  uiRedTotal=uiRedTotal-(uiRedTotal%60);//去秒取整分

                                  ucDisplayUpdate=1;//更新显示
                  uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                      break;

                 case 2://窗口2,代表黑棋正在运行中的状态       
                      break;

                 case 3://窗口3,代表黑棋在中途暂停的状态
                      break;

                 case 4://窗口4,代表红棋正在运行中的状态          
                      break;

                 case 5://窗口5,代表红棋在中途暂停的状态
                      break;

          }
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;      

    case 2:// 2号键 对应朱兆祺学习板的S2键红棋减分 按键
              switch(ucWd)//本程序最核心的变量ucWd
              {
                 case 1://窗口1,代表刚上电,完成或者复位后的状态          
                              if(uiRedTotal>=60)
                                  {
                                 uiRedTotal=uiRedTotal-60;//减红棋分的时间,此处60秒代表一分
                                  }
                  uiRedTotal=uiRedTotal-(uiRedTotal%60);//去秒取整分

                                  ucDisplayUpdate=1;//更新显示
                  uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                      break;

                 case 2://窗口2,代表黑棋正在运行中的状态       
                      break;

                 case 3://窗口3,代表黑棋在中途暂停的状态
                      break;

                 case 4://窗口4,代表红棋正在运行中的状态          
                      break;

                 case 5://窗口5,代表红棋在中途暂停的状态
                      break;

          }
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   

    case 3:// 3号键 对应朱兆祺学习板的S3键黑棋加分 按键
              switch(ucWd)//本程序最核心的变量ucWd
              {
                 case 1://窗口1,代表刚上电,完成或者复位后的状态          
                              uiBlackTotal=uiBlackTotal+60;//加黑棋分的时间,此处60秒代表一分
                                  if(uiBlackTotal>5940)
                                  {
                                     uiBlackTotal=5940;
                                  }
                  uiBlackTotal=uiBlackTotal-(uiBlackTotal%60);//去秒取整分

                                  ucDisplayUpdate=1;//更新显示
                  uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                      break;

                 case 2://窗口2,代表黑棋正在运行中的状态       
                      break;

                 case 3://窗口3,代表黑棋在中途暂停的状态
                      break;

                 case 4://窗口4,代表红棋正在运行中的状态          
                      break;

                 case 5://窗口5,代表红棋在中途暂停的状态
                      break;

          }
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         

    case 4:// 4号键 对应朱兆祺学习板的S4键黑棋减分 按键
              switch(ucWd)//本程序最核心的变量ucWd
              {
                 case 1://窗口1,代表刚上电,完成或者复位后的状态          
                              if(uiBlackTotal>=60)
                                  {
                                 uiBlackTotal=uiBlackTotal-60;//减黑棋分的时间,此处60秒代表一分
                                  }
                  uiBlackTotal=uiBlackTotal-(uiBlackTotal%60);//去秒取整分

                                  ucDisplayUpdate=1;//更新显示
                  uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                      break;

                 case 2://窗口2,代表黑棋正在运行中的状态       
                      break;

                 case 3://窗口3,代表黑棋在中途暂停的状态
                      break;

                 case 4://窗口4,代表红棋正在运行中的状态          
                      break;

                 case 5://窗口5,代表红棋在中途暂停的状态
                      break;

          }
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   

    case 5:// 5号键 对应朱兆祺学习板的S5键


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

    case 6:// 6号键 对应朱兆祺学习板的S6键中途暂停和启动按键
              switch(ucWd)//本程序最核心的变量ucWd
              {
                 case 1://窗口1,代表刚上电,完成或者复位后的状态          

                      break;

                 case 2://窗口2,代表黑棋正在运行中的状态       
                  ucRedFlag=0;    //暂停计时
                  ucBlackFlag=0;//暂停计时
                                  ucWd=3; //切换到黑棋中途暂停的状态

                                  ucDisplayUpdate=1;//更新显示
                  uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                      break;

                 case 3://窗口3,代表黑棋在中途暂停的状态
                  ucRedFlag=0;   //红棋暂停计时
                  ucBlackFlag=1; //黑棋继续计时
                                  ucWd=2;       //切换到黑棋正在运行中的状态

                                  ucDisplayUpdate=1;//更新显示
                  uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                      break;

                 case 4://窗口4,代表红棋正在运行中的状态          
                  ucRedFlag=0;    //暂停计时
                  ucBlackFlag=0;//暂停计时
                                  ucWd=5;       //切换到红棋中途暂停的状态

                                  ucDisplayUpdate=1;//更新显示
                  uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                      break;

                 case 5://窗口5,代表红棋在中途暂停的状态
                  ucRedFlag=1;   //红棋继续计时
                  ucBlackFlag=0; //黑棋暂停计时
                                  ucWd=4;       //切换到红棋正在运行中的状态

                                  ucDisplayUpdate=1;//更新显示
                  uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。

                      break;

          }
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   

    case 7:// 7号键 对应朱兆祺学习板的S7键在第一个窗口下,把计时器的值恢复为开机时的默认值20分钟
              switch(ucWd)//本程序最核心的变量ucWd
              {
                 case 1://窗口1,代表刚上电,完成或者复位后的状态          
                  uiRedTotal=1200;    //红棋的总时间
                  uiBlackTotal=1200;//黑棋的总时间

                                  ucDisplayUpdate=1;//更新显示
                                  uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                      break;

                 case 2://窗口2,代表黑棋正在运行中的状态       

                      break;

                 case 3://窗口3,代表黑棋在中途暂停的状态

                      break;

                 case 4://窗口4,代表红棋正在运行中的状态          

                      break;

                 case 5://窗口5,代表红棋在中途暂停的状态

                      break;

          }
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 8:// 8号键 对应朱兆祺学习板的S8键

          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 9:// 9号键 对应朱兆祺学习板的S9键

          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 10:// 10号键 对应朱兆祺学习板的S10键

          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 11:// 11号键 对应朱兆祺学习板的S11键

          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 12:// 12号键 对应朱兆祺学习板的S12键

          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 13:// 13号键 对应朱兆祺学习板的S13键红棋按下
              switch(ucWd)//本程序最核心的变量ucWd
              {
                 case 1://窗口1,代表刚上电,完成或者复位后的状态          
                  ucRedFlag=0;    //红棋暂停计时
                  ucBlackFlag=1;//黑棋继续计时
                                  ucWd=2; //切换到黑棋正在运行中的状态

                                  ucDisplayUpdate=1;//更新显示
                      break;

                 case 2://窗口2,代表黑棋正在运行中的状态       

                      break;

                 case 3://窗口3,代表黑棋在中途暂停的状态

                      break;

                 case 4://窗口4,代表红棋正在运行中的状态          
                  ucRedFlag=0;    //红棋暂停计时
                  ucBlackFlag=1;//黑棋继续计时
                                  ucWd=2; //切换到黑棋正在运行中的状态

                                  ucDisplayUpdate=1;//更新显示
                      break;

                 case 5://窗口5,代表红棋在中途暂停的状态

                      break;

          }

          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 14:// 14号键 对应朱兆祺学习板的S14键

          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 15:// 15号键 对应朱兆祺学习板的S15键

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

    case 16:// 16号键 对应朱兆祺学习板的S16键    黑棋按下
              switch(ucWd)//本程序最核心的变量ucWd
              {
                 case 1://窗口1,代表刚上电,完成或者复位后的状态          
                  ucRedFlag=1;    //红棋继续计时
                  ucBlackFlag=0;//黑棋暂停计时
                                  ucWd=4; //切换到红棋正在运行中的状态

                                  ucDisplayUpdate=1;//更新显示
                      break;

                 case 2://窗口2,代表黑棋正在运行中的状态       
                  ucRedFlag=1;    //红棋继续计时
                  ucBlackFlag=0;//黑棋暂停计时
                                  ucWd=4; //切换到红棋正在运行中的状态

                                  ucDisplayUpdate=1;//更新显示
                      break;

                 case 3://窗口3,代表黑棋在中途暂停的状态

                      break;

                 case 4://窗口4,代表红棋正在运行中的状态          

                      break;

                 case 5://窗口5,代表红棋在中途暂停的状态

                      break;

          }
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
}               
}



void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断
key_scan(); //放在定时中断里的按键扫描函数
time_service();//放在定时中断里的时间应用程序

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

display_drive();//放在定时中断里的数码管驱动函数

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

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


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


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

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

hc595_drive(0x00,0x00);
TMOD=0x01;//设置定时器0为工作方式1

TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral() //第二区 初始化外围
{


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



}
总结陈词:
这节讲了象棋比赛专用计时器的项目程序。为了继续加深读者理解按键和显示是如何有规律关联起来的,下节会继续讲一个相关的小项目程序。欲知详情,请听下回分解-----带数码管显示的加法简易计算器。

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

shinehjx 发表于 2014-3-21 09:05:25

精彩,楼主出书吧

szzyq 发表于 2014-3-21 10:24:23

记得不久前楼主还很喜欢用延时函数,包括在中断里,按键消抖等

吴坚鸿 发表于 2014-3-21 13:01:12

本帖最后由 吴坚鸿 于 2014-3-21 16:57 编辑

szzyq 发表于 2014-3-21 10:24
记得不久前楼主还很喜欢用延时函数,包括在中断里,按键消抖等

第一:我去年刚开始发表矩阵按键驱动程序的时候,是在定时中断里加了一个delay(4)的延时,但是这个延时不是用来去抖动的,而是用来等待输出信号线稳定,我本意是在此处插入4个nop空指令而已,于是乎,一部分人看到我居然敢在定时中断里用delay延时,立马嘲笑我的技术水平,后来为了避免误会,我就干脆删掉delay(4)这4个空指令,程序没有任何影响。但是说实话,如果是薄膜按键或者走线比较长的按键,此处是应该加几个空延时(或者用计数延时的方式),否则不好使。等待列输出信号稳定再判断输入的IO口状态,这个是我在复杂的工控项目中总结出来的宝贵经验。

第二:我去年在刚发表动态数码管扫描程序的时候,也在定时中断里加了一个短延时的delay函数,是想让数码管显示更加漂亮均匀而已,当然大多时候都可以不用加delay,可以直接用计数延时的方式替代。但是有些情况,比如定时器的中断重装值很小的时候,要很久才能中断一次,这个时候就不能用计数延时方式,只能用delay小延时,只要在定时中断里用了delay小延时后对系统没有影响,谁规定不能在中断里用delay?

第三:综上所述,大多数的情况下,我都不建议在定时中断里加入delay延时。但是在一些项目上我敢在定时中断里加入delay函数,恰好证明鸿哥艺高人胆大。只要能满足项目需求,哪里有那么多条条框框,有时候以毒攻毒才是最好的解药。

吴坚鸿 发表于 2014-3-21 13:11:58

shinehjx 发表于 2014-3-21 09:05
精彩,楼主出书吧

目前是有三个出版社跟我联系,但是我暂时不考虑出书。出书很麻烦的。我不愿意花那么多时间去迎合出版社的条条框框。我喜欢随心所欲在论坛上多分享一些技术资料和经验,我享受这个过程。而且这样免费的传播效果更好,它能像“病毒”一样到处传播,让更多初学者获益。

skyhuo 发表于 2014-3-21 13:14:38

好人!!!!

qxwan250 发表于 2014-3-21 13:57:37

支持一下

renmin 发表于 2014-3-21 14:00:29

期待串口通讯出现,辛苦了。

qxwan250 发表于 2014-3-21 14:08:31

mark,支持!

Pupil 发表于 2014-3-21 14:19:18

学习过楼主的按键检测,顶起

ourAVR_m16 发表于 2014-3-21 15:08:31

吴坚鸿 发表于 2014-3-10 13:58
第八节:在定时中断函数里执行独立按键的扫描程序。

开场白:


鸿哥,把按键扫描程序全部放在定时中断函数里头处理,定时中断处理时间增加了,是否会导致定时中断的精度下降啊?实际项目中影响如何?

武林古代史 发表于 2014-3-21 15:38:33

以人为鉴···

grash 发表于 2014-3-21 15:47:23

确实受益匪浅啊

吴坚鸿 发表于 2014-3-21 16:50:00

renmin 发表于 2014-3-21 14:00
期待串口通讯出现,辛苦了。

等我再写两三节数码管显示的例子之后,就会开始跟大家分享我常用的几种串口程序框架与数据结构,绝对非常精彩和震撼,敬请关注。

吴坚鸿 发表于 2014-3-21 16:54:43

ourAVR_m16 发表于 2014-3-21 15:08
鸿哥,把按键扫描程序全部放在定时中断函数里头处理,定时中断处理时间增加了,是否会导致定时中断的精度 ...

(1)只要你不是用单片机来做实时时钟,大部分的项目都不会有影响。
(2)少数对时间比较敏感的项目,你可以把我的按键扫描放在主函数的循环里扫描。

yeyo 发表于 2014-3-21 23:58:05

感谢楼主分享,顶一个!

renmin 发表于 2014-3-22 15:53:48

本帖最后由 renmin 于 2014-3-22 15:55 编辑

吴坚鸿 发表于 2014-3-21 16:50
等我再写两三节数码管显示的例子之后,就会开始跟大家分享我常用的几种串口程序框架与数据结构,绝对非常 ...

辛苦了,谢谢。
期待中..............

caizhihe11 发表于 2014-3-22 16:20:44

mark,期待下节课

yeyuaihaozhe 发表于 2014-3-22 16:51:17

还没有看呢,先顶一下

hmzwm 发表于 2014-3-22 16:54:24

严重同意楼主的话,心平气和地交流!十分感谢楼主的分享精神——赠人玫瑰,手有余香!

hmzwm 发表于 2014-3-22 16:59:51

看了一节,觉得楼主比大学时的老师讲的更清晰。

吴坚鸿 发表于 2014-3-22 17:22:20

hmzwm 发表于 2014-3-22 16:59
看了一节,觉得楼主比大学时的老师讲的更清晰。

感谢你给予这么高的评价。

dragonbbc 发表于 2014-3-22 17:28:28

感觉是要加精的节奏

fengyunyu 发表于 2014-3-22 18:01:22

教程全部发完了么?以前21icbbs上有个农民讲习所发的入门文章是不错的。

leon...... 发表于 2014-3-22 19:27:47

这个要顶,分享经验

吴坚鸿 发表于 2014-3-22 19:36:48

fengyunyu 发表于 2014-3-22 18:01
教程全部发完了么?以前21icbbs上有个农民讲习所发的入门文章是不错的。

后面还有很多内容要分享,我每个星期都会抽点时间来写一两节,计划不会少于100节。

dongwang_fl 发表于 2014-3-22 21:46:51

这个不错哦。支持

yyinfo263 发表于 2014-3-22 22:03:39

看了几节很详细 谢谢楼主的好文

wiser803 发表于 2014-3-23 04:19:47

本帖最后由 wiser803 于 2014-3-23 04:20 编辑

连载很精彩,也很实用,期待楼主后续文章会更精彩。{:biggrin:}

wjwjwjwj98 发表于 2014-3-23 09:29:59

这个不错,慢慢跟着学,现在努力学C,过段时间学,先收藏!{:biggrin:}

liss 发表于 2014-3-23 11:02:03

谢谢LZ分享,先收藏。。。

lyx1218 发表于 2014-3-23 14:38:26

听课中。。。。。

laip11011 发表于 2014-3-23 16:06:34

支持一下!

shower.xu 发表于 2014-3-23 16:58:28

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

我也想呵呵LZ这句话,不过想想他可能比我们幸福,不用纠结4位机那点可怜的性能,连timer中断都没有,call指令都要自己写

wjy80 发表于 2014-3-23 18:22:53

支持一下!

minicatcatcn 发表于 2014-3-23 18:39:08

后排留爪

bbandpp 发表于 2014-3-23 18:41:09

顶起来

radar_12345 发表于 2014-3-23 19:04:02


支持一下!            

lkllkl 发表于 2014-3-24 08:55:03

不错,支持楼主!!

gofygba 发表于 2014-3-24 09:30:52

支持支持

yangyiyin 发表于 2014-3-24 11:58:20

楼主资料非常给力!

renmin 发表于 2014-3-24 14:11:49

每天来拜读几遍

suomi 发表于 2014-3-24 14:44:58

菜鸟学习中~~

lryxr2507 发表于 2014-3-24 14:48:16

支持楼主!

yangyiyin 发表于 2014-3-24 21:48:42

一天一个程序向老大哥学习!

Skyujian 发表于 2014-3-24 22:32:30

楼主请继续。

无敌笨笨熊 发表于 2014-3-24 22:36:26

不错A 努力学习中

皮爱了西 发表于 2014-3-24 22:41:00

这个必须占位,希望楼主坚持写完。

勤劳的小码农 发表于 2014-3-24 22:52:20

mark。此贴必须mark

qq635274216 发表于 2014-3-25 19:09:19

二姨家里看过

ourAVR_m16 发表于 2014-3-25 23:22:55

把鸿哥前面的内容都看完了,程序架构思想和逻辑分析很有启发性,期待更新……

ourAVR_m16 发表于 2014-3-25 23:23:40

把鸿哥前面的内容都看完了,程序架构思想和逻辑分析很有启发性,期待更新……

Juggernaut 发表于 2014-3-26 02:45:16

支持楼主mark

ZJSXHWL000000 发表于 2014-3-26 06:14:04

拜读,mark下,谢谢分享!

lianzr 发表于 2014-3-26 06:47:11

标记下!

lianzr 发表于 2014-3-26 06:47:58

标记下,

wq_601840968 发表于 2014-3-26 10:18:17

楼主可以出本书呀

yinyanqing 发表于 2014-3-26 17:09:15

mark,标记一下,有时间就看看

陶新成 发表于 2014-3-26 17:18:30

从头学习一下51吧,谢谢分享!

wazhiyi 发表于 2014-3-26 17:35:03

顶一下,这么火啊

Insist 发表于 2014-3-26 22:26:03

支持 支持

NM2012 发表于 2014-3-26 23:17:20

不错,希望楼主经常更新{:victory:}

wildcat7261 发表于 2014-3-26 23:24:54

学习            

andy2003hunan 发表于 2014-3-27 01:02:00

顶起,辛苦了!

superplaim 发表于 2014-3-27 01:34:02

吴坚鸿 发表于 2014-3-10 11:27
第一节:吴坚鸿谈初学单片机的误区。

(1)很难记住繁杂的寄存器?寄存器不用死记硬背,我做了那么久单片 ...

楼主好牛 忍不住回个贴

tragedy 发表于 2014-3-27 10:25:36

新手福利呀,现在学单片机越来越简单了

ourAVR_m16 发表于 2014-3-27 11:52:11

给大家把鸿哥用的开发板的原理图找来了,方面大家学习

shiyuzuxia1111 发表于 2014-3-27 12:01:53

就凭楼主这份真诚,大赞啊!!!{:lol:}

吴坚鸿 发表于 2014-3-27 12:36:14

第三十六节:带数码管显示的加法简易计算器。

开场白:
    这一节要做一个简单的计算器。这个计算器不带小数点,只能进行不超过8位数据的加法运算,它麻雀虽小但是五脏俱全,它能清晰地勾勒出商业计算器的程序框架和思路。读者只要看懂本节程序框架的规律,以后自己想做一个复杂一点的计算器应该是没问题的。复杂的计算器在算法上要用数组进行特殊处理,不能简单地直接用C语言的+,-,*,/运算符,这方面的内容我会在以后的章节中跟大家分享。
这一节要教会大家两个知识点:
第一个:数字按键的输入和十进制数值的移位方法。
第二个:继续加深理解按键与数码管的关联程序框架。

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

(1)硬件平台:
基于朱兆祺51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。加号键对应S13,等于号键对应S14,清除复位按键对应S16。其它按键不用。

(2)实现功能:
常用的加法计算器功能。有连加功能。
本程序有2个窗口。
第1个窗口:原始数据和运算结果窗口。比如加法运算中的被加数
第2个窗口:第二个参与运行的数据窗口。比如加法运算中的加数

(3)源代码讲解如下:

#include "REG52.H"

#define const_voice_short40   //蜂鸣器短叫的持续时间
#define const_voice_long   900   //蜂鸣器长叫的持续时间

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

#define const_1s   422   //产生一秒钟的时间基准

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void T0_time();//定时中断函数
void key_service();
void key_scan(); //按键扫描函数 放在定时中断里

void number_key_input(unsigned long ucWhichKey);//由于数字按键的代码相似度高,因此封装在这个函数里

void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive();//放在定时中断里的数码管驱动函数
void display_service();


sbit key_sr1=P0^0; //第一行输入
sbit key_sr2=P0^1; //第二行输入
sbit key_sr3=P0^2; //第三行输入
sbit key_sr4=P0^3; //第四行输入

sbit key_dr1=P0^4; //第一列输出
sbit key_dr2=P0^5; //第二列输出
sbit key_dr3=P0^6; //第三列输出
sbit key_dr4=P0^7; //第四列输出

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


sbit led_dr=P3^5; //LED指示灯


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 ucKeyStep=1;//按键扫描步骤变量

unsigned char ucKeySec=0;   //被触发的按键编号
unsigned intuiKeyTimeCnt=0; //按键去抖动延时计数器
unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

unsigned char ucRowRecord=1; //记录当前扫描到第几列了

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

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


unsigned char ucDigShowTemp=0; //临时中间变量

unsigned char ucDisplayDriveStep=1;//动态扫描数码管的步骤变量


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

unsigned long ulSource=0;//原始数据    比如在加法运算中的被加数
unsigned long ulOther=0; //另外一个参与运算的数据比如在加法运算中的加数
unsigned long ulResult=0; //运算结果
unsigned char ucOperator=0; //运行符号。0代表当前没有选择运行符号。1代表当前的运算符是加法。

/* 注释一:
*ucWd变量是本程序最核心的变量,代表数码管显示哪一个窗口
*本程序只有两个窗口,他们分别是:
*第一个窗口:原始数据和运算结果窗口。比如加法运算中的被加数
*第二个窗口:第二个参与运行的数据窗口。比如加法运算中的加数
*/
unsigned char ucWd=1;

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

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

}



void display_service()//放在定时中断里的显示应用程序
{
if(ucDisplayUpdate==1)//有数据更新显示
{
   ucDisplayUpdate=0;
         switch(ucWd)   //本程序最核心的变量ucWd
         {
         case 1://窗口1原始数据和运算结果窗口
                if(ulSource>=10000000)
                                {
                                   ucDigShow8=ulSource/10000000;
                                }
                                else
                                {
                             ucDigShow8=10;//数据显示空
                                }


                if(ulSource>=1000000)
                                {
                                   ucDigShow7=ulSource%10000000/1000000;
                                }
                                else
                                {
                             ucDigShow7=10;//数据显示空
                                }


                if(ulSource>=100000)
                                {
                                   ucDigShow6=ulSource%1000000/100000;
                                }
                                else
                                {
                             ucDigShow6=10;//数据显示空
                                }

                if(ulSource>=10000)
                                {
                                   ucDigShow5=ulSource%100000/10000;
                                }
                                else
                                {
                             ucDigShow5=10;//数据显示空
                                }

                if(ulSource>=1000)
                                {
                                   ucDigShow4=ulSource%10000/1000;
                                }
                                else
                                {
                             ucDigShow4=10;//数据显示空
                                }

                if(ulSource>=100)
                                {
                                   ucDigShow3=ulSource%1000/100;
                                }
                                else
                                {
                             ucDigShow3=10;//数据显示空
                                }

                if(ulSource>=10)
                                {
                                   ucDigShow2=ulSource%100/10;
                                }
                                else
                                {
                             ucDigShow2=10;//数据显示空
                                }

                                ucDigShow1=ulSource%10;

                break;
         case 2://窗口2第二个参与运算数据的窗口比如加法运算中的加数
                if(ulOther>=10000000)
                                {
                                   ucDigShow8=ulOther/10000000;
                                }
                                else
                                {
                             ucDigShow8=10;//数据显示空
                                }


                if(ulOther>=1000000)
                                {
                                   ucDigShow7=ulOther%10000000/1000000;
                                }
                                else
                                {
                             ucDigShow7=10;//数据显示空
                                }


                if(ulOther>=100000)
                                {
                                   ucDigShow6=ulOther%1000000/100000;
                                }
                                else
                                {
                             ucDigShow6=10;//数据显示空
                                }

                if(ulOther>=10000)
                                {
                                   ucDigShow5=ulOther%100000/10000;
                                }
                                else
                                {
                             ucDigShow5=10;//数据显示空
                                }

                if(ulOther>=1000)
                                {
                                   ucDigShow4=ulOther%10000/1000;
                                }
                                else
                                {
                             ucDigShow4=10;//数据显示空
                                }

                if(ulOther>=100)
                                {
                                   ucDigShow3=ulOther%1000/100;
                                }
                                else
                                {
                             ucDigShow3=10;//数据显示空
                                }

                if(ulOther>=10)
                                {
                                   ucDigShow2=ulOther%100/10;
                                }
                                else
                                {
                             ucDigShow2=10;//数据显示空
                                }

                                ucDigShow1=ulOther%10;

                break;
         }
}
}

void display_drive()//放在定时中断里的数码管驱动函数
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1://显示第1位
         ucDigShowTemp=dig_table;
         dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2://显示第2位
         ucDigShowTemp=dig_table;
         dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3://显示第3位
         ucDigShowTemp=dig_table;
         dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4://显示第4位
         ucDigShowTemp=dig_table;
         dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5://显示第5位
         ucDigShowTemp=dig_table;
         dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6://显示第6位
         ucDigShowTemp=dig_table;
         dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7://显示第7位
         ucDigShowTemp=dig_table;
         dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8://显示第8位
         ucDigShowTemp=dig_table;
         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;

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

         ucTempData=ucTempData<<1;
   }

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

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

         ucTempData=ucTempData<<1;
   }

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

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

}


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

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

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

         ucTempData=ucTempData<<1;
   }

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

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

         ucTempData=ucTempData<<1;
   }

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

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

}


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

switch(ucKeyStep)
{
   case 1:   //按键扫描输出第ucRowRecord列低电平
            if(ucRowRecord==1)//第一列输出低电平
                  {
             key_dr1=0;      
             key_dr2=1;
             key_dr3=1;   
             key_dr4=1;
                  }
            else if(ucRowRecord==2)//第二列输出低电平
                  {
             key_dr1=1;      
             key_dr2=0;
             key_dr3=1;   
             key_dr4=1;
                  }
            else if(ucRowRecord==3)//第三列输出低电平
                  {
             key_dr1=1;      
             key_dr2=1;
             key_dr3=0;   
             key_dr4=1;
                  }
            else   //第四列输出低电平
                  {
             key_dr1=1;      
             key_dr2=1;
             key_dr3=1;   
             key_dr4=0;
                  }

          uiKeyTimeCnt=0;//延时计数器清零
          ucKeyStep++;   //切换到下一个运行步骤
            break;

   case 2:   //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
          uiKeyTimeCnt++;
                  if(uiKeyTimeCnt>1)
                  {
                     uiKeyTimeCnt=0;
             ucKeyStep++;   //切换到下一个运行步骤
                  }
            break;

   case 3:
          if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
          {
             ucKeyStep=1;//如果没有按键按下,返回到第一个运行步骤重新开始扫描
             ucKeyLock=0;//按键自锁标志清零
             uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙   
   
                         ucRowRecord++;//输出下一列
                         if(ucRowRecord>4)
                         {
                            ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
                         }

          }
                  else if(ucKeyLock==0)//有按键按下,且是第一次触发
                  {
                     if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=1;//触发1号键 对应朱兆祺学习板的S1键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=2;//触发2号键 对应朱兆祺学习板的S2键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=3;//触发3号键 对应朱兆祺学习板的S3键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=4;//触发4号键 对应朱兆祺学习板的S4键
                           }

                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=5;//触发5号键 对应朱兆祺学习板的S5键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=6;//触发6号键 对应朱兆祺学习板的S6键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=7;//触发7号键 对应朱兆祺学习板的S7键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=8;//触发8号键 对应朱兆祺学习板的S8键
                           }
                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=9;//触发9号键 对应朱兆祺学习板的S9键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=10;//触发10号键 对应朱兆祺学习板的S10键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=11;//触发11号键 对应朱兆祺学习板的S11键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=12;//触发12号键 对应朱兆祺学习板的S12键
                           }
                              }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
                         {
                            uiKeyTimeCnt++;//去抖动延时计数器
                              if(uiKeyTimeCnt>const_key_time)
                              {
                                 uiKeyTimeCnt=0;
                                 ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                     if(ucRowRecord==1)//第一列输出低电平
                           {
                                    ucKeySec=13;//触发13号键 对应朱兆祺学习板的S13键
                           }
                     else if(ucRowRecord==2)//第二列输出低电平
                           {
                                    ucKeySec=14;//触发14号键 对应朱兆祺学习板的S14键
                           }
                     else if(ucRowRecord==3)//第三列输出低电平
                           {
                                    ucKeySec=15;//触发15号键 对应朱兆祺学习板的S15键
                           }
                     else   //第四列输出低电平
                           {
                                    ucKeySec=16;//触发16号键 对应朱兆祺学习板的S16键
                           }
                              }
                        
                         }
                  
                  }
            break;

}


}

/* 注释三:
*按键服务程序操作的精髓在于根据当前系统处于什么窗口下,在此窗口下的运算符处于
*什么状态,然后紧紧围绕着不同的窗口ucWd,不同的ucOperator来执行不同的操作。
*/
void key_service() //第三区 按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
    case 1:// 1号键 对应朱兆祺学习板的S1键
          number_key_input(1);//由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;      
    case 2:// 2号键 对应朱兆祺学习板的S2键
          number_key_input(2);//由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 3:// 3号键 对应朱兆祺学习板的S3键
          number_key_input(3);//由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
    case 4:// 4号键 对应朱兆祺学习板的S4键
          number_key_input(4);//由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 5:// 5号键 对应朱兆祺学习板的S5键
          number_key_input(5);//由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 6:// 6号键 对应朱兆祺学习板的S6键
          number_key_input(6);//由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 7:// 7号键 对应朱兆祺学习板的S7键
          number_key_input(7);//由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 8:// 8号键 对应朱兆祺学习板的S8键
          number_key_input(8);//由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 9:// 9号键 对应朱兆祺学习板的S9键
          number_key_input(9);//由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 10:// 把这个按键专门用来输入数字0    对应朱兆祺学习板的S10键
          number_key_input(0);//由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 11:// 11号键 对应朱兆祺学习板的S11键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 12:// 12号键 对应朱兆祺学习板的S12键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 13:// 13号键 加号按键对应朱兆祺学习板的S13键
          switch(ucWd)
                 {
                case 1:   //在原始数据和运算结果的窗口下
                   ucOperator=1; //加法
                   ulOther=ulSource;//第二个运算数默认等于原始数
                   ucDisplayUpdate=1;//刷新显示窗口
                               break;
                case 2:   //在第二个参与运算数据的窗口下
                   ulResult=ulSource+ulOther;//连加
                   ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
                   ucWd=1;      //切换到第一个窗口
                   ucDisplayUpdate=1;//刷新显示窗口
                           break;
          }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 14:// 14号键 等于号按键对应朱兆祺学习板的S14键
          switch(ucWd)
                 {
                case 1:   //在原始数据和运算结果的窗口下
                           switch(ucOperator)//根据不同的运算符号进行不同的操作
                           {
                                   case 0://无运算符号

                                      break;
                                   case 1://加法
                            ulResult=ulSource+ulOther;//连加
                            ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
                            ucDisplayUpdate=1;//刷新显示窗口
                                      break;
                                   case 2://减法本程序没有减法功能,如果读者想增加减法程序,可以按键这个框架添加下去

                                      break;
                       
                           }
                               break;
                case 2:   //在第二个参与运算数据的窗口下
                           switch(ucOperator)//根据不同的运算符号进行不同的操作
                           {
                                   case 1://加法
                            ulResult=ulSource+ulOther;//连加
                            ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
                            ucWd=1;      //切换到第一个窗口
                            ucDisplayUpdate=1;//刷新显示窗口
                                      break;
                                   case 2://减法本程序没有减法功能,如果读者想增加减法程序,可以按键这个框架添加下去

                                      break;
                       
                           }
                           break;
          }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 15:// 15号键 对应朱兆祺学习板的S15键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 16:// 16号键 清除按键 相当于复位的功能。重新输入数据对应朱兆祺学习板的S16键
              ulSource=0;
                  ulOther=0;
          ulResult=0;
                  ucOperator=0;
          ucWd=1;      //切换到第一个窗口
          ucDisplayUpdate=1;//刷新显示窗口
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
}               
}


/* 注释四:
* 此处参与运算的输入数字ucWhichKey记得用最大变量类型unsigned long,可以避免数据溢出等错误
*/
void number_key_input(unsigned long ucWhichKey)//由于数字按键的代码相似度高,因此封装在这个函数里
{


    switch(ucWd)
           {
           case 1:   //在原始数据和运算结果的窗口下
            switch(ucOperator)//根据不同的运算符号进行不同的操作
                        {
                           case 0://无运算符号按键输入原始数据,比如被加输
                                if(ulSource<=9999999) //最大只能输入8位数
                                        {
                     ulSource=ulSource*10+ucWhichKey;//十进制的数值移位方法。
                                        }
                                break;
                           default://在已经按下了运算符号的情况下
                  ulOther=0;//第二个运算数先清零,再输入新的数据,然后马上切换到第2个窗口下
                  ulOther=ucWhichKey;
                  ucWd=2; //马上切换到第二个窗口下
                                break;
                       
                        }

            ucDisplayUpdate=1;//刷新显示窗口
                        break;
           case 2:   //在第二个参与运算数据的窗口下   按键输入第二个参与运算的数据
                        if(ulOther<=9999999) //最大只能输入8位数
                        {
               ulOther=ulOther*10+ucWhichKey;//十进制的数值移位方法。
                  }

            ucDisplayUpdate=1;//刷新显示窗口
                  break;
    }

}


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

display_drive();//放在定时中断里的数码管驱动函数

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

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


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


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

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

hc595_drive(0x00,0x00);
TMOD=0x01;//设置定时器0为工作方式1

TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral() //第二区 初始化外围
{


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



}
总结陈词:
这节讲了加法简易计算器的程序项目。为了让读者理解运动,按键,显示是如何有规律关联起来的,下节会继续讲一个相关的小项目程序。欲知详情,请听下回分解-----数码管作为仪表盘显示跑马灯的方向,速度和运行状态。

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

guxingganyue 发表于 2014-3-28 11:07:04

写的不错,期待更多的精彩内容。

673835452 发表于 2014-3-28 11:27:59

新人学习!!

yeksw206 发表于 2014-3-28 12:10:40

好东西~~~~~

jeoo8888 发表于 2014-3-28 15:53:34

很好写得不错大力支持

zyc 发表于 2014-3-29 07:18:20

谢谢分享

牛东 发表于 2014-3-29 08:38:32

ourAVR_m16 发表于 2014-3-27 11:52
给大家把鸿哥用的开发板的原理图找来了,方面大家学习

谢谢,兄弟有心了!!呵呵

apolloalfred 发表于 2014-4-1 13:05:45

多谢分享,有学到东西,辛苦辛苦!

lukefan2008 发表于 2014-4-1 17:14:44

收藏了,楼主讲的都是精华啊
页: 1 2 [3] 4 5 6 7 8 9 10 11 12
查看完整版本: 从业将近十年!手把手教你单片机程序框架(连载)