全功能数字电子钟(C51单片机应用开发)

发布者:真实幻想最新更新时间:2017-01-08 来源: eefocus关键字:数字电子钟  C51单片机 手机看文章 扫描二维码
随时随地手机看文章

一、设计目的:

  通过单片机应用产品的设计与调试过程,巩固课程所学理论知识,初步了解单片机应用系统设计与调试的方法。

 

二、设计要求:

      设计一个以AT89S51单片机为核心的数字电子钟控制器,实现电子钟的时间、日期交替显示、闹钟功能,并可通过按钮开关或键盘切换显示内容、调整参数、设置闹钟,在单片机实验板上模拟调试实现控制器的功能。具体设计要求如下:

1.开机自检,检查相关接口及数码管显示器、指示灯、蜂鸣器等外设是否正常。

2.8位数码管显示器平常以一定的时间间隔、合适的格式显示时间和日期信息,时间显示时、分、秒;日期显示年(2000~2099)、月、日;设置闹钟功能时显示时、分、开/关状态。

3.可通过按键设定时间、日期、闹钟等参数、手动切换显示。按键可用独立式按键或行列式键盘实现。设定参数过程有合适的方式指示当前可修改的内容。

4.对开关量输入进行软件消抖动处理,参数的设定有容错处理,如:小时不能超过23,日期中每月最大天数、闰年等。

5.用Protel设计可实现上述功能的控制器的原理图(最小应用系统)。

扩展功能(选做):

1.可设置多次闹钟。

2.显示星期功能。

3.参数设定过程中,较长时间无操作,则自动恢复为正常显示方式。。

4.其它自选的扩展功能。

 

三、总体方案设计及说明

总体功能框图:

硬件:

    8个LED采用动态扫描以节约驱动成本;

    走时采用内部T0计时中断;

    4x4矩阵键盘扫描采用线反转法,以中断扫描计数防止抖动;

……

软件:

     采用C语言实现。

四、系统资源分配说明(接口、存储器分配)

 

1.接口:

89S51的P1口接8个LED小灯;

89S51的P3_2接蜂鸣器(低电平鸣响);

外扩一片8255:

89S51单片机的P0口是低8位地址与数据复用的,现在我们用74HC373分离出地址,89S51高位地址的P2_0(A8)接8255的片选端(/CS), 低位地址Q1Q0(A1A0)与8255的A1A0连接,数据位P0_7~P0_0分别接8255的D_7~D_0。 以此得到的8255端口的地址分别为:

PA:xxxxxxx0 xxxxxx00取0x0fefc;  PB:xxxxxxx0 xxxxxx01取0x0fefd;

PC:xxxxxxx0 xxxxxx10取0x0fefd; CTL:xxxxxxx0 xxxxxx11取0x0feff;

8255的PA口控制LED数码管的8个显示段;PB口分别接8个LED数码管的共阳极;

PC口分别接4x4矩阵键盘的行线和列线。

 

2.存储分配:

struct{  //闹钟时、分、秒 ,共设6个闹钟(初始状态默认:00-00-F1)

        uchar hour;

        uchar minute;

        uchar isON;

      }alarm[6]={{0,0,0}};

uchar hour=12,minute=0,second=0;//时、分、秒

uchar temp_second;  //用于立即切换显示时间/日期

 

uint year=2011;// 年

uchar month=12;// 月

uchar day=1; //   日

uchar week=6;//  星期

uchar Mdays[]={0,31,28,31,30,31,30,31,31,30,31,30,31};//各月天数

 

uchar alarm_isON=1;  //闹钟总开关

uchar alarm_station=0; //闹钟状态

uchar ano; //闹钟号(当前时间到的闹钟号)

uchar start_minute;//开始响铃的时间(也就是所定闹钟的时间)

uint count_ms25=0; //软件计数器(计数40个25毫秒达1s)

uchar show_model=0; // 显示模式:[0]切换显示时间/日期  [1]切换显示日期/时间

const uchar fixtime=0x00;//时间修正量

uchar key=0xff;//获得的当前键值

uchar last_key=0xff; //最后一次扫描到的按键(非0xff)

uchar key_count=0;//扫描到同一按键的次数

uchar Edown=0; //闹钟开关键是否按下

uchar led_buf[8]={24,24,24,24,24,24,24,24};  //时间日期显示缓冲区

uchar code led_table1[]={0x0c0,0x0f9,0x0a4,0x0b0, 0x99,0x92,0x82,0x0f8,0x80,0x90,

0x88,0x83,0x0C6,0x0a1,0x86,0x8e,0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,

0x08,0x03,0x46,0x21,0x06,0x0e,0x7f,0x0bf,0xff};//数码管段码

uchar code KBTable[] = {'1','2','3','F','4','5','6','E','7','8','9','C','0','A','B','D'};//键值(可有可无)

 

五、软件流程图及说明

1.流程图:

 

2.主要程序段说明:

(1)显示:

动态显示:即各位数码管轮流点亮,对于显示器各位数码管,每隔一段延时时间循环点亮一次。利用人的视觉暂留功能可以看到整个显示,但须保证扫描速度足够快,人的视觉暂留功能才可察觉不到字符闪烁。显示器的亮度与导通电流、点亮时间及间隔时间的比例有关。调整参数可以实现较高稳定度的显示。动态显示节省了驱动和I/O口,降低了能耗。

void LED_show(uchar buf[])

{

  uchar i,num,pLED=0x80;

  for(i=0;i<8;i++)

  {

    num=buf[i];

    PA=led_table1[num];  /*送字段码*/

    PB=pLED;            /*送字位码*/

    pLED>>=1;           /*右移一位*/

    Delay(1);            /*延时*/

  }

}

(2)键盘(本次设计对下面两种扫描方式都进行了实现):

a. 行扫描法:依次从第一至最末行线上发出低电平信号, 如果该行线所连接的键没有按下的话, 则列线所接的端口得到的是全“1”信号, 如果有键按下的话, 则得到非全“1”信号。

