找回密码
 立即注册

QQ登录

只需一步,快速开始

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

89c51+LCD1602+DS1302时钟和闹钟,汉显+图标+带菜单可调 附单片机源码+仿真

  [复制链接]
跳转到指定楼层
楼主
本人是新人,刚学51单片机,准备学好51后再转ARM.通过这段时间的学习和参阅论坛各大神们的51代码。在自己的理解基础上,做了这么一个小闹钟。
过程是苦恼的。但是也加深了对51的理解。特别是时序、指令、指针。还有就是LCD1602的移屏、光标显示、对CGRAM区,RAM区的一些操作。对DS1302的数据格式、指令、外存储存器的操作有了一定的认识。因为是自学的。所在在代码编写上肯定还有一些不足,对于优化代码更是谈不上。所以也就不怕献丑,把代码发上论坛。让各位老师们批评与指导。让我能在学习中有进步。谢谢了。这里放上有Keil 5写的代码和Proteus 8 Professional仿真程序。Proteus 8 Professional仿真程序中不能称之为电路图了。只能算是原理图吧。呵呵。
这个时钟具有LCD1602不带字库。通过对CGRAM的操作来显示汉字。能过对LCD1602的移屏指令来实现菜单功能、通过对DS1302的外存储器的操作来实现闹钟掉电能保持、DS1302第一次通电可初始化。主机掉电继续运行,简单的定时闹铃功能。,通过SETKEY按键来移动光标。

仿真原理图如下(proteus仿真工程文件可到本帖附件中下载)


制作出来的实物图如下:


