打印
[51单片机]

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

[复制链接]
楼主: jianhong_wu
手机看帖
扫描二维码
随时随地手机跟帖
401
楼主,50岁的我跟你学单片机

使用特权

评论回复
402
狮子fish| | 2018-5-22 19:16 | 只看该作者
这个按键的局部变量为什么一定要加static

使用特权

评论回复
403
jianhong_wu|  楼主 | 2018-5-29 09:55 | 只看该作者
第一百一十八节: 按键让某位数码管闪烁跳动来设置参数。
第一百一十八节.pdf (127.77 KB)
【118.1   按键让某位数码管闪烁跳动来设置参数。】
   
                上图118.1.1  数码管




                上图118.1.2  独立按键

         
                上图118.1.3  有源蜂鸣器

       当一个窗口只有一个数据的时候,只需以“窗口”为支点,切换到某个窗口下去设置某个数据即可。但是,当某个窗口有几个数据时,就必须在以“窗口”为支点的前提下,再细分出一个二级的支点,这个二级支点就是“局部”(或者称为子菜单)。“窗口”对应一个“窗口选择”的全局变量Gu8Wd,“局部”对应一个“局部选择”的全局变量Gu8Part。数据需要更新显示输出到屏幕(数码管)时,有两种更新方式,一种是“整屏更新”,另一种是“局部更新”。“整屏更新”只有一个整屏的更新变量Gu8WdUpdate,而“局部更新”有N个更新变量Gu8PartUpdate_x(Gu8PartUpdate_1,Gu8PartUpdate_2,Gu8PartUpdate_3),一个窗口下有多少个数据就存在多少个局部的更新变量Gu8PartUpdate_x,这些局部的更新变量在不同的窗口下是可以共用的。当某个局部被选中的时候,可以有很多种表现方式,比如在液晶屏上,常见的有光标跳动,某行文字的底色变色(反显),本节例程用的数码管,当某个局部被选中的时候,用某位数码管闪烁跳动的方式。
       本节小项目的程序功能,在一个窗口下,对单片机内部四个参数Gu8SetData_4,Gu8SetData_3,Gu8SetData_2,Gu8SetData_1进行编辑。这四个参数的范围是从0到9,从左到右分别显示在四位数码管上,每一位数码管对应一个数据。比如左起第1位是Gu8SetData_4,左起第2位是Gu8SetData_3,左起第3位是Gu8SetData_2,左起第4位是Gu8SetData_1。K1是局部选择的切换按键,每按一次,数码管从左到右,依次闪烁跳动,表示某个数据被选中。K2是数字累加按键,每按一次,闪烁跳动的数字会累加1。K3是数字递减按键,每按一次,闪烁跳动的数字会递减1。代码如下:
#include "REG52.H"  

#define KEY_FILTER_TIME  25
#define SCAN_TIME  1   
#define VOICE_TIME   50   
#define BLINK_TIME   250    //数码管闪烁跳动的时间的间隔


void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void KeyScan(void);  
void KeyTask(void);  

void VoiceScan(void);  
void DisplayScan(void);  
void DisplayTask(void);  //上层显示的任务函数
void Wd1(void);   //窗口1显示函数
void PartUpdate(unsigned char u8Part);  //局部选择对应的某个局部变量更新显示输出

void BeepOpen(void);   
void BeepClose(void);

sbit KEY_INPUT1=P2^2;  
sbit KEY_INPUT2=P2^1;  
sbit KEY_INPUT3=P2^0;  

sbit P1_0=P1^0;  
sbit P1_1=P1^1;  
sbit P1_2=P1^2;  
sbit P1_3=P1^3;

sbit P3_4=P3^4;

//数码管转换表
code unsigned char Cu8DigTable[]=
{
0x3f,  //0       序号0
0x06,  //1       序号1
0x5b,  //2       序号2
0x4f,  //3       序号3
0x66,  //4       序号4
0x6d,  //5       序号5
0x7d,  //6       序号6
0x07,  //7       序号7
0x7f,  //8       序号8
0x6f,  //9       序号9
0x00,  //不显示  序号10
0x40,  //横杠-   序号11
};

volatile unsigned char vGu8ScanTimerFlag=0;  
volatile unsigned int vGu16ScanTimerCnt=0;  

volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  

volatile unsigned char vGu8BlinkTimerFlag=0;   //数码管闪烁跳动的定时器
volatile unsigned int vGu16BlinkTimerCnt=0;  

unsigned char Gu8SetData_4=0; //单片机内部第4个可编辑的参数
unsigned char Gu8SetData_3=0; //单片机内部第3个可编辑的参数
unsigned char Gu8SetData_2=0; //单片机内部第2个可编辑的参数
unsigned char Gu8SetData_1=0; //单片机内部第1个可编辑的参数

unsigned char Gu8Wd=1;   //窗口选择变量。人机交互程序框架的支点。初始化开机后显示第1个窗口。
unsigned char Gu8WdUpdate=1;  //整屏更新变量。初始化为1开机后整屏更新一次显示。
unsigned char Gu8Part=0;   //局部选择变量。0代表当前窗口下没有数据被选中。
unsigned char Gu8PartUpdate_1=0;   //局部1的更新变量,
unsigned char Gu8PartUpdate_2=0;   //局部2的更新变量
unsigned char Gu8PartUpdate_3=0;   //局部3的更新变量
unsigned char Gu8PartUpdate_4=0;   //局部4的更新变量


volatile unsigned char vGu8Display_Righ_4=0;   //左起第1位初始化显示数值“0”
volatile unsigned char vGu8Display_Righ_3=0;   //左起第2位初始化显示数值“0”
volatile unsigned char vGu8Display_Righ_2=0;   //左起第3位初始化显示数值“0”
volatile unsigned char vGu8Display_Righ_1=0;   //左起第4位初始化显示数值“0”

volatile unsigned char vGu8Display_Righ_Dot_4=0;  
volatile unsigned char vGu8Display_Righ_Dot_3=0;     
volatile unsigned char vGu8Display_Righ_Dot_2=0;  
volatile unsigned char vGu8Display_Righ_Dot_1=0;  

volatile unsigned char vGu8KeySec=0;  

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
KeyTask();      //按键的任务函数
DisplayTask();  //数码管显示的上层任务函数
    }
}

void PartUpdate(unsigned char u8Part)  //局部选择对应的某个局部变量更新显示输出
{
    switch(u8Part)
{
   case 1:
Gu8PartUpdate_1=1;
break;
   case 2:
Gu8PartUpdate_2=1;
break;
   case 3:
Gu8PartUpdate_3=1;
break;
   case 4:
Gu8PartUpdate_4=1;
break;
}

}

void KeyTask(void)    //按键的任务函数
{
if(0==vGu8KeySec)
{
return;
}

switch(vGu8KeySec)
{
   case 1:     //局部切换的按键
        switch(Gu8Wd) //在某个窗口下
        {
           case 1:     //在窗口1下
                //以下之所以有两个PartUpdate(Gu8Part),是因为相邻的两个局部发生了变化。

                PartUpdate(Gu8Part);  //切换之前的局部进行更新。
                Gu8Part++;  //切换到下一个局部
                if(Gu8Part>4)
                {
Gu8Part=0;
}
                PartUpdate(Gu8Part);  //切换之后的局部进行更新。
                break;
}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

   case 2:     //累加的按键
        switch(Gu8Wd) //在某个窗口下
        {
           case 1:     //在窗口1下
                switch(Gu8Part)  //二级支点的局部选择
                {
                    case 1:  //局部1被选中,代表左起第1位数据Gu8SetData_4被选中。
Gu8SetData_4++;
if(Gu8SetData_4>9)
{
Gu8SetData_4=9;
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                          break;

                    case 2:  //局部2被选中,代表左起第2位数据Gu8SetData_3被选中。
Gu8SetData_3++;
if(Gu8SetData_3>9)
{
Gu8SetData_3=9;
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                          break;

                    case 3:  //局部3被选中,代表左起第3位数据Gu8SetData_2被选中。
Gu8SetData_2++;
if(Gu8SetData_2>9)
{
Gu8SetData_2=9;
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                          break;

                    case 4:  //局部4被选中,代表左起第4位数据Gu8SetData_1被选中。
Gu8SetData_1++;
if(Gu8SetData_1>9)
{
Gu8SetData_1=9;
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                          break;

}
break;

           case 2:     //在窗口2下(本节只用到窗口1)
                    break;
}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

   case 3:     //递减的按键
        switch(Gu8Wd) //在某个窗口下
        {
           case 1:     //在窗口1下
                switch(Gu8Part)  //二级支点的局部选择
                {
                    case 1:  //局部1被选中,代表左起第1位数据Gu8SetData_4被选中。
if(Gu8SetData_4>0)
{
Gu8SetData_4--;
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                          break;

                    case 2:  //局部2被选中,代表左起第2位数据Gu8SetData_3被选中。
if(Gu8SetData_3>0)
{
Gu8SetData_3--;
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                          break;

                    case 3:  //局部3被选中,代表左起第3位数据Gu8SetData_2被选中。
if(Gu8SetData_2>0)
{
Gu8SetData_2--;
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                          break;

                    case 4:  //局部4被选中,代表左起第4位数据Gu8SetData_1被选中。
if(Gu8SetData_1>0)
{
Gu8SetData_1--;
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                          break;

}
break;

           case 2:     //在窗口2下(本节只用到窗口1)
                    break;
}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;
}
}

void DisplayTask(void) //数码管显示的上层任务函数
{
  switch(Gu8Wd)  //以窗口选择Gu8Wd为支点,去执行对应的窗口显示函数。又一次用到switch语句
{
    case 1:
        Wd1();   //窗口1显示函数
        break;
    case 2:      //窗口2显示选择(本节只用到窗口1)
        break;
}

}

void Wd1(void)   //窗口1显示函数
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量
static unsigned char Su8BlinkFlag=0;  //两种状态的切换判断的中间变量


if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

//不显示任何一个小数点
vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=0;  
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  

Gu8PartUpdate_1=1;  //局部1更新显示
Gu8PartUpdate_2=1  ;//局部2更新显示
Gu8PartUpdate_3=1;  //局部3更新显示
Gu8PartUpdate_4=1;  //局部4更新显示

}

if(1==Gu8PartUpdate_1) //局部1更新显示
{
Gu8PartUpdate_1=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_4=Gu8SetData_4;  //显示左起第1个数据Gu8SetData_4

//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
}

if(1==Gu8PartUpdate_2) //局部2更新显示
{
Gu8PartUpdate_2=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_3=Gu8SetData_3;  //显示左起第2个数据Gu8SetData_3

//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_3=Su8Temp_3;  //过渡需要显示的数据到底层驱动变量
}

if(1==Gu8PartUpdate_3) //局部3更新显示
{
Gu8PartUpdate_3=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_2=Gu8SetData_2;  //显示左起第3个数据Gu8SetData_2

//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
}

if(1==Gu8PartUpdate_4) //局部4更新显示
{
Gu8PartUpdate_4=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_1=Gu8SetData_1;  //显示左起第4个数据Gu8SetData_1

//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量
}

if(0==vGu16BlinkTimerCnt)  //某位被选中的数码管跳动闪烁的定时器
{
vGu8BlinkTimerFlag=0;
    vGu16BlinkTimerCnt=BLINK_TIME;  //重设定时器的定时时间
vGu8BlinkTimerFlag=1;

    switch(Gu8Part)  //某个局部被选中,则闪烁跳动
    {
        case 1:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_4=10;  //左起第1个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_4=Gu8SetData_4;  //左起第1个显示数据Gu8SetData_4
}

             break;

        case 2:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_3=10;  //左起第2个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_3=Gu8SetData_3;  //左起第2个显示数据Gu8SetData_3
}

             break;

        case 3:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_2=10;  //左起第3个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_2=Gu8SetData_2;  //左起第3个显示数据Gu8SetData_2
}

             break;

        case 4:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_1=10;  //左起第3个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_1=Gu8SetData_1;  //左起第4个显示数据Gu8SetData_1
}

             break;
        default:   //都没有被选中的时候
       Su8Temp_4=Gu8SetData_4;  //左起第1个显示数据Gu8SetData_4
       Su8Temp_3=Gu8SetData_3;  //左起第2个显示数据Gu8SetData_3
       Su8Temp_2=Gu8SetData_2;  //左起第3个显示数据Gu8SetData_2
       Su8Temp_1=Gu8SetData_1;  //左起第4个显示数据Gu8SetData_1
             break;
}

vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量


}

}


void KeyScan(void)  //按键底层的驱动扫描函数,放在定时中断函数里
{
   static unsigned char Su8KeyLock1;
   static unsigned int  Su16KeyCnt1;
   static unsigned char Su8KeyLock2;
   static unsigned int  Su16KeyCnt2;
    static unsigned char Su8KeyLock3;
   static unsigned int  Su16KeyCnt3;

   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;
      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
         Su8KeyLock1=1;  
         vGu8KeySec=1;   
      }
   }

   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;  
         vGu8KeySec=2;   
      }
   }

   if(0!=KEY_INPUT3)
   {
      Su8KeyLock3=0;
      Su16KeyCnt3=0;      
   }
   else if(0==Su8KeyLock3)
   {
      Su16KeyCnt3++;
      if(Su16KeyCnt3>=KEY_FILTER_TIME)
      {
         Su8KeyLock3=1;  
         vGu8KeySec=3;   
      }
   }

}