/*键盘扫描(行扫描法,延时消抖)********************************************************

uchar code KBTable[] = {

    0xEE,'1',0xDE,'4',0xBE,'7',0x7E,'0',

    0xED,'2',0xDD,'5',0xBD,'8',0x7D,'A',

    0xEB,'3',0xDB,'6',0xBB,'9',0x7B,'B',

    0xE7,'F',0xD7,'E',0xB7,'C',0x77,'D',

    0x00,0xff};

 

uchar Get_key(void); // 获取最终键值

{   uchar i;

    uchar line, row, k_value;

    static uchar lastkey=0xff;

   CTL=0x88; //CH输入,CL输出 10001000

   PC=PC & 0xf0;  // PC0~PC3输出0 , 输入PC4~ PC7(默认1无键按下)

   if  ((PC & 0xf0) == 0xf0)

     {

        lastkey=0xff;

        return 0xff;  //无键按下

     }

   row = PC;

   Delay(4);  //延时,消除抖动

   if (row != PC)

    {

      lastkey=0xff;

      return 0xff;  //判为抖动

    }

   line=0xFE;

   for (i=0;i<4;i++)

   {      PC = line;  //输出扫描信号

          row=PC;       //读键盘口

          if  ((row & 0xf0) != 0xf0)

               break;

          line=(line<<1)+1;

   }

   if (i==4)

   {   lastkey=0xff;       return 0xff; }

   k_value = (row & 0xf0) | (line & 0x0f) ;

   for  (i=0; i<32; i+=2)

        if  (k_value == KBTable[i])

               break;

   if(lastkey==KBTable[i+1])

          return 0xff;

   lastkey=KBTable[i+1];

   return  KBTable[i+1];

}

b.线反转法:线反转法也是识别闭合键的一种常用方法, 该法比行扫描速度快, 但在硬件上要求行线与列线外接上拉电阻。先将行线作为输出线, 列线作为输入线, 行线输出全“0”信号, 读入列线的值, 那么在闭合键所在的列线上的值必为0;然后从列线输出全“0”信号,再读取行线的输入值,闭合键所在的行线值必为 0。这样,当一个键被按下时, 必定可读到一对唯一的行列值。再由这一对行列值可以求出闭合键所在的位置。

 

//一次键盘扫描(线反转法,中断扫描计数去抖)*********************************************************

uchar code KBTable[] = {'1','2','3','F','4','5','6','E','7','8','9','C','0','A','B','D'};

           //key_index   0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15

 

uchar key_scan(void)  //返回 '0','1','2'...'E','F',0xff

{ uchar key_index,temp=0;

  CTL=0x88; //CH输入,CL输出 10001000

  PC=PC & 0xf0;       //将低四位置0

  if(PC!=0xF0)  //判断按键是否按下 如果按钮按下 会拉低CH其中的一个端口

  {

    temp=PC;                 //读PC口

    temp=temp&0xf0;          //屏蔽低四位

    temp=~((temp>>4)|0xf0); // 高四位取反值  ~(1111****)

    if(temp==1)   // pC.4 被拉低      1110  第一行

        key_index=0;

    else if(temp==2)   // PC.5 被拉低 1101  第二行

        key_index=4;

    else if(temp==4)   // PC.6 被拉低 1011  第三行

        key_index=8;

    else if(temp==8)   // PC.7 被拉低 0111  第四行

        key_index=12;

    else         //没有键按下

        return 0xff;

   

    CTL=0x81; //CL输入,CH输出 10000001

    PC=PC & 0xf0;             //CH输出0000  初始: 00001111    若有键按下,根据低4位哪一位被拉低确定第二维坐标

    temp=PC;             //读PC口

    temp=temp&0x0f;

    temp=~(temp|0xf0);  // 低四位取反值  ~(1111****)

    if(temp==1)        //PC.0  被拉低 1110  第一列

        key_index+=0;

    else if(temp==2)   //PC.1  被拉低 1101  第二列

        key_index+=1;

    else if(temp==4)   //PC.2  被拉低 1011  第三列

        key_index+=2;

    else if(temp==8)   //PC.3  被拉低 0111  第四列

        key_index+=3;

    else           //没有键按下

        return 0xff;

       

    return KBTable[key_index];

  }

  else return 0xff;

}

 

c.消抖方法:

(1)硬件消抖法:就是在键盘中附加去抖动电路,从根上消除抖动产生的可能性。右图所示电路实际上是由R-S触发器构成的单脉冲电路。当按钮开关按下时Q端输出低电平,当开关松开时Q端恢复高电平,即输出一个负脉冲,以此消除抖动。

(2)软件消抖法:键按下的时间与操作者的按键动作有关,约为十分之几到几秒不等。而键抖动时间与按键的机械特性有关,一般为5~10ms不等。软件消抖法即是采用延时(一般延时10~20ms)的方法,以避开按键的抖动,即在按键已稳定地闭合或断开时才读出其状态。

(3)定时中断扫描计数消抖法:把扫描函数放在定时中断里调用,扫描有键值则保存至kast_key并计数(key_count++),下次中断时再扫描,键值与上次相同则计数累加,若键值不同则计数从1开始,……,直到相同的键值被计数>=6,则认为该键按下。

假设1s最快可以按5下,则每下约持续200ms,  每次25ms中断时就扫描一次键盘,在这8次(或更多)里若有6次以上检测到有同一键则判为有效键。

 

uchar Get_key(void)//返回 '0','1','2'...'E','F',0xff

{

      if(key_count>=6)   

   {

       key_count=0;

       return last_key;

   }

   else return 0xff;

}

(3)中断服务子程序

/*25ms中断子函数**********************************************************************/

void Time_over(void) interrupt 1 using 2

{

  uchar key_temp;

  TH0 = 0x0A6; //晶振频率11.0592MHz下,定时25ms的计数初值

  TL0 = 0x00+fixtime;//(2^16-X)*(12*1/11.0592)=25ms;  -->X=42496=A600H

  count_ms25++;

  if(count_ms25>=40)  //达到1秒

  {

    DateTime_change(); //走时更新时分秒等

    count_ms25=0; 

  }

 

    //每次25ms中断就扫描一次按键

    key_temp=key_scan();

    if((last_key==0xff)&&(key_temp!=0xff))

    {

       last_key=key_temp;

       key_count++;

    }

    else

    {

      if(key_temp==last_key)

        {key_count++;}

      else

        if(key_temp!=0xff){last_key=key_temp;key_count=1;}

    }

}

 

 

 

 

 