这部份是代码
main.c
********************************************************
  1. #include "main.h"
  2. void main(void)
  3. {
  4.   LCMInit();   //初始化LCD1602
  5. WriteCGRAM();  //CGRAM区存入自定义字符
  6. DS1302_Init();  //初始化DS1302
  7. while(1)
  8. {
  9.    if(flag==0)
  10.   {
  11.    Read_RTC();   //读取DS1302时间日期数据
  12.       Disp_Write_Lcd();   //将时间日期数据显示到LCD1602
  13.    ShowAlarmBit();   //显示闹钟标志并执行闹钟动作
  14.    bebuzz();         //蜂鸣器简单出声
  15.   }
  16.   Setkeyscan();     //扫描设置按钮
  17.     OKkeyscan();      //扫描确认按钮
  18.     Backkeyscan();    //扫描返回按钮
  19.     Addkeyscan();     //扫描增加按钮
  20.     Subkeyscan();    //扫描减少按钮
  21. }
  22. }
  23. ************************************************************
  24. main.h
  25. **************************************#ifndef __MAIN_H__
  26. #define __MAIN_H__
  27. #include <reg52.h>
  28. #include <string.h>
  29. #include "LCD1602.H"
  30. #include "DS1302.H"
  31. #include "keybutton.h"
  32. #include "bell.h"
  33. #include <intrins.h>
  34. extern bit Moveflag,SETMoveflag,AlarmMoveflag,flag,ShowAlarm,ibell;  //移屏显示标志
  35. extern uchar setn,usetn,msetn;   //设置按键计数
  36. extern uchar My_tmpdate[7];  //初始时间
  37. extern uchar code write_add[7];  //写入DS1302数据地址
  38. extern uchar code read_add[7];   //读取DS1302数据地址
  39. extern uchar code time0[];       //显示日期字符串
  40. extern uchar code time1[];       //显示时间字符串
  41. #endif
  42. ************************************
  43. LCD1602.C
  44. *********************************************
  45. #include "main.h"
  46. uchar code mychar[8][8]={
  47. 0x08,0x0F,0x12,0x0F,0x0A,0x1F,0x02,0x02, //年 0x00
  48. 0x0F,0x09,0x0F,0x09,0x0F,0x09,0x13,0x00, //月 0x01
  49. 0x1F,0x11,0x11,0x1F,0x11,0x11,0x1F,0x00, //日 0x02
  50. 0x10,0x06,0x09,0x08,0x08,0x08,0x09,0x06, // ℃  0x03
  51. 0x01,0x03,0x1D,0x11,0x1D,0x03,0x01,0x00,  //小喇叭标记 0x04
  52. 0x1F,0x0A,0x0A,0x1F,0x0A,0x0A,0x1A,0x00,  // 开  0x05
  53. 0x11,0x0A,0x1F,0x04,0x1F,0x0A,0x11,0x00,  // 关  0x06
  54. 0x04,0x0E,0x0E,0x0E,0x1F,0x1F,0x04,0x00,  // 闹钟标记 0x07
  55. };
  56. /****************延时函数************************/
  57. void delay(uint i)
  58. {
  59.   while(--i);  //自减 当i为1时,跳出循环
  60. }
  61. /****************侧忙函数************************/
  62. bit  ReadStatusLCD()
  63. {
  64. bit result;
  65. LCD_RS = 0;
  66. LCD_RW = 1;
  67. LCD_E = 1;
  68.   delay(50);
  69. result = (bit)(LCD_Data&0x80);  //当LCD_DATA&0x80不为0时result=1,否则result=0
  70. LCD_E = 0;
  71. return result;
  72. }
  73. /****************LCD1602写指令函数************************/
  74. /*
  75. /*写指令数据到LCD                                                         
  76. /*RS=L,RW=L,E=高脉冲,D0-D7=指令码。
  77. /*
  78. /*********************************************************/
  79. void WriteCommandLCM(uchar WDLCD)
  80. {
  81. while(ReadStatusLCD());
  82.   LCD_RS = 0; //表示向LCM写入指令
  83.   LCD_RW = 0; //表示向LCM写入指令
  84.   /****在高脉冲下命令输入*****/
  85.   LCD_E = 0;
  86. delay(50);   
  87.   LCD_Data = WDLCD; //指令代码
  88.   delay(50);;
  89.   LCD_E = 1;
  90.   delay(50);
  91.   LCD_E = 0;
  92. }
  93. /****************LCD1602写数据函数************************/
  94. /*                                                                        
  95. /*写显示数据到LCD                                                         
  96. /*RS=H,RW=L,E=高脉冲,D0-D7=数据。                                       
  97. /*                                                                        
  98. /*******************************************************************/     
  99. void WriteDataLCM(uchar WDLCD)
  100. {
  101. while(ReadStatusLCD());
  102. LCD_RS = 1;//写数据使能
  103.   LCD_RW = 0;
  104.   /****在高脉冲下数据输入*****/
  105.   LCD_E = 0;
  106.   LCD_Data = WDLCD;  //数据内容
  107.   delay(50);
  108.   LCD_E = 1;
  109.   delay(50);
  110.   LCD_E = 0;
  111. }
  112. /****************CGRAM区写入自定义的字符************************/
  113. void WriteCGRAM(void)
  114. {
  115. uchar x,y;
  116. WriteCommandLCM(0x40);
  117. for(y = 0; y < 8; y++)
  118. {
  119.   for(x = 0; x < 8; x++)
  120. {
  121.    WriteDataLCM(mychar[y][x]);   //写入自定义汉字字模,作为临时字库。掉电会清除。
  122. }
  123. }
  124. }
  125. /****************初始化LCD1602函数************************/   
  126. void LCMInit(void)
  127. {
  128.   uchar i;
  129. delay(50);
  130.   WriteCommandLCM(0x38);//显示模式设置
  131.   delay(50);
  132.   WriteCommandLCM(0x38);
  133.   delay(50);
  134.   WriteCommandLCM(0x38);
  135.   WriteCommandLCM(0x08);//显示关闭
  136.   delay(50);
  137.   WriteCommandLCM(0x01);//清除DDRAM的所有单元,光标被移动到屏幕左上角,也就是清除屏幕内容
  138.   WriteCommandLCM(0x06);//光标移动设置整屏不移动
  139.   WriteCommandLCM(0x0c);//屏幕开、光标不闪(0)不显示(0)
  140.   delay(50);
  141.   WriteCommandLCM(0x80);   //指定第一行
  142.   for(i = 0;i < 16;i++)
  143.   {
  144.     WriteDataLCM(time0[i]);   //显示字符 日期
  145.   delay(5);
  146.   switch(i)
  147.   {
  148.     case 4 : WriteDataLCM(0x00);break;   //显示自定义汉字 年
  149.    case 6 : WriteDataLCM(0x01);break;   //显示自定义汉字 月
  150.    case 8 : WriteDataLCM(0x02);break;   //显示自定义汉字 日
  151.   }
  152.   }
  153.   WriteCommandLCM(0x80+0x40);  //指定第二行
  154.   for(i = 0;i < 12;i++)
  155.   {
  156.     WriteDataLCM(time1[i]);    //显示字符 时间
  157.     delay(5);
  158.   }
  159. }
  160. **************************
  161. LCD1602.H
  162. *******************************
  163. /*****************************************************************************
  164. 接口定义是为配合本人的普中HC6800 V2.8开发板上的接口而定义。可以自行改动
  165. *****************************************************************************
  166.                              DS1302  命令字节表
  167. 1602使用三条控制线:EN、RW、RS。其中EN起到类似片选和时钟线的作用,RW和RS指示了读、写的方向和内容。
  168. 在读数据(或者Busy标志)期间,EN线必须保持高电平;而在写指令(或者数据)过程中,EN线上必须送出一个正脉冲。
  169. RW、RS的组合一共有四种情况,分别对应四种操作:
  170. RS=0、RW=0——表示向LCM写入指令
  171. RS=0、RW=1——表示读取Busy标志
  172. RS=1、RW=0——表示向LCM写入数据
  173. RS=1、RW=1——表示从LCM读取数据
  174. LCD在使用的过程中,可以在RS=0、RW=0的情况下,向LCM写入一个字节的控制指令。使用的控制指令一共八个类别。有的类别又有几条不同的指令。具体的情况罗列在下:
  175. 指令功能      RS      R?W    DB7   DB6   DB5   DB4  DB3  DB2  DB1  DB0
  176. 清屏          0        0      0     0     0     0    0    0    0    1
  177. ①01H:清除DDRAM的所有单元,光标被移动到屏幕左上角。
  178. ②02H:DDRAM所有单元的内容不变,光标移至左上角。
  179. ③输入方式设置(EnterModeSet),这些指令规定了两个方面:一是写入一个DDRAM单元后,地址指针如何改变(加一还是减一);二是屏幕上的内容是否滚动。
  180. 04H:写入DDRAM后,地址指针减一,比如第一个字符写入8FH,则下一个字符会写入8EH;屏幕上的内容不滚动。
  181. 05H:写入DDRAM后,地址指针减一,同上一种情况;每一个字符写入以后,屏幕上的内容向右滚动一个字符位。
  182. 06H:写入DDRAM后,地址指针加一,比如第一个字符写入80H,则下一个字符会写入81H;屏幕上的内容也是不滚动。这应该是最常用的一种显示方式。
  183. 07H:写入DDRAM后,地址指针加一,同上一种情况;每一个字符写入以后,屏幕上的内容向左滚动一个字符位。
  184. ④屏幕开关、光标开关、闪烁开关。
  185. 08H、09H、0AH、0BH:关闭显示屏,实质上是不把DDRAM中的内容对应显示在屏幕上,对DDRAM的操作还是在进行的,执行这条指令,
  186. 接着对 DDRAM进行写入,屏幕上没有任何内容,但是接着执行下面的某条指令,就能看到刚才屏幕关闭期间,对DDRAM操作的效果了。
  187. 0cH:打开显示屏,不显示光标,光标所在位置的字符不闪烁。
  188. 0dH:打开显示屏,不显示光标,光标所在位置的字符闪烁。
  189. 0eH:打开显示屏,显示光标,光标所在位置的字符不闪烁。
  190. 0fH:打开显示屏,显示光标,光标所在位置的字符闪烁。
  191. 关于光标的位置:光标所在的位置指示了下一个被写入的字符所处的位置,加入在写入下一个字符前没有通过指令设置DDRAM的地址,那么这个字符就应该显示在光标指定的地方。
  192. ⑤设置光标移动(本质就是AC的增加还是减少)、整体画面是否滚动。
  193. 指令功能              RS      R?W    DB7   DB6   DB5   DB4  DB3  DB2  DB1  DB0
  194. 进入设置模式          0        0      0     0     0     0    0    1   I?D   s
  195. I?D 0= 写入新数据后光标左移  1=写入新数据后光标右移
  196. s   0= 写入新数据后显示屏不移 1=写入新数据后屏幕整体右移1个字符
  197. 例如: WriteCommandLCM(0x06);   也就是 I?D = 1  S = 0  DB2固定不变  110 转成16进制就为0X06
  198. 指令功能              RS      R?W    DB7   DB6   DB5   DB4   DB3   BDB2    DB1  DB0
  199. 进入设置模式          0        0      0     0     0     1    S?C    R?L     X    X
  200. S?C =0 R?L=0  每输入一次该指令,AC(地址计数器)就减一,对应了光标向左移动一格。整体的画面不滚动。 10100 转成16进制就为0X10
  201. S?C =0 R?L=1  每输入一次该指令,AC(地址计数器)就加一,对应了光标向右移动一格。整体的画面不滚动 10100转成16进制就为0X14
  202. S?C =1 R?L=0  每输入一次该指令,整体的画面就向左滚动一个字符位。11000 转成16进制就为0X18
  203. S?C =1 R?L=1  每输入一次该指令,整体的画面就向右滚动一个字符位  11100 转成16进制就为0X1C
  204. 例如: WriteCommandLCM(0x10);   也就是 S?C = 0  R?L = 1  DB1固定不变为0  DB0固定不变为0  10100 转成16进制就为0X10
  205. 10H:每输入一次该指令,AC(地址计数器)就减一,对应了光标向左移动一格。整体的画面不滚动。
  206. 14H:每输入一次该指令,AC就加一,对应了光标向右移动一格。整体的画面不滚动。
  207. 18H:每输入一次该指令,整体的画面就向左滚动一个字符位。
  208. 1CH:每输入一次该指令,整体的画面就向右滚动一个字符位。画面在滚动的时候,每行的首尾是连在一起的,也就是每行的第一个字符,若左移25次,就会显示在该行的最后一格。
  209. 在画面滚动的过程中,AC的值也是变化的。
  210. ⑥显示模式设定指令,设定了显示几行,显示什么样的点阵字符,数据总线占用几位。
  211. 20H:4位总线,单行显示,显示5×7的点阵字符。
  212. 24H:4位总线,单行显示,显示5×10的点阵字符。
  213. 28H:4位总线,双行显示,显示5×7的点阵字符。
  214. 2CH:4位总线,双行显示,显示5×10的点阵字符。
  215. 30H:8位总线,单行显示,显示5×7的点阵字符。
  216. 34H:8位总线,单行显示,显示5×10的点阵字符。
  217. 38H:8位总线,双行显示,显示5×7的点阵字符。这是最常用的一种模式。
  218. 3CH:8位总线,双行显示,显示5×10的点阵字符。
  219.                                                                                               作者:Anksy   发表与51黑电子论坛 转载注明
  220. ********************************************************************************************************************/


  221. #ifndef __LCD1602_H__              //防止一个头文件被include两次
  222. #define __LCD1602_H__
  223. #define LCD_Data  P0               //宏定义单片机P0口为LCD1062,后面的LCD_Data都为P0。
  224. #define uchar unsigned char        //把 unsigned char 宏定义为 uchar 以后只要写uchar 就是unsigned char 的意思。方便程序编写。 无符号字节型(0~255)
  225. #define uint  unsigned int         //把 unsigned int 宏定义为 uint 以后只要写uint 就是unsigned int 的意思。方便程序编写。无符号整型,最高位不表示符号

  226. sbit LCD_RS = P2^6;                //外部位声明定义单片机P2.6口为LM1602的数据命令/选择口
  227. sbit LCD_RW = P2^5;                //外部位声明定义单片机P2.5口为LM1602的数据读/写选口
  228. sbit LCD_E = P2^7;                 //外部位声明定义单片机P2.7口为LM1602的使能口
  229. /****************延时函数************************/
  230. void delay(uint i);
  231. /****************侧忙函数************************/
  232. bit ReadStatusLCM(void);
  233. /****************LCD1602写指令函数************************/
  234. void WriteCommandLCM(uchar WDLCD);
  235. /****************LCD1602写数据函数************************/
  236. void WriteDataLCM(uchar WDLCD);
  237. /****************初始化LCD1602函数************************/
  238. void LCMInit();         
  239. /****************CGRAM区写入自定义的字符************************/
  240. void WriteCGRAM(void);
  241. #endif
  242. ********************************************************************
  243. DS1302.C
  244. #include "main.h"
  245. uchar code time0[]=" 20             ";
  246. uchar code time1[]="      :  :      ";
  247. uchar code time2[]="      :         ";
  248. uchar My_tmpdate[7] = {10,47,23,11,9,2,18};//秒分时日月周年// 可随时更改
  249. uchar code Set_tmpdate[] = "> SET Time&Day  ";//显示时间日期设置
  250. uchar code Set_tmpdate1[] = "> SET Alarm Time";
  251. uchar code read_add[7] = {0x81,0x83,0x85,0x87,0x89,0x8b,0x8d}; //秒分时日月周年 读的寄存器地址
  252. uchar code write_add[7] = {0x80,0x82,0x84,0x86,0x88,0x8a,0x8c}; //秒分时日月周年 写的寄存器地址
  253. uchar data str[3] = " ";
  254. /****************向DS1302写入一字节数据************************/
  255. void DS1302_Write_Byte(uchar dat)
  256. {
  257.   uchar i;
  258.   for (i = 0;i < 8;i++)    //循环8次写入数据
  259.   {
  260.   SCK = 0;
  261.     SDA = dat&0x01;   //每次传输低字节
  262.     dat >>= 1;    //右移一位
  263.     SCK = 1;
  264.   }
  265. }
  266. /****************向DS1302时间寄存器写入数据************************/
  267. void Write_Ds1302(uchar address,uchar dat )   
  268. {
  269.   RST=0;
  270.   _nop_();
  271.   SCK = 0;
  272.   _nop_();
  273.   RST = 1;//启动
  274.   _nop_();   
  275.   DS1302_Write_Byte(address); //发送地址
  276.   DS1302_Write_Byte(dat); //发送数据
  277.   RST = 0;    //恢复
  278. }
  279. /****************从DS1302读出一字节数据***********************/
  280. uchar DS1302_Read_Byte()
  281. {
  282.   uchar i,temp = 0x00;
  283.   for (i = 0;i < 8;i++)   //循环8次 读取数据
  284.   {
  285.   if(SDA)
  286.     temp |= 0x80;   //每次传输低字节
  287.     SCK = 1;        // 这里的时序如果是反的。真机能通过,可正常显示,但是Proteus 8 Professional显是乱码
  288.     SCK = 0;        //
  289.   temp >>= 1;   //右移一位
  290.   }
  291.   return(temp);
  292. }
  293. /****************从DS1302任意地址读出一字节数据***********************/
  294. uchar Read_DS1302_Byte(uchar addr)
  295. {
  296. uchar i;
  297.   uchar temp;
  298.   RST = 1;
  299.   addr = addr | 0x01; //最低RD,有效为高电平
  300.   for (i = 0; i < 8; i ++)
  301.   {
  302.   if (addr & 0x01)
  303.   {
  304.   SDA = 1;
  305.   }
  306.   else
  307.   {
  308.     SDA = 0;
  309.   }
  310.   SCK = 1;             //这里的时序如果是反的。真机能通过,可正常显示,但是Proteus 8 Professional显是乱码
  311.   SCK = 0;
  312.   addr = addr >> 1;  
  313.   }
  314.   //输出数据:temp
  315.   for (i = 0; i < 8; i ++)
  316.   {
  317.     temp = temp >> 1;
  318.     if (SDA)
  319.     {
  320.       temp |= 0x80;
  321.     }
  322.     else
  323.     {
  324.       temp &= 0x7F;
  325.     }
  326.     SCK = 1;             //这里的时序如果是反的。真机能通过,可正常显示,但是Proteus 8 Professional显是乱码
  327.     SCK = 0;
  328.   }
  329.   RST = 0; //停止DS1302总线
  330.   return temp;
  331. }
  332. /*************************从DS1302指定时间寄存器地址读数据**************************************/              
  333. uchar Read_Ds1302(uchar address)
  334. {
  335.   uchar date;
  336.   RST = 0;
  337.   _nop_();
  338.   SCK = 0;
  339.   _nop_();
  340.   RST = 1;
  341.   _nop_();
  342.   DS1302_Write_Byte(address);   //发送地址
  343.   date = DS1302_Read_Byte();    //返回一字节数据
  344.   RST = 0;
  345.   _nop_();  //以下为DS1302复位的稳定时间
  346.   RST = 0;
  347.   SCK = 1;            //这里的时序如果是反的。真机能通过,可正常显示,但是Proteus 8 Professional显是乱码
  348.   _nop_();
  349.   SCK = 0;
  350.   _nop_();
  351.   SDA = 0;
  352.   _nop_();
  353.   SDA = 1;
  354.   _nop_();
  355.   return (date);
  356. }
  357. /*************从DS1302读取日历数据*********************/
  358. void Read_RTC(void)  
  359. {
  360.   uchar i,*p;
  361.   p = read_add;   //读的地址传递
  362.   for(i = 0;i < 7;i++) //分7次读取 秒分时日月周年
  363.   {
  364.     My_tmpdate[i] = Read_Ds1302(*p);
  365.     p++;
  366.   }
  367. }

  368. /***************往DS1302写入日历数据*********************/
  369. void Set_RTC(void)
  370. {
  371.   uchar i,*p,tmp;
  372.   for(i=0;i<7;i++)
  373.   {           //BCD处理,将十六进制转换成十进制
  374.     tmp = My_tmpdate[i] / 10;
  375.     My_tmpdate[i] = My_tmpdate[i] % 10;
  376.     My_tmpdate[i] = My_tmpdate[i] + tmp*16;
  377.   }
  378.   Write_Ds1302(0x8e,0x00);//关闭写保护
  379.   p = write_add;//传要写的寄存器地址
  380.   for(i=0;i<7;i++)  //7次写入秒分时日月周年
  381.   {
  382.     Write_Ds1302(*p,My_tmpdate[i]);
  383.     p++;  
  384.   }
  385.   Write_Ds1302(0x8E,0x80);//打开写保护
  386. }
  387. /****************DS1302初始化函数************************/
  388. void DS1302_Init(void)
  389. {
  390. uchar i,x;  //定义一个标志,第一次写入DS1302的RAM,改动时钟。修改i的赋值后。可重新写时间到DS1302
  391. i = 0x0a;
  392. RST = 0; //RST脚置低
  393.   SCK = 0; //SCK脚置低
  394. Write_Ds1302(0x80,0x00);   //让DS1302工作(强制起振)
  395. if(Read_DS1302_Byte(0xc1) != i)   //如RAM C1标志不是原来的,则写入时钟,达到掉电继续走时。
  396.   {
  397.    Write_Ds1302(0x8e,0x00);//关闭写保护,允许写入数据
  398.     Write_Ds1302(0xc0,i);   //写入标志
  399.   for(x=0;x<3;x++)
  400.   {
  401.    Write_Ds1302(0xc2 + x*2,0x12);
  402.   }
  403.     Write_Ds1302(0x8e,0x80); //打开写保护,禁止写入数据
  404.    Set_RTC();          //写入时间
  405. }
  406. }
  407. /****************星期数字转星期字母************************/
  408. void AutoWeek(void)
  409. {
  410.   switch(My_tmpdate[5]&0x0f)  //星期数据
  411.   {  
  412.    case 7:
  413.    {
  414.     str[0] = 'S';
  415.     str[1] = 'u';
  416.     str[2] = 'n';
  417.     break;
  418.     }
  419.     case 1:
  420.     {
  421.      str[0] = 'M';
  422.      str[1] = 'o';
  423.      str[2] = 'n';
  424.      break;
  425.     }
  426.     case 2:
  427.    {
  428.     str[0] = 'T';
  429.     str[1] = 'u';
  430.     str[2] = 'e';
  431.     break;
  432.    }
  433.     case 3:
  434.    {
  435.     str[0] = 'W';
  436.     str[1] = 'e';
  437.    str[2] = 'd';
  438.     break;
  439.    }
  440.    case 4:
  441.    {
  442.     str[0] = 'T';
  443.     str[1] = 'h';
  444.     str[2] = 'u';
  445.     break;
  446.    }
  447.    case 5:
  448.    {
  449.     str[0] = 'F';
  450.     str[1] = 'r';
  451.     str[2] = 'i';
  452.     break;
  453.    }
  454.    case 6:
  455.    {
  456.     str[0] = 'S';
  457.     str[1] = 'a';
  458.     str[2] = 't';
  459.     break;
  460.    }
  461.    default:
  462.     break;
  463.    }
  464. }
  465. /**********************发送到LCD1602显示出来*******************************/
  466. void Disp_Write_Lcd(void)
  467. {
  468.   static uchar i,temp[2];
  469. for (i = 0; i<7;i++)
  470. {
  471.     switch(i)
  472.     {
  473.       case 0: WriteCommandLCM(0x80 + 0x40 + 0x0a);break;//秒显示坐标
  474.       case 1: WriteCommandLCM(0x80 + 0x40 + 7);break;//分显示坐标
  475.       case 2: WriteCommandLCM(0x80 + 0x40 + 4);break;//时显示坐标
  476.       case 3: WriteCommandLCM(0x80 + 0x9);break;//日显示坐标
  477.       case 4: WriteCommandLCM(0x80 + 6);break;//月显示坐标
  478.     case 5:
  479.    {
  480.         WriteCommandLCM(0x80 + 0x0d);//星期显示坐标
  481.       AutoWeek();                  //转换星期数字为英文
  482.       WriteDataLCM(str[0]);        //显示出来
  483.         WriteDataLCM(str[1]);
  484.       WriteDataLCM(str[2]);
  485.     break;
  486.      }
  487.       case 6: WriteCommandLCM(0x80 + 3);break;//年显示坐标
  488.     }
  489.     temp[0] = My_tmpdate[i]/16;//数据的转换
  490.     temp[1] = My_tmpdate[i]&0x0f;
  491.     WriteDataLCM(0x30 + temp[0]);    //显示出来
  492.     WriteDataLCM(0x30 + temp[1]);
  493. }
  494. }
  495. /**********************发送副屏到LCD1602显示出来*******************************/
  496. void Move_Write_Lcd(void)
  497. {
  498.   static uchar i,temp[2];
  499. WriteCommandLCM(0x02);
  500. WriteCommandLCM(0x80 + 0x10);   //指定第一行
  501.   for(i = 0;i < 16;i++)
  502.   {
  503.      WriteDataLCM(time0[i]);   //显示字符 日期
  504.    delay(5);
  505.    switch(i)
  506.    {
  507.      case 4 : WriteDataLCM(0x00);break;   //显示自定义汉字 年
  508.     case 6 : WriteDataLCM(0x01);break;   //显示自定义汉字 月
  509.     case 8 : WriteDataLCM(0x02);break;   //显示自定义汉字 日
  510.   }
  511.   }
  512.   WriteCommandLCM(0x80+0x40+0x10);  //指定第二行
  513.   for(i = 0;i < 12;i++)
  514.   {
  515.     WriteDataLCM(time1[i]);    //显示字符 时间
  516.     delay(5);
  517.   }
  518. for (i = 0; i<7;i++)
  519. {
  520.     switch(i)
  521.     {
  522.       case 0: WriteCommandLCM(0x80 + 0x40 + 0x1a);break;//秒显示坐标
  523.       case 1: WriteCommandLCM(0x80 + 0x40 + 0x17);break;//分显示坐标
  524.       case 2: WriteCommandLCM(0x80 + 0x40 + 0x14);break;//时显示坐标
  525.       case 3: WriteCommandLCM(0x80 + 0x19);break;//日显示坐标
  526.       case 4: WriteCommandLCM(0x80 + 0x16);break;//月显示坐标
  527.     case 5: WriteCommandLCM(0x80 + 0x1d);break;//星期显示坐标  
  528.       case 6: WriteCommandLCM(0x80 + 0x13);break;//年显示坐标
  529.     }
  530.     temp[0] = My_tmpdate[i] / 16;//数据的转换
  531.     temp[1] = My_tmpdate[i]&0x0f;
  532.     WriteDataLCM(0x30 + temp[0]);
  533.     WriteDataLCM(0x30 + temp[1]);
  534. }
  535. for (i = 0;i <16;i++)
  536. {
  537.   WriteCommandLCM(0x18);      //移动屏幕
  538.   delay(10000);
  539. }
  540. Moveflag = 1;
  541. }
  542. /**********************发送设置副屏到LCD1602显示出来*******************************/
  543. void Move_SET_Write_Lcd(void)
  544. {
  545.   static uchar i;
  546.   for(i = 0;i < 16;i++)
  547.   {
  548.   WriteCommandLCM(0x80 + i);  //设置日期时间字符坐标
  549.   WriteDataLCM(Set_tmpdate[i]);       //将日期时间菜单显示出来
  550.   WriteCommandLCM(0x80 + 0x40 + i);  //设置闹钟字符坐标
  551.   WriteDataLCM(Set_tmpdate1[i]);      //将闹钟菜单显示出来
  552. }
  553. for (i = 0;i <16;i++)
  554. {
  555.   WriteCommandLCM(0x18);              //移动屏幕
  556.   delay(10000);
  557. }
  558. WriteCommandLCM(0x02);
  559. SETMoveflag =1;
  560. }
  561. /**********************发送设置副屏到LCD1602显示出来*******************************/
  562. void Move_Alarm_Write_Lcd(void)
  563. {
  564.   static uchar i,temp[2];
  565. WriteCommandLCM(0x02);
  566. WriteCommandLCM(0x80+0x10);   //指定第二行
  567. for(i = 0;i<16;i++)
  568. {
  569.   WriteDataLCM(0x20);
  570. }
  571. WriteCommandLCM(0x80+0x40+0x10);   //指定第二行
  572. for(i = 0;i < 16;i++)
  573.   {
  574.     WriteDataLCM(time2[i]);    //显示字符 时间
  575.     delay(5);
  576.   }
  577. for (i = 0; i<2;i++)
  578. {
  579.     switch(i)
  580.     {
  581.       case 0: WriteCommandLCM(0x80+ 0x40 + 0x17);break;//分显示坐标
  582.       case 1: WriteCommandLCM(0x80+ 0x40 + 0x14);break;//时显示坐标
  583.     }
  584.   temp[0] = Read_DS1302_Byte(0xc5+i*2)/10;  //16进制转10进制取整
  585.   temp[1] = Read_DS1302_Byte(0xc5+i*2)%10;  //16进制转10进制取余
  586.     WriteDataLCM(0x30 + temp[0]);
  587.     WriteDataLCM(0x30 + temp[1]);
  588. }
  589. if(Read_DS1302_Byte(0xc9)== 0x00)
  590. {
  591.    WriteCommandLCM(0x80+0x40+0x1e);   //指定第二行
  592.    WriteDataLCM(0x06);    //写入自定义字符 关
  593.   }
  594.   if(Read_DS1302_Byte(0xc9)== 0x01)
  595. {
  596.    WriteCommandLCM(0x80+0x40+0x1e);   //指定第二行
  597.    WriteDataLCM(0x05);    //写入自定义字符 开
  598.   }
  599. for (i = 0;i <16;i++)
  600. {
  601.   WriteCommandLCM(0x18);      //移动屏幕
  602.   delay(10000);
  603. }
  604. AlarmMoveflag=1;
  605. }
  606. /****************显示闹钟标志************************/
  607. void ShowAlarmBit(void)             //读GCRAM区标志来判断
  608. {
  609. if(Read_DS1302_Byte(0xc9)== 0x01)   
  610. {
  611.    WriteCommandLCM(0x80+0x40);   //指定第二行
  612.    WriteDataLCM(0x07);
  613.   if (HEX_DEC(Read_DS1302_Byte(0xc5))==change(My_tmpdate[1])&HEX_DEC(Read_DS1302_Byte(0xc7))==change(My_tmpdate[2]))   
  614.   {
  615.    ibell = 1;    //打开蜂鸣器
  616.    WriteCommandLCM(0x80+0x40);   //指定第二行
  617.    WriteCommandLCM(0x0d);//不显示光标字符闪烁
  618.   }
  619.   else
  620.   {
  621.    ibell = 0;    //关闭蜂鸣器
  622.    WriteCommandLCM(0x80+0x40);   //指定第二行
  623.    WriteCommandLCM(0x0c);//不显示光标并且不闪烁
  624.   }
  625. }
  626. if(Read_DS1302_Byte(0xc9)== 0x00)
  627. {
  628.    WriteCommandLCM(0x80+0x40);   //指定第二行
  629.    WriteDataLCM(0x20);   
  630. }
  631. }
