第89節(jié):用單片機內部定時器做一個時鐘
開場白:
很多網友建議,為了方便初學者學習編程思路,我應該用單片機定時器做一個時鐘程序供大家參考學習。其實我前面第48節(jié)就已經用ds1302做了一個可以顯示和更高時間的時鐘,這一節(jié)只要在第48節(jié)的源代碼基礎上,大的框架不用動,只需要把ds1302產生的時間改成用定時中斷產生的時間就可以了,改動的地方非常小。但是為了讓時間的精度更高,最后必須跟標準時間進行校驗,來修正系統(tǒng)中一秒鐘需要多個定時中斷的誤差,這個誤差決定了系統(tǒng)的時間精度,其實這個校驗方法我在前面很多章節(jié)上跟大家介紹過了:
第一步:在程序代碼上先寫入1秒鐘大概需要200個定時中斷。
第二步:把程序燒錄進單片機后,上電開始測試,手上同步打開手機里的秒表,當手機的標準時間跑了780秒(這個標準時間跑得越長校驗精度越高),而此時單片機僅僅跑了1632秒。那么最終得出1秒鐘需要的定時中斷次數是:const_time_1s=(200*1632)/780=418。
第三步:如果發(fā)現時鐘還是不太準,可以繼續(xù)返回第一步根據最新1秒鐘的時間是418次,多校驗幾次,來不斷調整const_time_1s的數值,直到找到相對精度的時間為止。
本系統(tǒng)僅供學習,精度不可能做得很好,因為影響時間精度的因素還有定時中斷的重裝值,定時中斷里面的代碼盡量少,以及晶振等不好控制的因素。所以鴻哥一直不推薦在實際項目中用單片機的內部定時器做實時時鐘,因為精度有限。真正想要準確的時鐘時間,還是強烈建議大家用外部專用的時鐘芯片或者用CPLD/FPGA來做。
具體內容,請看源代碼講解。
(1硬件平臺.
基于朱兆祺51單片機學習板。
(2)實現功能:
本程序有2兩個窗口。
第1個窗口顯示日期。顯示格式“年-月-日”。注意中間有“-”分開。
第2個窗口顯示時間。顯示格式“時 分 秒”。注意中間沒“-”,只有空格分開。
系統(tǒng)上電后,默認顯示第2個窗口,實時顯示動態(tài)的“時 分 秒”時間。此時按下S13按鍵不松手就會切換到顯示日期的第1個窗口。松手后自動切換回第2個顯示動態(tài)時間的窗口。
需要更改時間的時候,長按S9按鍵不松手超過3秒后,系統(tǒng)將進入修改時間的狀態(tài),切換到第1個日期窗口,并且顯示“年”的兩位數碼管會閃爍,此時可以按S1或者S5加減按鍵修改年的參數,修改完年后,繼續(xù)短按S9按鍵,會切換到“月”的參數閃爍狀態(tài),只要依次不斷按下S9按鍵,就會依次切換年,月,日,時,分,秒的參數閃爍狀態(tài),最后修改完秒的參數后,系統(tǒng)會自動把我們修改設置的日期時間一次性更改到定時中斷函數內部的時間變量,達到修改日期時間的目的。
S13是電平變化按鍵,用來切換窗口的,專門用來查看當前日期。按下S13按鍵時顯示日期窗口,松手后返回到顯示實時時間的窗口。
[size=10.5000pt](3)源代碼講解如下:
- #include "REG52.H"
- #define const_dpy_time_half200//數碼管閃爍時間的半值
- #define const_dpy_time_all 400//數碼管閃爍時間的全值 一定要比const_dpy_time_half 大
- #define const_voice_short40 //蜂鳴器短叫的持續(xù)時間
- #define const_key_time120 //按鍵去抖動延時的時間
- #define const_key_time220 //按鍵去抖動延時的時間
- #define const_key_time320 //按鍵去抖動延時的時間
- #define const_key_time420 //按鍵去抖動延時的時間
- #define const_key_time171200//長按超過3秒的時間
- /* 注釋一:
- * const_timer_1s這個是產生多少次定時中斷才算1秒鐘的標準。這個標準決定了時鐘的精度。這個標準最后是需要校驗的。
- * 那么是如何檢驗的呢?根據我們前面介紹的校驗時間方法:
- * 步驟:
- * 第一步:在程序代碼上先寫入1秒鐘大概需要200個定時中斷。
- * 第二步:把程序燒錄進單片機后,上電開始測試,手上同步打開手機里的秒表,當手機的標準時間跑了780秒(這個標準時間跑得越長校驗精度越高),
- * 而此時單片機僅僅跑了1632秒。那么最終得出1秒鐘需要的定時中斷次數是:const_time_1s=(200*1632)/780=418。
- * 第三步:如果發(fā)現時鐘還是不太準,可以繼續(xù)返回第一步根據最新1秒鐘的時間是418次,多校驗幾次。本系統(tǒng)僅供學習,精度不可能做得很好,因為
- * 影響時間精度的因素還有定時中斷的重裝值,定時中斷里面的代碼盡量少,以及晶振等不好控制的因素。所以鴻哥一直不推薦在實際項目中
- * 用單片機的內部定時器做實時時鐘,因為精度有限。真正想要準確的時鐘時間,還是強烈建議大家用外部專用的時鐘芯片或者用CPLD/FPGA來做。
- */
- //#define const_timer_1s200 //第一次假設大概1秒的時間需要200個定時中斷
- #define const_timer_1s418//第二次校驗后,最終選定大概1秒的時間需要418個定時中斷。如果發(fā)現時間還是不準,可以在此基礎上繼續(xù)校驗來調整此數據。
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- //驅動數碼管的74HC595
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(void); //顯示數碼管字模的驅動函數
- void display_service(void); //顯示的窗口菜單服務程序
- //驅動LED的74HC595
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
- void T0_time(void);//定時中斷函數
- void key_service(void); //按鍵服務的應用程序
- void key_scan(void);//按鍵掃描函數 放在定時中斷里
- void timer_sampling(void); //定時器采樣程序,內部每秒鐘采集更新一次
- unsigned char get_date(unsigned char ucYearTemp,unsigned char ucMonthTemp);//獲取當前月份的最大天數
- //日調整 每個月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
- unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp); //日調整
- sbit key_sr1=P0^0; //對應朱兆祺學習板的S1鍵
- sbit key_sr2=P0^1; //對應朱兆祺學習板的S5鍵
- sbit key_sr3=P0^2; //對應朱兆祺學習板的S9鍵
- sbit key_sr4=P0^3; //對應朱兆祺學習板的S13鍵
- sbit key_gnd_dr=P0^4; //模擬獨立按鍵的地GND,因此必須一直輸出低電平
- sbit beep_dr=P2^7; //蜂鳴器的驅動IO口
- sbit dig_hc595_sh_dr=P2^0; //數碼管的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED燈的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- unsigned char ucKeySec=0; //被觸發(fā)的按鍵編號
- unsigned intuiKeyTimeCnt1=0; //按鍵去抖動延時計數器
- unsigned char ucKeyLock1=0; //按鍵觸發(fā)后自鎖的變量標志
- unsigned intuiKeyTimeCnt2=0; //按鍵去抖動延時計數器
- unsigned char ucKeyLock2=0; //按鍵觸發(fā)后自鎖的變量標志
- unsigned intuiKeyTimeCnt3=0; //按鍵去抖動延時計數器
- unsigned char ucKeyLock3=0; //按鍵觸發(fā)后自鎖的變量標志
- unsigned int uiKey4Cnt1=0;//在軟件濾波中,用到的變量
- unsigned int uiKey4Cnt2=0;
- unsigned char ucKey4Sr=1;//實時反映按鍵的電平狀態(tài)
- unsigned char ucKey4SrRecord=0; //記錄上一次按鍵的電平狀態(tài)
- unsigned intuiVoiceCnt=0;//蜂鳴器鳴叫的持續(xù)時間計數器
- unsigned charucVoiceLock=0;//蜂鳴器鳴叫的原子鎖
- unsigned char ucDigShow8;//第8位數碼管要顯示的內容
- unsigned char ucDigShow7;//第7位數碼管要顯示的內容
- unsigned char ucDigShow6;//第6位數碼管要顯示的內容
- unsigned char ucDigShow5;//第5位數碼管要顯示的內容
- unsigned char ucDigShow4;//第4位數碼管要顯示的內容
- unsigned char ucDigShow3;//第3位數碼管要顯示的內容
- unsigned char ucDigShow2;//第2位數碼管要顯示的內容
- unsigned char ucDigShow1;//第1位數碼管要顯示的內容
- unsigned char ucDigDot8;//數碼管8的小數點是否顯示的標志
- unsigned char ucDigDot7;//數碼管7的小數點是否顯示的標志
- unsigned char ucDigDot6;//數碼管6的小數點是否顯示的標志
- unsigned char ucDigDot5;//數碼管5的小數點是否顯示的標志
- unsigned char ucDigDot4;//數碼管4的小數點是否顯示的標志
- unsigned char ucDigDot3;//數碼管3的小數點是否顯示的標志
- unsigned char ucDigDot2;//數碼管2的小數點是否顯示的標志
- unsigned char ucDigDot1;//數碼管1的小數點是否顯示的標志
- unsigned char ucDigShowTemp=0; //臨時中間變量
- unsigned char ucDisplayDriveStep=1;//動態(tài)掃描數碼管的步驟變量
- unsigned char ucWd=2;//本程序的核心變量,窗口顯示變量。類似于一級菜單的變量。代表顯示不同的窗口。
- unsigned char ucPart=0;//本程序的核心變量,局部顯示變量。類似于二級菜單的變量。代表顯示不同的局部。
- unsigned char ucWd1Update=0; //窗口1更新顯示標志
- unsigned char ucWd2Update=1; //窗口2更新顯示標志
- unsigned char ucWd1Part1Update=0;//在窗口1中,局部1的更新顯示標志
- unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新顯示標志
- unsigned char ucWd1Part3Update=0; //在窗口1中,局部3的更新顯示標志
- unsigned char ucWd2Part1Update=0;//在窗口2中,局部1的更新顯示標志
- unsigned char ucWd2Part2Update=0; //在窗口2中,局部2的更新顯示標志
- unsigned char ucWd2Part3Update=0; //在窗口2中,局部3的更新顯示標志
- unsigned charucYear=15; //用來顯示和設置的時間變量
- unsigned charucMonth=1;
- unsigned charucDate=1;
- unsigned charucHour=12;
- unsigned charucMinute=0;
- unsigned charucSecond=0;
- unsigned int uiTimerCnt=0; //計時器的時基
- unsigned charucTimerYear=15; //在定時器內部時基產生的時間變量
- unsigned charucTimerMonth=1;
- unsigned charucTimerDate=1;
- unsigned charucTimerHour=12;
- unsigned charucTimerMinute=0;
- unsigned charucTimerSecond=0;
- unsigned charucTimerDateMax=31; //當前月份的最大天數
- unsigned charucTimerUpdate=0; //定時器每1秒鐘所產生的標志
- unsigned charucTimerStart=1;//是否打開定時器內部時間的標志,在本程序相當于原子鎖的作用。
- unsigned char ucTemp1=0;//中間過渡變量
- unsigned char ucTemp2=0;//中間過渡變量
- unsigned char ucTemp4=0;//中間過渡變量
- unsigned char ucTemp5=0;//中間過渡變量
- unsigned char ucTemp7=0;//中間過渡變量
- unsigned char ucTemp8=0;//中間過渡變量
- unsigned char ucDelayTimerLock=0; //原子鎖
- unsigned intuiDelayTimer=0;
- unsigned char ucDpyTimeLock=0; //原子鎖
- unsigned intuiDpyTimeCnt=0;//數碼管的閃爍計時器,放在定時中斷里不斷累加
- //根據原理圖得出的共陰數碼管字模表
- code unsigned char dig_table[]=
- {
- 0x3f,//0 序號0
- 0x06,//1 序號1
- 0x5b,//2 序號2
- 0x4f,//3 序號3
- 0x66,//4 序號4
- 0x6d,//5 序號5
- 0x7d,//6 序號6
- 0x07,//7 序號7
- 0x7f,//8 序號8
- 0x6f,//9 序號9
- 0x00,//無 序號10
- 0x40,//- 序號11
- 0x73,//P 序號12
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- key_service(); //按鍵服務的應用程序
- timer_sampling(); //定時器采樣程序,內部每秒鐘采集更新一次
- display_service(); //顯示的窗口菜單服務程序
- }
- }
- /* 注釋二:
- * 系統(tǒng)不用時時刻刻采集定時器的內部數據,每隔1秒鐘的時間更新采集一次就可以了。
- * 這個1秒鐘的時間是根據定時器內部ucTimerUpdate變量來判斷。
- */
- void timer_sampling(void) //采樣定時器的程序,內部每秒鐘采集更新一次
- {
- if(ucPart==0)//當系統(tǒng)不是處于設置日期和時間的情況下
- {
- if(ucTimerUpdate==1)//每隔1秒鐘時間就更新采集一次定時器的時間數據
- {
- ucTimerUpdate=0;//及時清零,避免一直更新。
- ucYear=ucTimerYear; //讀取定時器內部的年
- ucMonth=ucTimerMonth; //讀取定時器內部的月
- ucDate=ucTimerDate;//讀取定時器內部的日
- ucHour=ucTimerHour; //讀取定時器內部的時
- ucMinute=ucTimerMinute;//讀取定時器內部的分
- ucSecond=ucTimerSecond;//讀取定時器內部的秒
- ucWd2Update=1; //窗口2更新顯示時間
- }
- }
- }
- /* 注釋三:
- * 根據年份和月份來獲取當前這個月的最大天數。每個月份的天數最大取值不同,有的最大28日,
- * 有的最大29日,有的最大30,有的最大31。
- */
- unsigned char get_date(unsigned char ucYearTemp,unsigned char ucMonthTemp)
- {
- unsigned char ucDayResult;
- unsigned int uiYearTemp;
- unsigned int uiYearYu;
- ucDayResult=31; //默認最大是31天,以下根據不同的年份和月份來決定是否需要修正這個值
- switch(ucMonthTemp)//根據不同的月份來獲取當前月份天數的最大值
- {
- case 2://二月份要計算是否是閏年
- uiYearTemp=2000+ucYearTemp;
- uiYearYu=uiYearTemp%4;
- if(uiYearYu==0) //閏年
- {
- ucDayResult=29;
- }
- else
- {
- ucDayResult=28;
- }
- break;
- case 4:
- case 6:
- case 9:
- case 11:
- ucDayResult=30;
- break;
- }
- return ucDayResult;
- }
- //日調整 每個月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
- unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp) //日調整
- {
- unsigned char ucDayResult;
- unsigned int uiYearTemp;
- unsigned int uiYearYu;
- ucDayResult=ucDateTemp;
- switch(ucMonthTemp)//根據不同的月份來修正不同的日最大值
- {
- case 2://二月份要計算是否是閏年
- uiYearTemp=2000+ucYearTemp;
- uiYearYu=uiYearTemp%4;
- if(uiYearYu==0) //閏年
- {
- if(ucDayResult>29)
- {
- ucDayResult=29;
- }
- }
- else
- {
- if(ucDayResult>28)
- {
- ucDayResult=28;
- }
- }
- break;
- case 4:
- case 6:
- case 9:
- case 11:
- if(ucDayResult>30)
- {
- ucDayResult=30;
- }
- break;
- }
- return ucDayResult;
- }
- void display_service(void) //顯示的窗口菜單服務程序
- {
- switch(ucWd)//本程序的核心變量,窗口顯示變量。類似于一級菜單的變量。代表顯示不同的窗口。
- {
- case 1: //顯示日期窗口的數據數據格式 NN-YY-RR 年-月-日
- if(ucWd1Update==1)//窗口1要全部更新顯示
- {
- ucWd1Update=0;//及時清零標志,避免一直進來掃描
- ucDigShow6=11;//顯示一杠"-"
- ucDigShow3=11;//顯示一杠"-"
- ucWd1Part1Update=1;//局部年更新顯示
- ucWd1Part2Update=1;//局部月更新顯示
- ucWd1Part3Update=1;//局部日更新顯示
- }
- if(ucWd1Part1Update==1)//局部年更新顯示
- {
- ucWd1Part1Update=0;
- ucTemp8=ucYear/10;//年
- ucTemp7=ucYear%10;
- ucDigShow8=ucTemp8; //數碼管顯示實際內容
- ucDigShow7=ucTemp7;
- }
- if(ucWd1Part2Update==1)//局部月更新顯示
- {
- ucWd1Part2Update=0;
- ucTemp5=ucMonth/10;//月
- ucTemp4=ucMonth%10;
- ucDigShow5=ucTemp5; //數碼管顯示實際內容
- ucDigShow4=ucTemp4;
- }
- if(ucWd1Part3Update==1) //局部日更新顯示
- {
- ucWd1Part3Update=0;
- ucTemp2=ucDate/10;//日
- ucTemp1=ucDate%10;
- ucDigShow2=ucTemp2; //數碼管顯示實際內容
- ucDigShow1=ucTemp1;
- }
- //數碼管閃爍
- switch(ucPart)//相當于二級菜單,根據局部變量的值,使對應的參數產生閃爍的動態(tài)效果。
- {
- case 0://都不閃爍
- break;
- case 1://年參數閃爍
- if(uiDpyTimeCnt==const_dpy_time_half)
- {
- ucDigShow8=ucTemp8; //數碼管顯示實際內容
- ucDigShow7=ucTemp7;
- }
- else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
- {
- ucDpyTimeLock=1; //原子鎖加鎖
- uiDpyTimeCnt=0; //及時把閃爍記時器清零
- ucDpyTimeLock=0;//原子鎖解鎖
- ucDigShow8=10; //數碼管顯示空,什么都不顯示
- ucDigShow7=10;
- }
- break;
- case 2: //月參數閃爍
- if(uiDpyTimeCnt==const_dpy_time_half)
- {
- ucDigShow5=ucTemp5; //數碼管顯示實際內容
- ucDigShow4=ucTemp4;
- }
- else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
- {
- ucDpyTimeLock=1; //原子鎖加鎖
- uiDpyTimeCnt=0; //及時把閃爍記時器清零
- ucDpyTimeLock=0;//原子鎖解鎖
- ucDigShow5=10; //數碼管顯示空,什么都不顯示
- ucDigShow4=10;
- }
- break;
- case 3: //日參數閃爍
- if(uiDpyTimeCnt==const_dpy_time_half)
- {
- ucDigShow2=ucTemp2; //數碼管顯示實際內容
- ucDigShow1=ucTemp1;
- }
- else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
- {
- ucDpyTimeLock=1; //原子鎖加鎖
- uiDpyTimeCnt=0; //及時把閃爍記時器清零
- ucDpyTimeLock=0;//原子鎖解鎖
- ucDigShow2=10; //數碼管顯示空,什么都不顯示
- ucDigShow1=10;
- }
- break;
- }
- break;
- case 2: //顯示時間窗口的數據數據格式 SS FF MM 時 分 秒
- if(ucWd2Update==1)//窗口2要全部更新顯示
- {
- ucWd2Update=0;//及時清零標志,避免一直進來掃描
- ucDigShow6=10;//顯示空
- ucDigShow3=10;//顯示空
- ucWd2Part3Update=1;//局部時更新顯示
- ucWd2Part2Update=1;//局部分更新顯示
- ucWd2Part1Update=1;//局部秒更新顯示
- }
- if(ucWd2Part1Update==1)//局部時更新顯示
- {
- ucWd2Part1Update=0;
- ucTemp8=ucHour/10;//時
- ucTemp7=ucHour%10;
- ucDigShow8=ucTemp8; //數碼管顯示實際內容
- ucDigShow7=ucTemp7;
- }
- if(ucWd2Part2Update==1)//局部分更新顯示
- {
- ucWd2Part2Update=0;
- ucTemp5=ucMinute/10;//分
- ucTemp4=ucMinute%10;
- ucDigShow5=ucTemp5; //數碼管顯示實際內容
- ucDigShow4=ucTemp4;
- }
- if(ucWd2Part3Update==1) //局部秒更新顯示
- {
- ucWd2Part3Update=0;
- ucTemp2=ucSecond/10;//秒
- ucTemp1=ucSecond%10;
- ucDigShow2=ucTemp2; //數碼管顯示實際內容
- ucDigShow1=ucTemp1;
- }
- //數碼管閃爍
- switch(ucPart)//相當于二級菜單,根據局部變量的值,使對應的參數產生閃爍的動態(tài)效果。
- {
- case 0://都不閃爍
- break;
- case 1://時參數閃爍
- if(uiDpyTimeCnt==const_dpy_time_half)
- {
- ucDigShow8=ucTemp8; //數碼管顯示實際內容
- ucDigShow7=ucTemp7;
- }
- else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
- {
- ucDpyTimeLock=1; //原子鎖加鎖
- uiDpyTimeCnt=0; //及時把閃爍記時器清零
- ucDpyTimeLock=0;//原子鎖解鎖
- ucDigShow8=10; //數碼管顯示空,什么都不顯示
- ucDigShow7=10;
- }
- break;
- case 2: //分參數閃爍
- if(uiDpyTimeCnt==const_dpy_time_half)
- {
- ucDigShow5=ucTemp5; //數碼管顯示實際內容
- ucDigShow4=ucTemp4;
- }
- else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
- {
- ucDpyTimeLock=1; //原子鎖加鎖
- uiDpyTimeCnt=0; //及時把閃爍記時器清零
- ucDpyTimeLock=0;//原子鎖解鎖
- ucDigShow5=10; //數碼管顯示空,什么都不顯示
- ucDigShow4=10;
- }
- break;
- case 3: //秒參數閃爍
- if(uiDpyTimeCnt==const_dpy_time_half)
- {
- ucDigShow2=ucTemp2; //數碼管顯示實際內容
- ucDigShow1=ucTemp1;
- }
- else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
- {
- ucDpyTimeLock=1; //原子鎖加鎖
- uiDpyTimeCnt=0; //及時把閃爍記時器清零
- ucDpyTimeLock=0;//原子鎖解鎖
- ucDigShow2=10; //數碼管顯示空,什么都不顯示
- ucDigShow1=10;
- }
- break;
- }
- break;
- }
- }
- void key_scan(void)//按鍵掃描函數 放在定時中斷里
- {
- if(key_sr1==1)//IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標志位
- {
- ucKeyLock1=0; //按鍵自鎖標志清零
- uiKeyTimeCnt1=0;//按鍵去抖動延時計數器清零,此行非常巧妙,是我實戰(zhàn)中摸索出來的。
- }
- else if(ucKeyLock1==0)//有按鍵按下,且是第一次被按下
- {
- uiKeyTimeCnt1++; //累加定時中斷次數
- if(uiKeyTimeCnt1>const_key_time1)
- {
- uiKeyTimeCnt1=0;
- ucKeyLock1=1;//自鎖按鍵置位,避免一直觸發(fā)
- ucKeySec=1; //觸發(fā)1號鍵
- }
- }
- if(key_sr2==1)//IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標志位
- {
- ucKeyLock2=0; //按鍵自鎖標志清零
- uiKeyTimeCnt2=0;//按鍵去抖動延時計數器清零,此行非常巧妙,是我實戰(zhàn)中摸索出來的。
- }
- else if(ucKeyLock2==0)//有按鍵按下,且是第一次被按下
- {
- uiKeyTimeCnt2++; //累加定時中斷次數
- if(uiKeyTimeCnt2>const_key_time2)
- {
- uiKeyTimeCnt2=0;
- ucKeyLock2=1;//自鎖按鍵置位,避免一直觸發(fā)
- ucKeySec=2; //觸發(fā)2號鍵
- }
- }
- /* 注釋四:
- * 注意,此處把一個按鍵的短按和長按的功能都實現了。
- */
- if(key_sr3==1)//IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標志位
- {
- ucKeyLock3=0; //按鍵自鎖標志清零
- uiKeyTimeCnt3=0;//按鍵去抖動延時計數器清零,此行非常巧妙,是我實戰(zhàn)中摸索出來的。
- }
- else if(ucKeyLock3==0)//有按鍵按下,且是第一次被按下
- {
- uiKeyTimeCnt3++; //累加定時中斷次數
- if(uiKeyTimeCnt3>const_key_time3)
- {
- uiKeyTimeCnt3=0;
- ucKeyLock3=1;//自鎖按鍵置位,避免一直觸發(fā)
- ucKeySec=3; //短按觸發(fā)3號鍵
- }
- }
- else if(uiKeyTimeCnt3
- {
- uiKeyTimeCnt3++; //累加定時中斷次數
- if(uiKeyTimeCnt3==const_key_time17)//等于3秒鐘,觸發(fā)17號長按按鍵
- {
- ucKeySec=17; //長按3秒觸發(fā)17號鍵
- }
- }
- /* 注釋五:
- * 注意,此處是電平按鍵的濾波抗干擾處理
- */
- if(key_sr4==1)//對應朱兆祺學習板的S13鍵
- {
- uiKey4Cnt1=0; //在軟件濾波中,非常關鍵的語句?。?!類似按鍵去抖動程序的及時清零
- uiKey4Cnt2++; //類似獨立按鍵去抖動的軟件抗干擾處理
- if(uiKey4Cnt2>const_key_time4)
- {
- uiKey4Cnt2=0;
- ucKey4Sr=1;//實時反映按鍵松手時的電平狀態(tài)
- }
- }
- else
- {
- uiKey4Cnt2=0; //在軟件濾波中,非常關鍵的語句?。?!類似按鍵去抖動程序的及時清零
- uiKey4Cnt1++;
- if(uiKey4Cnt1>const_key_time4)
- {
- uiKey4Cnt1=0;
- ucKey4Sr=0;//實時反映按鍵按下時的電平狀態(tài)
- }
- }
- }
- void key_service(void) //按鍵服務的應用程序
- {
- switch(ucKeySec) //按鍵服務狀態(tài)切換
- {
- case 1:// 加按鍵 對應朱兆祺學習板的S1鍵
- switch(ucWd)//在不同的窗口下,設置不同的參數
- {
- case 1:
- switch(ucPart) //在不同的局部變量下,相當于二級菜單
- {
- case 1://年
- ucYear++;
- if(ucYear>99)
- {
- ucYear=99;
- }
- ucWd1Part1Update=1;//更新顯示
- break;
- case 2: //月
- ucMonth++;
- if(ucMonth>12)
- {
- ucMonth=12;
- }
- ucWd1Part2Update=1;//更新顯示
- break;
- case 3: //日
- ucDate++;
- if(ucDate>31)
- {
- ucDate=31;
- }
- ucWd1Part3Update=1;//更新顯示
- break;
- }
- break;
- case 2:
- switch(ucPart) //在不同的局部變量下,相當于二級菜單
- {
- case 1://時
- ucHour++;
- if(ucHour>23)
- {
- ucHour=23;
- }
- ucWd2Part1Update=1;//更新顯示
- break;
- case 2: //分
- ucMinute++;
- if(ucMinute>59)
- {
- ucMinute=59;
- }
- ucWd2Part2Update=1;//更新顯示
- break;
- case 3: //秒
- ucSecond++;
- if(ucSecond>59)
- {
- ucSecond=59;
- }
- ucWd2Part3Update=1;//更新顯示
- break;
- }
- break;
- }
- ucVoiceLock=1;//原子鎖加鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。
- ucVoiceLock=0;//原子鎖解鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
- ucKeySec=0;//響應按鍵服務處理程序后,按鍵編號清零,避免一致觸發(fā)
- break;
- case 2:// 減按鍵 對應朱兆祺學習板的S5鍵
- switch(ucWd)//在不同的窗口下,設置不同的參數
- {
- case 1:
- switch(ucPart) //在不同的局部變量下,相當于二級菜單
- {
- case 1://年
- ucYear--;
- if(ucYear>99)
- {
- ucYear=0;
- }
- ucWd1Part1Update=1;//更新顯示
- break;
- case 2: //月
- ucMonth--;
- if(ucMonth<1)
- {
- ucMonth=1;
- }
- ucWd1Part2Update=1;//更新顯示
- break;
- case 3: //日
- ucDate--;
- if(ucDate<1)
- {
- ucDate=1;
- }
- ucWd1Part3Update=1;//更新顯示
- break;
- }
- break;
- case 2:
- switch(ucPart) //在不同的局部變量下,相當于二級菜單
- {
- case 1://時
- ucHour--;
- if(ucHour>23)
- {
- ucHour=0;
- }
- ucWd2Part1Update=1;//更新顯示
- break;
- case 2: //分
- ucMinute--;
- if(ucMinute>59)
- {
- ucMinute=0;
- }
- ucWd2Part2Update=1;//更新顯示
- break;
- case 3: //秒
- ucSecond--;
- if(ucSecond>59)
- {
- ucSecond=0;
- }
- ucWd2Part3Update=1;//更新顯示
- break;
- }
- break;
- }
- ucVoiceLock=1;//原子鎖加鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。
- ucVoiceLock=0;//原子鎖解鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
- ucKeySec=0;//響應按鍵服務處理程序后,按鍵編號清零,避免一致觸發(fā)
- break;
- case 3://短按設置按鍵 對應朱兆祺學習板的S9鍵
- switch(ucWd)//在不同的窗口下,設置不同的參數
- {
- case 1:
- ucPart++;
- if(ucPart>3)
- {
- ucPart=1;
- ucWd=2; //切換到第二個窗口,設置時分秒
- ucWd2Update=1;//窗口2更新顯示
- }
- ucWd1Update=1;//窗口1更新顯示
- break;
- case 2:
- if(ucPart>0) //在窗口2的時候,要第一次激活設置時間,必須是長按3秒才可以,這里短按激活不了第一次
- {
- ucPart++;
- if(ucPart>3)//設置時間結束
- {
- ucPart=0;
- /* 注釋六:
- * 每個月份的天數最大值是不一樣的,在寫入ds1302時鐘芯片內部數據前,應該做一次調整。
- * 有的月份最大28天,有的月份最大29天,有的月份最大30天,有的月份最大31天,
- */
- ucDate=date_adjust(ucYear,ucMonth,ucDate); //日調整 避免日的數值在某個月份超范圍
- ucTimerStart=0;//關閉定時器的時間。在更改定時器內部時間數據時,先關閉它,相當于原子鎖的加鎖作用。
- ucTimerYear=ucYear;//把設置和顯示的數據更改到定時器內部的時間變量
- ucTimerMonth=ucMonth;
- ucTimerDate=ucDate;
- ucTimerHour=ucHour;
- ucTimerMinute=ucMinute;
- ucTimerSecond=ucSecond;
- ucTimerStart=1;//打開定時器的時間。在更改定時器內部時間數據后,再打開它,相當于原子鎖的解鎖作用。
- }
- ucWd2Update=1;//窗口2更新顯示
- }
- break;
- }
- ucVoiceLock=1;//原子鎖加鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。
- ucVoiceLock=0;//原子鎖解鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
- ucKeySec=0;//響應按鍵服務處理程序后,按鍵編號清零,避免一致觸發(fā)
- break;
- case 17://長按3秒設置按鍵 對應朱兆祺學習板的S9鍵
- switch(ucWd)//在不同的窗口下,設置不同的參數
- {
- case 2:
- if(ucPart==0) //處于非設置時間的狀態(tài)下,要第一次激活設置時間,必須是長按3秒才可以
- {
- ucWd=1;
- ucPart=1;//進入到設置日期的狀態(tài)下
- ucWd1Update=1;//窗口1更新顯示
- }
- break;
- }
- ucVoiceLock=1;//原子鎖加鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。
- ucVoiceLock=0;//原子鎖解鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
- ucKeySec=0;//響應按鍵服務處理程序后,按鍵編號清零,避免一致觸發(fā)
- break;
- }
- /* 注釋七:
- * 注意,此處就是第一次出現的電平按鍵程序,跟以往的下降沿按鍵不一樣。
- * ucKey4Sr是經過軟件濾波處理后,直接反應IO口電平狀態(tài)的變量.當電平發(fā)生
- * 變化時,就會切換到不同的顯示界面,這里多用了一個ucKey4SrRecord變量
- * 記錄上一次的電平狀態(tài),是為了避免一直刷新顯示。
- */
- if(ucKey4Sr!=ucKey4SrRecord)//說明S13的切換按鍵電平狀態(tài)發(fā)生變化
- {
- ucKey4SrRecord=ucKey4Sr;//及時記錄當前最新的按鍵電平狀態(tài)避免一直進來觸發(fā)
- if(ucKey4Sr==1) //松手后切換到顯示時間的窗口
- {
- ucWd=2; //顯示時分秒的窗口
- ucPart=0;//進入到非設置時間的狀態(tài)下
- ucWd2Update=1;//窗口2更新顯示
- }
- else//按下去切換到顯示日期的窗口
- {
- ucWd=1; //顯示年月日的窗口
- ucPart=0;//進入到非設置時間的狀態(tài)下
- ucWd1Update=1;//窗口1更新顯示
- }
- }
- }
- void display_drive(void)
- {
- //以下程序,如果加一些數組和移位的元素,還可以壓縮容量。但是鴻哥追求的不是容量,而是清晰的講解思路
- switch(ucDisplayDriveStep)
- {
- case 1://顯示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- if(ucDigDot1==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2://顯示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- if(ucDigDot2==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3://顯示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4://顯示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- if(ucDigDot4==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5://顯示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- if(ucDigDot5==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6://顯示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- if(ucDigDot6==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7://顯示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8://顯示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- if(ucDigDot8==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8)//掃描完8個數碼管后,重新從第一個開始掃描
- {
- ucDisplayDriveStep=1;
- }
- }
- //數碼管的74HC595驅動函數
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09;//先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01;//再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0;//ST引腳把兩個寄存器的數據更新輸出到74HC595的輸出引腳上并且鎖存起來
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干擾就增強
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED燈的74HC595驅動函數
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09;//先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01;//再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0;//ST引腳把兩個寄存器的數據更新輸出到74HC595的輸出引腳上并且鎖存起來
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干擾就增強
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void T0_time(void) interrupt 1 //定時中斷
- {
- TF0=0;//清除中斷標志
- TR0=0; //關中斷
- /* 注釋八:
- * 以下是本節(jié)內容的核心程序,是定時器內部產生的時間。const_timer_1s這個是產生多少次定時中斷才
- * 算1秒鐘的標準。這個標準決定了時鐘的精度。這個標準最后是需要校驗的。
- */
- if(ucTimerStart==1)//定時器的時間已經打開
- {
- uiTimerCnt++;//產生1秒鐘的時基
- if(uiTimerCnt>=const_timer_1s) //一秒鐘的時間到。這個const_timer_1s具體數值最后需要校驗得出。
- {
- uiTimerCnt=0; //清零為產生下一個1秒鐘準備
- ucTimerUpdate=1; //定時器每1秒鐘所產生的標志,通知主函數及時更新采集時間數據
- ucTimerSecond++; //秒時間累加1
- if(ucTimerSecond>=60)
- {
- ucTimerSecond=0;
- ucTimerMinute++; //分時間累加1
- if(ucTimerMinute>=60)
- {
- ucTimerMinute=0;
- ucTimerHour++;//小時的時間累加1,為了避免if的嵌套過多,把小時的判斷放到外面兩層的if來繼續(xù)判斷
- }
- }
- if(ucTimerHour>=24)
- {
- ucTimerHour=0;
- ucTimerDate++; //天時間累加1
- ucTimerDateMax=get_date(ucTimerYear,ucTimerMonth);//根據年和月獲取當前月份的最大天數
- if(ucTimerDate>ucTimerDateMax)//
- {
- ucTimerDate=1; //每個月都是從1號開始
- ucTimerMonth++;//月時間累加1
- if(ucTimerMonth>12)
- {
- ucTimerMonth=1; //每年從1月份開始
- ucTimerYear++; //年時間累加1
- if(ucTimerYear>99) //本系統(tǒng)的最高有效年份是2099年
- {
- ucTimerYear=99;
- }
- }
- }
- }
- }
- }
- if(ucVoiceLock==0) //原子鎖判斷
- {
- if(uiVoiceCnt!=0)
- {
- uiVoiceCnt--; //每次進入定時中斷都自減1,直到等于零為止。才停止鳴叫
- beep_dr=0;//蜂鳴器是PNP三極管控制,低電平就開始鳴叫。
- }
- else
- {
- ; //此處多加一個空指令,想維持跟if括號語句的數量對稱,都是兩條指令。不加也可以。
- beep_dr=1;//蜂鳴器是PNP三極管控制,高電平就停止鳴叫。
- }
- }
- if(ucDpyTimeLock==0) //原子鎖判斷
- {
- uiDpyTimeCnt++;//數碼管的閃爍計時器
- }
- key_scan(); //按鍵掃描函數
- display_drive();//數碼管字模的驅動函數
- TH0=0xfe; //重裝初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1;//開中斷
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i
- {
- ; //一個分號相當于執(zhí)行一條空語句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i
- {
- for(j=0;j<500;j++)//內嵌循環(huán)的空指令數量
- {
- ; //一個分號相當于執(zhí)行一條空語句
- }
- }
- }
- void initial_myself(void)//第一區(qū) 初始化單片機
- {
- key_gnd_dr=0; //模擬獨立按鍵的地GND,因此必須一直輸出低電平
- beep_dr=1; //用PNP三極管控制蜂鳴器,輸出高電平時不叫。
- hc595_drive(0x00,0x00);//關閉所有經過另外兩個74HC595驅動的LED燈
- TMOD=0x01;//設置定時器0為工作方式1
- TH0=0xfe; //重裝初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- }
- void initial_peripheral(void) //第二區(qū) 初始化外圍
- {
- ucDigDot8=0; //小數點全部不顯示
- ucDigDot7=0;
- ucDigDot6=0;
- ucDigDot5=0;
- ucDigDot4=0;
- ucDigDot3=0;
- ucDigDot2=0;
- ucDigDot1=0;
- EA=1; //開總中斷
- ET0=1; //允許定時中斷
- TR0=1; //啟動定時中斷
- }
總結陳詞:
任何一個電子產品在投入生產的時候都要考慮到生產的測試,朱兆祺51單片機學習板在生產加工后也一樣要進行測試。那么這個測試的程序如何能夠做到快速,全面,易用這三個要求呢?欲知詳情,請聽下回分解-----生產朱兆祺51學習板的從機自檢測試程序源代碼.。
評論