這次課程設計要完成的是制作一個基於51單片機的電子時鍾的萬年歷(protues仿真),需要用到1602液晶屏+DS1302+DS18B20+按鍵等模塊。各個的模塊就不在一一介紹,直接講解這個系統的功能,首先是四個按鍵,第一個按鍵是選中需要修改時間位置,在按一次選中下一個,依次類推,第二個按鍵是對數值進行加一,第三個按鍵對數值進行減一,第四個按鍵確認。
先放仿真圖
然后是代碼:
main.c
#include <REG52.H> #include <intrins.h> #define uint unsigned int #define uchar unsigned char sbit DS1302_CLK = P1^7; //實時時鍾時鍾線引腳 sbit DS1302_IO = P1^6; //實時時鍾數據線引腳 sbit DS1302_RST = P1^5; //實時時鍾復位線引腳 sbit wireless_1 = P3^0; sbit wireless_2 = P3^1; sbit wireless_3 = P3^2; sbit wireless_4 = P3^3; sbit ACC0 = ACC^0; sbit ACC7 = ACC^7; char hide_sec,hide_min,hide_hour,hide_day,hide_week,hide_month,hide_year; //秒,分,時到日,月,年位閃的計數 sbit Set = P2^0; //模式切換鍵 sbit Up = P2^1; //加法按鈕 sbit Down = P2^2; //減法按鈕 sbit out = P2^3; //立刻跳出調整模式按鈕 sbit DQ = P1^0; //溫度傳送數據IO口 char done,count,temp,flag,up_flag,down_flag; uchar temp_value; //溫度值 uchar TempBuffer[5],week_value[2]; void show_time(); //液晶顯示程序 /***********1602液晶顯示部分子程序****************/ //Port Definitions********************************************************** sbit LcdRs = P2^5; sbit LcdRw = P2^6; sbit LcdEn = P2^7; sfr DBPort = 0x80; //P0=0x80,P1=0x90,P2=0xA0,P3=0xB0.數據端口 //內部等待函數************************************************************************** unsigned char LCD_Wait(void) { LcdRs=0; LcdRw=1; _nop_(); LcdEn=1; _nop_(); LcdEn=0; return DBPort; } //向LCD寫入命令或數據************************************************************ #define LCD_COMMAND 0 // Command #define LCD_DATA 1 // Data #define LCD_CLEAR_SCREEN 0x01 // 清屏 #define LCD_HOMING 0x02 // 光標返回原點 void LCD_Write(bit style, unsigned char input) { LcdEn=0; LcdRs=style; LcdRw=0; _nop_(); DBPort=input; _nop_();//注意順序 LcdEn=1; _nop_();//注意順序 LcdEn=0; _nop_(); LCD_Wait(); } //設置顯示模式************************************************************ #define LCD_SHOW 0x04 //顯示開 #define LCD_HIDE 0x00 //顯示關 #define LCD_CURSOR 0x02 //顯示光標 #define LCD_NO_CURSOR 0x00 //無光標 #define LCD_FLASH 0x01 //光標閃動 #define LCD_NO_FLASH 0x00 //光標不閃動 void LCD_SetDisplay(unsigned char DisplayMode) { LCD_Write(LCD_COMMAND, 0x08|DisplayMode); } //設置輸入模式************************************************************ #define LCD_AC_UP 0x02 #define LCD_AC_DOWN 0x00 // default #define LCD_MOVE 0x01 // 畫面可平移 #define LCD_NO_MOVE 0x00 //default void LCD_SetInput(unsigned char InputMode) { LCD_Write(LCD_COMMAND, 0x04|InputMode); } //初始化LCD************************************************************ void LCD_Initial() { LcdEn=0; LCD_Write(LCD_COMMAND,0x38); //8位數據端口,2行顯示,5*7點陣 LCD_Write(LCD_COMMAND,0x38); LCD_SetDisplay(LCD_SHOW|LCD_NO_CURSOR); //開啟顯示, 無光標 LCD_Write(LCD_COMMAND,LCD_CLEAR_SCREEN); //清屏 LCD_SetInput(LCD_AC_UP|LCD_NO_MOVE); //AC遞增, 畫面不動 } //液晶字符輸入的位置************************ void GotoXY(unsigned char x, unsigned char y) { if(y==0) LCD_Write(LCD_COMMAND,0x80|x); if(y==1) LCD_Write(LCD_COMMAND,0x80|(x-0x40)); } //將字符輸出到液晶顯示 void Print(unsigned char *str) { while(*str!='\0') { LCD_Write(LCD_DATA,*str); str++; } } /***********DS1302時鍾部分子程序******************/ typedef struct __SYSTEMTIME__ { unsigned char Second; unsigned char Minute; unsigned char Hour; unsigned char Week; unsigned char Day; unsigned char Month; unsigned char Year; unsigned char DateString[11]; unsigned char TimeString[9]; }SYSTEMTIME; //定義的時間類型 SYSTEMTIME CurrentTime; #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 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; } } 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); } void Write1302(unsigned char ucAddr, unsigned char ucDa) //ucAddr: DS1302地址, ucData: 要寫的數據 { DS1302_RST = 0; DS1302_CLK = 0; DS1302_RST = 1; DS1302InputByte(ucAddr); // 地址,命令 DS1302InputByte(ucDa); // 寫1Byte數據 DS1302_CLK = 1; DS1302_RST = 0; } 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); } 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); } void DateToStr(SYSTEMTIME *Time) //將時間年,月,日,星期數據轉換成液晶顯示字符串,放到數組里DateString[] { if(hide_year<2) //這里的if,else語句都是判斷位閃爍,<2顯示數據,>2就不顯示,輸出字符串為 2007/07/22 { Time->DateString[0] = '2'; Time->DateString[1] = '0'; Time->DateString[2] = Time->Year/10 + '0'; Time->DateString[3] = Time->Year%10 + '0'; } else { Time->DateString[0] = ' '; Time->DateString[1] = ' '; Time->DateString[2] = ' '; Time->DateString[3] = ' '; } Time->DateString[4] = '/'; if(hide_month<2) { Time->DateString[5] = Time->Month/10 + '0'; Time->DateString[6] = Time->Month%10 + '0'; } else { Time->DateString[5] = ' '; Time->DateString[6] = ' '; } Time->DateString[7] = '/'; if(hide_day<2) { Time->DateString[8] = Time->Day/10 + '0'; Time->DateString[9] = Time->Day%10 + '0'; } else { Time->DateString[8] = ' '; Time->DateString[9] = ' '; } if(hide_week<2) { week_value[0] = Time->Week%10 + '0'; //星期的數據另外放到 week_value[]數組里,跟年,月,日的分開存放,因為等一下要在最后顯示 } else { week_value[0] = ' '; } week_value[1] = '\0'; Time->DateString[10] = '\0'; //字符串末尾加 '\0' ,判斷結束字符 } void TimeToStr(SYSTEMTIME *Time) //將時,分,秒數據轉換成液晶顯示字符放到數組 TimeString[]; { if(hide_hour<2) { Time->TimeString[0] = Time->Hour/10 + '0'; Time->TimeString[1] = Time->Hour%10 + '0'; } else { Time->TimeString[0] = ' '; Time->TimeString[1] = ' '; } Time->TimeString[2] = ':'; if(hide_min<2) { Time->TimeString[3] = Time->Minute/10 + '0'; Time->TimeString[4] = Time->Minute%10 + '0'; } else { Time->TimeString[3] = ' '; Time->TimeString[4] = ' '; } Time->TimeString[5] = ':'; if(hide_sec<2) { Time->TimeString[6] = Time->Second/10 + '0'; Time->TimeString[7] = Time->Second%10 + '0'; } else { Time->TimeString[6] = ' '; Time->TimeString[7] = ' '; } Time->DateString[8] = '\0'; } void Initial_DS1302(void) //時鍾芯片初始化 { unsigned char Second=Read1302(DS1302_SECOND); if(Second&0x80) //判斷時鍾芯片是否關閉 { Write1302(0x8e,0x00); //寫入允許 Write1302(0x8c,0x12); //以下寫入初始化時間 日期:12/05/14.星期: 1. 時間: 23:59:55 Write1302(0x88,0x05); Write1302(0x86,0x14); Write1302(0x8a,0x01); Write1302(0x84,0x23); Write1302(0x82,0x59); Write1302(0x80,0x55); Write1302(0x8e,0x80); //禁止寫入 } } /***********ds18b20子程序*************************/ /***********ds18b20延遲子函數(晶振12MHz )*******/ void delay_18B20(unsigned int i) { while(i--); } /**********ds18b20初始化函數**********************/ void Init_DS18B20(void) { unsigned char x=0; DQ = 1; //DQ復位 delay_18B20(8); //稍做延時 DQ = 0; //單片機將DQ拉低 delay_18B20(80); //精確延時 大於 480us DQ = 1; //拉高總線 delay_18B20(14); x=DQ; //稍做延時后 如果x=0則初始化成功 x=1則初始化失敗 delay_18B20(20); } /***********ds18b20讀一個字節**************/ unsigned char ReadOneChar(void) { uchar i=0; uchar dat = 0; for (i=8;i>0;i--) { DQ = 0; // 給脈沖信號 dat>>=1; DQ = 1; // 給脈沖信號 if(DQ) dat|=0x80; delay_18B20(4); } return(dat); } /*************ds18b20寫一個字節****************/ void WriteOneChar(uchar dat) { unsigned char i=0; for (i=8; i>0; i--) { DQ = 0; DQ = dat&0x01; delay_18B20(5); DQ = 1; dat>>=1; } } /**************讀取ds18b20當前溫度************/ void ReadTemp(void) { unsigned char a=0; unsigned char b=0; unsigned char t=0; Init_DS18B20(); WriteOneChar(0xCC); // 跳過讀序號列號的操作 WriteOneChar(0x44); // 啟動溫度轉換 delay_18B20(100); // this message is wery important Init_DS18B20(); WriteOneChar(0xCC); //跳過讀序號列號的操作 WriteOneChar(0xBE); //讀取溫度寄存器等(共可讀9個寄存器) 前兩個就是溫度 delay_18B20(100); a=ReadOneChar(); //讀取溫度值低位 b=ReadOneChar(); //讀取溫度值高位 temp_value=b<<4; temp_value+=(a&0xf0)>>4; } void temp_to_str() //溫度數據轉換成液晶字符顯示 { TempBuffer[0]=temp_value/10+'0'; //十位 TempBuffer[1]=temp_value%10+'0'; //個位 TempBuffer[2]=0xdf; //溫度符號 TempBuffer[3]='C'; TempBuffer[4]='\0'; } void Delay1ms(unsigned int count) { unsigned int i,j; for(i=0;i<count;i++) for(j=0;j<120;j++); } /*延時子程序*/ void mdelay(uint delay) { uint i; for(;delay>0;delay--) {for(i=0;i<62;i++) //1ms延時. {;} } } void outkey() //跳出調整模式,返回默認顯示 { uchar Second; if(out==0||wireless_1==1) { mdelay(8); count=0; hide_sec=0,hide_min=0,hide_hour=0,hide_day=0,hide_week=0,hide_month=0,hide_year=0; Second=Read1302(DS1302_SECOND); Write1302(0x8e,0x00); //寫入允許 Write1302(0x80,Second&0x7f); Write1302(0x8E,0x80); //禁止寫入 done=0; while(out==0); while(wireless_1==1); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////// void Upkey()//升序按鍵 { Up=1; if(Up==0||wireless_2==1) { mdelay(8); switch(count) {case 1: temp=Read1302(DS1302_SECOND); //讀取秒數 temp=temp+1; //秒數加1 up_flag=1; //數據調整后更新標志 if((temp&0x7f)>0x59) //超過59秒,清零 temp=0; break; case 2: temp=Read1302(DS1302_MINUTE); //讀取分數 temp=temp+1; //分數加1 up_flag=1; if(temp>0x59) //超過59分,清零 temp=0; break; case 3: temp=Read1302(DS1302_HOUR); //讀取小時數 temp=temp+1; //小時數加1 up_flag=1; if(temp>0x23) //超過23小時,清零 temp=0; break; case 4: temp=Read1302(DS1302_WEEK); //讀取星期數 temp=temp+1; //星期數加1 up_flag=1; if(temp>0x7) temp=1; break; case 5: temp=Read1302(DS1302_DAY); //讀取日數 temp=temp+1; //日數加1 up_flag=1; if(temp>0x31) temp=1; break; case 6: temp=Read1302(DS1302_MONTH); //讀取月數 temp=temp+1; //月數加1 up_flag=1; if(temp>0x12) temp=1; break; case 7: temp=Read1302(DS1302_YEAR); //讀取年數 temp=temp+1; //年數加1 up_flag=1; if(temp>0x85) temp=0; break; default:break; } while(Up==0); while(wireless_2==1); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////// void Downkey()//降序按鍵 { Down=1; if(Down==0||wireless_3==1) { mdelay(8); switch(count) {case 1: temp=Read1302(DS1302_SECOND); //讀取秒數 temp=temp-1; //秒數減1 down_flag=1; //數據調整后更新標志 if(temp==0x7f) //小於0秒,返回59秒 temp=0x59; break; case 2: temp=Read1302(DS1302_MINUTE); //讀取分數 temp=temp-1; //分數減1 down_flag=1; if(temp==-1) temp=0x59; //小於0秒,返回59秒 break; case 3: temp=Read1302(DS1302_HOUR); //讀取小時數 temp=temp-1; //小時數減1 down_flag=1; if(temp==-1) temp=0x23; break; case 4: temp=Read1302(DS1302_WEEK); //讀取星期數 temp=temp-1; //星期數減1 down_flag=1; if(temp==0) temp=0x7;; break; case 5: temp=Read1302(DS1302_DAY); //讀取日數 temp=temp-1; //日數減1 down_flag=1; if(temp==0) temp=31; break; case 6: temp=Read1302(DS1302_MONTH); //讀取月數 temp=temp-1; //月數減1 down_flag=1; if(temp==0) temp=12; break; case 7: temp=Read1302(DS1302_YEAR); //讀取年數 temp=temp-1; //年數減1 down_flag=1; if(temp==-1) temp=0x85; break; default:break; } while(Down==0); while(wireless_3==1); } } void Setkey()//模式選擇按鍵 { Set=1; if(Set==0||wireless_4==1) { mdelay(8); count=count+1; //Setkey按一次,count就加1 done=1; //進入調整模式 while(Set==0); while(wireless_4==1); } } void keydone()//按鍵功能執行 { uchar Second; if(flag==0) //關閉時鍾,停止計時 { Write1302(0x8e,0x00); //寫入允許 temp=Read1302(0x80); Write1302(0x80,temp|0x80); Write1302(0x8e,0x80); //禁止寫入 flag=1; } Setkey(); //掃描模式切換按鍵 switch(count) {case 1:do //count=1,調整秒 { outkey(); //掃描跳出按鈕 Upkey(); //掃描加按鈕 Downkey(); //掃描減按鈕 if(up_flag==1||down_flag==1) //數據更新,重新寫入新的數據 { Write1302(0x8e,0x00); //寫入允許 Write1302(0x80,temp|0x80); //寫入新的秒數 Write1302(0x8e,0x80); //禁止寫入 up_flag=0; down_flag=0; } hide_sec++; //位閃計數 if(hide_sec>3) hide_sec=0; show_time(); //液晶顯示數據 }while(count==2);break; case 2:do //count=2,調整分 { hide_sec=0; outkey(); Upkey(); Downkey(); if(temp>0x60) temp=0; if(up_flag==1||down_flag==1) { Write1302(0x8e,0x00); //寫入允許 Write1302(0x82,temp); //寫入新的分數 Write1302(0x8e,0x80); //禁止寫入 up_flag=0; down_flag=0; } hide_min++; if(hide_min>3) hide_min=0; show_time(); }while(count==3);break; case 3:do //count=3,調整小時 { hide_min=0; outkey(); Upkey(); Downkey(); if(up_flag==1||down_flag==1) { Write1302(0x8e,0x00); //寫入允許 Write1302(0x84,temp); //寫入新的小時數 Write1302(0x8e,0x80); //禁止寫入 up_flag=0; down_flag=0; } hide_hour++; if(hide_hour>3) hide_hour=0; show_time(); }while(count==4);break; case 4:do //count=4,調整星期 { hide_hour=0; outkey(); Upkey(); Downkey(); if(up_flag==1||down_flag==1) { Write1302(0x8e,0x00); //寫入允許 Write1302(0x8a,temp); //寫入新的星期數 Write1302(0x8e,0x80); //禁止寫入 up_flag=0; down_flag=0; } hide_week++; if(hide_week>3) hide_week=0; show_time(); }while(count==5);break; case 5:do //count=5,調整日 { hide_week=0; outkey(); Upkey(); Downkey(); if(up_flag==1||down_flag==1) { Write1302(0x8e,0x00); //寫入允許 Write1302(0x86,temp); //寫入新的日數 Write1302(0x8e,0x80); //禁止寫入 up_flag=0; down_flag=0; } hide_day++; if(hide_day>3) hide_day=0; show_time(); }while(count==6);break; case 6:do //count=6,調整月 { hide_day=0; outkey(); Upkey(); Downkey(); if(up_flag==1||down_flag==1) { Write1302(0x8e,0x00); //寫入允許 Write1302(0x88,temp); //寫入新的月數 Write1302(0x8e,0x80); //禁止寫入 up_flag=0; down_flag=0; } hide_month++; if(hide_month>3) hide_month=0; show_time(); }while(count==7);break; case 7:do //count=7,調整年 { hide_month=0; outkey(); Upkey(); Downkey(); if(up_flag==1||down_flag==1) { Write1302(0x8e,0x00); //寫入允許 Write1302(0x8c,temp); //寫入新的年數 Write1302(0x8e,0x80); //禁止寫入 up_flag=0; down_flag=0; } hide_year++; if(hide_year>3) hide_year=0; show_time(); }while(count==8);break; case 8: count=0;hide_year=0; //count8, 跳出調整模式,返回默認顯示狀態 Second=Read1302(DS1302_SECOND); Write1302(0x8e,0x00); //寫入允許 Write1302(0x80,Second&0x7f); Write1302(0x8E,0x80); //禁止寫入 done=0; break; //count=7,開啟中斷,標志位置0並退出 default:break; } } void show_time() //液晶顯示程序 { DS1302_GetTime(&CurrentTime); //獲取時鍾芯片的時間數據 TimeToStr(&CurrentTime); //時間數據轉換液晶字符 DateToStr(&CurrentTime); //日期數據轉換液晶字符 ReadTemp(); //開啟溫度采集程序 temp_to_str(); //溫度數據轉換成液晶字符 GotoXY(12,1); //液晶字符顯示位置 Print(TempBuffer); //顯示溫度 GotoXY(0,1); Print(CurrentTime.TimeString); //顯示時間 GotoXY(0,0); Print(CurrentTime.DateString); //顯示日期 GotoXY(15,0); Print(week_value); //顯示星期 GotoXY(11,0); Print("Week"); //在液晶上顯示 字母 week Delay1ms(400); //掃描延時 } main() { flag=1; //時鍾停止標志 LCD_Initial(); //液晶初始化 Init_DS18B20( ) ; //DS18B20初始化 Initial_DS1302(); //時鍾芯片初始化 up_flag=0; down_flag=0; done=0; //進入默認液晶顯示 wireless_1=0; wireless_2=0; wireless_3=0; wireless_4=0; while(1) { while(done==1) keydone(); //進入調整模式 while(done==0) { show_time(); //液晶顯示數據 flag=0; Setkey(); //掃描各功能鍵 } } }
注意引腳,自己連的時候不要出錯