复制代码

*************************************************
DS1302.H
************************************************
/*****************************************************************************
             单片机IO接口定义是为配合本人的普中HC6800 V2.8开发板上的接口而定义。可以自行改动
                             DS1302  命令字节表
********************************************************************************************************************
READ   I  WRITE   I  BIT7  I   BIT6  I  BIT5 I  BIT4  I   BIT3     I    BIT2  I   BIT1    I  BIT0     RANGE
81H    I   80H    I  CH    I    10 Seconds            I    Seconds                                  I  00-59
83H    I   81H    I        I    10 Minutes            I    Minutes                                  I  00-59
85H    I   84H    I 12-24  I  0      I AM-PM I  Hour  I    Hour                                     I  1-12 0-23
87H    I   86H    I  0     I  0      I  10Date        I    Date                                     I  1-31   
89H    I   88H    I  0     I  0      I  0    I10 MonthI    Month                          I         I  1-12   
8BH    I   8AH    I  0     I  0      I  0    I  0     I    0       I         Day                    I  1-7     
8DH    I   8CH    I  10 Year                          I            Year                   I         I  00-99   
8FH    I   8EH    I   WP   I   0     I   0   I  0     I    0       I     0    I     0     I         I    --
91H    I   90H    I   TCS  I  TCS    I   TCS I  TCS   I   TCS      I    TCS   I    TCS    I         I    --
********************************************************************************************************************/
/*
   DS1302时钟的寄存器,其中8个和时钟有关的,5位地址分别是00000一直到00111这8个地址,还有一个寄存器的地址是01000,这是涓流充电所用的寄存器,我们这里不讲。
  在DS1302的数据手册里的地址,直接把第七位、第六位和第零位值给出来了,所以指令就成了80H、81H那些了,最低位是1,那么表示读,最低位是0表示写.
  
   寄存器一:最高位CH是一个时钟停止标志位。如果我们的时钟电路有备用电源部分,上电后,我们要先检测一下这一位,如果这一位是0,那说明我们的时钟在系
         统掉电后,由于备用电源的供给,时钟是持续正常运行的;如果这一位是1,那么说明我们的时钟在系统掉电后,时钟部分不工作了。若我们的Vcc1悬
       空或者是电池没电了,当我们下次重新上电时,读取这一位,那这一位就是1,我们可以通过这一位判断时钟在单片机系统掉电后是否持续运行。
       剩下的7位高3位是秒的十位,低4位是秒的个位,这里注意再提一次,DS1302内部是BCD码,而秒的十位最大是5,所以3个二进制位就够了。
      
   寄存器二:bit7没意义,剩下的7位高3位是分钟的十位,低4位是分钟的个位。
   
   寄存器三:bit7是1的话代表是12小时制,是0的话代表是24小时制,bit6固定是0,bit5在12小时制下0代表的是上午,1代表的是下午,
         在24小时制下和bit4一起代表了小时的十位,低4位代表的是小时的个位。
         
   寄存器四:高2位固定是0,bit5和bit4是日期的十位,低4位是日期的个位。
  
   寄存器五:高3位固定是0,bit4是月的十位,低4位是月的个位。
   
   寄存器六:高5位固定是0,低3位代表了星期。
   
   寄存器七:高4位代表了年的十位,低4位代表了年的个位。这里特别注意,这里的00到99年指的是2000年到2099年。
   
   寄存器八:bit7是一个保护位,如果这一位是1,那么是禁止给任何其他的寄存器或者那31个字节的RAM写数据的。因此在写数据之前,这一位必须先写成0。
******************************************************************************************************
                                         外存储器(RAM)
                 读地址     写地址        数据范围
                  C1          C0           00-FF                   这个地址在本程序中用于判断DS1302是否是第一次使用。用于初始时间设定
         C3          C2           00-FF                  
         C5          C4           00-FF                   C2\3,C4\C5,C6\C7,C8\C9 在本程序中定义为闹钟分钟的读和写的设定时间。好处是只要DS1302有后备电源则时间不丢。
         ..          ..           00-FF                    闹钟打开与关闭的标志
         ..          ..           00-FF
         ..          ..           00-FF                                                作者:Anksy   发表与51黑电子论坛 转载注明
             ..          ..           00-FF
            FD          FC        00-FF
*/