六、系统功能与操作说明;

  1.系统功能:

           时间、日期(包括星期)、闹钟显示及其设置;

           支持6个闹钟,可设置闹钟总开关及分开关;

           完善的设置容错处理;

           可自动/手动切换时间/日期显示模式;

           设置位闪烁标识;

           设置过程30s无操作,自动恢复为正常显示方式;

           4x4矩阵键盘扫描采用线反转法,以中断扫描计数去抖;

           开机自检功能;

           ......

  2.显示格式说明:

时间显示:12-30-02      12时-30分-02秒

日期显示:11.01.01-6  2011年01月01日-星期六

闹钟设置显示:-123456-  等待选择要设置的闹钟号

       06-00-E1  定时在06:00,闹钟1开(E开,F关)

                    闹钟总开关:时间显示时最后一位小数点亮代表总开,否则为关.

3.键盘操作说明:

 

  (1)数字键“0”~“9”,用于设置时输入时间和日期,正常工作时无效。

  (2)“时间设置”、“日期设置”、“闹钟设置”,用于进入相应功能的设置状态。

  (3)“闹钟开关”在闹钟设置状态时,用于设置当前闹钟开或关,正常工作时,用于手动关闹铃的声音和设置闹钟总开关。

  (4)“确认”用于设置参数的确认,并检查参数是否合理,如果符合要求,则参数有效;否则本次修改无效,保持原值。正常工作情况下按该键无操作。

  (5)“显示切换”用于正常工作时手动切换时间、日期的显示。

 

七、调试记录(主要问题及解决方法)

1.在写设置模块特别是容错处理时比较耗时,要求对各个细节都进行仔细地调试,修修改改在所难免,都是些小问题,略过;

     2.在实现功能模块的相互任意跳转时,起初只是直接调用功能入口函数fun_key(),出现递归调用的警告,因为要堆栈保护现场,而在任意跳转时多次递归调用必然会空间不足(即使通过reenstrant也不妥,也是这个问题),这样就有可能会造成数据覆盖使系统不稳定。对此,我在主函数里采用以下方法避免递归调用:

  while(1)

  {

      Show_control();

      alarm_scan();

      key=Get_key();

      while(key=='A'||key=='B'||key=='C') fun_key(); //功能跳转(可避免递归调用的处理)

      fun_key();

  }

       3.在键盘的实验过程中,我几乎尝试了所有的键盘处理方法,包括行扫描法、线反转法、延时消抖、定时中断扫描计数消抖……其间曾导致LED动态刷新速度不足,闪烁严重,把LED_show()里的Delay(8)改为Delay(1)即可;条件key_count>=6判为有效也是针对我自己的程序根据实际情况调试得出的经验值。

听说有一种类似的按键扫描,可以很方便识别出短按、短按抬起、长按、长按抬起共4种按键动作,大概思路如下:

用定时器中断2mS扫描一次,如果按键被按下则计数累加,否则计数清零并记按键抬起动作。

1、累加到5次(10mS完成消斗)认为是短按;

2、累加超过5次且小于500次(1秒),并已产生按键抬起动作,认为是短按抬起;

3、累加超过500次,认为是长按;

4、累加超过500次,并已产生按键抬起动作,认为是长按抬起。

   据此方法,我小试了下牛刀,具体实现未果,有空再试。

 

八、课程设计总结

其实以前就用Freescale单片机写过一个带温度显示和闹钟的数字时钟,只是实现走时、温度采集和键盘的基本功能,未考虑日期、容错处理、功能的完善性等细节问题。这次原打算尝试改用汇编实现,但后来迫于形式,时间有限,只好先用C顶着,也算是对这一类小产品的改进与完善吧。

至于时间的精确性,如果只用晶振是很难做到精确的,即使嵌入汇编精确计算或者调出一个最佳的时间偏差量、偏差函数,也难以确保其精确性,毕竟每个晶振的振荡频率与标称值存在误差也会老化。所以这里我没有做这方面的工作,目前普遍的做法是外接一个时钟芯片,比如说DS1302,价格也不贵。

九、附录:

1.Protel绘制的最小系统板原理图:

 

2.程序清单及详细注释

/*****************************************************

  描述:

               全功能数字电子钟

  作者:Estrong    2011.12.12写于福建工程学院.

  平台:Atmel 89S51 单片机

  实现功能:

           时间、日期(包括星期)、闹钟及其设置;

           支持6个闹钟,可设置闹钟总开关及分开关;

           完善的设置容错处理;

           可自动/手动切换时间/日期显示模式;

           设置位闪烁标识;

           设置过程30s无操作,自动恢复为正常显示方式;

           4x4矩阵键盘扫描采用线反转法,以中断扫描计数去抖;

           开机自检功能;

           ......

******************************************************/

 

#include

#include

#define PA XBYTE[0x0fefc]

#define PB XBYTE[0x0fefd]

#define PC XBYTE[0x0fefe]

#define CTL XBYTE[0x0feff]

#define uchar unsigned char

#define uint unsigned int

//PA、PB、PC、CTL分别为8255的控制口

 

sbit P3_2=P3^2; //蜂鸣器引脚定义

 

struct{  //闹钟时、分、秒 ,共设6个闹钟(初始状态默认:00-00-F1)

        uchar hour;

        uchar minute;

        uchar isON;

      }alarm[6]={{0,0,0}};

uchar hour=12,minute=0,second=0;//时、分、秒

uchar temp_second;  //用于立即切换显示时间/日期

 

uint year=2011;// 年

uchar month=12;// 月

uchar day=1; //   日

uchar week=6;//  星期

uchar Mdays[]={0,31,28,31,30,31,30,31,31,30,31,30,31};//各月天数

 

uchar alarm_isON=1;  //闹钟总开关

uchar alarm_station=0; //闹钟状态

uchar ano; //闹钟号(当前时间到的闹钟号)