void DisplayScan(void)    //数码管底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8GetCode;  
static unsigned char Su8ScanStep=1;  

if(0==vGu16ScanTimerCnt)  
{


    P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

    switch(Su8ScanStep)
{
       case 1:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_1];

if(1==vGu8Display_Righ_Dot_1)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=0;
P1_1=1;  
P1_2=1;  
P1_3=1;            
break;

       case 2:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_2];
if(1==vGu8Display_Righ_Dot_2)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=0;
P1_2=1;
P1_3=1;         
break;

       case 3:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_3];
if(1==vGu8Display_Righ_Dot_3)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=0;  
P1_3=1;         
break;

       case 4:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_4];
if(1==vGu8Display_Righ_Dot_4)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=0;           
break;

}

Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;  
vGu8ScanTimerFlag=1;  
}
}


void VoiceScan(void) //蜂鸣器的驱动函数
{

          static unsigned char Su8Lock=0;  

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;  
BeepOpen();
     }
    else  
{     

                       vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;     
BeepClose();  
                   }

}
          }         
}

void BeepOpen(void)
{
P3_4=0;  
}

void BeepClose(void)
{
P3_4=1;  
}

void T0_time() interrupt 1     
{
VoiceScan();    //蜂鸣器的驱动函数
KeyScan();      //按键底层的驱动扫描函数
DisplayScan();  //数码管底层的驱动扫描函数

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  
{
vGu16ScanTimerCnt--;  //递减式的软件定时器
}

if(1==vGu8BlinkTimerFlag&&vGu16BlinkTimerCnt>0)   //数码管闪烁跳动的定时器
{
vGu16BlinkTimerCnt--;  //递减式的软件定时器
}

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}


使用特权

评论回复
404
风雨潇潇| | 2018-6-1 16:37 | 只看该作者
jianhong_wu 发表于 2017-10-9 10:02
第九十节: 多任务并行处理两路跑马灯。

【90.1   多任务并行处理。】

这个算多任务吗?

使用特权

评论回复
405
jianhong_wu|  楼主 | 2018-6-3 10:36 | 只看该作者
第一百一十九节: 一个完整的人机界面的程序框架的脉络。
第一百一十九节.pdf (73.52 KB)
【119.1   一个完整的人机界面的程序框架的脉络。】

       前面两节例子告诉我们,一个完整的人机界面的程序框架包含两个要素,分别是“支点”与“更新”。“支点”包括“窗口选择”和“局部选择”,“更新”包括“整屏更新”和“局部更新”。
       “支点”的作用是把显示函数与按键函数完美无缝的关联起来,两个函数同样的“支点”促使同样的“话语体系”,让“所见即所得”实时同步,确保按键操作的数据就是当前显示被选中的数据。
       “静态数据”与“动态数据”的概念。被窗口显示的数据通常有两种:一种是静态数据,比如装饰门面的数据,只能显示不能更改的数据,以及图片图标这类数据;另外一种是动态数据,这种数据在窗口显示上是活动的可编辑的,是需要经常修改的,往往也是系统核心的数据,需要保存或者需要跟某些关键运动密切相关的数据。比如,在前面章节中,数码管要显示三个窗口“1-XX”,“2-YY”,“3-ZZ”,其中“1-”、“2-”、“3-”是属于静态数据,它们是起“装饰”作用的。而“XX”、“YY”、“ZZ”则是动态数据,它们是可编辑的,也是单片机系统内部核心的数据。
       “整屏更新”与“局部更新”的分工。“整屏更新”主要负责在切换新窗口时,把“静态数据”一次性显示到当前窗口。而“局部更新”主要负责在当前窗口下显示“动态数据”。
       下面,我把一个完整的人机界面的程序框架的脉络勾勒出来,让大家有一个整体的观感,这种人机界面的程序框架放之四海而皆准,我已把它应用在各种数码管,单色液晶屏,彩屏,电脑上位机等项目上。假设某个项目中只有两个”窗口”只有两个“局部”,程序框架的脉络如下:

       显示部分:

void DisplayTask(void) //数码管显示的上层任务函数
{
  switch(Gu8Wd)  //以“窗口选择”Gu8Wd为支点
{
    case 1:
        Wd1();   //窗口1显示函数
        break;
    case 2:     
        Wd2();   //窗口2显示函数
        break;
}
}


void Wd1(void)   //窗口1显示函数
{
if(1==Gu8WdUpdate) //整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

          ......    //此处省略N行代码,用来显示静态的数据,比如图片图标,或者装饰的数据


//以下,“整屏更新”必然是要把所有的“局部更新”都触发一次
Gu8PartUpdate_1=1;  //局部1更新显示
Gu8PartUpdate_2=1  ;//局部2更新显示
}

if(1==Gu8PartUpdate_1) //局部1更新显示
{
Gu8PartUpdate_1=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

          ......    //此处省略N行代码,用来显示动态的数据。比如可编辑的数据,实时变化的数据

}

if(1==Gu8PartUpdate_2) //局部2更新显示
{
Gu8PartUpdate_2=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

          ......    //此处省略N行代码,用来显示动态的数据。比如可编辑的数据,实时变化的数据

}

if(0==vGu16BlinkTimerCnt)  //跳动的光标,或者动态闪烁的某位被选中的数据
{
vGu8BlinkTimerFlag=0;
    vGu16BlinkTimerCnt=BLINK_TIME;  //重设定时器的定时时间
vGu8BlinkTimerFlag=1;

        ......    //此处省略N行代码,用来制作跳动的光标或者某位被选中而闪烁的数据

}

}


void Wd2(void)   //窗口2显示函数
{
          ......    //此处省略N行代码,窗口2显示函数的代码跟窗口1类似
}



       按键部分:

void KeyTask(void)    //按键的任务函数
{
if(0==vGu8KeySec)
{
return;
}

switch(vGu8KeySec)
{
   case 1:     //1号按键
        switch(Gu8Wd) //以“窗口选择”Gu8Wd为支点
        {
           case 1:     //在窗口1下
                switch(Gu8Part)  //以“局部选择”Gu8Part为支点
                {
                    case 1:  

                          ......    //此处省略N行代码

                          break;

                    case 2:  //局部2被选中

                          ......    //此处省略N行代码

                          break;
}
break;

           case 2:     //在窗口2下
                switch(Gu8Part)  //以“局部选择”Gu8Part为支点
                {
                    case 1:  

                          ......    //此处省略N行代码

                          break;

                    case 2:  //局部2被选中

                          ......    //此处省略N行代码

                          break;
}
break;
}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

   case 2:     //2号按键

        ......    //此处省略N行代码,跟1号按键的代码类似

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

}
}


使用特权

评论回复
406
狮子fish| | 2018-6-7 18:39 | 只看该作者
支持楼主,坐等更新

使用特权

评论回复
407
jianhong_wu|  楼主 | 2018-6-10 12:13 | 只看该作者
第一百二十节: 按键切换窗口切换局部来设置参数。
第一百二十节.pdf (147.57 KB)
【120.1   按键切换窗口切换局部来设置参数。】
     
                上图120.1.1  数码管


                上图120.1.2  独立按键

           
                上图120.1.3  有源蜂鸣器

       为了更好理解上一节提出的人机界面程序框架的脉络,本节程序恰好包含了整屏更新与局部更新的应用,同时也引入了一个新的知识点:在人机界面的程序框架中,常常会遇到需要以“位”来编辑某个数据的情况,这种情况实际上是先把“待编辑数据”分解成几个“位”中间临时个体,然后显示并且编辑这些“位”中间临时个体,编辑结束后,再把这些“位”中间临时个体合并成一个完整的数据赋值给“待编辑数据”。
       本节程序功能如下:
      (1)有3个窗口1-XX,2-YY,3-ZZ,其中XX,YY,ZZ分别代表3个可编辑的数据Gu8SetDate_1,Gu8SetDate_2,Gu8SetDate_3。数据范围是从0到99。
      (2)K1按键。含“短按”与“长按”复合双功能。当数码管“没有闪烁”时,“短按”K1按键可以切换窗口,而“长按”K1按键会使数码管从“没有闪烁”进入到“闪烁模式”。当数码管处于“闪烁模式”时,“短按”K1可以使数码管在十位和个位之间切换“闪烁”的“局部位”,而“长按”K1表示更改完毕当前窗口数据并从“闪烁模式”退出到“没有闪烁”。
      (3)K2按键。当数码管处于“闪烁模式”时,每按一次K2按键就可以使当前闪烁的某位数码管“递增1”。
      (4)K3按键。当数码管处于“闪烁模式”时,每按一次K2按键就可以使当前闪烁的某位数码管“递减1”。
       上述功能,在窗口切换和退出“闪烁模式”时用到整屏更新,在闪烁的某位数码管切换“局部”时用到局部更新。代码如下:

#include "REG52.H"  

#define KEY_FILTER_TIME  25    //按键的“短按”兼“滤波”的“稳定时间”
#define KEY_LONG_TIME    500   //按键的“长按”兼“滤波”的“稳定时间”

#define SCAN_TIME  1   
#define VOICE_TIME   50   
#define BLINK_TIME   250    //数码管闪烁跳动的时间的间隔

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void KeyScan(void);  
void KeyTask(void);  

void VoiceScan(void);  
void DisplayScan(void);  
void DisplayTask(void);  //上层显示的任务函数
void Wd1(void);   //窗口1显示函数
void Wd2(void);   //窗口2显示函数
void Wd3(void);   //窗口3显示函数

void PartUpdate(unsigned char u8Part);  //局部选择对应的某个局部变量更新显示输出

void BeepOpen(void);   
void BeepClose(void);

sbit KEY_INPUT1=P2^2;  
sbit KEY_INPUT2=P2^1;  
sbit KEY_INPUT3=P2^0;  

sbit P1_0=P1^0;  
sbit P1_1=P1^1;  
sbit P1_2=P1^2;  
sbit P1_3=P1^3;

sbit P3_4=P3^4;

//数码管转换表
code unsigned char Cu8DigTable[]=
{
0x3f,  //0       序号0
0x06,  //1       序号1
0x5b,  //2       序号2
0x4f,  //3       序号3
0x66,  //4       序号4
0x6d,  //5       序号5
0x7d,  //6       序号6
0x07,  //7       序号7
0x7f,  //8       序号8
0x6f,  //9       序号9
0x00,  //不显示  序号10
0x40,  //横杠-   序号11
};

volatile unsigned char vGu8ScanTimerFlag=0;  
volatile unsigned int vGu16ScanTimerCnt=0;  

volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  

volatile unsigned char vGu8BlinkTimerFlag=0;   //数码管闪烁跳动的定时器
volatile unsigned int vGu16BlinkTimerCnt=0;  

unsigned char Gu8SetData_3=0; //单片机内部第3个可编辑的参数,在窗口3
unsigned char Gu8SetData_2=0; //单片机内部第2个可编辑的参数,在窗口2
unsigned char Gu8SetData_1=0; //单片机内部第1个可编辑的参数,在窗口1

/* 注释一:
*      在人机界面的程序框架中,常常会遇到需要以“位”来编辑某个数据的情况,这种情况
*  实际上是先把“待编辑数据”分解成几个“位”临时中间个体,然后显示并且编辑这些“位”
*  临时中间个体,编辑结束后,再把这些“位”临时中间个体合并成一个完整的数据赋值给
*  “待编辑数据”。以下Gu8EditData_2和Gu8EditData_1就是“位”临时中间个体的中间变量。
*/

unsigned char Gu8EditData_2=0;  //对应显示左起第3位数码管的“位”数据,是中间变量。
unsigned char Gu8EditData_1=0;  //对应显示左起第4位数码管的“位”数据,是中间变量。

unsigned char Gu8Wd=1;   //窗口选择变量。人机交互程序框架的支点。初始化开机后显示第1个窗口。
unsigned char Gu8WdUpdate=1;  //整屏更新变量。初始化为1开机后整屏更新一次显示。
unsigned char Gu8Part=0;   //局部选择变量。0代表当前窗口下没有数据被选中。
unsigned char Gu8PartUpdate_1=0;   //局部1的更新变量,
unsigned char Gu8PartUpdate_2=0;   //局部2的更新变量