#ifndef __DS1302_H__              //防止一个头文件被include两次
#define __DS1302_H__
#define uchar unsigned char        //把 unsigned char 宏定义为 uchar 以后只要写uchar 就是unsigned char 的意思。方便程序编写。 无符号字节型(0~255)
#define uint  unsigned int         //把 unsigned int 宏定义为 uint 以后只要写uint 就是unsigned int 的意思。方便程序编写。无符号整型,最高位不表示符号
sbit SCK = P3^6;                //外部位声明定义单片机P3.6口为DS1302时钟线
sbit SDA = P3^4;               //外部位声明定义单片机P3.4口为DS1302数据线
sbit RST = P3^5;                 //外部位声明定义单片机P3.5口为DS1302复位线
/****************从DS1302任意地址读出一字节数据***********************/
uchar Read_DS1302_Byte(uchar addr);
/****************向DS1302时间寄存器写入一字节数据************************/
void DS1302_Write_Byte(uchar dat);  
/*****************向DS1302写入数据************************/
void Write_Ds1302(uchar address,uchar dat );  
/****************从DS1302时间寄存器读出一字节数据***********************/
uchar DS1302_Read_Byte();
/*************************从DS1302指定时间寄存器地址读数据**************************************/
uchar Read_Ds1302(uchar  address);  
/************************读取日历*********************************/
void Read_RTC(void);  
/************************设定日历******************************/
void Set_RTC(void);  
/**********************发送到LCD1602显示出来*******************************/
void Disp_Write_Lcd(void);   
/**********************发送副屏到LCD1602显示出来*******************************/
void Move_Write_Lcd(void);  
/**********************发送设置副屏到LCD1602显示出来*******************************/
void Move_SET_Write_Lcd(void);
/**********************发送设置副屏到LCD1602显示出来*******************************/
void Move_Alarm_Write_Lcd(void);
/****************DS1302初始化函数************************/
void DS1302_Init(void);
/****************星期数字转星期字母************************/
void AutoWeek(void);
/****************显示闹钟标志************************/
void ShowAlarmBit(void);
#endif
********************************************************
keybutton.c
***********************#include "main.h"
bit flag;//是否读取时间的标志位
uchar setn=0; // 复位键按下次数
uchar msetn;  // 确认按键按下次数
uchar usetn;  // 调时间日期时 复位键按下次数
bit Moveflag,SETMoveflag,AlarmMoveflag,ShowAlarm,alterTime,alterAlarm; //一些移动按键 的标志
/****************十进制转换成十六进制************************/
uchar change(uchar x)//十进制转换成十六进制
{
  uchar tep,y;
  y=x;
  tep=y/16;
  y=y%16;
  y=y+tep*10;
  return y;
}
/****************十六进制转换成十进制************************/
uchar HEX_DEC(uchar x)
{
uchar tep,y;
tep= x/10;   //数据转换 16进制转10进制 十位
  y = x%10;   //数据转换 16进制转10进制 十位
y = tep*10 + y;
return y;
}
/****************扫描设置按键动作************************/
void Setkeyscan(void)
{
  if(SET_KEY==0)
  {
  flag=1;//时间停止
     delay(10000);//去键盘按下抖动
     if(SET_KEY==0)
     {
       delay(10000);
    while(!SET_KEY);
       delay(10000);//去键盘抬起时抖动
       while(!SET_KEY);
       delay(10000);
      if(SETMoveflag == 0)
      {
       Move_SET_Write_Lcd();   //显示设置菜单界面
      }
   switch(setn)
   {
    case 0:
     {
     WriteCommandLCM(0x80 + 0x00);//将光标移动到时间日期设置
          WriteCommandLCM(0x0f);//显示光标并且闪烁
          //SETMoveflag = 1;
     msetn = 0;
      break;
    }
    case 1:
     {
     WriteCommandLCM(0x80 + 0x40+0x00);//将光标移动到闹钟设置
          WriteCommandLCM(0x0f);//显示光标并且闪烁
          //SETMoveflag = 1;
     msetn = 1;
     break;
     }
   }
    setn++;
   if(setn == 2)
   {
    setn = 0;   //菜单处光标循环数值归零
   }
/*********************以下循环闪烁要改变日期时间数值处的光标****************/
   if(Moveflag==1&SETMoveflag==1)
      {
     usetn++;
         if (usetn == 7)
     {
      usetn = 0;   //改变数值光标归零
     }
       switch(usetn)
       {
      case 0  :
           {
       WriteCommandLCM(0x80+0x40+27);//将光标移动到秒个位
             WriteCommandLCM(0x0f);//显示光标并且闪烁
       break;
         }
        case 1:
           {
       WriteCommandLCM(0x80+0x40+24);//将光标移动到分个位
             WriteCommandLCM(0x0f);//显示光标并且闪烁
       break;
         }
           case 2:
           {
       WriteCommandLCM(0x80+0x40+21);//将光标移动到时个位
             WriteCommandLCM(0x0f);//显示光标并且闪烁
             break;      
        }  
           case 3:
           {
       WriteCommandLCM(0x80+ 30);//将光标移动到星期个位
             WriteCommandLCM(0x0f);//显示光标并且闪烁
             break;      
        }
           case 4:
           {
       WriteCommandLCM(0x80+ 26);//将光标移动到天个位
             WriteCommandLCM(0x0f);//显示光标并且闪烁
             break;      
        }
           case 5:
           {
       WriteCommandLCM(0x80+ 23);//将光标移动到月个位
             WriteCommandLCM(0x0f);//显示光标并且闪烁
             break;      
        }
           case 6:
           {
       WriteCommandLCM(0x80+ 20);//将光标移动到年个位
             WriteCommandLCM(0x0f);//显示光标并且闪烁
             break;      
        }      
       }     
      }
/*********************以下循环闪烁要改变闹钟时间数值处的光标****************/
    if(AlarmMoveflag==1)
      {
     usetn++;
         if (usetn == 3)
     {
      usetn = 0;   //改变数值光标归零
     }
       switch(usetn)
       {
      /*case 0  :
           {
       WriteCommandLCM(0x80+0x40+27);//将光标移动到秒个位
             WriteCommandLCM(0x0f);//显示光标并且闪烁
       break;
         }*/
        case 0:
           {
       WriteCommandLCM(0x80+0x40+24);//将光标移动到分个位
             WriteCommandLCM(0x0f);//显示光标并且闪烁
       break;
         }
           case 1:
           {
       WriteCommandLCM(0x80+0x40+21);//将光标移动到时个位
             WriteCommandLCM(0x0f);//显示光标并且闪烁
             break;      
        }  
           case 2:
           {
       WriteCommandLCM(0x80+0x40 +30);//将光标移动到 开
             WriteCommandLCM(0x0f);//显示光标并且闪烁
             break;      
        }   
       }     
      }
  }
  }
}
/****************扫描确认按键动作************************/
void OKkeyscan(void)
{
if(OK_KEY == 0)
  {
     delay(10000);//去键盘按下抖动
     if(OK_KEY == 0)
     {
       delay(10000);
    while(!OK_KEY);
       delay(10000);//去键盘抬起时抖动
       while(!OK_KEY);
       delay(10000);
      if(Moveflag == 0&SETMoveflag ==1&AlarmMoveflag==0)    //防止反复进入调日期界面
      {
     switch(msetn)
     {
      case 0:
      {
       Move_Write_Lcd();  //进入调日期界面
       WriteCommandLCM(0x80+0x40+27);//将光标移动到秒个位
             WriteCommandLCM(0x0f);//关闭光标
       usetn = 0;
       alterTime = 1;
       alterAlarm = 0;
       break;
      }
      case 1:
      {
       OK_KEY = 1;
       Move_Alarm_Write_Lcd();  //进入调日期界面
       WriteCommandLCM(0x80+0x40+24);//将光标移动到秒个位
             WriteCommandLCM(0x0f);//关闭光标
       usetn = 0;
       alterTime = 0;
       alterAlarm = 1;
       break;
      }
     }
       }   
     }
   }
}
/****************扫描返回按键动作************************/
void Backkeyscan(void)
{
  if(Back_KEY==0)
  {
  delay(10000);
  while(!SET_KEY);
    delay(10000);//去键盘抬起时抖动
    while(!SET_KEY);
    delay(10000);
  LCMInit();
    flag = 0;//时钟正常运行
   Moveflag = 0;
   SETMoveflag = 0;
  AlarmMoveflag = 0;
   setn = 0;
   msetn = 0;
   usetn = 0;
  WriteCommandLCM(0x0c);//光标停止闪烁
}
}
/****************扫描增加键动作************************/
void Addkeyscan(void)                        
{
uchar tem[2],tp,AlarmTemp[2];
  if (Add_KEY == 0)
  {
   delay(5000);
   if (Add_KEY == 0 )
   {
    delay(5000);
    while(!Add_KEY);
    delay(5000);
    while(!Add_KEY);
    if (SETMoveflag == 1&alterTime== 1)
       {
    switch(usetn)                     //case 序号为LCD1602显示顺序。My_tmpdate[0]中括号内序号为DS1302时间格式顺序
    {
     case 0:                                    //调秒
     {
      My_tmpdate[0] = change(My_tmpdate[0]);     
            My_tmpdate[0]++;
      if(My_tmpdate[0] == 60)
      {
       My_tmpdate[0] = 0;
      }
      tp = My_tmpdate[0]/10;
            My_tmpdate[0] = My_tmpdate[0]%10;
            My_tmpdate[0] = My_tmpdate[0]+tp*16;
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[0],My_tmpdate[0]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+0x40+26);
            tem[0]=My_tmpdate[0]/16;//数据的转换
            tem[1]=My_tmpdate[0]&0x0f;
            WriteDataLCM(0x30+tem[0]);                    //显示出来
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+0x40+27);
      break;
     }
     case 1:                                      //调分钟
     {
      My_tmpdate[1] = change(My_tmpdate[1]);      
            My_tmpdate[1]++;
      if(My_tmpdate[1] == 60)
      {
       My_tmpdate[1] = 0;
      }
      tp = My_tmpdate[1]/10;
            My_tmpdate[1] = My_tmpdate[1]%10;
            My_tmpdate[1] = My_tmpdate[1]+tp*16;
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[1],My_tmpdate[1]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+0x40+23);
            tem[0]=My_tmpdate[1]/16;//数据的转换
            tem[1]=My_tmpdate[1]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+0x40+24);
      break;
     }
     case 2:                                        //调小时
     {
      My_tmpdate[2] = change(My_tmpdate[2]);     
            My_tmpdate[2]++;
      if(My_tmpdate[2] >= 24)
      {
       My_tmpdate[2] = 0;
      }
      tp = My_tmpdate[2]/10;
            My_tmpdate[2] = My_tmpdate[2]%10;
            My_tmpdate[2] = My_tmpdate[2]+tp*16;
      Write_Ds1302(0x8e,0X00);//关闭写保护
      Write_Ds1302(write_add[2],My_tmpdate[2]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+0x40+20);
            tem[0]=My_tmpdate[2]/16;//数据的转换
            tem[1]=My_tmpdate[2]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+0x40+21);
      break;
     }
     case 3:                                        //调星期
     {
      My_tmpdate[5] = change(My_tmpdate[5]);     
            My_tmpdate[5]++;
      if(My_tmpdate[5] == 8)
      {
       My_tmpdate[5] = 1;
      }
      tp = My_tmpdate[5]/10;
            My_tmpdate[5] = My_tmpdate[5]%10;
            My_tmpdate[5] = My_tmpdate[5]+tp*16;
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[5],My_tmpdate[5]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+29);
            tem[0]=My_tmpdate[5]/16;//数据的转换
            tem[1]=My_tmpdate[5]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+30);
      break;
     }
     case 4:                                        //调天
     {
      My_tmpdate[3] = change(My_tmpdate[3]);     
            My_tmpdate[3]++;
      if(My_tmpdate[3] == 32)
      {
       My_tmpdate[3] = 1;
      }
       tp = My_tmpdate[3]/10;
            My_tmpdate[3] = My_tmpdate[3]%10;
            My_tmpdate[3] = My_tmpdate[3]+tp*16;
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[3],My_tmpdate[3]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+25);
            tem[0]=My_tmpdate[3]/16;//数据的转换
            tem[1]=My_tmpdate[3]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+26);
      break;
     }
     case 5:                                        //调月
     {
      My_tmpdate[4] = change(My_tmpdate[4]);      
            My_tmpdate[4]++;
      if(My_tmpdate[4] == 13)
      {
       My_tmpdate[4] = 1;
      }
      tp = My_tmpdate[4]/10;
            My_tmpdate[4] = My_tmpdate[4]%10;
            My_tmpdate[4] = My_tmpdate[4]+tp*16;
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[4],My_tmpdate[4]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+22);
            tem[0]=My_tmpdate[4]/16;//数据的转换
            tem[1]=My_tmpdate[4]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+23);
      break;
     }
     case 6:                                        //调年
     {
      My_tmpdate[6] = change(My_tmpdate[6]);      
            My_tmpdate[6]++;
      if(My_tmpdate[6] == 100)
      {
       My_tmpdate[6] = 0;
      }
       tp = My_tmpdate[6]/10;
            My_tmpdate[6] = My_tmpdate[6]%10;
            My_tmpdate[6] = My_tmpdate[6]+tp*16;
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[5],My_tmpdate[6]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+19);
            tem[0]=My_tmpdate[6]/16;//数据的转换
            tem[1]=My_tmpdate[6]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+20);
      break;
     }
    }
   }
