開頭嘮一嘮:
趁着寒假的時間,也趁着課程設計正好是做一個萬年曆。就打算好好從頭到尾來一遍。漲漲知識。首先說的是本人也是小白一顆,大神們能幫忙指正錯誤的話,不勝感激。寫博客只是爲了總結經驗,要是幫到一部分人就更好了。我想是從硬件到軟件都介紹的詳細一點,還想說一說自己遇到的一些問題,可能要寫的長一點。代碼的話我會在後面上傳。好,閒話不多說。進入正題。
概述:
首先說一下我用到的東西,硬件方面(電路都是自己拿萬能板焊的):一片51單片機,一塊12864液晶,一片ds1302時鐘芯片,四個按鍵。還有些電容、電阻、晶振什麼的,下面講到的時候再說吧。主要的就這麼多吧。再簡單說一下按鍵的功能吧,假設按鍵分別是k1,k2,k3,k4。首先lcd主界面是顯示的當前的日期時間和四路鬧鐘的時間。附圖。k1,k2,k3,k4最開始被按下時分別對應的功能是k1:進入時間設定模式;k2:進入日期設定模式;k3:進入鬧鐘設定模式;k4:進入秒錶計數模式。進入不同的模式後,四個按鍵有都有了新的功能,首先k4一直是退出,就是退出到最開始的選四種模式。k1,k2,k3對於日期和時間設定模式是一樣的功能k1:數值加1,k2:數值減1,k3:更換調的是小時還是分鐘抑或年份還是月份。對於鬧鐘模式,k1:數值加1,k2:更換調的是小時還是分鐘,k3:更換調的是第幾個鬧鐘。對於秒錶模式,k1:第一次按是開始計數,然後再按就是記錄一下當前是多少秒,最多可以記錄9次。k2:暫停/開始,k3:重新計數。有點繞得慌,簡單的的說就是有兩重循環。要是還沒理解,可以看後面的代碼。
一:硬件電路
這部分怎麼說,說簡單也挺簡單的。但其中有個梗我現在還沒過去。就是最開始我打算自己焊個下載電路在上面的,結果總是下不進去程序。這部分算是題外話了,但還是想簡單說一說。最開始打算用CH340芯片直接usb轉uart的,結果芯片買回來發現好像沒有直插的。自己腐板子什麼的又嫌太麻煩。最後打算先用usb轉九針串口轉成rs232電平,再用max232轉成uart電平的。照着電路圖一頓焊,結果果然不出我所料,不可能一下就成功下進去程序。就找問題啊,找啊找,找啊找。好像是找到了一個,就是51下程序不是有一個斷電在上電的過程嗎?我是這樣做的,但其中好像有問題,斷的這個電應該只是單片機的電,而不包括max232的電。於是又改電路,改完還是不行。算了,這個我以後搞明白了再來說說吧。
其餘的應該就不算什麼難的了,找一個51最小系統原理圖照着焊唄,沒什麼太大的問題的。法
對了,還有幾個小的點,提一提吧。51的P0口是相當於集電極開路的門電路的,記得接上拉電阻。LCD屏導完程序時,最開始如果什麼也不顯示的話,記得調一下3腳接的電位器調一下背光。
二.軟件設計
1.按鍵檢測
這一部分在我最開始看來是沒有什麼大文章的,也沒有什麼可以值得寫的,有點基礎的人幾分鐘就可以把程序寫出了。可是當自己正真寫的時候,才知道自己不懂得太多,要學的也太多。單片機的IO口最普通的兩種功能,輸入和輸出嘛。記得自己學stm32時,IO口的輸入輸出是要在最開始初始化的是定義的。也就是IO口在同一時刻只能有一種功能吧,總不能又輸入有輸出吧。可是51呢?讓我懵逼,在任何地方,包括啓動文件裏都沒有定義IO口是輸入還是輸出。這讓我很鬱悶,總不會我讓一個IO口輸出一個高電平後,還可以從IO口讀輸入吧,那樣不一直應該讀到的就是我輸出的高電平嗎。直到我好好研究了一波51IO口的內部電路,才明白其中的玄機。
這裏是最簡單的P1口的內部結構圖。有點數電基礎的人大概可以看明白。具體我就不講了。你可以參考這裏http://www.eeworld.com.cn/mcu/article_2017120236473_2.html
由上圖可見,要正確地從引腳上讀入外部信息,必須先使場效應管關斷,以便由外部輸入的信息確定引腳的狀態。爲此,在作引腳讀入前,必須先對該端口寫入l。具有這種操作特點的輸入/輸出端口,稱爲準雙向I/O口。8051單片機的P1、P2、P3都是準雙向口。P0端口由於輸出有三態功能,輸入前,端口線已處於高阻態,無需先寫入l後再作讀操作。弄懂IO口的內部結構之後。我就直接上程序了,慢慢研究吧。註釋的和沒有用到的部分大家就不要糾結了。
/************************************************************************************************* 程序說明:按鍵的檢測程序(基於51單片機),現在只有獨立按鍵檢測函數 Author: xingcheng IO說明:按鍵接的 **************************************************************************************************/ #include"key.h" sbit KeyPort2=P1^5; sbit KeyPort0=P1^7; sbit KeyPort1=P1^6; sbit KeyPort3=P1^4; //自己焊的按鍵接的單片機引腳 //sbit KeyPort2=P1^2; //sbit KeyPort0=P1^0; //sbit KeyPort1=P1^1; //sbit KeyPort3=P1^3; /************************************************************************ 函數名稱:key_scan() 函數功能:4個獨立按鍵檢測 輸入參數:無 返回值:KeyV 通過返回值的不同來確定哪個按鍵被按下 *************************************************************************/ uchar key_scan() { uchar KeyV; KEYPORT=0xff; //從51IO口讀數據,一般要先給鎖存器寫1, //具體請參考51IO口內部結構 if(KeyPort0==0||KeyPort1==0||KeyPort2==0||KeyPort3==0) //判斷是否有按鍵按下 //這裏改成if((P3&0xf0)!=0xf0)總是錯,原因可能是P3讀數據不是從引腳讀的 //而是從鎖存器讀的,一直是0xff { delay_ms(10); //防止抖動(拿板子實驗時,發現這裏延不延時並無影響) if(KeyPort0==0) //判斷哪個按鍵被按下// { KeyV=K1; } else if(KeyPort1==0) { KeyV= K2; } else if(KeyPort2==0) { KeyV=K3; } else if(KeyPort3==0) { KeyV=K4; } else { KeyV= 0; } //判斷哪個按鍵被按下// if(KeyV != 0) //有按鍵按下時,進行鬆手檢測 while(KeyPort0==0||KeyPort1==0||KeyPort2==0||KeyPort3==0); delay_ms(10); //延時消抖(拿板子實驗,這裏延時非常必要) } return KeyV; //返回KeyV來標識哪一個按鍵被按下 } /*****************************有時間再完善連按,長按等功能************************/ /* while((KEYPORT&0Xf0)!=NO_KEY) { delay_ms(15); PressCnt--; if(PressCnt==0) { PressCnt=SHORTCNT; return KeyV; } } } delay_ms(15); if((KEYPORT&0Xf0)==NO_KEY) { ReleaseCon=0; return KeyV; } */
#ifndef __KEY_H#define _KEY_H #include<reg52.h>#include"delay.h" #ifndef uchar #define uchar unsigned char#endif #define KEYPORT P3 // 四個按鍵接在了P3口的四個引腳#define NO_KEY 0xf0#define K1 0X01#define K2 0X02#define K3 0X03#define K4 0X04#define KEYSUB 0X02#define KEYADD 0X01#define KEYSET 0X04#define KEYNEXT 0X03 //K1,2,3,4,和這些是一樣的,只是寫.c文件時#define LONGCNT 150#define SHORTCNT 12 uchar key_scan(); #endif
2.lcd12864
這個就是真的沒什麼好說的了。就是記得調電位器調背光。對了,還有一個 好坑的地方,不知道各位有沒有解決方法,就是那個光標(一閃一閃的那個)每次移動都是兩個字兩個字的移。上程序。
/*********************************************************************** 程序功能:12864液晶驅動程序 其他: 只包括基本的字符串顯示功能 *************************************************************************/ #include <LCD12864.h> #define uchar unsigned char #define uint unsigned int #define LCD_data P0 //數據口 /******************************************************************* 函數名稱:delay(int ms) 函數功能:延時 輸入參數:ms 要延時的ms數 返回值: 無 *******************************************************************/ void delay(int ms) { while(ms--) { uchar i; for(i=0;i<250;i++) { ; ; ; ; } } } /******************************************************************* 函數名稱:lcd_busy() 函數功能:檢測LCD忙狀態。 輸入參數:無 返回值: result result爲1時,忙等待;result爲0時,閒,可寫指令數據 *******************************************************************/ bit lcd_busy() { bit result; LCD_RS = 0; LCD_RW = 1; LCD_EN = 1; delay_ms(1); result = (bit)(LCD_data&0x80); LCD_EN = 0; return(result); } /*******************************************************************/ /*寫指令數據到LCD */ /*RS=L,RW=L,E=高脈衝,D0-D7=指令碼。 */ /*******************************************************************/ void lcd_wcmd(uchar cmd) { while(lcd_busy()); LCD_RS = 0; LCD_RW = 0; LCD_EN = 0; delay_ms(1); LCD_data = cmd; delay_ms(1); LCD_EN = 1; delay_ms(1); LCD_EN = 0; } /*******************************************************************/ /*寫顯示數據到LCD */ /*RS=H,RW=L,E=高脈衝,D0-D7=數據。 */ /*******************************************************************/ void lcd_wdat(uchar dat) { while(lcd_busy()); LCD_RS = 1; LCD_RW = 0; LCD_EN = 0; LCD_data = dat; delay_ms(1); LCD_EN = 1; delay_ms(1); LCD_EN = 0; } /*******************************************************************/ /* LCD初始化設定 */ /*******************************************************************/ void lcd_init() { LCD_PSB = 1; //並口方式 lcd_wcmd(0x34); //擴充指令操作 delay_ms(5); lcd_wcmd(0x30); //基本指令操作 delay_ms(5); lcd_wcmd(0x0C); //顯示開,關光標 delay(5); lcd_wcmd(0x01); //清除LCD的顯示內容 delay(5); } /*********************************************************/ /* 設定顯示位置 X:行數 Y:列數 */ /*********************************************************/ void lcd_pos(uchar X,uchar Y) { uchar pos; if (X==0) {X=0x80;} else if (X==1) {X=0x90;} else if (X==2) {X=0x88;} else if (X==3) {X=0x98;} pos = X+Y ; lcd_wcmd(pos); //顯示地址 } /*********************************************************/ /* 在設定位置顯示字符(串) */ /*********************************************************/ void zifu_dis (uchar X,uchar Y,uchar *dis) { uchar i; lcd_pos(X,Y); i = 0; while(dis[i] != '\0') { //顯示字符 lcd_wdat(dis[i]); i++; } }
/**************dis_12864.h***************/#ifndef __LCD12864_H__#define __LCD12864_H__ #include"delay.h" #include <reg52.h>#define uchar unsigned char #define uint unsigned int /*12864端口定義*/ #define LCD_data P0 //數據口 sbit LCD_RS = P2^3; //寄存器選擇輸入 sbit LCD_RW = P2^4; //液晶讀/寫控制 sbit LCD_EN = P2^5; //液晶使能控制 sbit LCD_PSB = P3^3; //串/並方式控制 /*函數聲明*/ void delay(int ms); void lcd_init(); void beep(); void dataconv(); void lcd_pos(uchar X,uchar Y); //確定顯示位置 void zifu_dis (uchar X,uchar Y,uchar *dis); #endif
3.ds1302時鐘
直接給程序,相應的資料大家可以網上搜的。
/************************************************************************** THE REAL TIMER DS1302 DRIVER LIB COPYRIGHT (c) 2005 BY JJJ. -- ALL RIGHTS RESERVED -- File Name: DS1302.h Author: Jiang Jian Jun Created: 2003/7/21 Modified: NO Revision: 1.0 re ***************************************************************************/ #include"ds1302.h" /*************************************************************************** 函數名稱:DS1302InputByte(unsigned char d) 函數功能:實時時鐘寫入一個字節(內部函數) 輸入參數:d 要寫入的數據 返回值:無 ***************************************************************************/ void DS1302InputByte(unsigned char d) { unsigned char i; ACC = d; for(i=8; i>0; i--) { DS1302_IO = ACC0; //相當於彙編中的 RRC DS1302_CLK = 1; DS1302_CLK = 0; ACC = ACC >> 1; } } /*************************************************************************** 函數名稱:DS1302OutputByte(void) 函數功能:實時時鐘讀取一個字節(內部函數) 輸入參數:無 返回值:ACC 讀到的數據 ***************************************************************************/ unsigned char DS1302OutputByte(void) { unsigned char i; for(i=8; i>0; i--) { ACC = ACC >>1; //相當於彙編中的 RRC ACC7 = DS1302_IO; DS1302_CLK = 1; DS1302_CLK = 0; } return(ACC); } /*************************************************************************** 函數名稱:Write1302(unsigned char ucAddr, unsigned char ucDa) 函數功能:往實時時鐘指定地址寫數據 輸入參數:ucAddr 要寫數據的地址 ucDa 要寫入的數據 返回值:無 ***************************************************************************/ void Write1302(unsigned char ucAddr, unsigned char ucDa) { DS1302_RST = 0; DS1302_CLK = 0; DS1302_RST = 1; DS1302InputByte(ucAddr); // 地址,命令 DS1302InputByte(ucDa); // 寫1Byte數據 // DS1302_CLK = 1; DS1302_RST = 0; } /*************************************************************************** 函數名稱:Read1302(unsigned char ucAddr) 函數功能:讀取ds1302某地址的數據 輸入參數:ucAddr 要讀數據的地址 返回值: ucData 讀出的數據 ***************************************************************************/ unsigned char Read1302(unsigned char ucAddr) //讀取ds1302某地址的數據 { unsigned char ucData; DS1302_RST = 0; DS1302_CLK = 0; DS1302_RST = 1; DS1302InputByte(ucAddr|0x01); // 地址,命令 ucData = DS1302OutputByte(); // 讀1Byte數據 // DS1302_CLK = 1; DS1302_RST = 0; return(ucData); } /*************************************************************************** 函數名稱:DS1302_SetProtect(bit flag) 函數功能:是否寫保護 輸入參數:flag 返回值: 無 其他:flag爲1時,0x8E對應的control register最高位爲1,寫保護開啓 ***************************************************************************/ void DS1302_SetProtect(bit flag) //是否寫保護 { if(flag) Write1302(0x8E,0x80); else Write1302(0x8E,0x00); } /*************************************************************************** 函數名稱:DS1302_SetTime(unsigned char Address, unsigned char Value) 函數功能:向指定寄存器寫時間 輸入參數:Address 寄存器地址 Value 要寫入的時間(hex碼) 返回值: 無 其他:可以先用宏定義定義好year,month,hour等的地址 ***************************************************************************/ void DS1302_SetTime(unsigned char Address, unsigned char Value) // 設置時間函數 { DS1302_SetProtect(0); Write1302(Address, ((Value/10)<<4 | (Value%10))); //將hex碼轉化爲BCD碼 } /*************************************************************************** 函數名稱:DS1302_GetTime(SYSTEMTIME *Time) 函數功能:讀出日期和時間,將它們存入Time這個結構體中 輸入參數:*Time 要存日期和時間的結構體的地址 返回值: 無 ***************************************************************************/ void DS1302_GetTime(SYSTEMTIME *Time) { unsigned char ReadValue; ReadValue = Read1302(DS1302_SECOND); Time->Second = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //八進制轉爲十進制 ReadValue = Read1302(DS1302_MINUTE); Time->Minute = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); ReadValue = Read1302(DS1302_HOUR); Time->Hour = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); ReadValue = Read1302(DS1302_DAY); Time->Day = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); ReadValue = Read1302(DS1302_WEEK); Time->Week = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); ReadValue = Read1302(DS1302_MONTH); Time->Month = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); ReadValue = Read1302(DS1302_YEAR); Time->Year = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); } /*************************************************************************** 函數名稱:DateToStr(SYSTEMTIME *Time) 函數功能:將讀出的日期變成便於顯示的字符形式 輸入參數:*Time 要存字符的結構體 返回值:無 ***************************************************************************/ void DateToStr(SYSTEMTIME *Time) { Time->DateString[0] = Time->Year/10+0x30 ; //·分離個位和十位 Time->DateString[1] = Time->Year%10+0x30 ; Time->DateString[2] = '-'; Time->DateString[3] = Time->Month/10+0x30; Time->DateString[4] = Time->Month%10+0x30 ; Time->DateString[5] = '-'; Time->DateString[6] = Time->Day/10+0x30 ; Time->DateString[7] = Time->Day%10+0x30 ; //用LCD顯示,要變成ascii碼所以加了0x30,用數碼管顯示的話就不用加了 Time->DateString[8] = '\0'; } /*************************************************************************** 函數名稱:TimeToStr(SYSTEMTIME *Time) 函數功能:將讀出的時間變成便於顯示的字符形式 輸入參數:*Time 要存字符的結構體 返回值:無 ***************************************************************************/ void TimeToStr(SYSTEMTIME *Time) { Time->TimeString[0] = Time->Hour/10+0x30 ; Time->TimeString[1] = Time->Hour%10+0x30 ; Time->TimeString[2] = ':'; Time->TimeString[3] = Time->Minute/10+0x30 ; Time->TimeString[4] = Time->Minute%10+0x30 ; Time->TimeString[5] = ':'; Time->TimeString[6] = Time->Second/10+0x30; Time->TimeString[7] = Time->Second%10+0x30 ;//用LCD顯示,要變成ascii碼所以加了0x30,用數碼管顯示的話就不用加了 Time->DateString[8] = '\0'; } /*************************************************************************** 函數名稱:Initial_DS1302(void) 函數功能:初始化ds1302 輸入參數:無 返回值:無 ***************************************************************************/ void Initial_DS1302(void) { unsigned char Second=Read1302(DS1302_SECOND); if(Second&0x80) DS1302_SetTime(DS1302_SECOND,0); } /******************************************************************************** void BurstWrite1302(unsigned char *pWClock) //往ds1302寫入時鐘數據(多字節方式) { unsigned char i; Write1302(0x8e,0x00); // 控制命令,WP=0,寫操作 DS1302_RST = 0; DS1302_CLK = 0; DS1302_RST = 1; DS1302InputByte(0xbe); // 0xbe:時鐘多字節寫命令 for (i = 8; i>0; i--) //8Byte = 7Byte 時鐘數據 + 1Byte 控制 { DS1302InputByte(*pWClock); // 寫1Byte數據 pWClock++; } DS1302_CLK = 1; DS1302_RST = 0; } void BurstRead1302(unsigned char *pRClock) //讀取ds1302時鐘數據(時鐘多字節方式) { unsigned char i; DS1302_RST = 0; DS1302_CLK = 0; DS1302_RST = 1; DS1302InputByte(0xbf); // 0xbf:時鐘多字節讀命令 for (i=8; i>0; i--) { *pRClock = DS1302OutputByte(); // 讀1Byte數據 pRClock++; } DS1302_CLK = 1; DS1302_RST = 0; } void DS1302_TimeStop(bit flag) // 是否將時鐘停止 { unsigned char Data; Data=Read1302(DS1302_SECOND); DS1302_SetProtect(0); if(flag) Write1302(DS1302_SECOND, Data|0x80); else Write1302(DS1302_SECOND, Data&0x7F); } ********************************************************************************/
#ifndef _DS1302_H#define _DS1302_H #include<reg52.h>#include"delay.h"#include<intrins.h> #ifndef uchar #define uchar unsigned char#endifsbit DS1302_CLK = P3^6; //實時時鐘時鐘線引腳sbit DS1302_IO = P3^5; //實時時鐘數據線引腳sbit DS1302_RST = P3^4; //實時時鐘復位線引腳//sbit DS1302_CLK = P3^6; //實時時鐘時鐘線引腳//sbit DS1302_IO = P3^4; //實時時鐘數據線引腳//sbit DS1302_RST = P3^5; //實時時鐘復位線引腳 sbit ACC0 = ACC^0;sbit ACC7 = ACC^7; typedef struct __SYSTEMTIME__{ unsigned int Second; unsigned char Minute; unsigned char Hour; unsigned char Week; unsigned char Day; unsigned char Month; unsigned char Year; unsigned char DateString[9]; unsigned char TimeString[9]; }SYSTEMTIME; //定義的時間類型 #define AM(X) X#define PM(X) (X+12) // 轉成24小時制#define DS1302_SECOND 0x80#define DS1302_MINUTE 0x82#define DS1302_HOUR 0x84 #define DS1302_WEEK 0x8A#define DS1302_DAY 0x86#define DS1302_MONTH 0x88#define DS1302_YEAR 0x8C#define DS1302_RAM(X) (0xC0+(X)*2) //用於計算ds1302RAM地址的宏 void DS1302InputByte(unsigned char d) ;unsigned char DS1302OutputByte(void) ;void Write1302(unsigned char ucAddr, unsigned char ucDa);void DS1302_SetProtect(bit flag);unsigned char Read1302(unsigned char ucAddr);void DS1302_SetTime(unsigned char Address, unsigned char Value);void DS1302_GetTime(SYSTEMTIME *Time);void DateToStr(SYSTEMTIME *Time);void TimeToStr(SYSTEMTIME *Time);void Initial_DS1302(void);#end
主要的程序模塊到這裏基本上就算準備好了。完整的程序我壓縮一下上傳到資源吧,(沒辦法想賺點積分,理解理解),其實到這步,大家應該把完整的程序寫出來也不是問題了。
再來說說其他的吧。在使用keil軟件時,總是報這樣的錯誤*** ERROR L107: ADDRESS SPACE OVERFLOW。也是多方查找才找到問題所在。就是我們所定義變量是定義在51的RAM裏的,而且供變量存儲的只有256或者128個字節(看型號吧),這裏看網上說在變量前面加idata,然而並不管用。還是儘量節省RAM吧。只讀的數組定義前面加上code,全局變量儘量少點。不行就只能換單片機了畢竟51是一個資源很少 的單片機,不適合一些大工程。最後加上張效果圖
算了,我還是把,所有的程序也貼上來吧,也不在乎那幾個積分啦 。
下面的是按鍵處理程序(這個纔是核心程序),和主函數。我從KEIL上覆制過來的時候改了一下把edit configuration裏的Encode in ANSI 改成了Chinese GB2312.要不然複製過來時中文是亂碼。你複製到自己的工程裏時應該要改回來吧。
#include"keyProcess.h" void array2show(ARRAY2SHOW *arrayshow0,uchar wch); //函數聲明// void sec2show(SYSTEMTIME *secshow); SYSTEMTIME showtime; extern SYSTEMTIME CurrentTime; extern ARRAY2SHOW Alarmandshow; /**************************************************************************************************** 函數名稱:key_process(uchar mode) 函數功能:按鍵處理函數(調節日期,時間,秒錶,鬧鐘) 輸入參數:mode 用來選擇模式,是修改日期,時間還是鬧鐘 返回值:無 ****************************************************************************************************/ void key_process(uchar mode) { uchar Wch=0; uchar flag=0; uchar AlarmWch=0; uchar HourSecWch=0; uchar temp=0; switch(mode) //在最外層循環中檢測按鍵,確定要設置什麼 { DS1302_GetTime(&CurrentTime); case MODE0: //設置時間 showtime=CurrentTime; while(1) { DateToStr(&CurrentTime); zifu_dis(1,0,&CurrentTime.DateString[0]); //修改時間不影響從1302讀日期顯示 //(麻煩的思想)TArray3=show2array3(&CurrentTime.TimeString[0]); //將顯示的字符形式變成可以直接加1的形式 if(key_scan()==K1||key_scan()==K2||key_scan()==K3||key_scan()==K4)//檢測有沒有按鍵按下,有按鍵按下才執行操作 { switch(key_scan()) //再次檢測按鍵 { case K3: //K3按下,選擇時間的哪一位被更改 Wch++; if(Wch==3) Wch=0; break; case K1: //K1按下,數字加一 //(麻煩的思想)TArray3[TimeWch]++; //轉化成單個字符形式顯示 if(Wch==0) { showtime.Hour++; if(showtime.Hour==24) showtime.Hour=0; } else if(Wch==1) { showtime.Minute++; if(showtime.Minute==60) showtime.Minute=0; } else if(Wch==2) { showtime.Second++; if(showtime.Second==60) showtime.Second=0; } TimeToStr(&showtime); zifu_dis(0,0,&showtime.TimeString[0]); break; case K2: //K2按下,數字減一 //(麻煩的思想)TArray3[TimeWch]--; //(麻煩的思想)zifu_dis(1,0,array32show(TArray3)); if(Wch==0) { showtime.Hour--; if(showtime.Hour==0xff) showtime.Hour=0; } else if(Wch==1) { showtime.Minute--; if(showtime.Minute==0xff) showtime.Minute=0; } else if(Wch==2) { showtime.Second--; if(showtime.Second==0xff) showtime.Second=0; } TimeToStr(&showtime); zifu_dis(0,0,&showtime.TimeString[0]); break; case K4: //K4按下,確定修改, flag=1;break; } } if(flag==1) //flag爲1時,確定修改,將1302裏的時間重置,並退到最初的模式檢測 { DS1302_SetTime(DS1302_HOUR,showtime.Hour); DS1302_SetTime(DS1302_MINUTE,showtime.Minute); DS1302_SetTime(DS1302_SECOND,showtime.Second); Wch=0; flag=0; break; } } break; case MODE1: //設置日期 showtime=CurrentTime; while(1) { DS1302_GetTime(&CurrentTime); TimeToStr(&CurrentTime); zifu_dis(0,0,&CurrentTime.TimeString[0]); //修改日期,不影響從1302讀時間顯示 //(麻煩的思想)DArray3=show2array3(&CurrentTime.DateString); //將顯示的字符形式變成可以直接加1的形式 if(key_scan()==K1||key_scan()==K2||key_scan()==K3||key_scan()==K4)//檢測有沒有按鍵按下,有按鍵按下才執行操作 { switch(key_scan()) //再次檢測按鍵 { case K3: //K3按下,選擇日期的哪一位被更改 Wch++; if(Wch==3) Wch=0; break; case K1: //K1按下,數字加一 //(麻煩的思想)DArray3[DateWch]=DArray3[DateWch]+1; //(麻煩的思想)zifu_dis(0,0,array32show(DArray3)); if(Wch==0) showtime.Year++; else if(Wch==1) { showtime.Month++; if(showtime.Month==13) showtime.Month=1; } else if(Wch==2) { showtime.Day++; if(showtime.Month==1||showtime.Month==3||showtime.Month==5||showtime.Month==7||showtime.Month==8||showtime.Month==10||showtime.Month==12) if(showtime.Day==32) showtime.Day=0; else if(showtime.Month==2) if(showtime.Day=30) showtime.Day=0; else if(showtime.Day==31) showtime.Day=0; } DateToStr(&showtime); zifu_dis(1,0,&showtime.DateString[0]); break; case K2: //K2按下,數字減一 //(麻煩的思想)DArray3[DateWch]--; //(麻煩的思想)zifu_dis(0,0,array32show(DArray3)); if(Wch==0) showtime.Year--; else if(Wch==1) showtime.Month--; else if(Wch==2) showtime.Day--; DateToStr(&showtime); zifu_dis(1,0,&showtime.DateString[0]); break; case K4: //K4按下退出此循環,回到模式檢測循環 flag=1; break; } } if(flag==1) //flag爲1時,確定修改,將1302裏的日期重置,並退到最初的模式檢測 { DS1302_SetTime(DS1302_YEAR,showtime.Year); DS1302_SetTime(DS1302_MONTH,showtime.Month); DS1302_SetTime(DS1302_DAY,showtime.Day); flag=0; Wch=0; break; } } break; case MODE2: //設置鬧鐘 while(1) { DS1302_GetTime(&CurrentTime); DateToStr(&CurrentTime); TimeToStr(&CurrentTime); zifu_dis(0,0,&CurrentTime.TimeString[0]); //在設置鬧鐘時不讓時間的顯示停下 if(key_scan()==K1||key_scan()==K2||key_scan()==K3||key_scan()==K4)//檢測有沒有按鍵按下,有按鍵按下才執行操作 { switch(key_scan()) //再次檢測按鍵 { case K3: //K1按下,選擇哪一個鬧鐘被更改 AlarmWch++; if(AlarmWch==4) AlarmWch=0; break; case K2: //K2按下,選擇鬧鐘的小時還是秒被更改 HourSecWch++; if(HourSecWch==2) HourSecWch=0; break; case K1: //K3按下,數字加1 Alarmandshow.Alarm[AlarmWch][HourSecWch]++; if(Alarmandshow.Alarm[AlarmWch][HourSecWch]==60) Alarmandshow.Alarm[AlarmWch][HourSecWch]=0; array2show(&Alarmandshow,AlarmWch); zifu_dis(2+AlarmWch%2,2+AlarmWch/2*3,&Alarmandshow.showstring[0]); break; case K4: //K4按下退出此循環,回到模式檢測循環 flag=1;break; } } if(flag==1) { AlarmWch=0; HourSecWch=0; //最好要將AlarmWch,HourSecWch清零,後面要用 flag=0; break; } } break; case MODE3: //秒錶 while(1) //此層循環用來顯示秒錶的初始界面 { temp=0; showtime.Second=0; lcd_init(); zifu_dis(0,3,"00.0"); if(key_scan()==K1) //K1按下,秒錶開始計時 { while(1) //此層循環是秒錶開始後的循環 { delay_ms(73); //再算上程序執行的時間,一共爲100ms sec2show(&showtime); zifu_dis(0,3,&showtime.TimeString[0]); if(flag==0) showtime.Second++; //每過100ms,Second++, switch(key_scan()) { case K1: zifu_dis(temp/3+1,temp*3%9,&showtime.TimeString[0]); temp++; //讀一下秒錶,記錄下 if(temp==9) temp=0; break; case K2: flag=~flag; break; case K3: flag=2; break; case K4: flag=1; break; } if(flag==2||flag==1) { if(flag==2) flag=0; break; } } } if(flag==1) { flag=0; break; } } lcd_init(); for(;AlarmWch<4;AlarmWch++) { //arrayshow.array2[AlarmWch][HourSecWch]=0; array2show(&Alarmandshow,AlarmWch); zifu_dis(AlarmWch/2+2,AlarmWch%2*3+2,&Alarmandshow.showstring[0]); } zifu_dis(2,0,"鬧鐘"); AlarmWch=0; HourSecWch=0; break; } } /*********************************************************************************************** 函數名稱:array32show(uchar *array3) 函數功能:將存在array[3]裏的小時,分鐘,秒轉換成可以直接顯示的形式 輸入參數:*array3 array[3]的首地址 返回值: show show[9]的首地址,可以直接用來顯示 *************************************************************************************************/ /*uchar *array32show(uchar *array3) { uchar show[5]; show[0] = *array3/10+0x30 ; show[1] = *array3++%10+0x30 ; show[2] = ':'; show[3] = *array3/10+0x30 ; show[4] = *array3%10+0x30 ;//用LCD顯示,要變成ascii碼所以加了0x30,用數碼管顯示的話就不用加了 show[5] = '\0'; return show; } *///沒有用到 /*********************************************************************************************** 函數名稱:show2array3(uchar *show) 函數功能:將存在show[]裏的可直接顯示的字符轉換成可以直接加一的array[3] 輸入參數:*show show數組的首地址 返回值: array3 array數組的首地址,可以直接用來做加一操作 *************************************************************************************************/ /*uchar *show2array3(uchar *show) { uchar array3[3]; array3[0]=(show[0]-0x30)*10+(show[1]-0x30); array3[1]=(show[3]-0x30)*10+(show[4]-0x30); array3[2]=(show[6]-0x30)*10+(show[7]-0x30); return array3; }*/ void array2show(ARRAY2SHOW *arrayshow0,uchar wch) { arrayshow0->showstring[0] = arrayshow0->Alarm[wch][0]/10+0x30 ; arrayshow0->showstring[1] = arrayshow0->Alarm[wch][0]%10+0x30 ; arrayshow0->showstring[2] =':'; arrayshow0->showstring[3] = arrayshow0->Alarm[wch][1]/10+0x30 ; arrayshow0->showstring[4] = arrayshow0->Alarm[wch][1]%10+0x30 ; //用LCD顯示,要變成ascii碼所以加了0x30,用數碼管顯示的話就不用加了 arrayshow0->showstring[5] = '\0'; } void sec2show(SYSTEMTIME *secshow) { secshow->TimeString[0]=secshow->Second/100+0x30; secshow->TimeString[1]=secshow->Second%100/10+0x30; secshow->TimeString[2]='.'; secshow->TimeString[3]=secshow->Second%10+0x30; secshow->TimeString[4]='\0'; }
#ifndef __KEYPROCESS_H #define _KEYPROCESS_H #include<reg52.h> #include<stdio.h> #include"delay.h" #include"key.h" #include"ds1302.h" #include"LCD12864.h" #ifndef uchar #define uchar unsigned char #endif typedef struct _ARRAYSHOW_ { unsigned char showstring[6]; unsigned char Alarm[4][2]; }ARRAY2SHOW; typedef struct _SHOW_ { unsigned char showstring[6]; unsigned char array2[4][2]; }show; #define MODE0 0X00 #define MODE1 0X01 #define MODE2 0X02 #define MODE3 0X03 #define TIMESET MODE0 #define DATESET MODE1 #define ALARMSET MODE2 #define SECCON MODE3 void key_process(uchar mode); #endif
這個是蜂鳴器要用到的,就是一個IO口拉高拉低。/*********************************************************************************** 程序說明:利用12864液晶和ds1302配合按鍵實現 萬年曆,四路可調鬧鐘,秒錶(基於51單片機) 作者:哈爾濱工程大學 黃上城 ***********************************************************************************/ #include <reg52.h> #include<stdio.h> #include"delay.h" #include"ds1302.h" #include"LCD12864.h" #include"key.h" #include"buzzer.h" #include"keyProcess.h" SYSTEMTIME CurrentTime; //存儲當前從ds1302中讀到的時間日期等 ARRAY2SHOW Alarmandshow; //存儲鬧鐘的時間,和用於鬧鐘顯示的字符串 char code table[7][20]={{"星期壹"},{"星期貳"},{"星期叄"},{"星期肆"},{"星期伍"},{"星期陸"},{"星期日"}}; sbit led=P1^7; void main() { uchar mode; Initial_DS1302(); //ds1302初始化 // DS1302_SetTime(DS1302_HOUR,10); // DS1302_SetTime(DS1302_MINUTE,0); // DS1302_SetTime(DS1302_SECOND,0);//向ds1302中寫初始時間 // DS1302_SetTime(DS1302_YEAR,17); // DS1302_SetTime(DS1302_MONTH,1); // DS1302_SetTime(DS1302_DAY,16); //向ds1302中寫初始日期 DS1302_SetTime(DS1302_WEEK,3); lcd_init(); //lcd12864初始化 zifu_dis(2,0,"鬧鐘"); zifu_dis(2,2,"00:00"); zifu_dis(2,5,"00:00"); zifu_dis(3,2,"00:00"); zifu_dis(3,5,"00:00"); //設置鬧鐘的初始顯示 while(1) { if(key_scan()==K1||key_scan()==K2||key_scan()==K3||key_scan()==K4) { switch (key_scan()) { case K1: mode=MODE0;break; //MODE0設置時間 case K2: mode=MODE1;break; //MODE0設置日期 case K3: mode=MODE2;break; //MODE0設置鬧鐘 case K4: mode=MODE3;break; //MODE0設置秒錶 } key_process(mode); //按鍵處理函數 } DS1302_GetTime(&CurrentTime); DateToStr(&CurrentTime); TimeToStr(&CurrentTime); zifu_dis(0,0,&CurrentTime.TimeString[0]); zifu_dis(1,0,&CurrentTime.DateString[0]); //讀出ds1302裏的時間,在lcd上顯示 zifu_dis(1,4,table[CurrentTime.Week]); if((CurrentTime.Hour==Alarmandshow.Alarm[0][0]&&CurrentTime.Minute==Alarmandshow.Alarm[0][1])|| (CurrentTime.Hour==Alarmandshow.Alarm[1][0]&&CurrentTime.Minute==Alarmandshow.Alarm[1][1])|| (CurrentTime.Hour==Alarmandshow.Alarm[2][0]&&CurrentTime.Minute==Alarmandshow.Alarm[2][1])|| (CurrentTime.Hour==Alarmandshow.Alarm[3][0]&&CurrentTime.Minute==Alarmandshow.Alarm[3][1])) //檢查所設的鬧鐘時間和現在的時間是否一致,是則響蜂鳴器。 buzzer_delay(); } }
#include"buzzer.h" void buzzer_on(void) { BuzzerPort=0; } void buzzer_off(void) { BuzzerPort=1; } void buzzer_delay(void) { BuzzerPort=0; delay_ms(400); BuzzerPort=1; delay_ms(400); }
#ifndef __BUZZER_H #define _BUZZER_H #include<reg52.h> #include"delay.h" #ifndef uchar #define uchar unsigned char #endif sbit BuzzerPort=P2^2; void buzzer_on(void); void buzzer_off(void); void buzzer_delay(void); #endif
完工~
基於51單片機的萬年曆(包含鬧鐘,秒錶)實現
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.