volatile unsigned char vGu8Display_Righ_4=1;   //左起第1位初始化显示窗口“1”
volatile unsigned char vGu8Display_Righ_3=11;  //左起第2位初始化显示横杠“-”
volatile unsigned char vGu8Display_Righ_2=0;   //左起第3位初始化显示数值“0”
volatile unsigned char vGu8Display_Righ_1=0;   //左起第4位初始化显示数值“0”

//不显示小数点
volatile unsigned char vGu8Display_Righ_Dot_4=0;  
volatile unsigned char vGu8Display_Righ_Dot_3=0;     
volatile unsigned char vGu8Display_Righ_Dot_2=0;  
volatile unsigned char vGu8Display_Righ_Dot_1=0;  

volatile unsigned char vGu8KeySec=0;  

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
KeyTask();      //按键的任务函数
DisplayTask();  //数码管显示的上层任务函数
    }
}

void PartUpdate(unsigned char u8Part)  //局部选择对应的某个局部变量更新显示输出
{
    switch(u8Part)
{
   case 1:
Gu8PartUpdate_1=1;
break;
   case 2:
Gu8PartUpdate_2=1;
break;
}

}

void KeyTask(void)    //按键的任务函数
{
if(0==vGu8KeySec)
{
return;
}

switch(vGu8KeySec)
{
   case 1:     //K1按键的“短按”,具有“切换窗口”和“切换局部”的双功能。
        if(0==Gu8Part)  //处于“没有闪烁”的时候,是“切换窗口”
{
            switch(Gu8Wd) //在某个窗口下
            {
                case 1:     //在窗口1下
Gu8Wd=2;  //切换到窗口2
Gu8EditData_2=Gu8SetData_2/10%10;  //“待编辑数据”分解成中间个体
Gu8EditData_1=Gu8SetData_2/1%10;   //“待编辑数据”分解成中间个体
              Gu8WdUpdate=1;  //整屏更新
                      break;

                case 2:     //在窗口2下
Gu8Wd=3;  //切换到窗口3
Gu8EditData_2=Gu8SetData_3/10%10;  //“待编辑数据”分解成中间个体
Gu8EditData_1=Gu8SetData_3/1%10;   //“待编辑数据”分解成中间个体
              Gu8WdUpdate=1;  //整屏更新
                      break;

                case 3:     //在窗口3下
Gu8Wd=1;  //切换到窗口1
Gu8EditData_2=Gu8SetData_1/10%10;  //“待编辑数据”分解成中间个体
Gu8EditData_1=Gu8SetData_1/1%10;   //“待编辑数据”分解成中间个体
             Gu8WdUpdate=1;  //整屏更新
                     break;

}
}
            else    //处于“闪烁模式”的时候,是“切换局部”
{
    PartUpdate(Gu8Part);  //切换之前的局部进行更新。
    Gu8Part++;  //切换局部
            if(Gu8Part>2)
            {
Gu8Part=1;
}
            PartUpdate(Gu8Part);  //切换之后的局部进行更新。
}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

   case 2:     //递增按键K2
        switch(Gu8Wd) //在某个窗口下
        {
           case 1:     //在窗口1下
           case 2:     //在窗口2下,窗口2与窗口1的代码完全一模一样,因此可以这样共享
           case 3:     //在窗口3下,窗口3与窗口1的代码完全一模一样,因此可以这样共享
                switch(Gu8Part)  //二级支点的局部选择
                {
                    case 1:  //局部1被选中,代表左起第3位数码管被选中。
Gu8EditData_2++;  //编辑“十位”个体的中间变量
if(Gu8EditData_2>9)
{
Gu8EditData_2=9;
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                          break;

                   case 2:  //局部2被选中,代表左起第4位数码管被选中。
Gu8EditData_1++;  //编辑“个位”个体的中间变量
if(Gu8EditData_1>9)
{
Gu8EditData_1=9;
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                          break;
}
break;
}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

   case 3:     //递减按键K3
        switch(Gu8Wd) //在某个窗口下
        {
           case 1:     //在窗口1下
           case 2:     //在窗口2下,窗口2与窗口1的代码完全一模一样,因此可以这样共享
           case 3:     //在窗口3下,窗口3与窗口1的代码完全一模一样,因此可以这样共享
                switch(Gu8Part)  //二级支点的局部选择
                {
                    case 1:  //局部1被选中,代表左起第3位数码管被选中。
if(Gu8EditData_2>0)
{
Gu8EditData_2--; //编辑“十位”个体的中间变量
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                          break;

                   case 2:  //局部2被选中,代表左起第4位数码管被选中。
if(Gu8EditData_1>0)
{
Gu8EditData_1--; //编辑“个位”个体的中间变量
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                          break;
}
break;
}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

   case 4:     //K1按键的“长按”,具有进入和退出“闪烁模式”的功能。“退出”隐含“确定”

        switch(Gu8Wd) //在某个窗口下
        {
           case 1:     //在窗口1下
                if(0==Gu8Part)  //处于“没有闪烁”的时候,将进入“闪烁模式”
{
    Gu8EditData_2=Gu8SetData_1/10%10;  //先把“待编辑数据”分解成中间个体
    Gu8EditData_1=Gu8SetData_1/1%10;   //先把“待编辑数据”分解成中间个体
    Gu8Part=1;  //进入“闪烁模式”,从“局部1”开始闪烁
}
                    else    //处于“闪烁模式”的时候,将退出到“没有闪烁”,隐含“确定”功能
{
    Gu8SetData_1=Gu8EditData_2*10+Gu8EditData_1; //把个体合并还原成数据
    Gu8Part=0;  //退出“闪烁模式”
            Gu8WdUpdate=1;  //整屏更新
}
                break;

           case 2:     //在窗口2下
                if(0==Gu8Part)  //处于“没有闪烁”的时候,将进入“闪烁模式”
{
    Gu8EditData_2=Gu8SetData_2/10%10;  //先把“待编辑数据”分解成中间个体
    Gu8EditData_1=Gu8SetData_2/1%10;   //先把“待编辑数据”分解成中间个体
    Gu8Part=1;  //进入“闪烁模式”,从“局部1”开始闪烁
}
                    else    //处于“闪烁模式”的时候,将退出到“没有闪烁”,隐含“确定”功能
{
    Gu8SetData_2=Gu8EditData_2*10+Gu8EditData_1; //把个体合并还原成数据
    Gu8Part=0;  //退出“闪烁模式”
            Gu8WdUpdate=1;  //整屏更新
}
                break;

           case 3:     //在窗口3下
                if(0==Gu8Part)  //处于“没有闪烁”的时候,将进入“闪烁模式”
{
    Gu8EditData_2=Gu8SetData_3/10%10;  //先把“待编辑数据”分解成中间个体
    Gu8EditData_1=Gu8SetData_3/1%10;   //先把“待编辑数据”分解成中间个体
    Gu8Part=1;  //进入“闪烁模式”,从“局部1”开始闪烁
}
                    else    //处于“闪烁模式”的时候,将退出到“没有闪烁”,隐含“确定”功能
{
    Gu8SetData_3=Gu8EditData_2*10+Gu8EditData_1; //把个体合并还原成数据
    Gu8Part=0;  //退出“闪烁模式”
            Gu8WdUpdate=1;  //整屏更新
}
                break;

}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

}
}

void DisplayTask(void) //数码管显示的上层任务函数
{
  switch(Gu8Wd)  //以窗口选择Gu8Wd为支点,去执行对应的窗口显示函数。又一次用到switch语句
{
    case 1:
        Wd1();   //窗口1显示函数
        break;
    case 2:
        Wd2();   //窗口2显示函数
        break;
    case 3:
        Wd3();   //窗口3显示函数
        break;
}

}

void Wd1(void)   //窗口1显示函数
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量
static unsigned char Su8BlinkFlag=0;  //两种状态的切换判断的中间变量


if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_4=1;   //左起第1位数码管,显示窗口“1”,属于静态数据,起“装饰”作用。
Su8Temp_3=11;  //左起第2位数码管,显示横杠“-”,属于静态数据,起“装饰”作用。

vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;  //过渡需要显示的数据到底层驱动变量

//不显示任何一个小数点,属于静态数据,起“装饰”作用,切换窗口后只扫描一次的代码。
vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=0;  
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  

Gu8PartUpdate_1=1;  //局部1更新显示
Gu8PartUpdate_2=1  ;//局部2更新显示
}

if(1==Gu8PartUpdate_1) //局部1更新显示
{
Gu8PartUpdate_1=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_2=Gu8EditData_2;  //显示“十位”的临时中间个体,属于动态数据。

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
}

if(1==Gu8PartUpdate_2) //局部2更新显示
{
Gu8PartUpdate_2=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_1=Gu8EditData_1;  //显示“个位”的临时中间个体,属于动态数据。

vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量
}


if(0==vGu16BlinkTimerCnt)  //某位被选中的数码管跳动闪烁的定时器
{
vGu8BlinkTimerFlag=0;
    vGu16BlinkTimerCnt=BLINK_TIME;  //重设定时器的定时时间
vGu8BlinkTimerFlag=1;

    switch(Gu8Part)  //某个局部被选中,则闪烁跳动
    {
        case 1:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_2=10;  //左起第3个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_2=Gu8EditData_2;  //显示“十位”的临时中间个体,属于动态数据。
}

             break;

        case 2:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_1=10;  //左起第4个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_1=Gu8EditData_1;  //显示“个位”的临时中间个体,属于动态数据。
}

             break;

        default:   //都没有被选中的时候
       Su8Temp_2=Gu8EditData_2;  //显示“十位”的临时中间个体,属于动态数据。
Su8Temp_1=Gu8EditData_1;  //显示“个位”的临时中间个体,属于动态数据。      
             break;
}

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量

}
}

void Wd2(void)   //窗口2显示函数
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量
static unsigned char Su8BlinkFlag=0;  //两种状态的切换判断的中间变量


if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_4=2;   //左起第1位数码管,显示窗口“2”,属于静态数据,起“装饰”作用。
Su8Temp_3=11;  //左起第2位数码管,显示横杠“-”,属于静态数据,起“装饰”作用。

vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;  //过渡需要显示的数据到底层驱动变量

//不显示任何一个小数点,属于静态数据,起“装饰”作用,切换窗口后只扫描一次的代码。
vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=0;  
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  

Gu8PartUpdate_1=1;  //局部1更新显示
Gu8PartUpdate_2=1  ;//局部2更新显示
}

if(1==Gu8PartUpdate_1) //局部1更新显示
{
Gu8PartUpdate_1=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_2=Gu8EditData_2;  //显示“十位”的临时中间个体,属于动态数据。

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
}

if(1==Gu8PartUpdate_2) //局部2更新显示
{
Gu8PartUpdate_2=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_1=Gu8EditData_1;  //显示“个位”的临时中间个体,属于动态数据。

vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量
}


if(0==vGu16BlinkTimerCnt)  //某位被选中的数码管跳动闪烁的定时器
{
vGu8BlinkTimerFlag=0;
    vGu16BlinkTimerCnt=BLINK_TIME;  //重设定时器的定时时间
vGu8BlinkTimerFlag=1;

    switch(Gu8Part)  //某个局部被选中,则闪烁跳动
    {
        case 1:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_2=10;  //左起第3个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_2=Gu8EditData_2;  //显示“十位”的临时中间个体,属于动态数据。
}

             break;

        case 2:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_1=10;  //左起第4个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_1=Gu8EditData_1;  //显示“个位”的临时中间个体,属于动态数据。
}

             break;

        default:   //都没有被选中的时候
       Su8Temp_2=Gu8EditData_2;  //显示“十位”的临时中间个体,属于动态数据。
Su8Temp_1=Gu8EditData_1;  //显示“个位”的临时中间个体,属于动态数据。      
             break;
}

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量

}
}

void Wd3(void)   //窗口3显示函数
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量
static unsigned char Su8BlinkFlag=0;  //两种状态的切换判断的中间变量


if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_4=3;   //左起第1位数码管,显示窗口“3”,属于静态数据,起“装饰”作用。
Su8Temp_3=11;  //左起第2位数码管,显示横杠“-”,属于静态数据,起“装饰”作用。

vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;  //过渡需要显示的数据到底层驱动变量

//不显示任何一个小数点,属于静态数据,起“装饰”作用,切换窗口后只扫描一次的代码。
vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=0;  
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  

Gu8PartUpdate_1=1;  //局部1更新显示
Gu8PartUpdate_2=1  ;//局部2更新显示
}

if(1==Gu8PartUpdate_1) //局部1更新显示
{
Gu8PartUpdate_1=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_2=Gu8EditData_2;  //显示“十位”的临时中间个体,属于动态数据。

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
}

if(1==Gu8PartUpdate_2) //局部2更新显示
{
Gu8PartUpdate_2=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_1=Gu8EditData_1;  //显示“个位”的临时中间个体,属于动态数据。

vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量
}