/*************************************以下是修改闹钟时间***************************************************************/
   if(AlarmMoveflag==1&alterAlarm==1)
   {
    switch(usetn)                     //case 序号为LCD1602显示顺序。My_tmpdate[0]中括号内序号为DS1302时间格式顺序
    {
     case 0:                                      //调分钟
     {
      AlarmTemp[0] = Read_DS1302_Byte(0xc5);   
            AlarmTemp[0]++;
      if(AlarmTemp[0] == 60)
      {
       AlarmTemp[0] = 0;
       }
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(0xc4,AlarmTemp[0]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+0x40+23);
            tem[0]=AlarmTemp[0]/10;//数据的转换
            tem[1]=AlarmTemp[0]%10;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+0x40+24);                        //显示出来
      break;
     }
     case 1:                                        //调小时
     {
      AlarmTemp[1] = Read_DS1302_Byte(0xc7);   
            AlarmTemp[1]++;
      if(AlarmTemp[1] == 24)
      {
       AlarmTemp[1] = 0;
      }
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(0xc6,AlarmTemp[1]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+0x40+20);
            tem[0]=AlarmTemp[1]/10;//数据的转换
            tem[1]=AlarmTemp[1]%10;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+0x40+21);
      break;
     }
     case 2:
     {
      ShowAlarm = 1; //调闹钟关
      ScanShowAlarm();
      WriteCommandLCM(0x80+0x40+30);//将光标移动到 关
            WriteCommandLCM(0x0f);//显示光标并且闪烁
      break;        
     }
    }
     }
  }
}
}
/****************扫描减少键动作************************/
void Subkeyscan(void)                              //case 序号为LCD1602显示顺序。My_tmpdate[0]中括号内序号为DS1302时间格式顺序
{
uchar tem[2],tp,AlarmTemp[2];
  if (Sub_KEY == 0)
  {
   delay(5000);
   if (Sub_KEY == 0)
   {
    delay(5000);
    while(!Sub_KEY);
    delay(5000);
    while(!Sub_KEY);
    if (SETMoveflag == 1&alterTime==1)
       {
    switch(usetn)
    {
     case 0:                                    //调秒
     {
      My_tmpdate[0] = change(My_tmpdate[0]);
            if (My_tmpdate[0] !=0)      
            My_tmpdate[0]--;
      if(My_tmpdate[0] == 0)
      {
       My_tmpdate[0] = 59;
      }
      tp = My_tmpdate[0]/10;
            My_tmpdate[0] = My_tmpdate[0]%10;
            My_tmpdate[0] = My_tmpdate[0]+tp*16;
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[0],My_tmpdate[0]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+0x40+26);
            tem[0]=My_tmpdate[0]/16;//数据的转换
            tem[1]=My_tmpdate[0]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+0x40+27);                 //显示出来
      break;
     }
     case 1:                                      //调分钟
     {
      My_tmpdate[1] = change(My_tmpdate[1]);  
            if (My_tmpdate[1] !=0)      
            My_tmpdate[1]--;
      if(My_tmpdate[1] == 0)
      {
       My_tmpdate[1] = 59;
      }
      tp = My_tmpdate[1]/10;
            My_tmpdate[1] = My_tmpdate[1]%10;
            My_tmpdate[1] = My_tmpdate[1]+tp*16;
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[1],My_tmpdate[1]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+0x40+23);
            tem[0]=My_tmpdate[1]/16;//数据的转换
            tem[1]=My_tmpdate[1]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+0x40+24);
      break;
     }
     case 2:                                        //调小时
     {
      My_tmpdate[2] = change(My_tmpdate[2]);
            if (My_tmpdate[2] !=0)      
            My_tmpdate[2]--;
      if(My_tmpdate[2] == 0)
      {
       My_tmpdate[2] = 23;
      }
      tp = My_tmpdate[2]/10;
            My_tmpdate[2] = My_tmpdate[2]%10;
            My_tmpdate[2] = My_tmpdate[2]+tp*16;
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[2],My_tmpdate[2]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+0x40+20);
            tem[0]=My_tmpdate[2]/16;//数据的转换
            tem[1]=My_tmpdate[2]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+0x40+21);
      break;
     }
     case 3:                                        //调星期
     {
      My_tmpdate[5] = change(My_tmpdate[5]);  
            if (My_tmpdate[5] !=0)      
            My_tmpdate[5]--;
      if(My_tmpdate[5] == 0)
      {
       My_tmpdate[5] = 7;
      }
      tp = My_tmpdate[5]/10;
            My_tmpdate[5] = My_tmpdate[5]%10;
            My_tmpdate[5] = My_tmpdate[5]+tp*16;
       Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[5],My_tmpdate[5]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+29);
            tem[0]=My_tmpdate[5]/16;//数据的转换
            tem[1]=My_tmpdate[5]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+30);
      break;
     }
     case 4:                                        //调天
     {
      My_tmpdate[3] = change(My_tmpdate[3]);   
            if (My_tmpdate[3] !=0)      
            My_tmpdate[3]--;
      if(My_tmpdate[3] == 0)
      {
       My_tmpdate[3] = 31;
      }
      tp = My_tmpdate[3]/10;
            My_tmpdate[3] = My_tmpdate[3]%10;
            My_tmpdate[3] = My_tmpdate[3]+tp*16;
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[3],My_tmpdate[3]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+25);
            tem[0]=My_tmpdate[3]/16;//数据的转换
            tem[1]=My_tmpdate[3]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+26);
      break;
     }
     case 5:                                        //调月
     {
      My_tmpdate[4] = change(My_tmpdate[4]);
            if (My_tmpdate[4] !=0)      
            My_tmpdate[4]--;
      if(My_tmpdate[4] == 0)
      {
       My_tmpdate[4] = 12;
      }
       tp = My_tmpdate[4]/10;
            My_tmpdate[4] = My_tmpdate[4]%10;
            My_tmpdate[4] = My_tmpdate[4]+tp*16;
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[4],My_tmpdate[4]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+22);
            tem[0]=My_tmpdate[4]/16;//数据的转换
            tem[1]=My_tmpdate[4]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+23);
      break;
     }
     case 6:                                        //调年
     {
      My_tmpdate[6] = change(My_tmpdate[6]);
            if (My_tmpdate[6] !=0)      
            My_tmpdate[6]--;
      if(My_tmpdate[6] == 0)
      {
       My_tmpdate[6] = 99;
      }
       tp = My_tmpdate[6]/10;
            My_tmpdate[6] = My_tmpdate[6]%10;
            My_tmpdate[6] = My_tmpdate[6]+tp*16;
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(write_add[5],My_tmpdate[6]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+19);
            tem[0]=My_tmpdate[6]/16;//数据的转换
            tem[1]=My_tmpdate[6]&0x0f;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+20);
      break;
     }
    }
   }