uchar start_minute;//开始响铃的时间(也就是所定闹钟的时间)

uint count_ms25=0; //软件计数器(计数40个25毫秒达1s)

uchar show_model=0; // 显示模式:[0]切换显示时间/日期  [1]切换显示日期/时间

const uchar fixtime=0x00;//时间修正量

uchar key=0xff;//获得的当前键值

uchar last_key=0xff; //最后一次扫描到的按键(非0xff)

uchar key_count=0;//扫描到同一按键的次数

uchar Edown=0; //闹钟开关键是否按下

uchar led_buf[8]={24,24,24,24,24,24,24,24};  //时间日期显示缓冲区

uchar code led_table1[]={0x0c0,0x0f9,0x0a4,0x0b0, 0x99,0x92,0x82,0x0f8,0x80,0x90,0x88,0x83,0x0C6,0x0a1,0x86,0x8e,0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,0x08,0x03,0x46,0x21,0x06,0x0e,0x7f,0x0bf,0xff};

                         // 0    1      2     3     4     5   6     7     8   9    a    b    c     d     e    f   0.  1.   2.   3.   4.   5.   6.   7.   8.   9.   A.   B.   C.    D.  E.   F.    .    -   全灭

                                                                               //  10   11   12    13    14   15    16  17   18   19   20   21   22   23   24   25   26   27   28    29  30   31   32    33   34

 

/*函数声明*****************************************************************************/

void Init(void); //初始化

void fun_key(void); // 相应按键功能入口

void test(void);  // 开机自检

void LED_show(uchar buf[]);// 8个数码管动态显示

void Show_time(void);// 时间送缓存

void Show_date(void); //  日期送缓存

void Show_alarm(uchar i);// 闹钟送缓存

uchar IsLeapYear(uint y); // 判断闰年

void Update_Feb(void);//  更新2月天数

void Show_control(void);// 时间日期切换显示控制

void DateTime_change(void);//走时改变时间和日期

void Delay(uchar k);//  延时约:60μs×k

uchar key_scan(void);// 键盘扫描(一次)

uchar Get_key(void); // 获取最终键值

void alarm_scan(void);// 闹钟扫描与响铃

void set_time(void);// 设置时间

void set_date(void);// 设置日期

void set_alarm(void);// 设置闹钟

 

/*主函数*****************************************************************************/

main()

{

  Init();

  Update_Feb(); //初始时更新2月天数

  while(1)

  {

      Show_control();

      alarm_scan();

      key=Get_key();

      while(key=='A'||key=='B'||key=='C') fun_key(); //功能跳转(可避免递归调用的处理)

      fun_key();

  }

}

 

 

/*键盘功能入口********************************************************************/

void fun_key(void)

{

     switch(key)

     {

       case 'A':       //按'A'键 设置时间

          set_time();

          break;

       case 'B':       //按'B'键 设置日期

          set_date();

          break;

       case 'C':       //按'C'键 设置闹钟

          set_alarm();

          break;

       case 'E':       //按'D'键 闹钟总开关,时间模式下最后一位小数点亮代表总开,否则为关

          if(alarm_isON==0) alarm_isON=1;

          else alarm_isON=0;

          break;

       case 'F':       //按'F'键 时间/日期切换显示

          show_model++;

          if(show_model>1) show_model=0;

          temp_second=0;

          break;

       default:break;

      }

}

 

 

/*初始化************************************************************************/

void Init(void)

{

  int i;

  for(i=0;i<400;i++) Delay(8); /*硬件准备工作*/

  CTL=0x80;   /*8255控制字 方式0 ABC口皆设为输出*/

  //计数器初始化:

  TMOD=0x01;  //GATE0=0,C/T=0,M1M0=01

  TH0 = 0x0A6;//晶振频率11.0592MHz下,定时25ms的计数初值

  TL0 = 0x00+fixtime;  //(2^16-X)*(12*1/11.0592)=25ms;  -->X=42496=A600H

  test(); //开机自检

  TR0=1;    //启动T0计数

  IE=0x82;  //开放TF0中断

}

 

 

/*开机自检**********************************************************************/

void test(void)

{

  uchar i,j,num,pLED=0x80;

  P3_2=0;

  for(i=0;i<10;i++)Delay(250);

  P3_2=1;

  for(i=0;i<8;i++)

  {

    num=led_buf[i];

    PA=led_table1[num];  /*送字段码*/

    PB=pLED;            /*送字位码*/

    for(j=1;j<=5;j++)Delay(250);

    pLED>>=1;           /*右移一位*/

    Delay(10);            /*延时*/

  }

}

 

 

/*LED显示****************************************************************************/

void LED_show(uchar buf[])      //死循环里一般都要放该函数

{

  uchar i,num,pLED=0x80;

  for(i=0;i<8;i++)

  {

    num=buf[i];

    PA=led_table1[num];  /*送字段码*/

    PB=pLED;            /*送字位码*/

    pLED>>=1;           /*右移一位*/

    Delay(1);            /*延时*/

  }

}

 

 

/*更新显示缓存************************************************************************/

 

/*时间送缓存(12-00-00  时-分-秒)*/

void Show_time(void)   

{

    if(alarm_isON==0) led_buf[7]=second%10;

    else led_buf[7]=second%10+16;//闹钟开时间最后一位显示有小数点

    led_buf[6]=second/10;

    led_buf[5]=33;

    led_buf[4]=minute%10;

    led_buf[3]=minute/10;

    led_buf[2]=33;

    led_buf[1]=hour%10;

    led_buf[0]=hour/10;

}

 

 

/*日期送缓存(11.01.01-6,11年01月01日星期六)*/

void Show_date(void)

{ uint year_temp=year%100;

    led_buf[1]=year_temp%10+16;

    led_buf[0]=year_temp/10;

    led_buf[3]=month%10+16;

    led_buf[2]=month/10;

    led_buf[5]=day%10;

    led_buf[4]=day/10;

    led_buf[6]=33;

    led_buf[7]=week;

}