if(0==vGu16BlinkTimerCnt)  //某位被选中的数码管跳动闪烁的定时器
{
vGu8BlinkTimerFlag=0;
    vGu16BlinkTimerCnt=BLINK_TIME;  //重设定时器的定时时间
vGu8BlinkTimerFlag=1;

    switch(Gu8Part)  //某个局部被选中,则闪烁跳动
    {
        case 1:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_2=10;  //左起第3个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_2=Gu8EditData_2;  //显示“十位”的临时中间个体,属于动态数据。
}

             break;

        case 2:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_1=10;  //左起第4个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_1=Gu8EditData_1;  //显示“个位”的临时中间个体,属于动态数据。
}

             break;

        default:   //都没有被选中的时候
       Su8Temp_2=Gu8EditData_2;  //显示“十位”的临时中间个体,属于动态数据。
Su8Temp_1=Gu8EditData_1;  //显示“个位”的临时中间个体,属于动态数据。      
             break;
}

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量

}
}


void KeyScan(void)  //按键底层的驱动扫描函数,放在定时中断函数里
{
   static unsigned char Su8KeyShortFlag=0;  //按键“短按”触发的标志   
   static unsigned char Su8KeyLock1;
   static unsigned int  Su16KeyCnt1;
   static unsigned char Su8KeyLock2;
   static unsigned int  Su16KeyCnt2;
   static unsigned char Su8KeyLock3;
   static unsigned int  Su16KeyCnt3;

  //需要详细分析以下这段“短按”与“长按”代码的朋友,请参考第96节。
   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
      if(1==Su8KeyShortFlag)  
      {
Su8KeyShortFlag=0;   
vGu8KeySec=1;    //触发K1的“短按”
}  
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;

      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
            Su8KeyShortFlag=1;  
      }

      if(Su16KeyCnt1>=KEY_LONG_TIME)
      {
            Su8KeyLock1=1;      
Su8KeyShortFlag=0;  
            vGu8KeySec=4; //触发K1的“长按”
      }
   }

   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;  
         vGu8KeySec=2;   
      }
   }

   if(0!=KEY_INPUT3)
   {
      Su8KeyLock3=0;
      Su16KeyCnt3=0;      
   }
   else if(0==Su8KeyLock3)
   {
      Su16KeyCnt3++;
      if(Su16KeyCnt3>=KEY_FILTER_TIME)
      {
         Su8KeyLock3=1;  
         vGu8KeySec=3;   
      }
   }

}

void DisplayScan(void)    //数码管底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8GetCode;  
static unsigned char Su8ScanStep=1;  

if(0==vGu16ScanTimerCnt)  
{


    P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

    switch(Su8ScanStep)
{
       case 1:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_1];

if(1==vGu8Display_Righ_Dot_1)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=0;
P1_1=1;  
P1_2=1;  
P1_3=1;            
break;

       case 2:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_2];
if(1==vGu8Display_Righ_Dot_2)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=0;
P1_2=1;
P1_3=1;         
break;

       case 3:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_3];
if(1==vGu8Display_Righ_Dot_3)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=0;  
P1_3=1;         
break;

       case 4:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_4];
if(1==vGu8Display_Righ_Dot_4)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=0;           
break;

}

Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;  
vGu8ScanTimerFlag=1;  
}
}


void VoiceScan(void) //蜂鸣器的驱动函数
{

          static unsigned char Su8Lock=0;  

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;  
BeepOpen();
     }
    else  
{     

                       vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;     
BeepClose();  
                   }

}
          }         
}

void BeepOpen(void)
{
P3_4=0;  
}

void BeepClose(void)
{
P3_4=1;  
}

void T0_time() interrupt 1     
{
VoiceScan();    //蜂鸣器的驱动函数
KeyScan();      //按键底层的驱动扫描函数
DisplayScan();  //数码管底层的驱动扫描函数

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  
{
vGu16ScanTimerCnt--;  //递减式的软件定时器
}

if(1==vGu8BlinkTimerFlag&&vGu16BlinkTimerCnt>0)   //数码管闪烁跳动的定时器
{
vGu16BlinkTimerCnt--;  //递减式的软件定时器
}

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}


使用特权

评论回复
408
jianhong_wu|  楼主 | 2018-6-21 14:36 | 只看该作者
第一百二十一节: 可调参数的数码管倒计时。
第一百二十一节.pdf (156.86 KB)
【121.1   可调参数的数码管倒计时。】

                上图121.1.1  数码管



                上图121.1.2  独立按键

        
                上图121.1.3  有源蜂鸣器

       上节讲如何设置数据,本节讲“数据”如何关联“某种功能”,本节的“可调参数”就是“数据”,“倒计时”就是“某种功能”。程序功能如下:
      (1)倒计时范围从0.00秒到99.99秒,范围可调。开机默认是:10.00秒。
      (2)K1[设置键]。当数码管“没有闪烁”时,“长按”K1键则进入“闪烁模式”,某位数码管开始闪烁,闪烁的位代表可修改的位数据,此时再“短按”K1按键可以使数码管在位之间切换闪烁。当数码管处于“闪烁模式”时,“长按”K1按键,代表数据修改完成并停止闪烁。
      (3)K2[加键]与[复位健]。当数码管某位正在闪烁时,此时K2是[加键],按K2会使位数据“自加1”。当数码管“没有闪烁”时,此时K2是[复位键],按K2会使当前倒计时数据恢复“设置值”。
      (4)K3[减键]与[开始健]。当数码管某位正在闪烁时,此时K3是[减键],按K3会使位数据“自减1”。当数码管“没有闪烁”时,此时K3是[开始键],按K3开始倒计时。
       代码如下:

#include "REG52.H"  

#define KEY_FILTER_TIME  25    //按键的“短按”兼“滤波”的“稳定时间”
#define KEY_LONG_TIME    500   //按键的“长按”兼“滤波”的“稳定时间”

#define SCAN_TIME  1   
#define VOICE_TIME   50   
#define BLINK_TIME   250    //数码管闪烁跳动的时间的间隔

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void KeyScan(void);  
void KeyTask(void);  

void VoiceScan(void);  
void DisplayScan(void);  
void DisplayTask(void);  //上层显示的任务函数
void RunTask(void);      //倒计时的应用程序

void Wd1(void);   //窗口1显示函数。用来设置参数。
void Wd2(void);   //窗口2显示函数。倒计时的运行显示窗口

void PartUpdate(unsigned char u8Part);  //局部选择对应的某个局部变量更新显示输出

void BeepOpen(void);   
void BeepClose(void);

sbit KEY_INPUT1=P2^2;  
sbit KEY_INPUT2=P2^1;  
sbit KEY_INPUT3=P2^0;  

sbit P1_0=P1^0;  
sbit P1_1=P1^1;  
sbit P1_2=P1^2;  
sbit P1_3=P1^3;

sbit P3_4=P3^4;

//数码管转换表
code unsigned char Cu8DigTable[]=
{
0x3f,  //0       序号0
0x06,  //1       序号1
0x5b,  //2       序号2
0x4f,  //3       序号3
0x66,  //4       序号4
0x6d,  //5       序号5
0x7d,  //6       序号6
0x07,  //7       序号7
0x7f,  //8       序号8
0x6f,  //9       序号9
0x00,  //不显示  序号10
0x40,  //横杠-   序号11
};

volatile unsigned char vGu8ScanTimerFlag=0;  
volatile unsigned int vGu16ScanTimerCnt=0;  

volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  

volatile unsigned char vGu8BlinkTimerFlag=0;   //数码管闪烁跳动的定时器
volatile unsigned int vGu16BlinkTimerCnt=0;  

//倒计时的软件定时器,注意,这里是unsigned long类型,范围是0到4294967295毫秒
volatile unsigned char vGu8CountdownTimerFlag=0;  
volatile unsigned long vGu32CountdownTimerCnt=10000;  //当前倒计时的计时值
unsigned long Gu32SetData_Countdown=10000;  //倒计时的设置值


//数码管上层每10ms就定时刷新一次显示的软件定时器。用于及时更新显示秒表当前的实时数值
volatile unsigned char vGu8UpdateTimerFlag=0;  
volatile unsigned int vGu16UpdateTimerCnt=0;  

unsigned char Gu8RunStart=0;  //应用程序的总启动
unsigned char Gu8RunStep=0;  //应用程序的总运行步骤。建议跟vGu8RunStart成双成对出现
unsigned char Gu8RunStatus=0; //当前倒计时的状态。0代表停止,1代表正在工作中

unsigned char Gu8EditData_4=0;  //对应显示右起第4位数码管的“位”数据,是中间变量。
unsigned char Gu8EditData_3=0;  //对应显示右起第3位数码管的“位”数据,是中间变量。
unsigned char Gu8EditData_2=0;  //对应显示右起第2位数码管的“位”数据,是中间变量。
unsigned char Gu8EditData_1=0;  //对应显示右起第1位数码管的“位”数据,是中间变量。

unsigned char Gu8Wd=1;   //窗口选择变量。人机交互程序框架的支点。初始化开机后显示第1个窗口。
unsigned char Gu8WdUpdate=1;  //整屏更新变量。初始化为1开机后整屏更新一次显示。
unsigned char Gu8Part=0;   //局部选择变量。0代表当前窗口下没有数据被选中。
unsigned char Gu8PartUpdate_1=0;   //局部1的更新变量,
unsigned char Gu8PartUpdate_2=0;   //局部2的更新变量
unsigned char Gu8PartUpdate_3=0;   //局部3的更新变量,
unsigned char Gu8PartUpdate_4=0;   //局部4的更新变量

volatile unsigned char vGu8Display_Righ_4=1;  //显示“1”,跟vGu32CountdownTimerCnt高位一致
volatile unsigned char vGu8Display_Righ_3=0;  
volatile unsigned char vGu8Display_Righ_2=0;  
volatile unsigned char vGu8Display_Righ_1=0;  

volatile unsigned char vGu8Display_Righ_Dot_4=0;  
volatile unsigned char vGu8Display_Righ_Dot_3=1;    //开机默认保留显示2个小数点
volatile unsigned char vGu8Display_Righ_Dot_2=0;  
volatile unsigned char vGu8Display_Righ_Dot_1=0;  

volatile unsigned char vGu8KeySec=0;  

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
KeyTask();      //按键的任务函数
DisplayTask();  //数码管显示的上层任务函数
RunTask();      //倒计时的应用程序
    }
}

void PartUpdate(unsigned char u8Part)  //局部选择对应的某个局部变量更新显示输出
{
    switch(u8Part)
{
   case 1:
Gu8PartUpdate_1=1;
break;
   case 2:
Gu8PartUpdate_2=1;
break;
   case 3:
Gu8PartUpdate_3=1;
break;
   case 4:
Gu8PartUpdate_4=1;
break;
}

}

void RunTask(void)  //倒计时的应用程序
{
if(0==Gu8RunStart)
{
   return;  // 如果总开关处于停止状态,则直接退出当前函数,不执行该函数以下的其它代码
}

switch(Gu8RunStep)
{
    case 0:  //在这个步骤里,主要用来初始化一些参数

vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;  //每10ms更新显示一次当前倒计时的时间
vGu8UpdateTimerFlag=1;

Gu8RunStep=1; //跳转到每10ms更新显示一次的步骤里
break;

    case 1:  //每10ms更新一次显示,确保实时显示当前倒计时的时间
       if(0==vGu16UpdateTimerCnt) //每10ms更新显示一次当前倒计时的时间
       {


    vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;  //重置定时器,为下一个10ms更新做准备
vGu8UpdateTimerFlag=1;

            Gu8WdUpdate=1;  //整屏更新一次显示当前倒计时的时间

            if(0==vGu32CountdownTimerCnt) //如果倒计时的时间到,则跳转到结束的步骤
            {
Gu8RunStep=2; //跳转到倒计时结束的步骤
}

}
break;

case 2:  //倒计时结束的步骤
//Gu8RunStatus=0; //这行代码注释掉,让每次新启动之前都必须按一次K1复位按键才有效

Gu8RunStart=0;  //倒计时的运行步骤的停止
            Gu8RunStep=0;   //总运行步骤归零。建议跟vGu8RunStart成双成对出现

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

        Gu8WdUpdate=1;  //整屏更新一次显示当前倒计时的时间

break;

}

}