/*************************************以下是修改闹钟时间***************************************************************/
if(AlarmMoveflag==1&alterAlarm== 1)
{  
    switch(usetn)                     //case 序号为LCD1602显示顺序。My_tmpdate[0]中括号内序号为DS1302时间格式顺序
    {
     case 0:                                      //调分钟
     {
      AlarmTemp[0] = Read_DS1302_Byte(0xc5);      
            AlarmTemp[0]--;
      if (AlarmTemp[0]== 0)
      {
       AlarmTemp[0] = 59;
      }
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(0xc4,AlarmTemp[0]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+0x40+23);
            tem[0]=AlarmTemp[0]/10;//数据的转换
            tem[1]=AlarmTemp[0]%10;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+0x40+24);                    //显示出来
      break;
     }
     case 1:                                        //调小时
     {
      AlarmTemp[1] = Read_DS1302_Byte(0xc7);      
            AlarmTemp[1]--;
      if (AlarmTemp[1]== 0)
      {
       AlarmTemp[1] = 23;
      }
      Write_Ds1302(0x8e,0x00);//关闭写保护
      Write_Ds1302(0xc6,AlarmTemp[1]);
      Write_Ds1302(0x8e,0x80);//打开写保护
      WriteCommandLCM(0x80+0x40+20);
            tem[0]=AlarmTemp[1]/10;//数据的转换
            tem[1]=AlarmTemp[1]%10;
            WriteDataLCM(0x30+tem[0]);
            WriteDataLCM(0x30+tem[1]);
      WriteCommandLCM(0x80+0x40+21);
      break;
     }
     case 2:
     {
      ShowAlarm = 0; //调闹钟关
      ScanShowAlarm();
      WriteCommandLCM(0x80+0x40+30);//将光标移动到 关
            WriteCommandLCM(0x0f);//显示光标并且闪烁
      break;        
     }
    }
     }
  }
}
}
/****************扫描闹钟开与关动作************************/
void ScanShowAlarm(void)
{
uchar i;
if(ShowAlarm == 0)                  //闹钟标志显示与不显示值
{
  WriteCommandLCM(0x80+0x40+30);//将光标移动到 关
   WriteDataLCM(0x06);    //写入自定义字符 关
  Write_Ds1302(0x8e,0x00);//关闭写保护
  Write_Ds1302(0xc8,0x00);   //写入不显示闹钟标志
  Write_Ds1302(0x8e,0x80);//打开写保护
  WriteCommandLCM(0x80+0x10);   //指定第一行
   for(i = 0;i<16;i++)
   {
    WriteDataLCM(0x20);
   delay(5);
   }
}
if(ShowAlarm == 1)
{
  WriteCommandLCM(0x80+0x40+30);//将光标移动到 开
  WriteDataLCM(0x05);    //写入自定义字符 开
   Write_Ds1302(0x8e,0x00);//关闭写保护
  Write_Ds1302(0xc8,0x01);     //写入显示闹钟标志
  Write_Ds1302(0x8e,0x80);//打开写保护
  WriteCommandLCM(0x80+0x10);   //指定第一行
   for(i = 0;i<16;i++)
   {
    WriteDataLCM(0x20);
   delay(5);
   }
}            
}
**************************************
keybutton.h
****************************************
#ifndef __KEYBUTTON_H__
#define __KEYBUTTON_H__
#define uchar unsigned char
#define uint  unsigned int
sbit SET_KEY = P1^4;         //外部位声明定义单片机P1.4口为设置按钮
sbit Add_KEY = P1^5;              //外部位声明定义单片机P1.5口为增加按钮
sbit Sub_KEY = P1^6;              //外部位声明定义单片机P1.6口为减少按钮
sbit OK_KEY = P1^7;          //外部位声明定义单片机P1.7口为确认按钮
sbit Back_KEY = P1^3;        //外部位声明定义单片机P1.4口为返回按钮
/****************十六进制转换成十进制************************/
uchar HEX_DEC(uchar x);
/****************BCD转码************************/
uchar change(uchar x);
/****************扫描设置按键动作************************/
void Setkeyscan(void);
/****************扫描确认按键动作************************/
void OKkeyscan(void);
/****************扫描返回按键动作************************/
void Backkeyscan(void);
/****************扫描增加键动作************************/
void Addkeyscan(void);
/****************扫描减少键动作************************/
void Subkeyscan(void);
/****************扫描闹钟开与关动作************************/
void ScanShowAlarm(void);
#endif