/*日期送缓存(2011.01.01,没有星期)

void Show_date()

{ uint year_temp=year;

    led_buf[3]=year_temp%10+16;

      year_temp=year_temp/10;

    led_buf[2]=year_temp%10;

      year_temp=year_temp/10;

    led_buf[1]=year_temp%10;

      year_temp=year_temp/10;

    led_buf[0]=year_temp;

    led_buf[5]=month%10+16;

    led_buf[4]=month/10;

    led_buf[7]=day%10;

    led_buf[6]=day/10;

}

*/

 

 

/*闹钟显示送缓存(06-00-E1 闹钟1开,定时在06:00)*/

void Show_alarm(uchar i)    

{

    led_buf[7]=i;

    if(alarm[i].isON)      //闹钟开L6显示'E'

      led_buf[6]=14;

    else led_buf[6]=15;      //闹钟关L6显示'F'

    led_buf[5]=33;

    led_buf[4]=alarm[i].minute%10;

    led_buf[3]=alarm[i].minute/10;

    led_buf[2]=33;

    led_buf[1]=alarm[i].hour%10;

    led_buf[0]=alarm[i].hour/10;

}

 

 

/*判断是否为闰年************************************************************************/

uchar IsLeapYear(uint y)

{

  if( (y%4==0)&&(y%100!=0)||(y%400==0) )

    return 1;

  else

    return 0;

}

 

 

/*更新2月天数**************************************************************************/

void Update_Feb(void)

{

  if(IsLeapYear(year)) Mdays[2]=29;  //闰年2月29天

  else Mdays[2]=28;              //平年2月28天

}

 

 

/*显示控制(时间日期切换显示)************************************************************/

void Show_control(void)

{

 

  if(show_model==0)

  {

    if(temp_second/10%2==0) Show_time();   //切换模式:每隔10秒切换显示时间/日期

    else Show_date();

  }

  else //if(show_model==1)

  {

    if(temp_second/10%2==0) Show_date();   //切换模式:每隔10秒切换显示日期/时间

    else Show_time();

  }

 

  LED_show(led_buf);

}

 

 

/*走时改变时间和日期**********************************************************************/

void DateTime_change(void)    //放中断,达1秒后执行

{

  second++;temp_second++;

  if(second>=60)

     {minute++;second=0;}

  if(temp_second>=60)

     {temp_second=0;}

  if(minute>=60)

     {hour++;minute =0;}

  if(hour>=24)

     {day++;hour=0;week++;if(week>7) week=1;}

  if(day>Mdays[month])

     {month++;day=1;}

  if(month>12)

     {year++;month=1;Update_Feb();}   //年份改变时更新2月天数

}

 

 

/*延时约:60μs×k ******************************************************************/

void Delay(uchar k)

{ uchar i;

    while((k--)!=0)

      for(i=0;i<25;i++);

}

 

/*键盘扫描(行扫描法,延时消抖)********************************************************

uchar code KBTable[] = {

    0xEE,'1',0xDE,'4',0xBE,'7',0x7E,'0',

    0xED,'2',0xDD,'5',0xBD,'8',0x7D,'A',

    0xEB,'3',0xDB,'6',0xBB,'9',0x7B,'B',

    0xE7,'F',0xD7,'E',0xB7,'C',0x77,'D',

    0x00,0xff};

 

uchar Get_key(void); // 获取最终键值

{   uchar i;

    uchar line, row, k_value;

    static uchar lastkey=0xff;

   CTL=0x88; //CH输入,CL输出 10001000

   PC=PC & 0xf0;  // PC0~PC3输出0 , 输入PC4~ PC7(默认1无键按下)

   if  ((PC & 0xf0) == 0xf0)

     {

        lastkey=0xff;

        return 0xff;  //无键按下

     }

   row = PC;

   Delay(4);  //延时,消除抖动

   if (row != PC)

    {

      lastkey=0xff;

      return 0xff;  //判为抖动

    }

   line=0xFE;

   for (i=0;i<4;i++)

   {      PC = line;  //输出扫描信号

          row=PC;       //读键盘口

          if  ((row & 0xf0) != 0xf0)

               break;

          line=(line<<1)+1;

   }

   if (i==4)

   {   lastkey=0xff;       return 0xff; }

   k_value = (row & 0xf0) | (line & 0x0f) ;

   for  (i=0; i<32; i+=2)

        if  (k_value == KBTable[i])

               break;

   if(lastkey==KBTable[i+1])

          return 0xff;

   lastkey=KBTable[i+1];

   return  KBTable[i+1];

}

 

*/

 

 

//获取最终键值*********************************************************

uchar Get_key(void)//返回 '0','1','2'...'E','F',0xff

{

  

   if(key_count>=6)  //假设1s最快可以按5下,则每下约持续200ms,  每次25ms中断时就扫描一次键盘,在这8次(或更多)里若有六次以上检测到有同一键则判为有效键。

   {

       key_count=0;

       return last_key;

   }

   else return 0xff;

}

 

 

//一次键盘扫描(线反转法,要求行列都要有上拉电阻,中断扫描计数去抖)*********************************************************

uchar code KBTable[] = {'1','2','3','F','4','5','6','E','7','8','9','C','0','A','B','D'};

           //key_index   0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15

 

uchar key_scan(void)  //返回 '0','1','2'...'E','F',0xff

{ uchar key_index,temp=0;

  CTL=0x88; //CH输入,CL输出 10001000

  PC=PC & 0xf0;       //将低四位置0

  if(PC!=0xF0)  //判断按键是否按下 如果按钮按下 会拉低CH其中的一个端口

  {

    temp=PC;                 //读PC口

    temp=temp&0xf0;          //屏蔽低四位

    temp=~((temp>>4)|0xf0); // 高四位取反值  ~(1111****)

    if(temp==1)   // pC.4 被拉低      1110  第一行

        key_index=0;

    else if(temp==2)   // PC.5 被拉低 1101  第二行

        key_index=4;

    else if(temp==4)   // PC.6 被拉低 1011  第三行

        key_index=8;

    else if(temp==8)   // PC.7 被拉低 0111  第四行

        key_index=12;

    else         //没有键按下

        return 0xff;

   

    CTL=0x81; //CL输入,CH输出 10000001

    PC=PC & 0xf0;             //CH输出0000  初始: 00001111    若有键按下,根据低4位哪一位被拉低确定第二维坐标

    temp=PC;             //读PC口

    temp=temp&0x0f;

    temp=~(temp|0xf0);  // 低四位取反值  ~(1111****)

    if(temp==1)        //PC.0  被拉低 1110  第一列

        key_index+=0;

    else if(temp==2)   //PC.1  被拉低 1101  第二列

        key_index+=1;

    else if(temp==4)   //PC.2  被拉低 1011  第三列

        key_index+=2;

    else if(temp==8)   //PC.3  被拉低 0111  第四列

        key_index+=3;

    else           //没有键按下

        return 0xff;

       

    return KBTable[key_index];

  }

  else return 0xff;

}

 

 