void KeyTask(void)    //按键的任务函数
{
if(0==vGu8KeySec)
{
return;
}

if(0!=Gu8RunStatus) //在“非停止”状态下,用return来拦截一些“不该响应”的按键
{
    if(2==vGu8KeySec) //在“非停止”状态下,只响应[复位]这个按键
    {
       ;   //这里没有return语句,表示可以继续往下扫描本函数余下的代码,没有被拦截。
}
else
{
   return;   //其余的按键则拦截退出
}
}


switch(vGu8KeySec)
{
   case 1:     //按键K1的“短按”。切换数码管闪烁的位。
        switch(Gu8Wd) //在某个窗口下
        {
            case 1:     //在窗口1下

                 if(0!=Gu8Part)  //处于“闪烁模式”的时候,是“切换局部”
{
              PartUpdate(Gu8Part);  //切换之前的局部进行更新。
              Gu8Part++;  //切换局部
                      if(Gu8Part>4)
                      {
Gu8Part=1;
}
                      PartUpdate(Gu8Part);  //切换之后的局部进行更新。

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;
}
                      break;
            }

vGu8KeySec=0;  
break;

   case 2:     //按键K2[加键]与[复位健]
        if(0!=Gu8Part)  //处于“闪烁模式”的时候,是[加键]
{
            switch(Gu8Wd) //在某个窗口下
            {
                case 1:     //在窗口1下
                     switch(Gu8Part)  //二级支点的局部选择
                     {
                          case 1:  //局部1被选中,代表右起第4位数码管被选中。
if(Gu8EditData_4<9)
{
Gu8EditData_4++;  //编辑“千位”个体的中间变量
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                                break;

                          case 2:  //局部2被选中,代表右起第3位数码管被选中。
if(Gu8EditData_3<9)
{
Gu8EditData_3++;  //编辑“百位”个体的中间变量
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                                break;

                          case 3:  //局部3被选中,代表右起第2位数码管被选中。
if(Gu8EditData_2<9)
{
Gu8EditData_2++;  //编辑“十位”个体的中间变量
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                                break;

                          case 4:  //局部4被选中,代表右起第1位数码管被选中。
if(Gu8EditData_1<9)
{
Gu8EditData_1++;  //编辑“个位”个体的中间变量
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                                break;
}
break;
}
            }
            else   //处于“没有闪烁”的时候,是[复位健]
{
Gu8EditData_4=Gu32SetData_Countdown/10000%10; //分解成“十秒”个体
Gu8EditData_3=Gu32SetData_Countdown/1000%10;  //分解成“个秒”个体
Gu8EditData_2=Gu32SetData_Countdown/100%10; //分解成“百毫秒”个体
Gu8EditData_1=Gu32SetData_Countdown/10%10;  //分解成“十毫秒”个体

Gu8RunStatus=0; //倒计时返回停止的状态

Gu8RunStart=0;  //倒计时的运行步骤的停止
                Gu8RunStep=0;  //总运行步骤归零。建议跟vGu8RunStart成双成对出现

                Gu8Wd=1; //返回设置数据的窗口
                Gu8WdUpdate=1;  //整屏更新一次显示
}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

   case 3:     //按键K3[减键]与[开始健]
        if(0!=Gu8Part)  //处于“闪烁模式”的时候,是[减键]
{
            switch(Gu8Wd) //在某个窗口下
            {
                case 1:     //在窗口1下
                     switch(Gu8Part)  //二级支点的局部选择
                     {
                          case 1:  //局部1被选中,代表右起第4位数码管被选中。
if(Gu8EditData_4>0)
{
Gu8EditData_4--;  //编辑“十秒”个体的中间变量
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                                break;

                          case 2:  //局部2被选中,代表右起第3位数码管被选中。
if(Gu8EditData_3>0)
{
Gu8EditData_3--;  //编辑“个秒”个体的中间变量
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                                break;

                          case 3:  //局部3被选中,代表右起第2位数码管被选中。
if(Gu8EditData_2>0)
{
Gu8EditData_2--;  //编辑“百毫秒”个体的中间变量
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                                break;

                          case 4:  //局部4被选中,代表右起第1位数码管被选中。
if(Gu8EditData_1>0)
{
Gu8EditData_1--;  //编辑“十毫位”个体的中间变量
}
PartUpdate(Gu8Part); //当前局部更新显示输出到数码管
                                break;
}
break;
}
            }
            else   //处于“没有闪烁”的时候,是[开始健]
{
            if(0==Gu8RunStatus) //在停止状态下
            {

vGu8CountdownTimerFlag=0;
vGu32CountdownTimerCnt=Gu32SetData_Countdown;   //从“设置值”开始倒计时
vGu8CountdownTimerFlag=1;      //允许倒计时的软件定时器的启动

                Gu8RunStatus=1;  //倒计时处于工作状态(并且,这一瞬间才正式启动倒计时)

Gu8RunStart=1;   //倒计时的运行步骤的总开关开启
                    Gu8RunStep=0;    //总运行步骤归零。建议跟vGu8RunStart成双成对出现

                    Gu8Wd=2; //进入倒计时运行的窗口
                Gu8WdUpdate=1;  //整屏更新一次显示,确保在启动的时候能显示到最新的数据
}
}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

   case 4:     //K1按键的“长按”,具有进入和退出“闪烁模式”的功能。“退出”隐含“确定”

        switch(Gu8Wd) //在某个窗口下
        {
           case 1:     //在窗口1下
                if(0==Gu8Part)  //处于“没有闪烁”的时候,将进入“闪烁模式”
{
    Gu8EditData_4=Gu32SetData_Countdown/10000%10; //分解成“十秒”个体
    Gu8EditData_3=Gu32SetData_Countdown/1000%10;  //分解成“个秒”个体
    Gu8EditData_2=Gu32SetData_Countdown/100%10; //分解成“百毫秒”个体
    Gu8EditData_1=Gu32SetData_Countdown/10%10;  //分解成“十毫秒”个体
    Gu8Part=1;  //进入“闪烁模式”,从“局部1”开始闪烁
}
                    else    //处于“闪烁模式”的时候,将退出到“没有闪烁”,隐含“确定”功能
{
//把个体合并还原成数据
Gu32SetData_Countdown=Gu8EditData_4*10000+Gu8EditData_3*1000;
Gu32SetData_Countdown=Gu32SetData_Countdown+Gu8EditData_2*100;
Gu32SetData_Countdown=Gu32SetData_Countdown+Gu8EditData_1*10;

    Gu8Part=0;  //退出“闪烁模式”
            Gu8WdUpdate=1;  //整屏更新
}

                break;

}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

}
}

void DisplayTask(void) //数码管显示的上层任务函数
{
  switch(Gu8Wd)  //以窗口选择Gu8Wd为支点,去执行对应的窗口显示函数。又一次用到switch语句
{
    case 1:
        Wd1();   //窗口1显示函数。用来设置参数。
        break;
    case 2:
        Wd2();   //窗口2显示函数。倒计时的运行显示窗口。
        break;
}

}

void Wd1(void)   //窗口1显示函数。用来设置参数。
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量
static unsigned char Su8BlinkFlag=0;  //两种状态的切换判断的中间变量


if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

//属于静态数据,起“装饰”作用,切换窗口后只扫描一次的代码。
vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=1;   //保留显示2位小数点
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  

Gu8PartUpdate_1=1;  //局部1更新显示
Gu8PartUpdate_2=1  ;//局部2更新显示
Gu8PartUpdate_3=1  ;//局部3更新显示
Gu8PartUpdate_4=1  ;//局部4更新显示

}

if(1==Gu8PartUpdate_1) //局部1更新显示
{
Gu8PartUpdate_1=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

      if(Gu32SetData_Countdown<10000)
      {
    Su8Temp_4=10;  //显示“无”
}
else
{
    Su8Temp_4=Gu8EditData_4;  //显示“十秒”的临时中间个体,属于动态数据。
}

vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
}

if(1==Gu8PartUpdate_2) //局部2更新显示
{
Gu8PartUpdate_2=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_3=Gu8EditData_3;  //显示“个秒”的临时中间个体,属于动态数据。

vGu8Display_Righ_3=Su8Temp_3;  //过渡需要显示的数据到底层驱动变量
}

if(1==Gu8PartUpdate_3) //局部3更新显示
{
Gu8PartUpdate_3=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_2=Gu8EditData_2;  //显示“百毫秒”的临时中间个体,属于动态数据。

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
}

if(1==Gu8PartUpdate_4) //局部4更新显示
{
Gu8PartUpdate_4=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_1=Gu8EditData_1;  //显示“十毫秒”的临时中间个体,属于动态数据。

vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量
}

if(0==vGu16BlinkTimerCnt)  //某位被选中的数码管跳动闪烁的定时器
{
vGu8BlinkTimerFlag=0;
    vGu16BlinkTimerCnt=BLINK_TIME;  //重设定时器的定时时间
vGu8BlinkTimerFlag=1;

    switch(Gu8Part)  //某个局部被选中,则闪烁跳动
    {
        case 1:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_4=10;  //右起第4个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_4=Gu8EditData_4;  //显示“十秒”的临时中间个体,属于动态数据。
}

             break;

        case 2:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_3=10;  //右起第3个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_3=Gu8EditData_3;  //显示“个秒”的临时中间个体,属于动态数据。
}

             break;

        case 3:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_2=10;  //右起第2个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_2=Gu8EditData_2;  //显示“百毫秒”的临时中间个体,属于动态数据。
}

             break;

        case 4:
             if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_1=10;  //右起第1个显示“不显示”(10代表不显示)
}
else
{
Su8BlinkFlag=0;
              Su8Temp_1=Gu8EditData_1;  //显示“十毫秒”的临时中间个体,属于动态数据。
}

             break;

        default:   //都没有被选中的时候
              if(Gu32SetData_Countdown<10000)
              {
           Su8Temp_4=10;  //显示“无”
}
else
{
            Su8Temp_4=Gu8EditData_4;  //显示“十秒”的临时中间个体,属于动态数据。
}
Su8Temp_3=Gu8EditData_3;  //显示“个秒”的临时中间个体,属于动态数据。     
       Su8Temp_2=Gu8EditData_2;  //显示“百毫秒”的临时中间个体,属于动态数据。
Su8Temp_1=Gu8EditData_1;  //显示“十毫秒”的临时中间个体,属于动态数据。      
             break;
}

vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量

}
}

void Wd2(void)   //窗口2显示函数。倒计时的运行显示窗口。
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量

if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

          //先分解数据,注意,这里分解的时候,“先整除后求余”必须用一行代码一气呵成,不能拆
          //分成两行代码,否则会有隐患会有bug。除非,把四个临时变都改成unsigned long类型。

          //Su8Temp_4提取“十秒”位。
Su8Temp_4=vGu32CountdownTimerCnt/10000%10; //实际精度是0.001秒,但显示精度是0.01秒

         //Su8Temp_3提取“个秒”位。
Su8Temp_3=vGu32CountdownTimerCnt/1000%10; //实际精度是0.001秒,但显示精度是0.01秒

         //Su8Temp_2提取“百毫秒”位。
Su8Temp_2=vGu32CountdownTimerCnt/100%10; //实际精度是0.001秒,但显示精度是0.01秒

          //Su8Temp_1提取“十毫秒”位。
Su8Temp_1=vGu32CountdownTimerCnt/10%10; //实际精度是0.001秒,但显示精度是0.01秒

          //判断数据范围,来决定最高位数码管是否需要显示。
          if(vGu32CountdownTimerCnt<10000) //10.000秒。实际4位数码管最大只能显示99.99秒
          {
             Su8Temp_4=10;  //在数码管转换表里,10代表一个“不显示”的数据
}

//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;  
vGu8Display_Righ_2=Su8Temp_2;  
vGu8Display_Righ_1=Su8Temp_1;  

vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=1;   //保留显示2位小数点
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  

}
}

void KeyScan(void)  //按键底层的驱动扫描函数,放在定时中断函数里
{
   static unsigned char Su8KeyShortFlag=0;  //按键“短按”触发的标志   
   static unsigned char Su8KeyLock1;
   static unsigned int  Su16KeyCnt1;
   static unsigned char Su8KeyLock2;
   static unsigned int  Su16KeyCnt2;
   static unsigned char Su8KeyLock3;
   static unsigned int  Su16KeyCnt3;

  //需要详细分析以下这段“短按”与“长按”代码的朋友,请参考第96节。
   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
      if(1==Su8KeyShortFlag)  
      {
Su8KeyShortFlag=0;   
vGu8KeySec=1;    //触发K1的“短按”
}  
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;

      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
            Su8KeyShortFlag=1;  
      }

      if(Su16KeyCnt1>=KEY_LONG_TIME)
      {
            Su8KeyLock1=1;      
Su8KeyShortFlag=0;  
            vGu8KeySec=4; //触发K1的“长按”
      }
   }

   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;  
         vGu8KeySec=2;   
      }
   }

   if(0!=KEY_INPUT3)
   {
      Su8KeyLock3=0;
      Su16KeyCnt3=0;      
   }
   else if(0==Su8KeyLock3)
   {
      Su16KeyCnt3++;
      if(Su16KeyCnt3>=KEY_FILTER_TIME)
      {
         Su8KeyLock3=1;  
         vGu8KeySec=3;   
      }
   }

}

