可调式电子日历的设计
1.概述
1.1 課題背景單片機運用在各種智能設備當中,比如平衡車、掃地機器人等。設備智能化是一個必然的趨勢,而要實現設備智能化離不開單片機的支持,所以學習單片機是每一位自動化學子的必修之路。之前雖然已經學過單片機的理論課,但是理論學好了,不一定就能學以致用,而為了達到這個目的就要進行單片機的實踐訓練。可調式電子日歷設計對于學習單片機十分的契合,完成這個項目需要用到單片機的定時器/計數器功能以及驅動外部模塊。而這正是單片機運用的最重要的兩個部分之一,學好了定時器/計數器以及懂得如何驅動外部模塊,那么很多單片機的應用就能舉一反三了。可調式電子日歷小巧便于攜帶,界面清爽,一目了然,方便的知曉當前時間, 并可對時間做出修改, 實在是居家旅行日常生活之必備用品。在現今的可調式電子日歷具有性能穩定、精確度高、成本低、易于產品化,以及方便、實用等特點。
1.2課題主要內容
設計并制作一個可調式電子日歷,開機后可以在LCD液晶顯示器上顯示當前的年、月、日、星期、時、分、秒等信息,按下K1鍵,選擇對年、月、日、星期、時、分、秒的設置;按下K2鍵,進行加設置;按下K3鍵,進行減設置;按下K4鍵,對設置進行確認。可調式電子日歷,可以顯示日期和時間信息,按下按鍵K1-K4,可設置與調整時間。
2.系統硬件設置
根據課題設計的基本方案,本系統主要有如下功能電路:單片機最小系統電路,DS1302時鐘電路,LCD液晶顯示電路,以及獨立按鍵。結構框圖如圖一所示:圖一
?2.1 STC單片機最小系統電路設計
本設計采用STC89C52單片機作為主控芯片,作為新生代單片機在抗干擾能力更加穩定,同時它的運行速度更快,功率更低,這讓用戶有了更多的打算。單片機最小系統包括復位電路、晶振電路。
復位電路本質是電容的充放電,該電路中有一個大小為10uF的電容和一個10KΩ左右的電阻,要想單片機可以穩定實現復位動作,必要使得復位引腳的高電平至少持續兩個機器周期,因此選取器件的時候一定要注意參數。
2.2 LCD顯示電路
本系統中應用1602液晶顯示器作為顯示器件。由于1602具有不需要持續刷新的特性,故只需要對數據口進行一次輸入,顯示屏上就會保持持續點亮。因此,液晶顯示器的畫質更高而且不會出現閃爍。液晶顯示器除了體積小、重量輕、功耗低等優點外并且還都是數字式的,這使得單片機系統的接口變得更加簡單可靠,操作也更加方便。
液晶顯示器的主要技術參數:
2.3 DS1302時鐘電路
2.31 DS1302的介紹
DS1302是美國DALLAS公司推出的一種高性能、低功耗的實時時鐘芯片,附加 31 字節靜態RAM,采用SPI三線接口與 CPU 進行同步通信,并可采用突發方式一次傳送多個字節的時鐘信號和 RAM 數據。實時時鐘可提供秒、分、時、日、星期、月和年,一個月小與31天時可以自動調整,且具有閏年補償功能。工作電壓2.5~5.5V。采用雙電源供電(主電源和備用電源),可設置備用電源充電方式,提供了對后背電源進行涓細電流充電的能力。DS1302 用于數據記錄,特別是對某些具有特殊意義的數據點的記錄上,能實現數據與出現該數據的時間同時記錄,因此廣泛應用于測量系統中。
DS1302內部結構圖
3.軟件設計
3.1 軟件開發工具介紹A.Keil C51簡介。Keil C51軟件是一款可以用C語言來編程的51系列兼容開發軟件,和傳統的匯編來說,它有更多的優勢,例如它擁有更加強大的功能、更加完整的結構、可讀性更強,這些優勢使得它學習起來更加的容易。于此同時它還提供了數量龐大的庫函數和一些開發調試工具等,通過編譯完的匯編代碼,更能體會出這款軟件的優越性。
3.3 程序代碼設計
3.3.1 DS1302時鐘程序
#include //52系列單片機頭文件 #include #include #define uchar unsigned char //宏定義 #define uint unsigned int sbit SDA=P1^0; // DS1302數據線 sbit CLK=P1^1; //DS1302時鐘線 sbit RST=P1^2; //DS1302復位線 char Adjust_Index=-1; //當前調節的時間:秒,分,時,日, void Display_LCD_String(uchar p,uchar s); uchar tCount=0;//一年中每個月的天數,2月的天數由年份決定 uchar MonthsDays[]={0,31,0,31,30,31,30,31,31,30,31,30,31}; uchar WEEK[]={“SUN”,“MON”,“TUS”,“WEN”,“THU”,“FRI”,“SAT”};//周日,周一到周六 void DS1302_Write_Byte(uchar x) //向DS1302寫入一字節 { uchar i; for(i=0;i<8;i++) {SDA=x&1; CLK=1; CLK=0; x>>=1; } } uchar DS1302_Read_Byte() //從DS1302讀取一字節 { uchar i,b,t; for(i=0;i<8;i++) { b>>=1; t=SDA; b|=t<<7; CLK=1; CLK=0; } return b/1610+b%16; } uchar Read_Data(uchar addr) //從DS1302指定位置讀數據 { uchar dat; RST=0; CLK=0; RST=1; DS1302_Write_Byte(addr); dat=DS1302_Read_Byte(); CLK=1; RST=0; return dat;} void Write_DS1302(uchar addr,uchar dat) //向DS1302指定位置寫入數據 { CLK=0; RST=1; DS1302_Write_Byte(addr); DS1302_Write_Byte(dat); CLK=0; RST=0; } void SET_DS1302() //設置時間 { uchar i; Write_DS1302(0x8e,0x00); for(i=0;i<7;i++) { Write_DS1302(0x80+2i,(DateTime[i]/10<<4|(DateTime[i]%10))); } Write_DS1302(0x8e,0x80); } void GetTime() //讀取當前時期時間 { uchar i; for(i=0;i<7;i++) { DateTime[i]=Read_Data(0x81+2*i); } } void Format_DateTime(uchar d,uchar *a) //時間和日期轉換成數字字符 { a[0]=d/10+‘0’;a[1]=d%10+‘0’; } uchar isLeapYear(uint y) //判斷是否為閏年 { return (y%40&&y%100!=0)||(y%4000); } void RefreshWeekDay() //星期轉換 { if(K10) //選擇調整對象:年,月,日,時,分,秒 { while(K10); if(Adjust_Index==-1||Adjust_Index1) Adjust_Index=7; Adjust_Index–; if(Adjust_Index5) Adjust_Index=4; LCD_DSY_BUFFER2[13]=’[’; LCD_DSY_BUFFER2[14]=Change_Flag[Adjust_Index]; LCD_DSY_BUFFER2[15]=’]’; } else if(K20) // 加 { while (K20); DateTime_Adjust(1); } else if (K30) // 減 { while (K30); DateTime_Adjust(-1); } else if (K40) // 確定 { while(K40); SET_DS1302(); //調整后的時間寫入DS1302 LCD_DSY_BUFFER2[13]=’ ‘; LCD_DSY_BUFFER2[14]=’ ‘; LCD_DSY_BUFFER2[15]=’ '; Adjust_Index=-1; } }3.3.2 LCD1602顯示驅動程序
對LCD1602進行控制,需要查閱對應的數據手冊,各引腳功能如下圖。
RS為lcd數據命令選擇,RS為L則寫命令 H則寫數據,R/W為讀寫控制信號一般我們只寫而不讀取LCD內的數據。E為使能端口,當EN腳產生下降沿時LCD可接收信號,BLA和BLK為背光電源,如果不接電源LCD顯示的信息將會看不清。
V0接入10K電阻分壓可調節對比度,如果電壓不合適,同樣無法正常顯示。送入LCD的信號分為控制信號和數據信號兩種,不同信號的區分取決于RS引腳的電平,所以定義write_com函數來實現LCD的指令操作。對數據的處理函數如下:
#include //52系列單片機頭文件 #include #include #define uchar unsigned char //宏定義 #define uint unsigned int uchar Read_LCD_State() //讀LCD1602狀態 { uchar state; RS=0; RW=1; EN=1; DelayMS(1); state=P0; EN=0; DelayMS(1); return state; } void LCD_Busy_Wait() { while((Read_LCD_State()&0x08)==0x80); DelayMS(5); } void Write_LCD_Data(uchar dat) //向LCD1602寫數據 { LCD_Busy_Wait(); RS=1; RW=0; //選擇寫數據模式 EN=0; P0=dat; //將要寫的數據送入數據總線 EN=1; //將使能端置給一個高脈沖 DelayMS(1); EN=0; //將使能端置0以完成高脈沖 } void Write_LCD_Command(uchar cmd)// 向LCD1602寫命令 { LCD_Busy_Wait(); RS=0; RW=0;//選擇寫命令模式 EN=0; P0=cmd;//將要寫的命令字送入數據總線 EN=1;//將使能端置給一個高脈沖 DelayMS(1); EN=0; //將使能端置0以完成高脈沖 } void Init_LCD() //LCD初始化 { Write_LCD_Command(0x38); DelayMS(1); Write_LCD_Command(0x01); DelayMS(1); Write_LCD_Command(0x06); DelayMS(1); Write_LCD_Command(0x0C); DelayMS(1); Display_LCD_String(0x00," dian zi ri li "); DelayMS(2000); } void DateTime_Adjust(char x) //年月日時分秒++/-- { switch (Adjust_Index) { case 6: //年 if(x== 1&&DateTime[6]<99) DateTime[6]++; if(x==-1&&DateTime[6]>0) DateTime[6]--; MonthsDays[2]=isLeapYear(2000+DateTime[6])? 29:28; if(DateTime[3]>MonthsDays[DateTime[4]]) DateTime[3]=MonthsDays[DateTime[4]]; RefreshWeekDay(); break; case 4: //月 if(x== 1&&DateTime[4]<12) DateTime[4]++; if(x==-1&&DateTime[4]>1) DateTime[4]--; MonthsDays[2]=isLeapYear(2000+DateTime[6])? 29:28; if(DateTime[3]>MonthsDays[DateTime[4]]) DateTime[3]=MonthsDays[DateTime[4]]; RefreshWeekDay(); break; case 3: //日 MonthsDays[2]=isLeapYear(2000+DateTime[6])? 29:28; if(x== 1&&DateTime[3]<MonthsDays[DateTime[4]]) DateTime[3]++; if(x==-1&&DateTime[3]>0) DateTime[3]--; RefreshWeekDay(); break; case 2: //時 if(x== 1&&DateTime[2]<23) DateTime[2]++; if(x==-1&&DateTime[2]>0) DateTime[2]--; break; case 1: //秒 if(x== 1&&DateTime[1]<59) DateTime[1]++; if(x==-1&&DateTime[1]>0) DateTime[1]--; break;} }3.3.3 定時器配置
要實現電子時鐘的顯示與設置功能,首先要知道單片機定時器的工作原理及整個配置過程。定義一個init函數用來初始化定時器和LCD,由于時鐘最小顯示時間為1秒,而定時器最大定時時間為六十多毫秒,所以需要定義一個定時器溢出次數的變量,設置定時器溢出的時間為50MS,當溢出次數為20次時,時間剛好為1秒,定時器初始化見下圖。
TMOD = 0x01; //定時器0工作方式1 TH0=(65536-45872)/256;//定時器0 16位計數器高8位初始計數 TL0=(65536-45872)%256;//定時器0 16位計數器低8位初始計數 EA=1; //中斷使能 中端總開關 ET0=1; //定時器0 中斷允許位 TR0=1; //初始中斷開始啟動3.3.4 主程序設置
#include //52系列單片機頭文件 #include #include #define uchar unsigned char //宏定義 #define uint unsigned int void main() //主程序 { Init_LCD(); //LCD初始化 IE=0x83; // 允許INT0,T0中斷 IP=0x01; IT0=0x01; TMOD=0x01;//設置定時器0為工作方式1(M1M0為01) TH0=-50000/256; //裝初值 TL0=-50000%256; TR0=1; while(1) { if(Adjust_Index==-1) GetTime(); }4.運行和調試
4.1 proteus 調試圖4.2 實物調試圖
5.課程設計體會
本次課程設計讓我對51單片機有了更深的理解,通過之前半個多學期的理論學習,終于有了實踐的機會,由于之前也學習過兩種單片機所以上手51并不困難。雖然我學過單片機但是之前都是比較淺顯的理解和學習,只知道怎么用以及如何在網上查找例子,并不清楚具體的配置過程以及單片機內部的一些電路原理。通過這次課設,我學會了如何使用基姆拉爾森公式算星期幾,并且使用巧妙的方法來計算每個月的天數。看似簡單的電子時鐘,其實程序也并不簡單,這讓我知道自己還需努力,通過多做項目來提高自己的水平是一個很好的路徑,對于自己未來的學習也能奠定很好的基礎,希望在之后的日子里我能創造出更多有意思的作品。參考文獻
【1】 劉瑞新 . 單片機原理及應用教程 . 北京 . 機械工業出版社 ,2003【2】 李群芳 .單片微型計算機與接口技術 .電子工業出版 ,2012
【3】 審慶陽 .郭庭杰 .單片機的實踐與應用 .清華大學出版 ,2002
【4】 沙占友 .王彥朋 .孟志永 .單片機外圍電路設計 .北京 .電子工業出版社 ,2003
【5】張蘭紅,陸廣平,倉思雨. 單片機課程設計仿真與實踐指導[M].北京:機械工業出版社,2018
程序清單
#include //52系列單片機頭文件 #include #include #define uchar unsigned char //宏定義 #define uint unsigned int sbit SDA=P1^0; // DS1302數據線 sbit CLK=P1^1; //DS1302時鐘線 sbit RST=P1^2; //DS1302復位線 sbit RS=P2^0; //數據命令選擇端(H/L) sbit RW=P2^1; //讀寫選擇端(H/L) sbit EN=P2^2; //使能信號 sbit K1=P3^4; // 選擇 sbit K2=P3^5; // 加 sbit K3=P3^6; // 減 sbit K4=P3^7; // 確定void Display_LCD_String(uchar p,uchar *s); uchar tCount=0;//一年中每個月的天數,2月的天數由年份決定 uchar MonthsDays[]={0,31,0,31,30,31,30,31,31,30,31,30,31}; uchar *WEEK[]={"SUN","MON","TUS","WEN","THU","FRI","SAT"};//周日,周一到周六 uchar LCD_DSY_BUFFER1[]={"Date 00-00-00 "}; //LCD顯示緩沖 uchar LCD_DSY_BUFFER2[]={" Time00-00-00 "}; uchar DateTime[7]; //所讀取的日期時間 char Adjust_Index=-1; //當前調節的時間:秒,分,時,日, uchar Change_Flag[]="-MHDM-Y"; uchar Read_LCD_State(); void LCD_Busy_Wait(); void Write_LCD_Data(uchar dat); void Write_LCD_Command(uchar cmd); void Init_LCD(); void Set_LCD_POS(uchar p); void DelayMS(uint x) //延時程序 { uchar i; while(x--) for(i=0;i<120;i++); } uchar Read_LCD_State() //讀LCD1602狀態 { uchar state; RS=0; RW=1; EN=1; DelayMS(1); state=P0; EN=0; DelayMS(1); return state; } void LCD_Busy_Wait() { while((Read_LCD_State()&0x08)==0x80); DelayMS(5); } void Write_LCD_Data(uchar dat) //向LCD1602寫數據 { LCD_Busy_Wait(); RS=1; RW=0; //選擇寫數據模式 EN=0; P0=dat; //將要寫的數據送入數據總線 EN=1; //將使能端置給一個高脈沖 DelayMS(1); EN=0; //將使能端置0以完成高脈沖 } void Write_LCD_Command(uchar cmd)// 向LCD1602寫命令 { LCD_Busy_Wait(); RS=0; RW=0;//選擇寫命令模式 EN=0; P0=cmd;//將要寫的命令字送入數據總線 EN=1;//將使能端置給一個高脈沖 DelayMS(1); EN=0; //將使能端置0以完成高脈沖 } void Init_LCD() //LCD初始化 { Write_LCD_Command(0x38); DelayMS(1); Write_LCD_Command(0x01); DelayMS(1); Write_LCD_Command(0x06); DelayMS(1); Write_LCD_Command(0x0C); DelayMS(1); Display_LCD_String(0x00," dian zi ri li "); DelayMS(2000); } void Set_LCD_POS(uchar p) { Write_LCD_Command(p|0x80); } void Display_LCD_String(uchar p,uchar *s) { uchar i; Set_LCD_POS(p); for(i=0;i<16;i++) { Write_LCD_Data(s[i]); DelayMS(1);} } void DS1302_Write_Byte(uchar x) //向DS1302寫入一字節 { uchar i; for(i=0;i<8;i++) {SDA=x&1; CLK=1; CLK=0; x>>=1; } } uchar DS1302_Read_Byte() //從DS1302讀取一字節 { uchar i,b,t; for(i=0;i<8;i++) { b>>=1; t=SDA; b|=t<<7; CLK=1; CLK=0; } return b/16*10+b%16; } uchar Read_Data(uchar addr) //從DS1302指定位置讀數據 { uchar dat; RST=0; CLK=0; RST=1; DS1302_Write_Byte(addr); dat=DS1302_Read_Byte(); CLK=1; RST=0; return dat;} void Write_DS1302(uchar addr,uchar dat) //向DS1302指定位置寫入數據 { CLK=0; RST=1; DS1302_Write_Byte(addr); DS1302_Write_Byte(dat); CLK=0; RST=0; } void SET_DS1302() //設置時間 { uchar i; Write_DS1302(0x8e,0x00); for(i=0;i<7;i++) { Write_DS1302(0x80+2*i,(DateTime[i]/10<<4|(DateTime[i]%10))); } Write_DS1302(0x8e,0x80); } void GetTime() //讀取當前時期時間 { uchar i; for(i=0;i<7;i++) { DateTime[i]=Read_Data(0x81+2*i); } } void Format_DateTime(uchar d,uchar *a) //時間和日期轉換成數字字符 { a[0]=d/10+'0';a[1]=d%10+'0'; } uchar isLeapYear(uint y) //判斷是否為閏年 { return (y%4==0&&y%100!=0)||(y%400==0); } void RefreshWeekDay() //星期轉換{ uint i,d,w=5; for(i=2000;i<2000+DateTime[6];i++) { d=isLeapYear(i) ? 366 : 365; w=(w+d)%7; } d=0; for(i=1;i<DateTime[4];i++) d+=MonthsDays[i]; d+=DateTime[3]; DateTime[5]=(w+d)%7+1; } void DateTime_Adjust(char x) //年月日時分秒++/-- { switch (Adjust_Index) { case 6: //年 if(x== 1&&DateTime[6]<99) DateTime[6]++; if(x==-1&&DateTime[6]>0) DateTime[6]--; MonthsDays[2]=isLeapYear(2000+DateTime[6])? 29:28; if(DateTime[3]>MonthsDays[DateTime[4]]) DateTime[3]=MonthsDays[DateTime[4]]; RefreshWeekDay(); break; case 4: //月 if(x== 1&&DateTime[4]<12) DateTime[4]++; if(x==-1&&DateTime[4]>1) DateTime[4]--; MonthsDays[2]=isLeapYear(2000+DateTime[6])? 29:28; if(DateTime[3]>MonthsDays[DateTime[4]]) DateTime[3]=MonthsDays[DateTime[4]]; RefreshWeekDay(); break; case 3: //日 MonthsDays[2]=isLeapYear(2000+DateTime[6])? 29:28; if(x== 1&&DateTime[3]<MonthsDays[DateTime[4]]) DateTime[3]++; if(x==-1&&DateTime[3]>0) DateTime[3]--; RefreshWeekDay(); break; case 2: //時 if(x== 1&&DateTime[2]<23) DateTime[2]++; if(x==-1&&DateTime[2]>0) DateTime[2]--; break; case 1: //秒 if(x== 1&&DateTime[1]<59) DateTime[1]++; if(x==-1&&DateTime[1]>0) DateTime[1]--; break;} } void T0_INT() interrupt 1 //定時器0中斷,定時器每秒刷新LCD顯示 { TH0=-50000/256; TL0=-50000%256; if(++tCount!=2) return; tCount=0; Format_DateTime(DateTime[6],LCD_DSY_BUFFER1+5); Format_DateTime(DateTime[4],LCD_DSY_BUFFER1+8); Format_DateTime(DateTime[3],LCD_DSY_BUFFER1+11); strcpy(LCD_DSY_BUFFER1+13,WEEK[DateTime[5]-1]); Format_DateTime(DateTime[2],LCD_DSY_BUFFER2+5); Format_DateTime(DateTime[1],LCD_DSY_BUFFER2+8); Format_DateTime(DateTime[0],LCD_DSY_BUFFER2+11); Display_LCD_String(0x00,LCD_DSY_BUFFER1); Display_LCD_String(0x40,LCD_DSY_BUFFER2); } void EX_INT0() interrupt 0 //鍵盤中斷 { if(K1==0) //選擇調整對象:年,月,日,時,分,秒 { while(K1==0); if(Adjust_Index==-1||Adjust_Index==1) Adjust_Index=7; Adjust_Index--; if(Adjust_Index==5) Adjust_Index=4; LCD_DSY_BUFFER2[13]='['; LCD_DSY_BUFFER2[14]=Change_Flag[Adjust_Index]; LCD_DSY_BUFFER2[15]=']'; } else if(K2==0) // 加 { while (K2==0); DateTime_Adjust(1); } else if (K3==0) // 減 { while (K3==0); DateTime_Adjust(-1); } else if (K4==0) // 確定 { while(K4==0); SET_DS1302(); //調整后的時間寫入DS1302 LCD_DSY_BUFFER2[13]=' '; LCD_DSY_BUFFER2[14]=' '; LCD_DSY_BUFFER2[15]=' '; Adjust_Index=-1; } } void main() //主程序 { Init_LCD(); //LCD初始化 IE=0x83; // 允許INT0,T0中斷 IP=0x01; IT0=0x01; TMOD=0x01;//設置定時器0為工作方式1(M1M0為01) TH0=-50000/256; //裝初值 TL0=-50000%256; TR0=1; while(1) { if(Adjust_Index==-1) GetTime(); } }元器件表單
附件下載
keil源程序和proteus仿真圖?
?
?
總結
以上是生活随笔為你收集整理的可调式电子日历的设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网络经济学期末复习——第一章 导论
- 下一篇: VNC下载安装与使用(树莓派篇)