51单片机初学3-从零开始制作一款电子时钟
今天我們用STC89C52制作一款簡單的單片機作品:電子時鐘。除了基本的走時功能,還能手動調節時間,設置鬧鐘,待機喚醒。
本文包括硬件與軟件設計。
我認為電子時鐘需要考慮的兩點:一是計時準確,誤差小;二是省電,使其能在移動電源供電下超長待機。
硬件設計:
首先我們需要構思好系統框架:
基本的時鐘電路與復位電路不用多說,我們用八位數碼管來作為時間顯示方式(顯示樣式為:12-00-00),其中P0口控制其段,P2口控制其位;以八個點動按鈕作為鍵盤輸入;蜂鳴器、LED分別作為提示音和指示燈。
接下來就可以設計原理圖:
可以看到數碼管的接線較復雜,其原理暫不多說(可參考文章51單片機初學2-數碼管動態掃描_#liufenges#的博客-CSDN博客_數碼管動態掃描),可以看到兩個數碼管的1、2、3、4、5、7、10、11是分別連起來的,然后引出來連接到P0口;兩個數碼管的6、8、9、12共8個腳與P2口連接。
需要注意,數碼管位控制與P2口之間加入了一個鎖存器74HC373,其作用是在待機時方便關閉數碼管。其11腳是地址鎖存端口,將其接高電平時,鎖存器為透明模式,輸入與輸出完全相同,這里我直接接入VCC;1腳為輸出鎖存,高電平時無輸出,低電平才有輸出,這里我們用P3.6來控制其輸出。下圖是74HC373引腳圖及其功能。
為了簡化電路,蜂鳴器與LED共用一個I/O口;
單片機的數據串口引出來接到排針上,方便程序燒錄。
需要注意,為了防止數碼管燒壞,在P0口應串聯470歐姆的限流電阻(原理圖中未畫出)。
所以得到所需材料:
STC89C52芯片(1塊),40P底座(1只),面包板(2片),3461BS數碼管(2只),點動按鈕(9只),LED燈(1只),74HC373鎖存芯片(1片),10K 9P排阻(4只),470歐電阻(15只),12M晶振(1只),30pF瓷片電容(兩個),排針(15針),led燈,有源蜂鳴器一只(關于有源與無源蜂鳴器的區別可在網上查閱),PNP型三極管一只。
最后我們按照原理圖焊接元件。以下為成品圖片可供參考
為了使作品看起來簡潔,我們采用雙主板設計,上層為數碼管、鍵盤,下層為單片機最小系統,兩層主板使用小螺栓固定。
由于定做PCB時間較長,所以我使用洞洞板來制作電路板(若是不擅長電子焊接,最好是制作PCB),可以看到飛線很多,兩塊主板之間有較多的連接線(為了防止焊點受力而脫落,可以將線繞在洞洞之間)。注意焊接單片機底座時,不要把單片機裝在底座上,以免焊接時燒壞單片機芯片;同樣,焊接晶振時,要盡可能快,避免長時間給晶振加熱而損壞晶振;安插單片機芯片時要注意對齊引腳,以免折斷或者接觸不良,插好后可以用萬用表測量一遍所有引腳是否與底座導通;排阻公共端判斷方法:在排阻最左邊或者最右邊會有個白色小點,有白點的一端為公共端;點動按鈕有四個引腳(一組常開觸點,一組常閉觸點),可按照原理圖所示將兩個引腳接入。
單片機程序開發常用 keil軟件(這里我們以Keil uVision3為例):
首先新建工程(點擊project→new→選擇一個文件地址后保存),然后選擇CPU型號。
STC89C52是完全兼容AT89C52的(因為STC是國產芯片,keil中沒有STC芯片,只能用其他芯片代替),所以我們選擇AT89C52即可(首先點Atmel,下拉之后,可以找到AT89C52)。
之后會彈出詢問窗口:Copy standard 8051 Startup code to Project Folder and Add File to project?(是否復制8051啟動編碼到工程文件夾?),點擊確認即可。若點擊取消,在創建文件時也會自動添加。
可以看到創建了一個Target1的工程文件,下拉時候還有一個Source Group1的文件夾。這個文件夾里有個STARTUP.A51的文件,這就是剛才復制的8051啟動編碼,里面包含51單片機的寄存器、I/O口等地址的分配,這些都是軟件自動生成的,一般不需要去更改。
之后添加C程序文件:File→new。然后會創建一個text1的空白文件。然后我們點擊保存(或者Ctrl+S),選擇保存地址(保存在一個容易找到的地方,后面需要用到),輸入文件名,注意文件名要加后綴.c保存為C文件。如果是用匯編語言寫程序,則加后綴.ASM。
接著右擊Source Group1,在菜單中找到Add Files To Group ‘Source Group1’點擊(這個選項在菜單中有加粗顯示)。然后將剛才的c程序文件添加至工程,關閉對話框。可以看到Source Group1下多了之前的C文件。
然后就可以寫程序了。
程序編寫:
定義單片機C程序的頭文件#include<reg51.h>
為了方便后面寫程序時,搞混I/O口,我們可以先定義一些功能引腳。例如蜂鳴器,我們查看原理圖可以看到,蜂鳴器是由P3.1控制的,所以我們定義P3.1為蜂鳴器:sbit fm=P3^1;(‘sbit’是單片機用于定義引腳的關鍵字,在C語言中是沒有這個關鍵字的;P3.1之間的點在程序中要用‘^’表示),這樣,在之后的程序中,如果我們要用到蜂鳴器,只要讓fm等于0或者等于1,就可以控制蜂鳴器的工作了,而不再需要使用P3^1了。
然后我們還要對數碼管進行編碼,數碼管需要顯示的字符較多,我們可以使用一個數組來定義:
char codeduan[]={0xc0,0xcf,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x7f,0xbf,0xff,0x89};
(char數據類型:在單片機中,char數據類型所占空間最少,只有1個字節(八位二進制),但他的范圍為?-128~127?(signed有符號型),unsigned為0~255。所以如果該變量數據范圍不大,一般用char類型,這樣做可以節省單片機空間) 。
接著定義全局變量 sec,min,hour.之所以定義為全局變量,是為了讓這三個量所有函數中都是能使用的。
在本作品中,延時函數必不可少,比如數碼管掃描,走時都需要延時函數(常用的方法還有定時器中斷)。關于延時函數的計算問題可自行百度,為了方便,我們可以直接使用STC-IPS軟件自動生成,只要輸入需要延時的時間,軟件可以自動生成一個延時函數,直接復制粘貼就可以(最小時間為1us)。? ?由于我們需要多種時間的延時,所以我們可以先把需要的延時函數先寫在前面,方便之后的調用。
定義好需要的變量,我們就可以開始寫主函數了。這里我們把數碼管掃描與計時作為主程序,數碼管掃描與計時同時進行。
接著編寫調時子函數,鬧鐘子函數。在主程序插入判定條件,以此調用子函數。
為了添加更多花樣,還添加了一個開機‘動畫’? motos();(詳情看后面的程序)
需要注意的是,子函數應置于主函數前面,否則編譯時會提示 未定義子函數 。
再說說鍵盤的處理。鍵盤排列與鍵位設置如下。
K1、K2控制光標的左右移動,K3、K4控制數字加減,K5為確定鍵,K6為調時(長按4秒進入),K7設置鬧鐘,K8待機模式。
其他細節暫不多說,看程序即可。
完整程序如下:
/*電子時鐘程序:基本電子時鐘功能,能調節時間,能設置鬧鐘(已刪減),有待機模式(已刪減)*/ /*LED數碼管顯示器設定; P0.0---P0.7段控線,接LED的顯示段a,b,c,d,e,f,g,dp. P2.0---P2.7位控線,從左至右************鍵位設置*******************W3(+) W1(光標左移) W5(確認) W2(光標右移) W6(調時) W7(鬧鈴) W8 (喚醒) W4(-) **************************************/ #include<reg51.h> #include<intrins.h> //定義單片機的頭文件 sbit fm=P3^1; //定義單片機蜂鳴器 sbit plays=P3^6; //定義73HC373輸出控制位// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 // char codeduan[]={0xc0,0xcf,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x7f,0xbf,0xff,0x89}; //數碼管段編碼// 0 1 2 3 4 5 6 7 8 9 dp - 空 H // char codebite[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x00}; //數碼管位編碼 char sec=0,min=0,hour=0; void Delay1ms() //@12.000MHz,1ms延時函數,用于數碼管動態輸出 {unsigned char i, j;i = 2;j = 239;do{while (--j);} while (--i); } void Delay50ms() //@12.000MHz,用于蜂鳴器提示音,30ms {unsigned char i, j, k;i = 2;j = 95;k = 43;do{ do{ while (--k);} while (--j);} while (--i); } void adjust() //時間調整模式子程序 {int H=0,cursor=3;char ks,twi,temps[8],K[8];temps[2]=11;temps[5]=11;fm=0;Delay50ms();fm=1; //蜂鳴器響一聲提示進入時間調整模式while(P1!=0xef) //如果沒有按下K8,則執行循環{if(H<180) {twi=0;} //進入調整模式后,光標閃爍if(H>180) {twi=1;} if(H==360) {H=0;} for(ks=0;ks<8;ks++){if(cursor==1&&twi==0){temps[0]=12;temps[1]=12;}else{temps[0]=sec%10; //求余計算秒個位temps[1]=sec/10;} //求商計算秒十位if(cursor==2&&twi==0){temps[3]=12;temps[4]=12;}else{temps[3]=min%10; //求余計算分個位temps[4]=min/10;} //求商計算分十位if(cursor==3&&twi==0){temps[6]=12;temps[7]=12;}else {temps[6]=hour%10; //求余計算時個位temps[7]=hour/10;} //求余計算時十位 P2=codebite[ks]; //數碼管輸出選位,從第0位開始//P0=codeduan[temps[ks]]; //輸出段,輸出要顯示的數字//Delay1ms(); //延時1ms,防止數碼管串碼H++;P0=codeduan[12];}if(P1==0xfe) /*按下‘左’鍵,將光標左移 */{ K[1]=1;}if(K[1]==1&&P1!=0xfe){K[1]=0; cursor++;}if(P1==0xfd) /*按下‘右’鍵,將光標右移 */{ K[2]=1;}if(K[2]==1&&P1!=0xfd){K[2]=0; cursor--;}if(cursor<1) { cursor=3;} if(cursor>3) { cursor=1;}if(P1==0xfb) /*按下‘上’鍵,將數字加一 */{ K[3]=1;}if(K[3]==1&&P1!=0xfb){ K[3]=0; switch(cursor){case 1:sec++;break;case 2:min++;break;case 3:hour++;break;default:break;}} if(P1==0xf7) /*按下‘下’鍵,將數字減一 */{ K[4]=1;}if(K[4]==1&&P1!=0xf7){ K[4]=0; switch(cursor){case 1:sec--;break;case 2:min--;break;case 3:hour--;break;default:break;}}if(sec>59) {sec=0; } /*對時,分,秒范圍進行限制 */if(sec<0) {sec=59;}if(min>59) {min=0; }if(min<0) {min=59;} if(hour>23){hour=0; }if(hour<0) {hour=23;} }return; //如果檢測到K8按下,則跳出循環,返回主函數 } /*開機動畫子程序*/ void motos() {int mot=0;char m;char motobit[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};char motoduan[8]={0xcf,0xa4,0xc0,0xa4,0x8e,0xc7,0xbf,0xbf}; /*編碼顯示“--LF2021” */while(mot<1800){for(m=0;m<8;m++){ P2=motobit[m]; //數碼管輸出選位,從第0位開始//P0=motoduan[m]; //輸出段,輸出要顯示的數字//Delay1ms(); //延時1ms,防止數碼管串碼P0=codeduan[12];mot++;}}fm=0;Delay50ms();fm=1;Delay50ms();fm=0;Delay50ms();fm=1;return; } /*主程序,包含數碼管顯示以及計時*/ void main(){ int num=0,ks=0;char k,temp[8],moto=1;plays=0; /*打開鎖存器74HC373使能端 */motos(); /*調用開機動畫 */temp[2]=11;temp[5]=11;while(1){for(k=0;k<8;k++){ temp[0]=sec%10; //求余計算秒個位temp[1]=sec/10; //求商計算秒十位temp[3]=min%10; //求余計算分個位temp[4]=min/10; //求商計算分十位temp[6]=hour%10; //求余計算時個位temp[7]=hour/10; //求商計算時十位P2=codebite[k]; //數碼管輸出選位,從第0位開始//P0=codeduan[temp[k]]; //輸出段,輸出要顯示的數字//num++;Delay1ms(); //延時1ms,防止數碼管串碼P0=codeduan[12];if(P1==0xdf) //每次循環判斷是否按下K1鍵{ if(num%10==0&&P1==0xdf) //每10次循環,10ms,判斷K1是否仍然按下{ ks++; //如果每10次循環K1均按下,ks則自加一次if(ks==300) //如果KS記到300,表明k1已經連續按下4s,則進入時間調整模式,并將Ks清零{ks=0;adjust();} } //如果K1仍然按下,則將KS+1}else{ks=0;} //如果K1不再按下,則清零ks if(num==865) //經過與電腦時鐘對比,找到最合適的值,以下為計時程序{sec++;num=0; if (sec==60){sec=0;min++;if (min==60){min=0;hour++;if (hour==24){hour=0;}}} }}} }程序燒錄:
由于單片機不能直接運行C語言或者匯編語言程序,必須將程序編譯成hex文件才能寫入單片機。寫好程序后,若是首次編譯,通常不會自動生成hex文件,需要進行如下設置:點擊圖中1處按鈕“Option for Target”,在彈出的窗口中點擊“Output”,然后勾選“Create HEX file”。點擊確定后,點擊序號4處的編譯按鈕,即可編譯程序。
如果編譯無誤,則會顯示0錯誤,0警告。并提示‘creating hex file from“#工程名#”’,說明HEX文件已經創建成功。
篇幅有限,這里暫不展示proteus仿真過程,可自行按照原理圖構建電路并驗證程序。
這里直接將程序寫入單片機產品中。
我們需要用到軟件STC—IPS,這是專門用于STC系列單片機的程序燒錄軟件,也附帶一些輔助功能,比如定時器函數、上文提到的延時函數自動生成。下圖是其窗口界面。
燒錄之前,我們需要使用USB-TTL將電腦與單片機連接。連接方式如下圖所示。
我們先要選擇對應的單片機型號,連接單片機之后,若提示“串口打開失敗”,則點擊“掃描”,電腦會自動找到對應的串口。
接著,我們點擊“打開程序文件”,選擇剛才生成的hex文件,然后點擊“下載/編程”即可將程序下載到單片機。若點擊下載之后無反應,則關閉單片機電源重新打開,程序便可寫入單片機。
這樣,整個作品就算完成了。以下是其寫入程序后的成品圖。
總結:
?功耗計算(暫時找不到標準的5V、3V電源):
充電寶供電:電壓5.15V,電流30~40mA,功耗5.15X(30~40)=154.5mW~206mW;
三節鎳氫電池:電壓3.91V,電流20mA左右,功耗3.91X20=78.2mW。
總的來說,功耗還是偏高,經過測試,主要的功率都消耗在數碼管。單片機的功耗不超過10mW,所以待機時將數碼管關閉能有效減小功耗。
?誤差問題:本時鐘經過實測,還是有可見的誤差。
可調的誤差:運行程序需要占用很多機器時間,總時間=延時函數的時間+其他程序執行時間。而其他程序執行時間是很難計算的,只能經過對比調試來壓縮延時函數的時間。
欲盡可能減小誤差,需要與標準時鐘(電腦或者手機的網絡時間)進行對比,計算出誤差,然后調節延時函數的時間。
比如:我們延時函數剛開始設置為1000ms,經過與標準時間對比1小時發現,我的時鐘慢了1S,說明我時鐘的誤差為1/3600=0.0002778s=0.2778ms=277.8us(為了更精確計算出誤差,我們可以提高對比時間,時間越長,誤差越好計算)。這樣,我們就可以把延時函數的時間減小278us,那延時函數就要設置為1000000-278us=999722us.為了調節的方便,我們可以使用兩級級延時,一級延時函數以ms為單位,二級延時函數以us為單位,這樣就很方便調試。
不可調的誤差是晶振的溫漂問題,晶振的震蕩頻率是按照25℃環境制作的,如果溫度偏大或者偏小,其震蕩頻率都會有略微變化,進而影響CPU執行速度,造成走時不準。
更為先進的辦法是使用wifi模塊esp8266從網絡獲取時間,再將時間送給單片機,這樣,走時不準的問題就能得到徹底的解決。還能使用LCD1602或者LCD12864作為顯示器,這樣可以顯示更多的內容,就可以加入更多的花樣(以后會專門介紹這兩款顯示器)。
關于esp8266的用法,稍微復雜些,以后再做介紹。
本文僅供參考,如有不足,還請指出。
總結
以上是生活随笔為你收集整理的51单片机初学3-从零开始制作一款电子时钟的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Windows】多显示器亮度调节工具
- 下一篇: Skyline软件二次开发初级——11如