void DisplayScan(void)    //数码管底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8GetCode;  
static unsigned char Su8ScanStep=1;  

if(0==vGu16ScanTimerCnt)  
{


    P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

    switch(Su8ScanStep)
{
       case 1:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_1];

if(1==vGu8Display_Righ_Dot_1)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=0;
P1_1=1;  
P1_2=1;  
P1_3=1;            
break;

       case 2:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_2];
if(1==vGu8Display_Righ_Dot_2)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=0;
P1_2=1;
P1_3=1;         
break;

       case 3:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_3];
if(1==vGu8Display_Righ_Dot_3)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=0;  
P1_3=1;         
break;

       case 4:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_4];
if(1==vGu8Display_Righ_Dot_4)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=0;           
break;

}

Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;  
vGu8ScanTimerFlag=1;  
}
}

void VoiceScan(void) //蜂鸣器的驱动函数
{

          static unsigned char Su8Lock=0;  

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;  
BeepOpen();
     }
    else  
{     

                       vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;     
BeepClose();  
                   }

}
          }         
}

void BeepOpen(void)
{
P3_4=0;  
}

void BeepClose(void)
{
P3_4=1;  
}

void T0_time() interrupt 1     
{
VoiceScan();    //蜂鸣器的驱动函数
KeyScan();      //按键底层的驱动扫描函数
DisplayScan();  //数码管底层的驱动扫描函数

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  
{
vGu16ScanTimerCnt--;  //递减式的软件定时器
}

if(1==vGu8BlinkTimerFlag&&vGu16BlinkTimerCnt>0)   //数码管闪烁跳动的定时器
{
vGu16BlinkTimerCnt--;  //递减式的软件定时器
}

//每10ms就定时更新一次显示的软件定时器
if(1==vGu8UpdateTimerFlag&&vGu16UpdateTimerCnt>0)  
{
vGu16UpdateTimerCnt--;  //递减式的软件定时器
}

//倒计时实际走的时间的软件定时器,注意,这里还附加了启动状态的条件“&&1==Gu8RunStatus”
if(1==vGu8CountdownTimerFlag&&vGu32CountdownTimerCnt>0&&1==Gu8RunStatus)
{
vGu32CountdownTimerCnt--;  //递减式的软件定时器
}

TH0=0xfd;   //此参数可根据具体的时间来修改,尽量确保每定时中断一次接近1ms
TL0=0x40;   //此参数可根据具体的时间来修改,尽量确保每定时中断一次接近1ms
}

void SystemInitial(void)
{
P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

TMOD=0x01;  
TH0=0xfd;   //此参数可根据具体的时间来修改,尽量确保每定时中断一次接近1ms
TL0=0x40;   //此参数可根据具体的时间来修改,尽量确保每定时中断一次接近1ms
EA=1;      
ET0=1;      
TR0=1;      

//上电初始化开机显示的窗口
Gu8EditData_4=Gu32SetData_Countdown/10000%10; //分解成“十秒”个体
Gu8EditData_3=Gu32SetData_Countdown/1000%10;  //分解成“个秒”个体
Gu8EditData_2=Gu32SetData_Countdown/100%10; //分解成“百毫秒”个体
Gu8EditData_1=Gu32SetData_Countdown/10%10;  //分解成“十毫秒”个体
    Gu8Wd=1; //返回设置数据的窗口
    Gu8WdUpdate=1;  //整屏更新一次显示

}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}


使用特权

评论回复
409
归途2018| | 2018-6-24 22:51 | 只看该作者
持续关注

使用特权

评论回复
410
arima| | 2018-6-29 19:55 | 只看该作者
楼主加油!!!

学习学习....

使用特权

评论回复
411
jianhong_wu|  楼主 | 2018-7-1 12:36 | 只看该作者
第一百二十二节: 利用定时中断做的“时分秒”数显时钟。
第一百二十二节.pdf (142.76 KB)
【122.1   利用定时中断做的“时分秒”数显时钟。】
   
                上图122.1.1  数码管




                上图122.1.2  独立按键

         
                上图122.1.3  有源蜂鸣器

      本节的数显时钟小项目,意在人机界面程序框架的综合训练。程序功能如下:
     (1)只有“时分秒”,没有“年月日”。
     (2)平时时钟正常工作的时候,四位数码管的显示格式是这样的“HH.MM”,其中HH代表“时”,MM代表“分”,而中间的小数点“.”每隔一秒闪烁一次。
     (3)K1[设置键]与[切换窗口键]。当数码管“没有闪烁”时(处于正常工作模式),“长按”K1键则进入“闪烁模式”(修改时钟模式),“闪烁模式”一共有3个窗口,分别是“1-HH”,“2-MM”,“3-SS”。其中“HH”“MM”“SS”分别代表可修改的“时”“分”“秒”,它们处于“闪烁”的状态,代表可编辑。此时,“短按”K1按键代表[切换窗口键],可以使数码管在“1-HH”,“2-MM”,“3-SS”三个窗口之间依次切换。修改完毕后,只需“长按”K1键代表确定完成并且退出当前“闪烁模式”返回到时钟的“正常工作模式”。
     (4)K2[加键]。当数码管某位正在闪烁时,此时K2是[加键],按K2会使数据“自加1”。
     (5)K3[减键]。当数码管某位正在闪烁时,此时K3是[减键],按K3会使数据“自减1”。
     (6)处于“闪烁模式”时的3个窗口的数据范围。处于修改“时”的“1-HH”窗口时,HH的范围是:0到23;处于修改“分”的“2-MM”窗口时,MM的范围是:0到59;处于修改“秒”的“3-SS”窗口时,SS的范围是:0到59。
      代码如下:


#include "REG52.H"  

#define KEY_FILTER_TIME  25   
#define KEY_LONG_TIME    500   

#define SCAN_TIME  1   
#define VOICE_TIME   50   
#define BLINK_TIME   250   

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void KeyScan(void);  
void KeyTask(void);  

void VoiceScan(void);  
void DisplayScan(void);  
void DisplayTask(void);  
void Wd1(void);   //窗口1。时钟正常工作的窗口“HH.MM”。小数点在闪烁跳动。
void Wd2(void);   //窗口2。闪烁模式,修改“时”的“1-HH”的窗口。
void Wd3(void);   //窗口3。闪烁模式,修改“分”的“2-MM”的窗口。
void Wd4(void);   //窗口4。闪烁模式,修改“秒”的“3-SS”的窗口。

void BeepOpen(void);   
void BeepClose(void);

sbit KEY_INPUT1=P2^2;  
sbit KEY_INPUT2=P2^1;  
sbit KEY_INPUT3=P2^0;  

sbit P1_0=P1^0;  
sbit P1_1=P1^1;  
sbit P1_2=P1^2;  
sbit P1_3=P1^3;

sbit P3_4=P3^4;

//数码管转换表
code unsigned char Cu8DigTable[]=
{
0x3f,  //0       序号0
0x06,  //1       序号1
0x5b,  //2       序号2
0x4f,  //3       序号3
0x66,  //4       序号4
0x6d,  //5       序号5
0x7d,  //6       序号6
0x07,  //7       序号7
0x7f,  //8       序号8
0x6f,  //9       序号9
0x00,  //不显示  序号10
0x40,  //横杠-   序号11
};

volatile unsigned char vGu8ScanTimerFlag=0;  
volatile unsigned int vGu16ScanTimerCnt=0;  

volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  

volatile unsigned char vGu8BlinkTimerFlag=0;   
volatile unsigned int vGu16BlinkTimerCnt=0;  

//时钟的软件定时器,注意,这里是unsigned long类型,范围是0到4294967295毫秒
volatile unsigned char vGu8ClockTimerFlag=0;  
volatile unsigned long vGu32ClockTimerCnt=0;  

//时钟正常工作的时候,每500ms更新显示一次
volatile unsigned char vGu8UpdateTimerFlag=0;
volatile unsigned int vGu16UpdateTimerCnt=0;  

unsigned char Gu8EditData_1=0;  //是中间变量,用于编辑窗口“1-HH”下的HH数据。
unsigned char Gu8EditData_2=0;  //是中间变量,用于编辑窗口“2-MM”下的MM数据。
unsigned char Gu8EditData_3=0;  //是中间变量,用于编辑窗口“3-SS”下的SS数据。

unsigned char Gu8Wd=0;   //窗口选择变量。人机交互程序框架的支点。
unsigned char Gu8WdUpdate=0;  //整屏更新变量。

unsigned char Gu8PartUpdate_1=0;   //局部1的更新变量,
unsigned char Gu8PartUpdate_2=0;   //局部2的更新变量
unsigned char Gu8PartUpdate_3=0;   //局部3的更新变量,

volatile unsigned char vGu8Display_Righ_4=0;  
volatile unsigned char vGu8Display_Righ_3=0;  
volatile unsigned char vGu8Display_Righ_2=0;  
volatile unsigned char vGu8Display_Righ_1=0;  

volatile unsigned char vGu8Display_Righ_Dot_4=0;  
volatile unsigned char vGu8Display_Righ_Dot_3=1;    //开机默认保留显示2个小数点
volatile unsigned char vGu8Display_Righ_Dot_2=0;  
volatile unsigned char vGu8Display_Righ_Dot_1=0;  

volatile unsigned char vGu8KeySec=0;  

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
KeyTask();      //按键的任务函数
DisplayTask();  //数码管显示的上层任务函数
    }
}

void KeyTask(void)    //按键的任务函数
{
if(0==vGu8KeySec)
{
return;
}

switch(vGu8KeySec)
{
   case 1:     //按键K1的“短按”。在“闪烁模式”下切换数码管的窗口。
        switch(Gu8Wd) //在某个窗口下
        {
            case 2:     //窗口2。修改“时”的“1-HH”窗口。
Gu8Wd=3; //切换到窗口3的“2-MM”窗口
Gu8WdUpdate=1; //整屏更新
                      break;

            case 3:     //窗口3。修改“分”的“2-MM”窗口。
Gu8Wd=4; //切换到窗口4的“3-SS”窗口
Gu8WdUpdate=1; //整屏更新
                      break;

            case 4:     //窗口4。修改“秒”的“3-SS”窗口。
Gu8Wd=2; //切换到窗口2的“1-HH”窗口
Gu8WdUpdate=1; //整屏更新
                      break;
            }

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;

vGu8KeySec=0;  
break;

   case 2:     //按键K2[加键]

        switch(Gu8Wd) //在某个窗口下
        {
            case 2:     //窗口2。修改“时”的“1-HH”窗口。
                  if(Gu8EditData_1<23) //“时”的范围是0到23
{
Gu8EditData_1++;
}
Gu8PartUpdate_1=1;   //局部1更新显示
                      break;

            case 3:     //窗口3。修改“分”的“2-MM”窗口。
                  if(Gu8EditData_2<59) //“分”的范围是0到59
{
Gu8EditData_2++;
}
Gu8PartUpdate_1=1;   //局部1更新显示
                      break;

            case 4:     //窗口4。修改“秒”的“3-SS”窗口。
                  if(Gu8EditData_3<59) //“秒”的范围是0到59
{
Gu8EditData_3++;
}
Gu8PartUpdate_1=1;   //局部1更新显示
                      break;
            }

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

   case 3:     //按键K3[减键]与[开始健]
        switch(Gu8Wd) //在某个窗口下
        {
            case 2:     //窗口2。修改“时”的“1-HH”窗口。
                  if(Gu8EditData_1>0)
{
Gu8EditData_1--;
}
Gu8PartUpdate_1=1;   //局部1更新显示
                      break;

            case 3:     //窗口3。修改“分”的“2-MM”窗口。
                  if(Gu8EditData_2>0)
{
Gu8EditData_2--;
}
Gu8PartUpdate_1=1;   //局部1更新显示
                      break;

            case 4:     //窗口4。修改“秒”的“3-SS”窗口。
                  if(Gu8EditData_3>0)
{
Gu8EditData_3--;
}
Gu8PartUpdate_1=1;   //局部1更新显示
                      break;
            }

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

   case 4:     //K1按键的“长按”,具有进入和退出“闪烁模式”的功能。“退出”隐含“确定”

        switch(Gu8Wd) //在某个窗口下
        {
           case 1:     //窗口1。时钟正常工作的窗口。   
                vGu8ClockTimerFlag=0;  //停止时钟的定时器

Gu8EditData_1=vGu32ClockTimerCnt/3600000;  //分解成“时”个体
Gu8EditData_2=vGu32ClockTimerCnt%3600000/60000;  //分解成“分”个体
Gu8EditData_3=vGu32ClockTimerCnt%3600000%60000/1000;  //分解成“秒”个体

Gu8Wd=2; //切换到窗口2的“1-HH”的闪烁窗口
Gu8WdUpdate=1; //整屏更新
break;

           case 2:     //窗口2。修改时钟时间的“1-HH”的闪烁窗口  
           case 3:     //窗口3。修改时钟时间的“2-MM”的闪烁窗口  
           case 4:     //窗口4。修改时钟时间的“3-SS”的闪烁窗口  
//把个体合并还原成当前时钟时间的数据
vGu32ClockTimerCnt=Gu8EditData_1*3600000+Gu8EditData_2*60000+Gu8EditData_3*1000;
vGu8ClockTimerFlag=1;  //启动时钟的定时器

Gu8Wd=1; //切换到窗口1的正常工作的窗口
Gu8WdUpdate=1; //整屏更新
break;
}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

}
}

