找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 6098|回复: 1
打印 上一主题 下一主题
收起左侧

电子钟单片机设计 原理图及源码与仿真

[复制链接]
跳转到指定楼层
楼主
基于at89c52的lcd602显示的电子时钟



原理图中有四个按钮:K1, K2, K3, K4


K1:  当闹钟响起时,按下触发外部中断1,停止闹钟
     注意:上电时按一下K1

K2: 进入设置模式
     首先进入设置闹钟的时,分,秒模式

K3: 通过不断按下K3,可以设置光标到闹钟的时,分,      秒,和正常时间的年,月,日,时,分,秒

K4:增加光标所在位置的年,月,日,时,分,秒
    (闹钟只有时,分,秒)


附件为电子钟的所有源码
原理图及源码.tar (511 KB, 下载次数: 71)


单片机源程序:
  1. #include <reg51.h>
  2. #include <intrins.h>
  3. unsigned char code dis_week[]={"SUN,MON,TUE,WED,THU,FRI,SAT"};
  4. unsigned char code para_month[13]={0,0,3,3,6,1,4,6,2,5,0,3,5}; //星期月参变数
  5. unsigned char data dis_buf1[16];  //lcd上排显示缓冲区
  6. unsigned char data dis_buf2[16];  //lcd下排显示缓冲区  
  7. unsigned char data year,month,date,week;//年、月、日、星期
  8. unsigned char data armhour,armmin,armsec;//闹钟时、分、秒
  9. unsigned char data hour,min,sec,sec100; //时、分、秒、百分之一秒
  10. unsigned char data flag,vkey,skey;//设置状态计数标志、按键先前值、按键当前值
  11. bit alarm; //标识是否启用闹钟,1--启用,0--关闭
  12. sbit  rs = P2^0;    //LCD数据/命令选择端(H/L)
  13. sbit  rw = P2^1;    //LCD读/写选择端(H/L)
  14. sbit  ep = P2^2;    //LCD使能控制
  15. sbit PRE = P1^0;    //调整键(AN3)
  16. sbit SET = P1^1;    //调整键(AN4)
  17. sbit SPK = P1^2;   
  18. void delayms(unsigned char ms); //延时程序
  19. bit  lcd_busy();    //测试LCD忙碌状态程序
  20. void lcd_wcmd(char cmd);  //写入指令到LCD程序
  21. void lcd_wdat(char dat);  //写入数据到LCD程序
  22. void lcd_pos(char pos);   //LCD数据指针位置程序
  23. void lcd_init();    //LCD初始化设定程序
  24. void pro_timedate();   //时间日期处理程序
  25. void pro_display();    //显示处理程序
  26. void pro_key();     //按键处理程序
  27. void time_alarm();    //定时报警功能(闹钟)
  28. unsigned char scan_key();  //按键扫描程序
  29. unsigned char week_proc();  //星期自动计算与显示函数
  30. bit leap_year();    //判断是否为闰年
  31. void lcd_sef_chr();    //LCD自定义字符程序
  32. void update_disbuf(unsigned char t1,unsigned char t2[],unsigned char dis_h,unsigned char dis_m,unsigned char dis_s);   
  33.         //更新显示缓冲区函数

  34. // 延时程序
  35. void delay(unsigned char ms)
  36. { while(ms--)
  37. { unsigned char i;
  38.   for(i = 0; i< 250; i++)   
  39.   {
  40.    _nop_();      //执行一条_nop_()指令为一个机器周期
  41.    _nop_();
  42.    _nop_();
  43.    _nop_();
  44.   }
  45. }
  46. }  


  47. //测试LCD忙碌状态
  48. bit lcd_busy()
  49. {
  50.          bit result;
  51.          rs = 0;
  52.          rw = 1;
  53.          ep = 1;
  54.          _nop_();
  55.          _nop_();
  56.          _nop_();
  57.          _nop_();
  58.          result =(bit)(P0&0x80); //LCD的D0--D7中,D7=1为忙碌,D7=0为空闲
  59.          ep = 0;
  60.          return result;
  61. }


  62. //写入指令到LCD
  63. void lcd_wcmd(char cmd)
  64. {      
  65.          while(lcd_busy()); //当lcd_busy为1时,再次检测LCD忙碌状态,lcd-busy为0时,开始写指令
  66.          rs = 0;
  67.          rw = 0;
  68.          ep = 0;
  69.          _nop_();
  70.          _nop_();
  71.          P0 = cmd;
  72.          _nop_();
  73.          _nop_();
  74.          _nop_();
  75.          _nop_();
  76.          ep = 1;
  77.          _nop_();
  78.          _nop_();
  79.          _nop_();
  80.          _nop_();
  81.          ep = 0;  
  82. }


  83. //写入数据到LCD
  84. void lcd_wdat(char dat)
  85. {      
  86.                 while(lcd_busy()); //当lcd_busy为1时,再次检测LCD忙碌状态,lcd-busy为0时,开始写数据
  87.                 rs = 1;
  88.                 rw = 0;
  89.                 ep = 0;
  90.                 P0 = dat;
  91.                 _nop_();
  92.                 _nop_();
  93.                 _nop_();
  94.                 _nop_();
  95.                 ep = 1;
  96.                 _nop_();
  97.                 _nop_();
  98.                 _nop_();
  99.                 _nop_();
  100.                 ep = 0;
  101. }
  102. //LCD数据指针位置程序
  103. void lcd_pos(char pos)
  104. {      
  105.         lcd_wcmd(pos|0x80); //数据指针=80+地址码(00H~27H,40H~67H)
  106. }

  107. //设定二个自定义字符,(注意:LCD1602中自定义字符的地址为0x00--0x07,即可定义8个字符)
  108. //这里我们设定把一个自定义字符放在0x00位置(000),另一个放在0x01位子(001)
  109. void lcd_sef_chr()
  110. { //第一个自定义字符
  111.                 lcd_wcmd(0x40); //"01 000 000"  第1行地址 (D7D6为地址设定命令形式D5D4D3为字符存放位置(0--7),D2D1D0为字符行地址(0--7))
  112.                 lcd_wdat(0x1f); //"XXX 11111" 第1行数据(D7D6D5为XXX,表示为任意数(一般用000),D4D3D2D1D0为字符行数据(1-点亮,0-熄灭)
  113.                 lcd_wcmd(0x41); //"01 000 001"  第2行地址
  114.                 lcd_wdat(0x11); //"XXX 10001" 第2行数据
  115.                 lcd_wcmd(0x42); //"01 000 010"  第3行地址
  116.                 lcd_wdat(0x15); //"XXX 10101" 第3行数据
  117.                 lcd_wcmd(0x43); //"01 000 011"  第4行地址
  118.                 lcd_wdat(0x11); //"XXX 10001" 第4行数据
  119.                 lcd_wcmd(0x44); //"01 000 100"  第5行地址
  120.                 lcd_wdat(0x1f); //"XXX 11111" 第5行数据
  121.                 lcd_wcmd(0x45); //"01 000 101"  第6行地址
  122.                 lcd_wdat(0x0a); //"XXX 01010" 第6行数据
  123.                 lcd_wcmd(0x46); //"01 000 110"  第7行地址
  124.                 lcd_wdat(0x1f); //"XXX 11111" 第7行数据
  125.                 lcd_wcmd(0x47); //"01 000 111"  第8行地址
  126.                 lcd_wdat(0x00); //"XXX 00000" 第8行数据
  127.                 //第二个自定义字符
  128.                 lcd_wcmd(0x48); //"01 001 000"  第1行地址
  129.                 lcd_wdat(0x01); //"XXX 00001" 第1行数据
  130.                 lcd_wcmd(0x49); //"01 001 001"  第2行地址
  131.                 lcd_wdat(0x1b); //"XXX 11011" 第2行数据
  132.                 lcd_wcmd(0x4a); //"01 001 010"  第3行地址
  133.                 lcd_wdat(0x1d); //"XXX 11101" 第3行数据
  134.                 lcd_wcmd(0x4b); //"01 001 011"  第4行地址
  135.                 lcd_wdat(0x19); //"XXX 11001" 第4行数据
  136.                 lcd_wcmd(0x4c); //"01 001 100"  第5行地址
  137.                 lcd_wdat(0x1d); //"XXX 11101" 第5行数据
  138.                 lcd_wcmd(0x4d); //"01 001 101"  第6行地址
  139.                 lcd_wdat(0x1b); //"XXX 11011" 第6行数据
  140.                 lcd_wcmd(0x4e); //"01 001 110"  第7行地址
  141.                 lcd_wdat(0x01); //"XXX 00001" 第7行数据
  142.                 lcd_wcmd(0x4f); //"01 001 111"  第8行地址
  143.                 lcd_wdat(0x00); //"XXX 00000" 第8行数据
  144. }



  145. //LCD初始化设定
  146. void lcd_init()
  147. {      
  148.          lcd_wcmd(0x38);  //设置LCD为16X2显示,5X7点阵,八位数据借口
  149.          delay(1);
  150.          lcd_wcmd(0x0c);  //LCD开显示及光标设置(光标不闪烁,不显示"-")
  151.          delay(1);
  152.          lcd_wcmd(0x06);  //LCD显示光标移动设置(光标地址指针加1,整屏显示不移动)
  153.          delay(1);
  154.          lcd_wcmd(0x01);  //清除LCD的显示内容
  155.          delay(1);
  156. }


  157. //闰年的计算
  158. bit leap_year()
  159. {
  160.          bit leap;
  161.          if((year%4==0&&year%100!=0)||year%400==0)//闰年的条件
  162.                 leap=1;
  163.          else
  164.                 leap=0;
  165.          return leap;
  166. }


  167. //星期的自动运算和处理
  168. unsigned char week_proc()
  169. {
  170.                 unsigned char num_leap;
  171.                 unsigned char c;
  172.                 num_leap=year/4-year/100+year/400;//自00年起到year所经历的闰年数
  173.                 if( leap_year()&& month<=2 )   //既是闰年且是1月和2月
  174.                                 c=5;
  175.                 else
  176.                                 c=6;
  177.                 week=(year+para_month[month]+date+num_leap+c)%7;//计算对应的星期
  178.                 return week;
  179. }


  180. //更新显示缓冲区
  181. void update_disbuf(unsigned char t1,unsigned char t2[],unsigned char dis_h,unsigned char dis_m,unsigned char dis_s)
  182. {
  183.                 dis_buf1[0]=t1;    //
  184.                 dis_buf1[1]=0x20;    //空格
  185.                 dis_buf1[2]=50;    //'2'
  186.                 dis_buf1[3]=48;             //'0'
  187.                 dis_buf1[4]=year/10+48;
  188.                 dis_buf1[5]=year%10+48;
  189.                 dis_buf1[6]=0x2d;
  190.                 dis_buf1[7]=month/10+48;
  191.                 dis_buf1[8]=month%10+48;
  192.                 dis_buf1[9]=0x2d;    //'-'
  193.                 dis_buf1[10]=date/10+48;
  194.                 dis_buf1[11]=date%10+48;
  195.                 dis_buf1[12]=0x20;
  196.                 dis_buf1[13]=dis_week[4*week];
  197.                 dis_buf1[14]=dis_week[4*week+1];
  198.                 dis_buf1[15]=dis_week[4*week+2];
  199.                 dis_buf2[0]=t2[0];
  200.                 dis_buf2[1]=t2[1];
  201.                 dis_buf2[2]=t2[2];
  202.                 dis_buf2[3]=t2[3];
  203.                 dis_buf2[4]=t2[4];
  204.                 dis_buf2[5]=t2[5];
  205.                 dis_buf2[6]=t2[6];   //空格
  206.                 if (alarm)
  207.                         dis_buf2[7]=0x01;  //alarm=1,显示闹钟启用标致(第二个自定义字符)
  208.                 else
  209.                 dis_buf2[7]=0x20;  //alarm=0,不显示闹钟启用标致
  210.                 dis_buf2[8]=dis_h/10+48;
  211.                 dis_buf2[9]=dis_h%10+48;
  212.                 dis_buf2[10]=0x3a;   //':'
  213.                 dis_buf2[11]=dis_m/10+48;
  214.                 dis_buf2[12]=dis_m%10+48;
  215.                 dis_buf2[13]=0x3a;
  216.                 dis_buf2[14]=dis_s/10+48;
  217.                 dis_buf2[15]=dis_s%10+48;
  218. }

  219. //时间和日期处理程序
  220. void pro_timedate()
  221. {
  222.                 sec++;
  223.          if(sec > 59)
  224.                 {
  225.                         sec = 0;
  226.                   min++;
  227.                   if(min>59)
  228.                  {
  229.                          min=0;
  230.                          hour++;
  231.                          if(hour>23)
  232.                          {
  233.                                  hour=0;
  234.                                  date++;
  235.                            if (month==1||month==3||month==5||month==7||month==8||month==10||month==12)
  236.                                    if (date>31)
  237.                                                 {
  238.                                                                 date=1;
  239.                                                                 month++;
  240.                                                 }     //大月31天
  241.                            if (month==4||month==6||month==9||month==11)  
  242.                                                 if (date>30)
  243.                                                 {
  244.                                                         date=1;
  245.                                                         month++;
  246.                                                 }     //小月30天
  247.                                  if (month==2)   
  248.                                         {
  249.                                                 if( leap_year())        //闰年的条件
  250.                                                         {
  251.                                                                 if (date>29)
  252.                                                                         {
  253.                                                                                 date=1;
  254.                                                                                 month++;
  255.                                                                         }
  256.                                                         }   //闰年2月为29天
  257.                                                 else
  258.                                                 {
  259.                                                         if (date>28)
  260.                                                         {
  261.                                                                 date=1;
  262.                                                                 month++;
  263.                                                         }
  264.                                                 }   //平年2月为28天
  265.                                         }   
  266.                          if (month>12)
  267.                                 {
  268.                                         month=1;
  269.                                         year++;
  270.                                 }
  271.                          if (year>99)
  272.                                     year=0;
  273.                         }
  274.                  }
  275.                 }
  276.          week_proc();
  277.          if (sec==armsec && min==armmin && hour==armhour)   
  278.                 {
  279.                  if (alarm)
  280.                  TR1=1;     //闹钟启用时,报警时间到,启动Timer1
  281.                 }
  282. }
  283. //显示处理程序
  284. void pro_display()
  285. {
  286.         unsigned char i;
  287.   lcd_pos(0x00);
  288.   for (i=0;i<=15;i++)
  289.    {
  290.      lcd_wdat(dis_buf1[i]);
  291.          }
  292.    lcd_pos(0x40);
  293.    for (i=0;i<=15;i++)
  294.          {
  295.                  lcd_wdat(dis_buf2[i]);
  296.          }
  297. }


  298. //Timer0中断处理程序,秒的产生
  299. void timer0() interrupt 1
  300. {
  301.          TH0=0xD8;   
  302.          TL0=0xF0;  
  303.          sec100++;
  304.          if(sec100 >= 100) //1秒时间 (100*10ms=1000ms=1s)
  305.                 {
  306.                         sec100 = 0;
  307.                   pro_timedate();//调用时间和日期处理程序
  308.                 }
  309.          if (sec&0x01)          //"gaohua"闪一秒,停一秒
  310.                 update_disbuf(0x00,"       ",hour,min,sec);    //0x00表示显示00位置的自定义字符  
  311.          else
  312.                 update_disbuf(0x00,"gaohua",hour,min,sec);
  313.          pro_display();  //调用显示处理函数

  314. }

  315. //按键扫描程序
  316. unsigned char  scan_key()
  317. {
  318.          skey=0x00;         //给变量vkey置初值
  319.          skey|=PRE;         //读取PRE键的状态
  320.          skey=skey<<1;        //将PRE键的状态存于skey的B1位
  321.          skey|=SET;         //读取SET键的状态,并存于skey的B0位
  322.          return skey;        //返回skey的键值(即PRE,SET的状态)
  323. }


  324. //外部中断INT0中断处理程序  
  325. void int0() interrupt 0
  326. {  
  327.   TR0=0;         //禁止Timer0
  328.   IE=0;         //禁止中断
  329.   lcd_wcmd(0x0e);       //显示光标"_",整个光标不闪烁
  330.   alarm=1;
  331.   update_disbuf(0x50,"alarm:",armhour,armmin,armsec); //更新显示数据,0x50表示要显示"P"
  332.   pro_display();       //调用显示处理程序
  333.   lcd_pos(0x47);          //使光标位于第一个调整项下      
  334.   flag=0;         
  335.   vkey=0x03;
  336.   while(flag^0x0a)
  337.    {
  338.                  skey = scan_key();     //扫描按键状态
  339.                  if (skey^vkey)      //若skey与vkey相同,跳出循环,相异执行循环体
  340.      {
  341.                          delay(10);     //去按键抖动
  342.        skey = scan_key();   //转回扫描按键状态   
  343.        if (skey^vkey)    //若skey与vkey相同,跳出循环,相异执行循环体  
  344.        {
  345.                                  vkey=skey;   //将skey的值付给vkey
  346.          if (skey==0x01)  //PRE键按下
  347.         {  
  348.                                         flag++; //调整标志位加1
  349.           switch (flag) //将光标置于相应调整位置
  350.           {
  351.               
  352.            case 1: lcd_pos(0x49);break;  //光标置小时报警设置位置      
  353.            case 2: lcd_pos(0x4c);break;  //光标置分钟报警设置位置     
  354.            case 3: lcd_pos(0x4f);break;  //光标置秒时报警设置位置
  355.            
  356.            case 4: update_disbuf(0x50,"time: ",hour,min,sec);
  357.              pro_display();
  358.              lcd_pos(0x05);break;  //光标置年调整位置            
  359.            case 5: lcd_pos(0x08);break;  //光标置月调整位置
  360.            case 6: lcd_pos(0x0b);break;  //光标置日调整位置
  361.            
  362.            case 7: lcd_pos(0x49);break;  //光标置时调整位置
  363.            case 8: lcd_pos(0x4c);break;  //光标置分调整位置
  364.            case 9: lcd_pos(0x4f);break;  //光标置秒调整位置
  365.             
  366.            default:break;
  367.           }
  368.         }
  369.        if (skey==0x02)      //SET键按下
  370.         {
  371.                                         pro_key();  //转设置按键处理程序
  372.         }
  373.       }
  374.     }        
  375.    }        
  376.   lcd_wcmd(0x0c);       //设置LCD开显示及光标不闪烁,不显示"-"
  377.   lcd_wcmd(0x01);          //清除LCD的显示内容
  378.   IE=0x8f;             //CPU开中断,INT0,INT1,开中断
  379.   TR0=1;         //Timer0启动
  380. }

  381. //主程序,初始化及初值设定
  382. void main()
  383. {
  384.          lcd_init();                      //初始化LCD
  385.          lcd_sef_chr();      //写入自定义字符号
  386.          hour=0;
  387.          min=0;
  388.          sec=0;        //开机时的时,分,秒显示
  389.          armhour=6;
  390.          armmin=30;
  391.          armsec=0;  //开机时的时,分,秒报警初值
  392.          year= 16;
  393.          month=6;
  394.          date=1;       //开机时的年,月,日,星期显示
  395.          week_proc();
  396.          alarm=1;       //初始开机,启用闹钟
  397.          IE = 0x8f;       //CPU开中断,INT0,INT1,Timer0,Timer1开中断
  398.          IP = 0x04;       //设置INT0为中断最高优先级
  399.          IT0=0;
  400.          IT1=0;      //外部INT0,INT1设置为电平触发方式(注意,触发不要选边沿方式,易误动)
  401.          TMOD = 0x11;      //Timer0,Timer1工作于模式1, 16位定时方式
  402.          TH0 = 0xdc;
  403.          TL0 = 0x00;    //Timer0置10ms定时初值  
  404.          TH1 = 0xff;
  405.          TL1 = 0x00;    //Timer1置初值  
  406.          TR0 = 1;       //Timer0启动
  407.          TR1 = 0;
  408.          while(1);
  409. }
  410. //设置按键处理程序
  411. void pro_key()
  412. {
  413. switch (flag)
  414.   {
  415.                 case 0:alarm=!alarm;   //启用或关闭闹钟(alarm=1:启用,alarm=0:关闭)
  416.                         update_disbuf(0x50,"alarm:",armhour,armmin,armsec); //更新显示数据
  417.                         pro_display();          //调用显示处理
  418.                         lcd_pos(0x47);break;        //光标回到原调整位置
  419.                 case 1:armhour++;
  420.                         if (armhour>23) armhour=0;
  421.                         update_disbuf(0x50,"alarm:",armhour,armmin,armsec); //更新显示数据
  422.                         pro_display();          //调用显示处理
  423.                         lcd_pos(0x49);break;        //光标回到原调整位置
  424.                 case 2:armmin++;
  425.                         if (armmin>59)
  426.                                 armmin=0;
  427.                         update_disbuf(0x50,"alarm:",armhour,armmin,armsec);
  428.                         pro_display();
  429.                         lcd_pos(0x4c);
  430.                         break;
  431.                 case 3:
  432.                         armsec++;
  433.                         if (armsec>59)
  434.                                 armsec=0;
  435.                         update_disbuf(0x50,"alarm:",armhour,armmin,armsec);
  436.                         pro_display();
  437.                         lcd_pos(0x4f);
  438.                         break;
  439.                 case 4:
  440.                         year++;
  441.                         if (year> 99)
  442.                                 year= 0;
  443.                         week_proc();                        //星期自动运算
  444.                         update_disbuf(0x50,"time: ",hour,min,sec);
  445.                         pro_display();
  446.                         lcd_pos(0x05);
  447.                         break;
  448.                  case 5:
  449.                         month++;
  450.                         if (month>12)
  451.                                 month=1;
  452.                         week_proc();      //星期自动运算
  453.                         update_disbuf(0x50,"time: ",hour,min,sec);
  454.                         pro_display();
  455.                         lcd_pos(0x08);
  456.                         break;
  457.                 case 6:
  458.                         date++;
  459.                         if (month==1||month==3||month==5||month==7||month==8||month==10||month==12)
  460.                          if (date>31) date=1;   //大月31天
  461.                         if (month==4||month==6||month==9||month==11)  
  462.                                 if (date>30) date=1;   //小月30天
  463.                         if (month==2)   
  464.                          {if(leap_year())    //闰年的条件
  465.                                 {if (date>29) date=1;}  //闰年2月为29天
  466.                                 else
  467.                                 {if (date>28) date=1;}}  //平年2月为28天
  468.                         week_proc();         //星期自动运算
  469.                         update_disbuf(0x50,"time: ",hour,min,sec);
  470.                         pro_display();
  471.                         lcd_pos(0x0b);
  472.                         break;
  473.                
  474.                 case 7:
  475.                         hour++;
  476.                         if (hour>23)
  477.                                 hour=0;
  478.                         update_disbuf(0x50,"time: ",hour,min,sec);
  479.                         pro_display();
  480.                         lcd_pos(0x49);break;
  481.                 case 8:
  482.                         min++;
  483.                         if (min>59)
  484.                                 min=0;
  485.                         update_disbuf(0x50,"time: ",hour,min,sec);
  486.                         pro_display();
  487.                         lcd_pos(0x4c);
  488.                         break;
  489.                 case 9:sec++;
  490.                         if (sec>59)
  491.                                 sec=0;
  492.                         update_disbuf(0x50,"time: ",hour,min,sec);
  493.                         pro_display();
  494.                         lcd_pos(0x4f);
  495.                         break;
  496.                 default:
  497.                         break ;            
  498.   }
  499. }
  500. //Timer1中断处理程序,产生报警的声音
  501. void timer1() interrupt 3
  502. {
  503.          TH1=0xff;   
  504.          TL1=0x00;
  505.          SPK=~SPK;
  506.    
  507. }
  508. //外部中断INT1中断处理程序,停止报警声音
  509. void int1() interrupt 2
  510. {
  511. if(TR1)
  512.   TR1=0;   
  513.         SPK = 0;
  514. }
复制代码

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏2 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:428308 发表于 2018-12-6 22:05 来自手机 | 只看该作者
这个实物是 LCD不显示也不亮
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

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