//闹钟扫描与响铃*****************************************************************

void alarm_scan(void)         //死循环里一般都要放该函数

{ uint i;

  if(alarm_isON==0) alarm_station=0;//闹钟总开关

  else //  if((alarm_isON==1)

  {  for(ano=0;ano<5;ano++)

       {if((alarm[ano].isON==1)&&(hour==alarm[ano].hour)&&(minute==alarm[ano].minute))

         {  start_minute=minute;

            alarm_station=1;

            break;

         }

        else alarm_station=0;

       }

  }

  if(alarm_station==0)Edown=0;

  while( (alarm_station==1)&&(Edown==0)&&(minute==start_minute) )

  {                           //闹钟开关键(E)被按下或者响过一分钟就停

      for(i=0;i<400;i++)         /* 循环400次,嘀0.2秒钟 */

      {

        P3_2=0;                 /* P3.4=0,晶体管导通 */

        Delay(2);             /* 调延时函数,延时500μs */

        P3_2=1;                 /* P3.4=1,晶体管截止 */

        Delay(2);             /* 调延时函数,延时500μs */

        P1=0x00;

      }

      P1=0xff;

      for(i=0;i<=50;i++) //延时作用

      {

        Show_control();

        key=Get_key();

        if(key=='E') {Edown=1;break;}

        else continue;

      }

      Show_control();

      key=Get_key();

      if(key=='E') Edown=1;

      else continue;

  }

}

 

 

/*时间设置*****************************************************************/

void set_time(void)

{  uchar i,time1,count=0;

   uchar key_buf[8];

   time1=second;

   Show_time();

   for(i=0;i<8;i++) key_buf[i]=led_buf[i];

   i=0;time1=second;

   while(1)

     {  if (time1!=second){count++;time1=second;}

        if(count>=30) break;    //长时间(30S,可变)无操作自动恢复正常显示

          if(i==2) i=3;

          else if(i==5) i=6;

          if(count_ms25<20) led_buf[i]=key_buf[i];  //当前设置位闪烁(间隔0.5s)

          else led_buf[i]=34;

          key=Get_key();

          if(key==0xff)

            {LED_show(led_buf);alarm_scan();continue;}//没有键按下

          else count=0;

          if(key>='0'&&key<='9')

          {

            

             if(i==0&&key>'2') i--;       //输入越界容错处理

             else if(i==1&&key_buf[0]==2&&key>'3') i--;

             else if((i==3||i==6)&&key>'5') i--;

             else key_buf[i]=key-'0';

             led_buf[i]=key_buf[i];

             i++;

             if(i>=8) i=0;

          }

          else if(key=='D')     //确定

          {

             hour=key_buf[0]*10+key_buf[1];

             minute=key_buf[3]*10+key_buf[4];

             second=key_buf[6]*10+key_buf[7];

             break;

          }

          else break;

     }

}

 

 

/*日期设置*****************************************************************/

void set_date(void)

{  uchar i,temp,time1,count=0;

   uchar key_buf[8];

   time1=second;

   Show_date();

   for(i=0;i<8;i++) key_buf[i]=led_buf[i];

   i=0;

   while(1)

     {  if (time1!=second){count++;time1=second;}

        if(count>=30) break;    //长时间(30S,可变)无操作自动恢复正常显示

          if(i==6) i=7;

          if(count_ms25<20) led_buf[i]=key_buf[i];  //当前设置位闪烁(间隔0.5s)

          else led_buf[i]=34;

          key=Get_key();

          if(key==0xff)  //没有键按下

            {LED_show(led_buf);alarm_scan();continue;}

          else count=0;

          if((key>='0')&&(key<='9'))

          {  if(i==0) key_buf[0]=key-'0';

             else if(i==1) key_buf[1]=key-'0'+16;

             else if(i==2)                     //月 输入越界容错处理  2:monthH 

               {

                 if(key>'1')i--;

                 else key_buf[2]=key-'0';

               }

             else if(i==3)   //3:monthL

               {

                 if(key_buf[2]==0)

                   {

                     if(key=='0') key_buf[3]=17;   //最少01月

                     else key_buf[3]=key-'0'+16;

                   }

                 else

                   {

                     if(key_buf[2]==1&&key>'2') i--;    //最多12月

                     else key_buf[3]=key-'0'+16;

                   }

               }

                                //日 输入越界容错处理  4:dayH  5:dayL

             else if(i==4)

               {

                 if((key_buf[2]==0)&&((key_buf[3]-16)==2))

                 {

                    if(key>'2') i--;            //2月dayH<=2

                    else key_buf[4]=key-'0';

                 }

                 else

                 {

                    if(key>'3') i--;            //其它月份dayH<=3

                    else key_buf[4]=key-'0';

                 }

               }

             else if(i==5)

               {

                 if((key_buf[2]==0)&&((key_buf[3]-16)==2))

                 {

                    if( !IsLeapYear(year/100*100+key_buf[0]*10+key_buf[1]-16) )    //若设置年份为平年

                    {

                      if((key_buf[4]==2)&&(key=='9')) i--;            //2月dayL<=2

                      else key_buf[5]=key-'0';

                    }

                    else key_buf[5]=key-'0';

                 }

                 else

                 {

                    temp=Mdays[key_buf[2]*10+key_buf[3]-16]%10;

                    if( (key_buf[4]==3) && (key>('0'+temp)) ) i--;            //其它月份dayL<=3

                    else key_buf[5]=key-'0';

                 }

                 if((key_buf[4]==0)&&(key_buf[5]==0))

                   key_buf[5]=17;   //最少01日

               }

             else  // if(i==7)     星期容错处理

               {

                 if(key>'7') i--;

                 else if(key=='0') key_buf[7]=1;

                 else key_buf[7]=key-'0';

               }

             led_buf[i]=key_buf[i];

             i++;

             if(i>=8) i=0;

          }

          else if(key=='D')     //确定

          {

             year=year/100*100+key_buf[0]*10+key_buf[1]-16;

             month=key_buf[2]*10+key_buf[3]-16;

             day=key_buf[4]*10+key_buf[5];

             week=key_buf[7];

             Update_Feb();

             break;

          }

          else break;

 

       LED_show(led_buf);alarm_scan();

     }

}

 

 