void DisplayTask(void) //数码管显示的上层任务函数
{
  switch(Gu8Wd)  //以窗口选择Gu8Wd为支点,去执行对应的窗口显示函数。又一次用到switch语句
{
    case 1:
        Wd1();   //窗口1。时钟正常运行的窗口
        break;
    case 2:
        Wd2();   //窗口2。修改“时”的“1-HH”窗口
        break;
    case 3:
        Wd3();   //窗口3。修改“分”的“2-MM”窗口
        break;
    case 4:
        Wd4();   //窗口4。修改“秒”的“3-SS”窗口
        break;
}
}

void Wd1(void)   //窗口1。时钟正常运行的窗口
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量
static unsigned char Su8BlinkFlag=0;  //两种状态的切换判断的中间变量


if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

//属于静态数据,起“装饰”作用,切换窗口后只扫描一次的代码。
vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=1;   //保留显示2位小数点
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  

Gu8PartUpdate_1=1;  //局部1更新显示
}

if(1==Gu8PartUpdate_1) //局部1更新显示,更新显示一次数据和闪烁的的小数点
{
Gu8PartUpdate_1=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_4=vGu32ClockTimerCnt/3600000/10; //时的十位
Su8Temp_3=vGu32ClockTimerCnt/3600000%10; //时的个位
Su8Temp_2=vGu32ClockTimerCnt%3600000/60000/10; //分的十位
Su8Temp_1=vGu32ClockTimerCnt%3600000/60000%10; //秒的个位

          //小数点的闪烁
          if(0==Su8BlinkFlag)
{
Su8BlinkFlag=1;
vGu8Display_Righ_Dot_3=1;   //显示第2位小数点。

}
          else
{
Su8BlinkFlag=0;
vGu8Display_Righ_Dot_3=0;   //不显示第2位小数点
}

vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量
}

if(0==vGu16UpdateTimerCnt)  //每隔500ms就更新显示一次数据和闪烁的的小数点
{
vGu8UpdateTimerFlag=0;
    vGu16UpdateTimerCnt=500;  //重设定时器的定时时间
vGu8UpdateTimerFlag=1;

Gu8PartUpdate_1=1;  //局部1更新显示
}

}

void Wd2(void)   //窗口2。修改“时”的“1-HH”窗口。
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_2,Su8Temp_1; //需要借用的中间变量
static unsigned char Su8BlinkFlag=0;  //两种状态的切换判断的中间变量


if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

//属于静态数据,起“装饰”作用,切换窗口后只扫描一次的代码。
vGu8Display_Righ_4=1;   //显示数字“1”
vGu8Display_Righ_3=11;  //显示横杠“-”

vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=0;   
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  

Gu8PartUpdate_1=1;  //局部1更新显示
}

if(1==Gu8PartUpdate_1) //局部1更新显示
{
Gu8PartUpdate_1=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_2=Gu8EditData_1/10;  //显示“时”的十位
Su8Temp_1=Gu8EditData_1%10;  //显示“时”的个位

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量
}


if(0==vGu16BlinkTimerCnt)  //某位被选中的数码管跳动闪烁的定时器
{
vGu8BlinkTimerFlag=0;
    vGu16BlinkTimerCnt=BLINK_TIME;  //重设定时器的定时时间
vGu8BlinkTimerFlag=1;

    if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_2=10;  //10代表不显示
Su8Temp_1=10;  //10代表不显示
}
else
{
Su8BlinkFlag=0;
  Su8Temp_2=Gu8EditData_1/10;  //显示“时”的十位
Su8Temp_1=Gu8EditData_1%10;  //显示“时”的个位

}

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量
}
}

void Wd3(void)   //窗口3。修改“分”的“2-MM”窗口。
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_2,Su8Temp_1; //需要借用的中间变量
static unsigned char Su8BlinkFlag=0;  //两种状态的切换判断的中间变量

if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

//属于静态数据,起“装饰”作用,切换窗口后只扫描一次的代码。
vGu8Display_Righ_4=2;   //显示数字“2”
vGu8Display_Righ_3=11;  //显示横杠“-”

vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=0;   
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  

Gu8PartUpdate_1=1;  //局部1更新显示
}

if(1==Gu8PartUpdate_1) //局部1更新显示
{
Gu8PartUpdate_1=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_2=Gu8EditData_2/10;  //显示“分”的十位
Su8Temp_1=Gu8EditData_2%10;  //显示“分”的个位

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量
}


if(0==vGu16BlinkTimerCnt)  //某位被选中的数码管跳动闪烁的定时器
{
vGu8BlinkTimerFlag=0;
    vGu16BlinkTimerCnt=BLINK_TIME;  //重设定时器的定时时间
vGu8BlinkTimerFlag=1;

    if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_2=10;  //10代表不显示
Su8Temp_1=10;  //10代表不显示
}
else
{
Su8BlinkFlag=0;
Su8Temp_2=Gu8EditData_2/10;  //显示“分”的十位
Su8Temp_1=Gu8EditData_2%10;  //显示“分”的个位
}

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量
}
}

void Wd4(void)   //窗口4。修改“秒”的“3-SS”窗口。
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_2,Su8Temp_1; //需要借用的中间变量
static unsigned char Su8BlinkFlag=0;  //两种状态的切换判断的中间变量

if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

//属于静态数据,起“装饰”作用,切换窗口后只扫描一次的代码。
vGu8Display_Righ_4=3;   //显示数字“3”
vGu8Display_Righ_3=11;  //显示横杠“-”

vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=0;   
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  

Gu8PartUpdate_1=1;  //局部1更新显示
}

if(1==Gu8PartUpdate_1) //局部1更新显示
{
Gu8PartUpdate_1=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_2=Gu8EditData_3/10;  //显示“秒”的十位
Su8Temp_1=Gu8EditData_3%10;  //显示“秒”的个位

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量
}


if(0==vGu16BlinkTimerCnt)  //某位被选中的数码管跳动闪烁的定时器
{
vGu8BlinkTimerFlag=0;
    vGu16BlinkTimerCnt=BLINK_TIME;  //重设定时器的定时时间
vGu8BlinkTimerFlag=1;

    if(0==Su8BlinkFlag)  //两种状态的切换判断
{
Su8BlinkFlag=1;
Su8Temp_2=10;  //10代表不显示
Su8Temp_1=10;  //10代表不显示
}
else
{
Su8BlinkFlag=0;
Su8Temp_2=Gu8EditData_3/10;  //显示“秒”的十位
Su8Temp_1=Gu8EditData_3%10;  //显示“秒”的个位
}

vGu8Display_Righ_2=Su8Temp_2;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_1=Su8Temp_1;  //过渡需要显示的数据到底层驱动变量
}
}

void KeyScan(void)  //按键底层的驱动扫描函数,放在定时中断函数里
{
   static unsigned char Su8KeyShortFlag=0;  //按键“短按”触发的标志   
   static unsigned char Su8KeyLock1;
   static unsigned int  Su16KeyCnt1;
   static unsigned char Su8KeyLock2;
   static unsigned int  Su16KeyCnt2;
   static unsigned char Su8KeyLock3;
   static unsigned int  Su16KeyCnt3;

  //需要详细分析以下这段“短按”与“长按”代码的朋友,请参考第96节。
   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
      if(1==Su8KeyShortFlag)  
      {
Su8KeyShortFlag=0;   
vGu8KeySec=1;    //触发K1的“短按”
}  
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;

      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
            Su8KeyShortFlag=1;  
      }

      if(Su16KeyCnt1>=KEY_LONG_TIME)
      {
            Su8KeyLock1=1;      
Su8KeyShortFlag=0;  
            vGu8KeySec=4; //触发K1的“长按”
      }
   }

   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;  
         vGu8KeySec=2;   
      }
   }

   if(0!=KEY_INPUT3)
   {
      Su8KeyLock3=0;
      Su16KeyCnt3=0;      
   }
   else if(0==Su8KeyLock3)
   {
      Su16KeyCnt3++;
      if(Su16KeyCnt3>=KEY_FILTER_TIME)
      {
         Su8KeyLock3=1;  
         vGu8KeySec=3;   
      }
   }

}

void DisplayScan(void)    //数码管底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8GetCode;  
static unsigned char Su8ScanStep=1;  

if(0==vGu16ScanTimerCnt)  
{


    P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

    switch(Su8ScanStep)
{
       case 1:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_1];

if(1==vGu8Display_Righ_Dot_1)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=0;
P1_1=1;  
P1_2=1;  
P1_3=1;            
break;

       case 2:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_2];
if(1==vGu8Display_Righ_Dot_2)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=0;
P1_2=1;
P1_3=1;         
break;

       case 3:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_3];
if(1==vGu8Display_Righ_Dot_3)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=0;  
P1_3=1;         
break;

       case 4:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_4];
if(1==vGu8Display_Righ_Dot_4)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=0;           
break;

}

Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;  
vGu8ScanTimerFlag=1;  
}
}

void VoiceScan(void) //蜂鸣器的驱动函数
{

          static unsigned char Su8Lock=0;  

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;  
BeepOpen();
     }
    else  
{     

                       vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;     
BeepClose();  
                   }

}
          }         
}

void BeepOpen(void)
{
P3_4=0;  
}

void BeepClose(void)
{
P3_4=1;  
}

void T0_time() interrupt 1     
{
VoiceScan();    //蜂鸣器的驱动函数
KeyScan();      //按键底层的驱动扫描函数
DisplayScan();  //数码管底层的驱动扫描函数

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  
{
vGu16ScanTimerCnt--;  //递减式的软件定时器
}

if(1==vGu8BlinkTimerFlag&&vGu16BlinkTimerCnt>0)   //数码管闪烁跳动的定时器
{
vGu16BlinkTimerCnt--;  //递减式的软件定时器
}

//在正常工作的窗口下,每500ms就定时更新一次显示的软件定时器
if(1==vGu8UpdateTimerFlag&&vGu16UpdateTimerCnt>0)  
{
vGu16UpdateTimerCnt--;  //递减式的软件定时器
}

//时钟实际走的时间的软件定时器,注意,这里是递增式的软件定时器
if(1==vGu8ClockTimerFlag)
{
vGu32ClockTimerCnt++;  //递增式的软件定时器
if(vGu32ClockTimerCnt>=86400000) //86400000毫秒代表24时
{
vGu32ClockTimerCnt=0;
}
}

TH0=0xfd;   //此参数可根据具体的时间来修改,尽量确保每定时中断一次接近1ms
TL0=0x40;   //此参数可根据具体的时间来修改,尽量确保每定时中断一次接近1ms
}

void SystemInitial(void)
{
P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

TMOD=0x01;  
TH0=0xfd;   //此参数可根据具体的时间来修改,尽量确保每定时中断一次接近1ms
TL0=0x40;   //此参数可根据具体的时间来修改,尽量确保每定时中断一次接近1ms
EA=1;      
ET0=1;      
TR0=1;      

//上电初始化一些关键的数据

Gu8Wd=1;   //窗口1。开机默认处于正常工作的窗口
Gu8WdUpdate=1;  //整屏更新变量

vGu8ClockTimerFlag=0;  
vGu32ClockTimerCnt=43200000;  //43200000毫秒开机默认12:00点。12时就是43200000毫秒
vGu8ClockTimerFlag=1;   //启动时钟的定时器

//时钟正常工作的时候,每500ms更新显示一次
    vGu16UpdateTimerCnt=500;  
vGu8UpdateTimerFlag=1; //启动小数点闪烁的定时器

}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}


使用特权

评论回复
412
海迹天涯| | 2018-7-3 11:25 | 只看该作者
楼主的无私奉献佩服的五体投地

使用特权

评论回复
413
jianhong_wu|  楼主 | 2018-7-9 10:08 | 只看该作者
第一百二十三节: 一种能省去一个lock自锁变量的按键驱动程序。
第一百二十三节.pdf (71.38 KB)
【123.1   一种能省去一个lock自锁变量的按键驱动程序。】

       一位群友给我提到了一个按键的改进建议,能巧妙的省去一个lock自锁变量。这个建议引起了我对“变量的分工要专一,一个变量尽量只用在一类事物上,尽量不取巧兼容”的思考。

       第一种:带lock自锁变量,也是我一直在用的代码。