********************************
bell.c
**************************************
#include "main.h"
bit ibell=0;
/****************蜂鸣器简单出声***********************/
void bebuzz(void)
{
  uchar i;
if (ibell == 1)
{
    for(i=0;i<200;i++)//喇叭发声的时间循环,改变大小可以改变发声时间长短
    {
     delay(80);//参数决定发声的频率,估算值
      buzz=!buzz;
    }
    buzz=1;        //喇叭停止工作,间歇的时间,可更改
    delay(20000);
  }
}
**************************
bell.h
****************************************************
#ifndef __BELL_H__
#define __BELL_H__
#define uchar unsigned char
#define uint  unsigned int
sbit buzz = P3^0;       //外部位声明定义单片机P3.0口为蜂鸣器
/****************蜂鸣器简单出声***********************/
void bebuzz(void);
#endif

下面是源代码与仿真程序
仿真Timer.rar (69.62 KB, 下载次数: 283)
Time.rar (93.8 KB, 下载次数: 326)




评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

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

使用道具 举报

沙发
ID:386381 发表于 2018-9-14 21:42 来自手机 | 只看该作者
谢谢分享
回复

使用道具 举报

板凳
ID:390236 发表于 2018-9-15 17:03 | 只看该作者
顶一下
谢谢分享
回复

使用道具 举报

地板
ID:59884 发表于 2019-4-11 12:58 | 只看该作者
谢谢楼主分享!
回复

使用道具 举报

5#
ID:21545 发表于 2019-5-3 22:13 | 只看该作者
谢谢分享
回复

使用道具 举报

6#
ID:84745 发表于 2020-9-19 22:36 来自手机 | 只看该作者
没Uv2文件,初学者不会
回复

使用道具 举报

7#
ID:328014 发表于 2020-9-20 01:53 | 只看该作者
8imi8 发表于 2020-9-19 22:36
没Uv2文件,初学者不会

楼主用的应该是Keil4 所以肯定是没有uv2文件的,你应该升级Keil
回复

使用道具 举报

8#
ID:938838 发表于 2021-6-17 22:00 | 只看该作者
看到代码的长度。。。我就知道楼主很厉害了。。。
回复

使用道具 举报

9#
ID:66328 发表于 2021-6-21 11:46 | 只看该作者
赞一个,写得非常详细,也感谢你的分享!
回复

使用道具 举报

10#
ID:1113925 发表于 2024-5-5 16:20 | 只看该作者
为什么会这样呢,在设定闹钟时变为了I4-I4

2.png (25.99 KB, 下载次数: 0)

2.png
回复

使用道具 举报

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

本版积分规则

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

Powered by 单片机教程网

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