摘要
本次畢業設計介紹一種定時鬧鈴LCD的設計,包括系統的硬體和軟體設計。該系統是Intel公司生產的MCS-51 系列單片機中的AT89S52構成最小系統的中央處理器,由1602液晶顯示、DS1302時鐘晶片、按鍵、蜂鳴器、電源等電路模塊構成。能夠在液晶上顯示年月日時分秒和鬧鈴時間(鬧鈴個數可以通過軟體實現,本實驗設計兩個),並且該系統可以通過DS1302時鐘晶片自動區分閏年和不同月份天數的不同。也可以通過按鍵手動調節顯示器上的各個時間。該系統的電源帶整流電路,可將直流或交流電轉化為直流5V供電。
關鍵詞:中央處理器 DS1302時鐘晶片 鬧鈴 整流電路
一、 系統整體結構框圖
二、 方案比較與論證
2.1微控制器模塊論證與選擇
方案一:採用可編程邏輯器件CPLD
優點:CPLD可以實現各種複雜的邏輯功能、規模大、密度高、體積小、穩定性高、IO資源豐富、易於進行功能擴展。其採用並行的輸入輸出方式,提高了系統的處理速度,適合作為大規模控制系統的控制核心。
缺點:價格比較昂貴,且正由於功能的強大,對使用者的硬體及軟體水平有更高要求,不利於中下學者和低成本使用。
方案二:採用單片機控制器
優點:選用intel公司的AT89sxx器件是完全集成的低功耗混合信號片上系統型MCU,具有高速、流水線結構的8051兼容的微控制器核(可達 50MIPS),全速、非侵入式的在系統調試接口(片內),價格便宜,中等難度,非常適合中下學者和此次設計的目的。
缺點:處理速度比較慢,功能相對比較少,不利於學者的後續學習。
綜上所述和考慮本系統不需要複雜的邏輯功能,對數據的處理速度的要求也不是非常高,且從使用及經濟的角度考慮等,所以我選擇方案二。
2.2顯示器模塊的論證與選擇
方案一:數碼管顯示
優點:數碼管顯示的由於其用較大二極體顯示,所以比較的清晰。且在軟體上不會涉及時序,很適合顯示數目較少和初學者。
缺點:價格相對較高(8位模塊22.60元),不能顯示字母,不適合顯示位數多實驗,且如果不製成板子,焊接電路布線複雜,更重要此題目明確要求用LCD。
方案二:液晶顯示
優點:可顯示的位數很多,顯示樣式多(字母、漢字、數字和圖案),價格便宜(1602模塊7.02元,12864模塊15.00元)。
缺點:涉及時序,軟體書寫較複雜,模塊集成化高,不利於硬體的提高。
綜上所訴和考慮此次不涉及顯示圖形(12864)。所以,我們選擇方案二中的LCD1602.
2.3定時器模塊的論證與選擇
方案一:AT89S52晶片內部的定時器模塊
優點:不需使用其它晶片和節省電路焊接,大大節約成本和焊接時間,編寫程序時簡單。
缺點:由於是通過內部延時且無閏年、月份等自動調節功能,誤差很大。
方案二:使用DS1302時鐘晶片
優點:晶片帶有閏年、月份等自動調節功能,誤差很小(這裡誤差主要由於某些硬體引起)。並且,可通過晶片外接電路連接帶有掉電保持功能,即使沒有外接電源,再次接電時,時間依然準確。且相比其它時鐘晶片價格便宜(DS1302晶片0.32元,DS12C887晶片9.70元,DS144Y晶片25.00元)。
缺點:和52晶片內部定時器模塊相比,價格較高和需焊接外部電路軟體編程涉及到時序。
2.4蜂鳴器的論證與選擇
方案一:高分貝報警器SFM-27
方案二:電磁式有源蜂鳴器
綜上所訴和考慮本人有電磁式有源蜂鳴器,故選擇方案二,但是如果用在大型項目,兩者不建議採用。如果價錢許可,建議用如高分貝喇叭蜂鳴器,以達到聲音悅耳動聽效果。
2.5按鍵的說明
因為單片機常用按鍵差不多,這裡不進行方案對比。本次總共用了四個輕觸按鍵開關,其中復位鍵沒有畫出,當按鍵0按下第一次時,開始顯示出起初設計時間;
當按鍵0按下第二次時,這時按下按鍵1,通過看顯示看顯示器上按鍵1按下的次數,再按下按鍵2可調節顯示器上各個時間(包括兩個鬧鈴時間);當按鍵0按下第三次時,時間開始走動。當時間走動時,不需暫停時間,通過按鍵1和按鍵2就可調節顯示器上各個時間。
2.6復位和震蕩晶振
2.7整流電路圖
這裡給出+12V和+5V的電路,主要是為了讓一些初學者明白通過整流電路獲得不同電壓,主要是整流晶片的不同。當然,這裡應注意的是電壓不同,電容的耐壓值也要跟著適當改變和防止電容接反。
2.8整體電路圖
三、 系統軟體設計
3.1軟體流程圖
3.2硬體的程序
#include<reg52.h> //包含單片機寄存器的頭文件
#include<intrins.h> //包含_nop_()函數定義的頭文件
#define uchar unsigned char //宏定義
#define uint unsigned int
uchar code digit[10]={"0123456789"}; //定義字符數組顯示數字
uchar nian=13,yue=12,ri=21,shi=21,fen=14,miao=50,shi1=21,fen1=15,shi2=21,fen2=20,cishu00=0,cishu10=0;
sbit SCLK=P1^0; //位定義1302晶片的接口,時鐘輸出埠定義在P1.0引腳
sbit DATA=P1^1; //位定義1302晶片的接口,數據輸出端定義在P1.1引腳
sbit RST=P1^2; //位定義1302晶片的接口,復位埠定義在P1.2引腳
sbit fengmingqi=P2^3;
sbit anjian0=P2^4;
sbit anjian1=P2^5;
sbit anjian2=P2^6;
/*************************
函數功能:延時若干微秒
入口參數:n
**************************/
void delaynus(uchar n)
{
uchar i;
for(i=0;i<n;i++);
}
/**************************
函數功能:向1302寫一個字節數據
入口參數:x
***************************/
void Write1302(uchar dat)
uchar i;
SCLK=0; //拉低SCLK,為脈衝上升沿寫入數據做好準備
delaynus(2); //稍微等待,使硬體做好準備
for(i=0;i<8;i++) //連續寫8個二進位位數據
{
DATA=dat&0x01; //取出dat的第0位數據寫入1302 低位在前,高位在後
delaynus(2); //稍微等待,使硬體做好準備
SCLK=1; //上升沿寫入數據
SCLK=0; //重新拉低SCLK,形成脈衝
dat>>=1; //將dat的各數據位右移1位,準備寫入下一個數據位
}
/********************************************
函數功能:根據命令字,向1302寫一個字節數據
入口參數:Cmd,儲存命令字;dat,儲存待寫的數據
*********************************************/
void WriteSet1302(uchar Cmd,uchar dat)
{
RST=0; //禁止數據傳遞
SCLK=0; //確保寫數居前SCLK被拉低
RST=1; //啟動數據傳輸
delaynus(2); //稍微等待,使硬體做好準備
Write1302(Cmd); //寫入命令字
Write1302(dat); //寫數據
SCLK=1; //將時鐘電平置於高電平狀態
RST=0; //禁止數據傳遞
/*****************************************************
函數功能:從1302讀一個字節數據
***************************************************/
uchar Read1302(void)
uchar i,dat;
delaynus(2); //稍微等待,使硬體做好準備
for(i=0;i<8;i++) //連續讀8個二進位位數據
{ dat>>=1;
if(DATA==1) //如果讀出的數據是1
dat|=0x80; //將1取出,寫在dat的最高位
SCLK=1; //將SCLK置於高電平,為下降沿讀出
delaynus(2); //稍微等待
SCLK=0; //拉低SCLK,形成脈衝下降沿
}
return dat; //將讀出的數據返回
}
函數功能:根據命令字,從1302讀取一個字節數據
入口參數:Cmd
uchar ReadSet1302(uchar Cmd)
uchar dat;
RST=0; //拉低RST
SCLK=0; //確保寫數居前SCLK被拉低
RST=1; //啟動數據傳輸
Write1302(Cmd); //寫入命令字
dat=Read1302(); //讀出數據
SCLK=1; //將時鐘電平置於已知狀態
RST=0; //禁止數據傳遞
return dat; //將讀出的數據返回
函數功能: 1302進行初始化設置
void Init_DS1302(void)
{
WriteSet1302(0x8E,0x00); //根據寫狀態寄存器命令字,寫入不保護指令
WriteSet1302(0x80,((miao/10)<<4|(miao%10))); //根據寫秒寄存器命令字,寫入秒的初始值
WriteSet1302(0x82,((fen/10)<<4|(fen%10))); //根據寫分寄存器命令字,寫入分的初始值
WriteSet1302(0x84,((shi/10)<<4|(shi%10))); //根據寫小時寄存器命令字,寫入小時的初始值
WriteSet1302(0x86,((ri/10)<<4|(ri%10))); //根據寫日寄存器命令字,寫入日的初始值
WriteSet1302(0x88,((yue/10)<<4|(yue%10))); //根據寫月寄存器命令字,寫入月的初始值
WriteSet1302(0x8c,((nian/10)<<4|(nian%10))); //根據寫年寄存器命令字,寫入年的初始值
WriteSet1302(0x90,0xa5); //打開充電功能 選擇2K電阻充電方式
WriteSet1302(0x8E,0x80); //根據寫狀態寄存器命令字,寫入保護指令
/* uchar flag;
*flag= ReadSet1302(0x81);
if(flag&0x80)
{ //判斷時鐘晶片是否關閉
WriteSet1302(0x8E,0x00); //根據寫狀態寄存器命令字,寫入不保護指令
WriteSet1302(0x80,((55/10)<<4|(55%10))); //根據寫秒寄存器命令字,寫入秒的初始值
WriteSet1302(0x82,((59/10)<<4|(59%10))); //根據寫分寄存器命令字,寫入分的初始值
WriteSet1302(0x84,((23/10)<<4|(23%10))); //根據寫小時寄存器命令字,寫入小時的初始值
WriteSet1302(0x86,((18/10)<<4|(18%10))); //根據寫日寄存器命令字,寫入日的初始值
WriteSet1302(0x88,((6/10)<<4|(6%10))); //根據寫月寄存器命令字,寫入月的初始值
WriteSet1302(0x8c,((9/10)<<4|(9%10))); //根據寫年寄存器命令字,寫入年的初始值
WriteSet1302(0x90,0xa5); //打開充電功能 選擇2K電阻充電方式
WriteSet1302(0x8E,0x80);*///根據寫狀態寄存器命令字,寫入保護指令
}
//如果不想每次都初始化時間,也就是掉電後還想讓時鐘繼續走時的話 就用上面的語句
/*******************************************************************************
以下是對液晶模塊的操作程序
*******************************************************************************/
sbit RS=P2^0; //寄存器選擇位,將RS位定義為P2.0引腳
sbit RW=P2^1; //讀寫選擇位,將RW位定義為P2.1引腳
sbit E=P2^2; //使能信號位,將E位定義為P2.2引腳
sbit BF=P0^7; //忙碌標誌位,,將BF位定義為P0.7引腳
函數功能:延時1ms
(3j+2)*i=(3×33+2)×10=1010(微秒),可以認為是1毫秒
void delay1ms()
uchar i,j;
for(i=0;i<10;i++)
for(j=0;j<33;j++);
函數功能:延時若干毫秒
void delaynms(uchar n)
uchar i;
for(i=0;i<n;i++)
delay1ms();
函數功能:判斷液晶模塊的忙碌狀態
返回值:result。result=1,忙碌;result=0,不忙
bit BusyTest(void)
{
bit result;
RS=0; //根據規定,RS為低電平,RW為高電平時,可以讀狀態
RW=1;
E=1; //E=1,才允許讀寫
_nop_(); //空操作
_nop_();
_nop_(); //空操作四個機器周期,給硬體反應時間
result=BF; //將忙碌標誌電平賦給result
E=0; //將E恢復低電平
return result;
函數功能:將模式設置指令或顯示地址寫入液晶模塊
入口參數:dictate
void WriteInstruction (uchar dictate)
{
while(BusyTest()==1); //如果忙就等待
RS=0; //根據規定,RS和R/W同時為低電平時,可以寫入指令
RW=0;
E=0; //E置低電平(根據表8-6,寫指令時,E為高脈衝,
// 就是讓E從0到1發生正跳變,所以應先置"0"
_nop_();
_nop_(); //空操作兩個機器周期,給硬體反應時間
P0=dictate; //將數據送入P0口,即寫入指令或地址
_nop_(); //空操作四個機器周期,給硬體反應時間
E=1; //E置高電平
_nop_(); //空操作四個機器周期,給硬體反應時間
E=0; //當E由高電平跳變成低電平時,液晶模塊開始執行命令
}
函數功能:指定字符顯示的實際地址
void WriteAddress(uchar x)
WriteInstruction(x|0x80); //顯示位置的確定方法規定為"80H+地址碼x"
函數功能:將數據(字符的標準ASCII碼)寫入液晶模塊
入口參數:y(為字符常量)
void WriteData(uchar y)
while(BusyTest()==1);
RS=1; //RS為高電平,RW為低電平時,可以寫入數據
RW=0;
E=0; //E置低電平(根據表8-6,寫指令時,E為高脈衝,
// 就是讓E從0到1發生正跳變,所以應先置"0"
P0=y; //將數據送入P0口,即將數據寫入液晶模塊
_nop_();
_nop_(); //空操作四個機器周期,給硬體反應時間
E=1; //E置高電平
_nop_(); //空操作四個機器周期,給硬體反應時間
E=0; //當E由高電平跳變成低電平時,液晶模塊開始執行命令
函數功能:對LCD的顯示模式進行初始化設置
void LcdInitiate(void)
delaynms(15); //延時15ms,首次寫指令時應給LCD一段較長的反應時間
WriteInstruction(0x38); //顯示模式設置:16×2顯示,5×7點陣,8位數據接口
delaynms(5); //延時5ms ,給硬體一點反應時間
WriteInstruction(0x38);
WriteInstruction(0x38); //連續三次,確保初始化成功
WriteInstruction(0x0c); //顯示模式設置:顯示開,無光標,光標不閃爍
WriteInstruction(0x06); //顯示模式設置:光標右移,字符不移
WriteInstruction(0x01); //清屏幕指令,將以前的顯示內容清除
/**************************************************************
以下是1302數據的顯示程序
**************************************************************/
函數功能:顯示秒
void DisplaySecond(uchar x)
uchar i,j; //j,k分別儲存十位和個位
i=x/10; //取十位
j=x%10; //取個位
WriteAddress(0x46); //寫顯示地址,將在第2行第7列開始顯示
WriteData(digit[i]); //將百位數字的字符常量寫入LCD
WriteData(digit[j]); //將十位數字的字符常量寫入LCD
delaynms(50); //延時1ms給硬體一點反應時間
函數功能:顯示分鐘
void DisplayMinute(uchar x)
uchar i,j; //j,k十位和個位
i=x/10; //取十位
j=x%10; //取個位
WriteAddress(0x43); //寫顯示地址,將在第2行第7列開始顯示
delaynms(50); //延時1ms給硬體一點反應時間
函數功能:顯示小時
void DisplayHour(uchar x)
uchar i,j; //j,k十位和個位
WriteAddress(0x40); //寫顯示地址,將在第2行第7列開始顯示
WriteData(digit[i]); //將百位數字的字符常量寫入LCD
WriteData(digit[j]); //將十位數字的字符常量寫入LCD
函數功能:顯示日
void DisplayDay(uchar x)
uchar i,j; //j,k十位和個位
i=x/10; //取十位
j=x%10; //取個位
WriteAddress(0x0c); //寫顯示地址,將在第2行第7列開始顯示
WriteData(digit[i]); //將十位數字的字符常量寫入LCD
WriteData(digit[j]); //將個位數字的字符常量寫入LCD
delaynms(50); //延時1ms給硬體一點反應時間
函數功能:顯示月
void DisplayMonth(uchar x)
uchar i,j; //j,k分別儲存十位和個位
WriteAddress(0x09); //寫顯示地址,將在第2行第7列開始顯示
WriteData(digit[i]); //將十位位數字的字符常量寫入LCD
函數功能:顯示年
void DisplayYear(uchar x)
uchar i,j; //j,k分別儲存十位和個位
WriteAddress(0x06); //寫顯示地址,將在第2行第7列開始顯示
WriteData(digit[i]); //將十位位數字的字符常量寫入LCD
WriteData(digit[j]); //將個位數字的字符常量寫入LCD
函數功能:顯示調節時間按鍵的次數,進而調節各個時間
void Displaycishu10(uchar x)
uchar i; //j,k分別儲存十位和個位
i=x%10; //取個位
WriteAddress(0x0f); //寫顯示地址,將在第2行第7列開始顯示
函數功能:顯示鬧鐘的分鐘
void Displayfen(uchar x)
i=x/10; //取十位
WriteAddress(0x4c); //寫顯示地址,將在第2行第7列開始顯示
WriteData(digit[j]); //將十位位數字的字符常量寫入LCD
delaynms(50); //延時1ms給硬體一點反應時間
/*****************************************************
函數功能:顯示鬧鐘的小時
void Displayshi(uchar x)
WriteAddress(0x49); //寫顯示地址,將在第2行第7列開始顯示
以下是按鍵調節程序
void anjian()
/*按鍵0的處理程序*/
if(anjian0==0)
delaynms(6);
if(anjian0==0)
{
cishu00=cishu00+1;
if(cishu00==3)
{
cishu00=0;
}
}
while(anjian0==0);
/*按鍵1的處理程序*/
if(anjian1==0)
delaynms(6);
if(anjian1==0)
{
cishu10=cishu10+1;
if(cishu10==9)
{
cishu10=0;
}
}
while(anjian1==0);
/*按鍵2的處理程序*/
//調節秒
if(cishu10==0)
if(anjian2==0)
if(anjian2==0)
miao=miao+1;
if(miao==60)
{
miao=0;
}
}
while(anjian2==0);
//調節分
if(cishu10==1)
{
delaynms(6);
if(anjian2==0)
fen=fen+1;
if(fen==60)
fen=0;
}
while(anjian2==0);
//調節時
if(cishu10==2)
if(anjian2==0)
shi=shi+1;
if(shi==24)
shi=0;
}
while(anjian2==0);
//調節日
if(cishu10==3)
if(anjian2==0)
ri=ri+1;
if(ri==32)
ri=0;
//調節月
if(cishu10==4)
yue=yue+1;
if(yue==8)
yue=0;
//調節年
if(cishu10==5)
nian=nian+1;
if(nian==20)
nian=0;
/*鬧鈴程序*/
//調節鬧鐘的分鐘1
if(cishu10==6)
fen1=fen1+1;
if(fen1==60)
fen1=0;
//調節鬧鐘的時鐘1
if(cishu10==7)
shi1=shi1+1;
if(shi1==24)
shi1=0;
//調節鬧鐘的分鐘2
if(cishu10==8)
delaynms(6);
fen2=fen2+1;
if(fen2==60)
fen2=0;
while(anjian2==0);
//調節鬧鐘的時鐘2
if(cishu10==9)
shi2=shi2+1;
if(shi2==24)
shi2=0;
函數功能:主函數
void main(void)
uchar second,minute,hour,day,month,year; //分別儲存秒、分、小時,日,月,年
uchar ReadValue; //儲存從1302讀取的數據
LcdInitiate(); //將液晶初始化
WriteAddress(0x00); //寫Date的顯示地址,將在第1行第2列開始顯示
WriteData('T'); //將字符常量寫入LCD
WriteData('o'); //將字符常量寫入LCD
WriteData('d'); //將字符常量寫入LCD
WriteData('a'); //將字符常量寫入LCD
WriteData('y'); //將字符常量寫入LCD
WriteData(':'); //將字符常量寫入LCD
WriteAddress(0x08); //寫年月分隔符的顯示地址, 顯示在第1行第9列
WriteData('-'); //將字符常量寫入LCD
WriteAddress(0x0b); //寫月日分隔符的顯示地址, 顯示在第1行第12列
WriteAddress(0x42); //寫小時與分鐘分隔符的顯示地址, 顯示在第2行第6列
WriteAddress(0x45); //寫分鐘與秒分隔符的顯示地址, 顯示在第2行第9列
WriteAddress(0x4b); //寫分鐘與秒分隔符的顯示地址, 顯示在第2行第13列
Init_DS1302();
fengmingqi=0; //將1302初始化
while(1)
anjian();
if(cishu00==1)
second=miao;
minute=fen;
hour=shi;
day=ri;
month=yue;
year=nian;
Init_DS1302();
Displaycishu10(cishu10);
DisplaySecond(second);
DisplayMinute(minute);
DisplayHour(hour);
DisplayDay(day);
DisplayMonth(month);
DisplayYear(year);
Displaycishu10(cishu10);
if((cishu10==6)||(cishu10==7))
Displayfen(fen1);
Displayshi(shi1);
else
if((cishu10==8)||(cishu10==9))
Displayfen(fen2);
Displayshi(shi2);
else
Displayfen(0);
Displayshi(0);
}
if((hour==shi1)&&(minute==fen1))
fengmingqi=1;
else
if((hour==shi2)&&(minute==fen2))
{
fengmingqi=1;
delaynms(100);
fengmingqi=0;
if(cishu00==2)
Displaycishu10(cishu10);
ReadValue = ReadSet1302(0x81); //從秒寄存器讀數據
second=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //將讀出數據轉化
miao=second;
DisplaySecond(second); //顯示秒
ReadValue = ReadSet1302(0x83); //從分寄存器讀
minute=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //將讀出數據轉化
fen=minute;
DisplayMinute(minute); //顯示分
ReadValue = ReadSet1302(0x85); //從分寄存器讀
hour=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //將讀出數據轉化
DisplayHour(hour); //顯示小時
ReadValue = ReadSet1302(0x87); //從分寄存器讀
day=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //將讀出數據轉化
ri=day;
DisplayDay(day); //顯示日
ReadValue = ReadSet1302(0x89); //從分寄存器讀
month=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //將讀出數據轉化
yue= month;
DisplayMonth(month); //顯示月
ReadValue = ReadSet1302(0x8d); //從分寄存器讀
year=((ReadValue&0xf0)>>4)*10 + (ReadValue&0x0F); //將讀出數據轉化
nian=year;
DisplayYear(year); //顯示年
{
if((cishu10==6)||(cishu10==7))
Displayfen(fen1);
Displayshi(shi1);
}
if((hour==shi1)&&(minute==fen1))
}
else
if((hour==shi2)&&(minute==fen2))
fengmingqi=0;
}
四、 設計總結
通過本次獨立完成畢業設計,我進一步複習了51單片機的同時,也更加深入了解51單片機和動手能力。學會了一些新軟體使用方法(如:startdraw)。當然,此次硬體、程序和論文的全面解析,我相信對任何一個單片機初學者和中下水平同學有很高的參考價值。有覺得不足,可以給我留言。