if(0!=KEY_INPUT1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
    Su8KeyLock1=0; //按键解锁
    Su16KeyCnt1=0; //按键去抖动延时计数器清零,此行非常巧妙,是全场的亮点。      
}
else if(0==Su8KeyLock1)//有按键按下,且是第一次被按下。这行如果有疑问,请看92节的专题分析。
{
    Su16KeyCnt1++; //累加定时中断次数
    if(Su16KeyCnt1>=KEY_FILTER_TIME) //滤波的“稳定时间”KEY_FILTER_TIME,长度是25ms。
    {
         Su8KeyLock1=1;  //按键的自锁,避免一直触发
         vGu8KeySec=1;    //触发1号键
    }
}


       第二种:省略掉一个lock自锁变量,群友提出的改进建议。
if(0!=KEY_INPUT1)
{
    Su16KeyCnt1=0;
}
else if(Su16KeyCnt1<KEY_FILTER_TIME) //巧妙的利用了Su16KeyCnt1等于滤波时间时,只执行一次
{
    Su16KeyCnt1++;
    if(KEY_FILTER_TIME==Su16KeyCnt1) //巧妙的利用了Su16KeyCnt1等于滤波时间时,只执行一次
    {
         vGu8KeySec=1;   
    }
}


分析:
       不得不佩服群友的智慧,第二种改进后看起来非常巧妙,犹如蜻蜓点水般轻盈洒脱。但是,为此代码狂欢片刻后,我又有了新的思考和看法。“计时器Su16KeyCnt1”和“自锁变量Su8KeyLock1”是两个不同的事物,是两个不同的范畴,就应该用两个不同的变量进行区分。如果逞一时之巧,把两种不同范畴的事物巧妙合并成一个变量,势必会导致程序的“易读性”和“后续维护的可扩展性”大打折扣。“自锁变量Su8KeyLock1”真的是可有可无吗?假设,如果“计时器Su16KeyCnt1”的消抖时间KEY_FILTER_TIME要求等于0,那么第二种改进后的代码立刻暴露出了问题,行不通。而第一种代码,因为有“自锁变量Su8KeyLock1”的存在,即使消抖时间KEY_FILTER_TIME等于0,也不影响代码功能的完整性,因为第一种代码的理念是“自锁与计时器是两种不同的功能范畴,用两个不同的变量进行分开隔离,各自管理两种不同的事物,计时器即使为0也不影响代码本该有的自锁功能”。通过此例子,给初学者一个建议,在代码的“队形感,易读性,扩展性,分类清晰”和“巧妙,节省代码”两者之间,建议大家优先考虑“队形感,易读性,扩展性,分类清晰”,追求一种原则上的“工整,不出奇兵,扎硬寨,打呆仗,步步为营”,这样阵脚不易乱,能走得更远,驾驭更多千军万马的代码。

使用特权

评论回复
评论
tianqi911 2018-9-28 17:17 回复TA
膜拜曾文正公。 
414
ztzp| | 2018-7-14 22:16 | 只看该作者
谢谢你的讲解,比任何书籍都讲得清楚,不管是初学者,还是提高者都适合。62岁的我也跟着你学习单片机。

使用特权

评论回复
415
jianhong_wu|  楼主 | 2018-7-24 11:15 | 只看该作者
本帖最后由 jianhong_wu 于 2018-8-5 09:16 编辑

第一百二十五节: “双线”的肢体接触通信。
第一百二十五节.pdf (67.66 KB)
【125.1   “双线”的肢体接触通信。】

      芯片之间通信,都离不开“数据信号”和“时钟信号”,缺一不可。“数据信号”和“时钟信号”是什么关系,它们是怎样相互配合来实现通信的功能?其实原理也很简单。打个比喻,甲乙两个人,规定只能靠一只“手”和一只“脚”进行肢体接触的通信,他们之间如何传输数据?“手”可以产生“两种”状态“握紧”和“松开”,“脚”可以产生“一种”状态“踢一脚”。他们之间约定,甲发送数据给乙,乙每被甲“踢一脚”就去记录一次手的状态是“握紧”还是“松开”,“握紧”代表二进制的0,“松开”代表二进制的1,这样,如果他们之间想传输一个字节的十六进制数据0x59,只需把十六进制的数据0x59展开成二进制01011001,从右到左(从低位到高位)以“位”为单位挨个发送,过程如下:
       第一次“踢一脚”:手的状态是“松开”,记录1。
       第二次“踢一脚”:手的状态是“握紧”,记录0。
       第三次“踢一脚”:手的状态是“握紧”,记录0。
       第四次“踢一脚”:手的状态是“松开”,记录1。
       第五次“踢一脚”:手的状态是“松开”,记录1。
       第六次“踢一脚”:手的状态是“握紧”,记录0。
       第七次“踢一脚”:手的状态是“松开”,记录1。
       第八次“踢一脚”:手的状态是“握紧”,记录0。
       上述肢体接触的通信过程,其实一只“手”就代表了一根“数据线”,可以产生高电平“1”和低电平“0”这两种状态,而一只“脚”代表了一根“时钟线”,但是“踢一脚”代表了“时钟线”上的一种什么状态呢?注意,“踢一脚”既不是高电平“1”也不是低电平“0”,而是瞬间只产生一次的“上升沿”或者“下降沿”。何谓“上升沿”何谓“下降沿”?“上升沿”是代表“时钟线从低电平跳变到高电平的瞬间”,“下升沿”是代表“时钟线从高电平跳变到低电平的瞬间”。“踢一脚”、“上升沿”、“下降沿”此三者都可以统一理解成“节拍”。
       芯片之间通信,“时钟信号”只需1个足矣,而“数据信号”却可以不止1个。1个“数据信号”往往叫“串行”通信,一个节拍只能传输1位数据。8个以上并列的“数据信号”往往叫“并行”通信,一个节拍能传输8位以上的数据。可见,并行的“数据信号”越多,传输的速率越快。
       常见的系统中,串口,IIC,SPI,USB,CAN这类都是“串行”通信。而32位单片机与外部的nandflash,norflash,sdram,sram这些芯片通信往往是“并行”通信,并行的数据信号多达8个16个甚至32个。
       本节标题之所以强调“双线”,是因为“手”代表数据线,“脚”代表时钟线,一共两条线因此为“双线”。现在把上述的肢体通信过程翻译成C语言代码,如下:

sbit Hand_DATA=P2^6;  //手的数据线
sbit Foot_CLK=P2^7;   //脚的时钟线

void SendByte(unsiged char u8Data) //肢体接触通信发送一个字节的数据的发送函数
{
    static unsigned char i;
    for(i=0;i<8;i++) //一个字节包含8个位数据,需要循环8次
    {
       if(0==(u8Data&0x01))  //根据数据的每一位状态,发送对应的位数据。
       {
            Hand_DATA=0;  //0代表“握紧”
       }
       else
       {
            Hand_DATA=1;  //1代表“松开”
       }

       Foot_CLK=1;
       Delay(); //为产生均匀的脉冲节拍,时钟线的高电平先延时一会
       Foot_CLK=0;  //从高电平跳变到低电平,产生瞬间的“下降沿”,代表“踢一脚”
       Delay(); //为产生均匀的脉冲节拍,时钟线的低电平先延时一会

       u8Data=u8Data>>1; //右移一位,为即将发送下一位做准备
    }
}



使用特权

评论回复
416
maxianhui120| | 2018-7-25 22:57 | 只看该作者
支持!

使用特权

评论回复
417
maxianhui120| | 2018-7-26 22:01 | 只看该作者
学习了,感谢无私奉献!

使用特权

评论回复
418
arima| | 2018-7-29 11:40 | 只看该作者
加油,持续学习!!!!

使用特权

评论回复
419
zhaodaren| | 2018-8-2 22:36 | 只看该作者
加油

使用特权

评论回复
420
jianhong_wu|  楼主 | 2018-8-5 12:40 | 只看该作者
第一百二十六节: “单线”的肢体接触通信。
第一百二十六节.pdf (70.85 KB)
【126.1   同步通信与异步通信。】

       既然芯片之间通信离不开“数据”和“时钟”这两种信号,那么是不是说,通信必须至少两根线(双线)以上?不是。单线也可以通信,继续拿甲乙两人的肢体通信做比喻,这一次只允许用一只“手”不许用“脚”,“手”继续做数据信号,那么时钟信号在哪?时钟信号在甲乙两人各自的“心跳”。用两个人的“心跳”作为时钟信号就有两个时钟节拍,初学者可能在这里会有疑惑,这两人的“心跳”频率可能不一致,时钟节拍可能不同步,怎么能进行通信呢?说到这里,恰好通讯界有两个专业的概念,一个是“同步通信”另一个是“异步通信”。像上一节讲那种用脚的动作“踢一脚”作为时钟信号,这个时钟信号只有一个,对于通讯的甲乙双方是实时“同步的”时钟信号,因此这种通信叫做“同步通信”。而本节提到的用两个人各自的“心跳”做时钟信号,有两个时钟源,时钟信号是“不同步的”,这种通信叫做“异步通信”。

【126.2   异步通信的原理。】

       既然两人各自的“心跳”不同步(异步),而且“心跳”是从甲乙两人出生开始就一直持续存在不停跳动的,那么发送一个字节的数据是从什么时候开始到什么时候结束就必须事先有一个约定。他们是这样约定的:
      (一)平时的待命状态。甲是发送方,乙是接收方,平时待命没有发送数据的时候,甲手的状态一直是“松开”的(电平1)。
      (二)1个开始位与8个数据位。当甲要发送数据给乙的时候,第1个心跳甲先“握紧”(电平0)代表“开始位”,“开始位”用来通知乙方请做好接收数据的准备,然后第2个到第9个心跳甲依次靠手的状态发送8个位的字节数据(数据位),乙方因为“甲的开始位”的存在已经做好了接收第2个心跳数据的准备,因此乙方能完全接收第2个心跳至第9个心跳的数据位的数据。
      (三)1个停止位。甲发送了第9个心跳的数据后,必须马上恢复到待命的状态“松开”(电平1),以便为下一次发送数据时能正确发送“开始位”,但是这个待命的状态“松开”至少应该持续多长的时间呢?至少持续1个“心跳”的时间以上。这样,虽然两个人的“心跳”不同步并且频率也不一样,但是只要8个“心跳”的累加误差不超过1个“心跳”的停止位时间,数据就肯定不会错位。这个至少持续1个“心跳”的待命状态就起到消除累加误差的作用。

【126.3   异步的肢体通信的例子。】

       “手”可以产生“两种”状态“握紧”和“松开”,甲发送数据给乙,乙每“心跳”一次就去判断一次手的状态,“握紧”代表二进制的0,“松开”代表二进制的1,这样,如果他们之间想传输一个字节的十六进制数据0x59,只需把十六进制的数据0x59展开成二进制01011001,从右到左(从低位到高位)以“位”为单位挨个发送,过程如下:
       平时手的状态一直处于“松开”的待命状态,直到手第一次出现“握紧”的状态......
       第一次“心跳”:手的状态是“握紧”,开始位,通知乙作好接收即将过来的8个“心跳”数据位。
       第二次“心跳”:手的状态是“松开”,数据位bit0,记录1。
       第三次“心跳”:手的状态是“握紧”,数据位bit1,记录0。
       第四次“心跳”:手的状态是“握紧”,数据位bit2,记录0。
       第五次“心跳”:手的状态是“松开”,数据位bit3,记录1。
       第六次“心跳”:手的状态是“松开”,数据位bit4,记录1。
       第七次“心跳”:手的状态是“握紧”,数据位bit5,记录0。
       第八次“心跳”:手的状态是“松开”,数据位bit6,记录1。
       第九次“心跳”:手的状态是“握紧”,数据位bit7,记录0。
       第十次“心跳”:手的状态是“松开”,停止位,至少持续1个“心跳”的待命状态。
       现在把上述的“单线”(异步)的肢体通信过程翻译成C语言代码,甲发送数据的代码如下:


sbit Hand_DATA=P2^6;  //手的数据线

void SendByte(unsiged char u8Data) //甲发送数据的发送函数
{
    static unsigned char i;

    Hand_DATA=0;  //开始位。0代表“握紧”
Delay();  //甲的心跳间隔时间
    for(i=0;i<8;i++) //发送8个数据位
    {
        if(0==(u8Data&0x01))  //根据数据的每一位状态,发送对应的位数据。
        {
            Hand_DATA=0;  //0代表“握紧”
        }
        else
        {
            Hand_DATA=1;  //1代表“松开”
        }

Delay();  //甲的心跳间隔时间

        u8Data=u8Data>>1; //右移一位,为即将发送下一位做准备
    }
    Hand_DATA=1;  //停止位。1代表“松开”
Delay();  //甲的心跳间隔时间
}


使用特权

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

本版积分规则