/*闹钟设置*****************************************************************/

void set_alarm(void)

{  uchar i,a_no,time1,count=0;

   uchar key_buf[8]={33,1,2,3,4,5,6,33};

   while(1)     //选择要设置的闹钟号 -123456-

   {

     LED_show(key_buf);

     key=Get_key();

     if(key>='1'&&key<='6')

     {

       a_no=key-'1';

       Show_alarm(a_no);

       for(i=0;i<=7;i++) key_buf[i]=led_buf[i];

       break;

     }

     else if(key>='A'&&key<='F'&&key!='E') break;

     else continue;

   }

 

   i=0;

   time1=second;

   while(1)

     {  if(key>='A'&&key<='F'&&key!='E') break;

        if (time1!=second){count++;time1=second;}

        if(count>=30) break;    //长时间(30S,可变)无操作自动恢复正常显示

          if(i==2) i=3;

          else if(i>=5) i=0;

          if(count_ms25<20) led_buf[i]=key_buf[i];  //当前设置位闪烁(间隔0.5s)

          else led_buf[i]=34;

          key=Get_key();

          if(key==0xff)

            {LED_show(led_buf);alarm_scan();continue;} //没有键按下

          else count=0;

          if(key>='0'&&key<='9')

          {  if(i==0)

               {

                 if(key>'2') i--;       //输入越界容错处理

                 else key_buf[i]=key-'0';

               }

             else if(i==1)

               {

                 if(key_buf[0]==2&&key>'3')i--;

                 else key_buf[i]=key-'0';

               }

             else if(i==3)

              {

                if(key>'5')i--;

                else key_buf[i]=key-'0';

              }

             else if(i==4) key_buf[i]=key-'0';

             led_buf[i]=key_buf[i];

             i++;

             if(i>=8) i=0;

          }

          else if(key=='E')

               {if(key_buf[6]==14) key_buf[6]=15;

                else key_buf[6]=14;

                led_buf[6]=key_buf[6];

               }

          else if(key=='D')     //确定

          {   

                alarm[a_no].hour=key_buf[0]*10+key_buf[1];

                alarm[a_no].minute=key_buf[3]*10+key_buf[4];

                if(key_buf[6]==14) alarm[a_no].isON=1;

                else alarm[a_no].isON=0;

                break;

          }

          else break;

 

       LED_show(led_buf);alarm_scan();

     }

}

 

 

 

/*25ms中断子函数**********************************************************************/

void Time_over(void) interrupt 1 using 2

{

  uchar key_temp;

  TH0 = 0x0A6; //晶振频率11.0592MHz下,定时25ms的计数初值

  TL0 = 0x00+fixtime;//(2^16-X)*(12*1/11.0592)=25ms;  -->X=42496=A600H

  count_ms25++;

 

    //每次25ms中断就扫描一次按键

    key_temp=key_scan();

    if((last_key==0xff)&&(key_temp!=0xff))

    {

       last_key=key_temp;

       key_count++;

    }

    else

    {

      if(key_temp==last_key)

        {key_count++;}

      else

        if(key_temp!=0xff){last_key=key_temp;key_count=1;}

    }

 

  if(count_ms25>=40)  //达到1秒

  {

    DateTime_change(); //走时更新时分秒等

    count_ms25=0; 

  }

}


关键字:数字电子钟  C51单片机 引用地址:全功能数字电子钟(C51单片机应用开发)

上一篇:8051单片机指令和寻址方式
下一篇:单片机内外部资源操作篇之数码管静态显示

推荐阅读最新更新时间:2024-03-16 15:29

