找回密码
 立即注册

QQ登录

只需一步,快速开始

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

带详细注释的LCD1602屏幕单片机电子时钟程序

  [复制链接]
跳转到指定楼层
楼主
ID:102668 发表于 2016-1-16 05:47 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
  1. //按K1按键进入设定状态
  2. //按K2,依次进入闹钟功能是否启用,闹钟时,分秒,年,月,日及时间时,分,秒的设置,直到退出设置状态
  3. //按K3,调整是否起用闹钟和调节闹钟时,分,秒,年,月,日,时间的时,分,秒的数字
  4. //LCD第二排中间显示小喇叭,表示启用闹钟功能,无则禁止闹钟功能(可在调整状态进行设置)
  5. //正常状态,LCD上排最前面显示自定义字符,LCD下排最前面闪动"RICHMCU"
  6. //设置状态,LCD上排最前面显示"P",下排最前面在设置闹钟时间时显示"alarm:",其它状态显示"time"
  7. //年代变化2000--2099,星期自动转换
  8. //程序中有自定义字符写入
  9. #include <reg51.h>
  10. #include <intrins.h>
  11. unsigned char code dis_week[]={"SUN,MON,TUE,WED,THU,FRI,SAT"};
  12. unsigned char code para_month[13]={0,0,3,3,6,1,4,6,2,5,0,3,5}; //星期月参变数
  13. unsigned char data dis_buf1[16];  //lcd上排显示缓冲区
  14. unsigned char data dis_buf2[16];  //lcd下排显示缓冲区  
  15. unsigned char data year,month,date,week;//年、月、日、星期
  16. unsigned char data armhour,armmin,armsec;//闹钟时、分、秒
  17. unsigned char data hour,min,sec,sec100; //时、分、秒、百分之一秒
  18. unsigned char data flag,vkey,skey;//设置状态计数标志、按键先前值、按键当前值
  19. bit alarm; //标识是否启用闹钟,1--启用,0--关闭
  20. sbit  rs = P3^7;    //LCD数据/命令选择端(H/L)
  21. sbit  rw = P3^6;    //LCD读/写选择端(H/L)
  22. sbit  ep = P3^5;    //LCD使能控制
  23. sbit PRE = P1^0;    //调整键(AN3)
  24. sbit SET = P1^1;    //调整键(AN4)
  25. sbit SPK = P3^3;   
  26. void delayms(unsigned char ms); //延时程序
  27. bit  lcd_busy();    //测试LCD忙碌状态程序
  28. void lcd_wcmd(char cmd);  //写入指令到LCD程序
  29. void lcd_wdat(char dat);  //写入数据到LCD程序
  30. void lcd_pos(char pos);   //LCD数据指针位置程序
  31. void lcd_init();    //LCD初始化设定程序
  32. void pro_timedate();   //时间日期处理程序
  33. void pro_display();    //显示处理程序
  34. void pro_key();     //按键处理程序
  35. void time_alarm();    //定时报警功能(闹钟)
  36. unsigned char scan_key();  //按键扫描程序
  37. unsigned char week_proc();  //星期自动计算与显示函数
  38. bit leap_year();    //判断是否为闰年
  39. void lcd_sef_chr();    //LCD自定义字符程序
  40. void update_disbuf(unsigned char t1,unsigned char t2[],unsigned char dis_h,unsigned char dis_m,unsigned char dis_s);   
  41.         //更新显示缓冲区函数

  42. // 延时程序
  43. void delay(unsigned char ms)
  44. { while(ms--)
  45. { unsigned char i;
  46.   for(i = 0; i< 250; i++)   
  47.   {
  48.    _nop_();      //执行一条_nop_()指令为一个机器周期
  49.    _nop_();
  50.    _nop_();
  51.    _nop_();
  52.   }
  53. }
  54. }  

  55. //测试LCD忙碌状态
  56. bit lcd_busy()
  57. {
  58. bit result;
  59. rs = 0;
  60. rw = 1;
  61. ep = 1;
  62. _nop_();
  63. _nop_();
  64. _nop_();
  65. _nop_();
  66. result =(bit)(P0&0x80); //LCD的D0--D7中,D7=1为忙碌,D7=0为空闲
  67. ep = 0;
  68. return result;
  69. }
  70. //写入指令到LCD
  71. void lcd_wcmd(char cmd)
  72. {      
  73. while(lcd_busy()); //当lcd_busy为1时,再次检测LCD忙碌状态,lcd-busy为0时,开始写指令
  74. rs = 0;
  75. rw = 0;
  76. ep = 0;
  77. _nop_();
  78. _nop_();
  79. P0 = cmd;
  80. _nop_();
  81. _nop_();
  82. _nop_();
  83. _nop_();
  84. ep = 1;
  85. _nop_();
  86. _nop_();
  87. _nop_();
  88. _nop_();
  89. ep = 0;  
  90. }
  91. //写入数据到LCD
  92. void lcd_wdat(char dat)
  93. {      
  94. while(lcd_busy()); //当lcd_busy为1时,再次检测LCD忙碌状态,lcd-busy为0时,开始写数据
  95. rs = 1;
  96. rw = 0;
  97. ep = 0;
  98. P0 = dat;
  99. _nop_();
  100. _nop_();
  101. _nop_();
  102. _nop_();
  103. ep = 1;
  104. _nop_();
  105. _nop_();
  106. _nop_();
  107. _nop_();
  108. ep = 0;
  109. }
  110. //LCD数据指针位置程序
  111. void lcd_pos(char pos)
  112. {      
  113. lcd_wcmd(pos|0x80); //数据指针=80+地址码(00H~27H,40H~67H)
  114. }

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


  152. }
  153. //LCD初始化设定
  154. void lcd_init()
  155. {      
  156. lcd_wcmd(0x38);  //设置LCD为16X2显示,5X7点阵,八位数据借口
  157. delay(1);
  158. lcd_wcmd(0x0c);  //LCD开显示及光标设置(光标不闪烁,不显示"-")
  159. delay(1);
  160. lcd_wcmd(0x06);  //LCD显示光标移动设置(光标地址指针加1,整屏显示不移动)
  161. delay(1);
  162. lcd_wcmd(0x01);  //清除LCD的显示内容
  163. delay(1);
  164. }
  165. //闰年的计算
  166. bit leap_year()
  167. {
  168. bit leap;
  169. if((year%4==0&&year%100!=0)||year%400==0)//闰年的条件
  170.   leap=1;
  171. else
  172.   leap=0;
  173. return leap;
  174. }
  175. //星期的自动运算和处理
  176. unsigned char week_proc()
  177. { unsigned char num_leap;
  178. unsigned char c;
  179. num_leap=year/4-year/100+year/400;//自00年起到year所经历的闰年数
  180. if( leap_year()&& month<=2 )   //既是闰年且是1月和2月
  181.   c=5;
  182. else
  183.   c=6;
  184. week=(year+para_month[month]+date+num_leap+c)%7;//计算对应的星期
  185. return week;
  186. }
  187. //更新显示缓冲区
  188. void update_disbuf(unsigned char t1,unsigned char t2[],unsigned char dis_h,unsigned char dis_m,unsigned char dis_s)
  189. { dis_buf1[0]=t1;    //
  190. dis_buf1[1]=0x20;    //空格
  191. dis_buf1[2]=50;    //'2'
  192. dis_buf1[3]=48;             //'0'
  193. dis_buf1[4]=year/10+48;
  194. dis_buf1[5]=year%10+48;
  195. dis_buf1[6]=0x2d;
  196. dis_buf1[7]=month/10+48;
  197. dis_buf1[8]=month%10+48;
  198. dis_buf1[9]=0x2d;    //'-'
  199. dis_buf1[10]=date/10+48;
  200. dis_buf1[11]=date%10+48;
  201. dis_buf1[12]=0x20;
  202. dis_buf1[13]=dis_week[4*week];
  203. dis_buf1[14]=dis_week[4*week+1];
  204. dis_buf1[15]=dis_week[4*week+2];
  205. dis_buf2[0]=t2[0];
  206. dis_buf2[1]=t2[1];
  207. dis_buf2[2]=t2[2];
  208. dis_buf2[3]=t2[3];
  209. dis_buf2[4]=t2[4];
  210. dis_buf2[5]=t2[5];
  211. dis_buf2[6]=t2[6];   //空格
  212.   if (alarm)
  213.   dis_buf2[7]=0x01;  //alarm=1,显示闹钟启用标致(第二个自定义字符)
  214. else
  215.   dis_buf2[7]=0x20;  //alarm=0,不显示闹钟启用标致
  216. dis_buf2[8]=dis_h/10+48;
  217. dis_buf2[9]=dis_h%10+48;
  218. dis_buf2[10]=0x3a;   //':'
  219. dis_buf2[11]=dis_m/10+48;
  220. dis_buf2[12]=dis_m%10+48;
  221. dis_buf2[13]=0x3a;
  222. dis_buf2[14]=dis_s/10+48;
  223. dis_buf2[15]=dis_s%10+48;
  224. }
  225. //时间和日期处理程序
  226. void pro_timedate()
  227. {
  228. sec++;
  229. if(sec > 59)
  230.   {sec = 0;
  231.    min++;
  232.    if(min>59)
  233.    {min=0;
  234.     hour++;
  235.     if(hour>23)
  236.     {hour=0;
  237.      date++;
  238.      if (month==1||month==3||month==5||month==7||month==8||month==10||month==12)
  239.       if (date>31) {date=1;month++;}     //大月31天
  240.      if (month==4||month==6||month==9||month==11)  
  241.        if (date>30) {date=1;month++;}     //小月30天
  242.      if (month==2)   
  243.       {if( leap_year())        //闰年的条件
  244.       {if (date>29) {date=1;month++;}}   //闰年2月为29天
  245.       else
  246.       {if (date>28) {date=1;month++;}}   //平年2月为28天
  247.       }   
  248.      if (month>12) {month=1;year++;}
  249.      if (year>99) year=0;
  250.     }
  251.    }
  252.   }
  253. week_proc();
  254. if (sec==armsec && min==armmin && hour==armhour)   
  255.   {if (alarm)
  256.    TR1=1;     //闹钟启用时,报警时间到,启动Timer1
  257.   }
  258. }
  259. //显示处理程序
  260. void pro_display()
  261. { unsigned char i;
  262. lcd_pos(0x00);
  263. for (i=0;i<=15;i++)
  264.   {lcd_wdat(dis_buf1[i]);}
  265. lcd_pos(0x40);
  266. for (i=0;i<=15;i++)
  267.   {lcd_wdat(dis_buf2[i]);}
  268. }
  269. //Timer0中断处理程序,秒的产生
  270. void timer0() interrupt 1
  271. {
  272. TH0=0xD8;   
  273. TL0=0xF0;  
  274. sec100++;
  275. if(sec100 >= 100) //1秒时间 (100*10ms=1000ms=1s)
  276.   {sec100 = 0;
  277.    pro_timedate();//调用时间和日期处理程序
  278.   }
  279. if (sec&0x01)          //"RICHMCU"闪一秒,停一秒
  280.   update_disbuf(0x00,"       ",hour,min,sec);    //0x00表示显示00位置的自定义字符  
  281. else
  282.   update_disbuf(0x00,"RICHMCU",hour,min,sec);
  283. pro_display();  //调用显示处理函数

  284. }

  285. //按键扫描程序
  286. unsigned char  scan_key()
  287. {
  288. skey=0x00;         //给变量vkey置初值
  289. skey|=PRE;         //读取PRE键的状态
  290. skey=skey<<1;        //将PRE键的状态存于skey的B1位
  291. skey|=SET;         //读取SET键的状态,并存于skey的B0位
  292. return skey;        //返回skey的键值(即PRE,SET的状态)
  293. }
  294. //外部中断INT0中断处理程序
  295. void int0() interrupt 0
  296. {  
  297.   TR0=0;         //禁止Timer0
  298.   IE=0;         //禁止中断
  299.   lcd_wcmd(0x0e);       //显示光标"_",整个光标不闪烁
  300.   alarm=1;
  301.   update_disbuf(0x50,"alarm:",armhour,armmin,armsec); //更新显示数据,0x50表示要显示"P"
  302.   pro_display();       //调用显示处理程序
  303.   lcd_pos(0x47);          //使光标位于第一个调整项下      
  304.   flag=0;         
  305.   vkey=0x03;
  306.   while(flag^0x0a)
  307.    {skey = scan_key();     //扫描按键状态
  308.    if (skey^vkey)      //若skey与vkey相同,跳出循环,相异执行循环体
  309.     { delay(10);     //去按键抖动
  310.      skey = scan_key();   //转回扫描按键状态   
  311.      if (skey^vkey)    //若skey与vkey相同,跳出循环,相异执行循环体  
  312.        { vkey=skey;   //将skey的值付给vkey
  313.        if (skey==0x01)  //PRE键按下
  314.         {  flag++; //调整标志位加1
  315.           switch (flag) //将光标置于相应调整位置
  316.           {
  317.               
  318.            case 1: lcd_pos(0x49);break;  //光标置小时报警设置位置      
  319.            case 2: lcd_pos(0x4c);break;  //光标置分钟报警设置位置     
  320.            case 3: lcd_pos(0x4f);break;  //光标置秒时报警设置位置
  321.            
  322.            case 4: update_disbuf(0x50,"time: ",hour,min,sec);
  323.              pro_display();
  324.              lcd_pos(0x05);break;  //光标置年调整位置            
  325.            case 5: lcd_pos(0x08);break;  //光标置月调整位置
  326.            case 6: lcd_pos(0x0b);break;  //光标置日调整位置
  327.            
  328.            case 7: lcd_pos(0x49);break;  //光标置时调整位置
  329.            case 8: lcd_pos(0x4c);break;  //光标置分调整位置
  330.            case 9: lcd_pos(0x4f);break;  //光标置秒调整位置
  331.             
  332.            default:break;
  333.           }
  334.         }
  335.        if (skey==0x02)      //SET键按下
  336.         { pro_key();  //转设置按键处理程序
  337.         }
  338.       }
  339.     }        
  340.    }        
  341.   lcd_wcmd(0x0c);       //设置LCD开显示及光标不闪烁,不显示"-"
  342.   lcd_wcmd(0x01);          //清除LCD的显示内容
  343.   IE=0x8f;             //CPU开中断,INT0,INT1,开中断
  344.   TR0=1;         //Timer0启动
  345. }

  346. //主程序,初始化及初值设定
  347. void main()
  348. {
  349. lcd_init();                      //初始化LCD
  350. lcd_sef_chr();      //写入自定义字符号
  351. hour=0;min=0;sec=0;        //开机时的时,分,秒显示
  352. armhour=0;armmin=0;armsec=0;  //开机时的时,分,秒报警初值
  353. year= 5; month=1;date=1;       //开机时的年,月,日,星期显示
  354. week_proc();
  355. alarm=1;       //初始开机,启用闹钟
  356. IE = 0x8f;       //CPU开中断,INT0,INT1,Timer0,Timer1开中断
  357. IP = 0x04;       //设置INT0为中断最高优先级
  358. IT0=0;IT1=0;      //外部INT0,INT1设置为电平触发方式(注意,触发不要选边沿方式,易误动)
  359. TMOD = 0x11;      //Timer0,Timer1工作于模式1, 16位定时方式
  360. TH0 = 0xdc;TL0 = 0x00;    //Timer0置10ms定时初值  
  361. TH1 = 0xff;TL1 = 0x00;    //Timer1置初值  
  362. TR0 = 1;       //Timer0启动
  363. TR1 = 0;
  364. while(1);
  365. }
  366. //设置按键处理程序
  367. void pro_key()
  368. {
  369. switch (flag)
  370.   {
  371.   case 0:alarm=!alarm;   //启用或关闭闹钟(alarm=1:启用,alarm=0:关闭)
  372.     update_disbuf(0x50,"alarm:",armhour,armmin,armsec); //更新显示数据
  373.     pro_display();          //调用显示处理
  374.     lcd_pos(0x47);break;        //光标回到原调整位置
  375.   case 1:armhour++;
  376.     if (armhour>23) armhour=0;
  377.     update_disbuf(0x50,"alarm:",armhour,armmin,armsec); //更新显示数据
  378.     pro_display();          //调用显示处理
  379.     lcd_pos(0x49);break;        //光标回到原调整位置
  380.   case 2:armmin++;
  381.     if (armmin>59) armmin=0;
  382.     update_disbuf(0x50,"alarm:",armhour,armmin,armsec);
  383.     pro_display();
  384.     lcd_pos(0x4c);break;
  385.   case 3:armsec++;
  386.     if (armsec>59) armsec=0;
  387.     update_disbuf(0x50,"alarm:",armhour,armmin,armsec);
  388.     pro_display();
  389.     lcd_pos(0x4f);break;
  390.   case 4:year++;
  391.     if (year> 99) year= 0;
  392.     week_proc();                        //星期自动运算
  393.     update_disbuf(0x50,"time: ",hour,min,sec);
  394.     pro_display();
  395.     lcd_pos(0x05);break;
  396.    case 5:month++;
  397.     if (month>12) month=1;
  398.     week_proc();      //星期自动运算
  399.     update_disbuf(0x50,"time: ",hour,min,sec);
  400.     pro_display();
  401.     lcd_pos(0x08);break;
  402.   case 6:date++;
  403.     if (month==1||month==3||month==5||month==7||month==8||month==10||month==12)
  404.      if (date>31) date=1;   //大月31天
  405.     if (month==4||month==6||month==9||month==11)  
  406.       if (date>30) date=1;   //小月30天
  407.     if (month==2)   
  408.      {if(leap_year())    //闰年的条件
  409.       {if (date>29) date=1;}  //闰年2月为29天
  410.       else
  411.       {if (date>28) date=1;}}  //平年2月为28天
  412.     week_proc();         //星期自动运算
  413.     update_disbuf(0x50,"time: ",hour,min,sec);
  414.     pro_display();
  415.     lcd_pos(0x0b);break;
  416.   
  417.   case 7:hour++;
  418.     if (hour>23) hour=0;
  419.     update_disbuf(0x50,"time: ",hour,min,sec);
  420.     pro_display();
  421.     lcd_pos(0x49);break;
  422.   case 8:min++;
  423.     if (min>59) min=0;
  424.     update_disbuf(0x50,"time: ",hour,min,sec);
  425.     pro_display();
  426.     lcd_pos(0x4c);break;
  427.   case 9:sec++;
  428.     if (sec>59) sec=0;
  429.     update_disbuf(0x50,"time: ",hour,min,sec);
  430.     pro_display();
  431.     lcd_pos(0x4f);break;
  432.   default: break ;            
  433.   }
  434. }
  435. //Timer1中断处理程序,产生报警的声音
  436. void timer1() interrupt 3
  437. {
  438. TH1=0xff;   
  439. TL1=0x00;
  440. SPK=~SPK;

  441.    
  442. }
  443. //外部中断INT1中断处理程序,停止报警声音
  444. void int1() interrupt 2
  445. {
  446. if(TR1)
  447.   TR1=0;   
  448. }
复制代码


评分

参与人数 1黑币 +8 收起 理由
stkh + 8 淡定

查看全部评分

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

使用道具 举报

沙发
ID:104763 发表于 2016-3-12 21:08 | 只看该作者
学习了
回复

使用道具 举报

板凳
ID:195749 发表于 2017-5-3 17:15 | 只看该作者
void time_alarm();这个程序没有写
回复

使用道具 举报

地板
ID:251052 发表于 2017-11-25 03:12 | 只看该作者
少了一个函数吧?time_alarm

评分

参与人数 1黑币 +20 收起 理由
admin + 20 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

5#
ID:77707 发表于 2018-3-24 21:11 | 只看该作者
楼主发的这个程序,不错吗!我试了很好,谢谢分享!
回复

使用道具 举报

6#
ID:348323 发表于 2018-9-27 21:09 | 只看该作者
设置状态和正常状态怎么切换?
回复

使用道具 举报

7#
ID:383998 发表于 2018-9-28 11:30 | 只看该作者
好样地!那里弄来的啊!
回复

使用道具 举报

8#
ID:307643 发表于 2018-9-28 17:10 | 只看该作者
带几张实物图就更好了
回复

使用道具 举报

9#
ID:409679 发表于 2018-10-17 16:43 | 只看该作者
很好,码一下。
回复

使用道具 举报

10#
ID:410182 发表于 2018-10-18 16:49 | 只看该作者
子函数写在主函数前面可以不要申明吧
回复

使用道具 举报

11#
ID:501909 发表于 2019-4-9 21:19 | 只看该作者
你好,有原理图吗?

回复

使用道具 举报

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

本版积分规则

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

Powered by 单片机教程网

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