基于AT89C51单片机的电池监测系统设计
1、前言 随着锂离子电池的广泛应用,其安全性问题越来越受重视。对锂离子电池的参数进行实时检测可以有效避免电池的不安全使用,并且可以尽量发挥电池的性能。有些应用领域由于条件限制,难于铺设线路,需要对电池进行远距离的监测,比如路灯蓄电池管理;或者由于大量使用,逐个连接监测线路比较麻烦如基站电源管理中电池的状态监测或者大量在通信电台集中的场合等,可通过无线网络对采集的数据进行传输管理。 该系统主要由锂离子电池组状态参数数据采集、信号无线传输、数据处理等几部分组成,系统框图如图1所示。前端由状态参数采集模块和无线发射控制模块组成,其中数据采集部分包括对锂离子电池组的电压、电流、内阻以及温度等参数进行测量,由单片机对采样数据进行初步处理
[单片机]
基于AT89<font color='red'>C51单片机</font>的电池监测系统设计
基于80C51单片机的多功能肌电测量仪设计
肌电测量或肌电图是检查人体神经、肌肉系统功能的重要方法,广泛应用于神经科、骨科、耳鼻喉科及口腔科。它可为临床诊断、治疗神经肌肉系统疾患提供客观的科学依据。肌电测量仪一般只具有在示波器上显示波形和记录波形的功能。早期,肌电信号通过照相对胶片进行显影才能看到;后来,把肌电信号描绘在肌电图纸上。这两种肌电信号记录法的机构都很复杂。这里介绍一种利用普通的示波器,通过单片机和A/D、D/A转换控制系统构成的,具有记忆、波形分析(诊断)功能和各种操作的实时处理的低功耗智能肌电测量仪。该肌电测量仪可实现一次采集后,多次重复显示、打印,实现了肌电信号测量仪的智能化 1 多功能肌电测量仪的硬件设计 1.1 系统硬件结构框图 系统硬件结构框图如图
[单片机]
基于80<font color='red'>C51单片机</font>的多功能肌电测量仪设计
基于AT89C51单片机的温度自动监控系统
引言 化工合成对温度检测与控制要求较高,是化工合成工艺的关键环节。对化工合成装置的温度进行检测,并按工艺要求,控制最高加热温度;在升温阶段,控制合成温度以每小时15℃的速率上升;加入触媒以后的温度采用恒值控制:前期为370℃,中期为380℃,后期为390℃;控制精度为±3℃l最高温度连续三次达到400℃时发出报警信号。显示检测温度值;每半小时打印一次最高温度值及检测时间;留有扩充余地,以实现多回路控制。 1 温度检测控制系统硬件结构 本系统的硬件电路由温度检测、信号放大、A/D转换、AT89C51单片机、功率放大及执行电路、打印、显示及报警电路等部分组成。选用AT89C51单片机作为主控机,采用带有死区的PID控制算法,当
[单片机]
基于AT89<font color='red'>C51单片机</font>的温度自动监控系统
Keil C51单片机集成开发环境编程与调试教程
同 VC 之类的通用 C 语言集成开发环境(IDE)一样,Keil 也采用“工程” (Project)的方式管理源代码及相关文件,这种管理方式为由多个源代码文件组 成的大型程序开发提供了方便。不管是最简单的 C51程序,还是复杂的多文件 程序都需要以下步骤: 1)先建立新的工程文件; 2)在工程中新建源代码文件,或是将已经存在的源代码文件加入工程; 3)编译; 4)调试,修正错误再编译; 5)将生成的二进制文件*.hex 烧入单片机。 本教程重点介绍上述前 4 个步骤。 二、Keil中新建工程的步骤 1. 单击菜单“Project——New uVision Project……” 出现新建工程对话框: 在此对话框中选择存
[单片机]
Keil <font color='red'>C51单片机</font>集成开发环境编程与调试教程
基于AT89C51单片机设计的简易智能机器人
   引言   随着微电子技术的不断发展,微处理器芯片的集成程度越来越高,单片机已可以在一块芯片上同时集成CPU、存储器、定时器/计数器、并行和串行接口、看门狗、前置放大器、A/D转换器、D/A转换器等多种电路,这就很容易将计算机技术与测量控制技术结合,组成智能化测量控制系统。这种技术促使机器人技术也有了突飞猛进的发展,目前人们已经完全可以设计并制造出具有某些特殊功能的简易智能机器人。    1 设计思想与总体方案   1.1 简易智能机器人的设计思想   本机器人能在任意区域内沿引导线行走,自动绕障,在有光源引导的条件下能沿光源行走。同时,能检测埋在地下的金属片,发出声光指示信息,并能实时存储、显示检测到的断点数目
[单片机]
基于AT89C51单片机设计的简易智能机器人
引言 随着微电子技术的不断发展,微处理器芯片的集成程度越来越高,单片机已可以在一块芯片上同时集成CPU、存储器、定时器/计数器、并行和串行接口、看门狗、前置放大器、A/D转换器、D/A转换器等多种电路,这就很容易将计算机技术与测量控制技术结合,组成智能化测量控制系统。这种技术促使机器人技术也有了突飞猛进的发展,目前人们已经完全可以设计并制造出具有某些特殊功能的简易智能机器人。 1 设计思想与总体方案 1.1 简易智能机器人的设计思想 本机器人能在任意区域内沿引导线行走,自动绕障,在有光源引导的条件下能沿光源行走。同时,能检测埋在地下的金属片,发出声光指示信息,并能实时存储、显示检测到的断点数目以及各断点至起跑线间的距离,
[单片机]
基于AT89C51单片机控制LED摇摇棒的研究
0 引言 随着现代科技的发展,高科技产品以其简洁化、便携等,给人们带来了很大的方便。而“摇摇棒”以其更加简捷与新颖的信息传递方式给人们带来耳目一新的感受,也必将会给人们带来一种新的方便的文化传递方式,常用在晚会及大型的娱乐节目场合。 本文通过研究和设计一个利用事先编好程序来控制16个LED发光二极管,并配合左右手的摇晃来显示字符和简易图形的电子装置(简称为“摇摇棒”),来传递有趣的信息。此装置利用AT89C51单片机对发光二极管阵列进行控制。用滚珠开关检测当前摇动状态,单片机控制16个发光二极管进行不同频率的亮灭刷新,则只要摇动就可以可显示输出文字及图案等信息,从而达到在该视觉平面上传达信息的作用。 1 硬件系统的组成
[单片机]
基于AT89<font color='red'>C51单片机</font>控制LED摇摇棒的研究
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
  • 学习ARM开发(16)
    ARM有很多东西要学习,那么中断,就肯定是需要学习的东西。自从CPU引入中断以来,才真正地进入多任务系统工作,并且大大提高了工作效率。采 ...
  • 学习ARM开发(17)
    因为嵌入式系统里全部要使用中断的,那么我的S3C44B0怎么样中断流程呢?那我就需要了解整个流程了。要深入了解,最好的方法,就是去写程序 ...
  • 学习ARM开发(18)
    上一次已经了解ARM的中断处理过程,并且可以设置中断函数,那么它这样就可以工作了吗?答案是否定的。因为S3C44B0还有好几个寄存器是控制中 ...
  • 嵌入式系统调试仿真工具
    嵌入式硬件系统设计出来后就要进行调试,不管是硬件调试还是软件调试或者程序固化,都需要用到调试仿真工具。 随着处理器新品种、新 ...
  • 最近困扰在心中的一个小疑问终于解惑了~~
    最近在驱动方面一直在概念上不能很好的理解 有时候结合别人写的一点usb的例子能有点感觉,但是因为arm体系里面没有像单片机那样直接讲解引脚 ...
  • 学习ARM开发(1)
  • 学习ARM开发(2)
  • 学习ARM开发(4)
  • 学习ARM开发(6)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved