當前位置:首頁 > 單片機 > 單片機
[導讀]通過單片機應(yīng)用產(chǎn)品的設(shè)計與調(diào)試過程,鞏固課程所學理論知識,初步了解單片機應(yīng)用系統(tǒng)設(shè)計與調(diào)試的方法。

一、設(shè)計目的:

通過單片機應(yīng)用產(chǎn)品的設(shè)計與調(diào)試過程,鞏固課程所學理論知識,初步了解單片機應(yīng)用系統(tǒng)設(shè)計與調(diào)試的方法。

二、設(shè)計要求:

設(shè)計一個以AT89S51單片機為核心的數(shù)字電子鐘控制器,實現(xiàn)電子鐘的時間、日期交替顯示、鬧鐘功能,并可通過按鈕開關(guān)或鍵盤切換顯示內(nèi)容、調(diào)整參數(shù)、設(shè)置鬧鐘,在單片機實驗板上模擬調(diào)試實現(xiàn)控制器的功能。具體設(shè)計要求如下:

1.開機自檢,檢查相關(guān)接口及數(shù)碼管顯示器、指示燈、蜂鳴器等外設(shè)是否正常。

2.8位數(shù)碼管顯示器平常以一定的時間間隔、合適的格式顯示時間和日期信息,時間顯示時、分、秒;日期顯示年(2000~2099)、月、日;設(shè)置鬧鐘功能時顯示時、分、開/關(guān)狀態(tài)。

3.可通過按鍵設(shè)定時間、日期、鬧鐘等參數(shù)、手動切換顯示。按鍵可用獨立式按鍵或行列式鍵盤實現(xiàn)。設(shè)定參數(shù)過程有合適的方式指示當前可修改的內(nèi)容。

4.對開關(guān)量輸入進行軟件消抖動處理,參數(shù)的設(shè)定有容錯處理,如:小時不能超過23,日期中每月最大天數(shù)、閏年等。

5.用Protel設(shè)計可實現(xiàn)上述功能的控制器的原理圖(最小應(yīng)用系統(tǒng))。

擴展功能(選做):

1.可設(shè)置多次鬧鐘。

2.顯示星期功能。

3.參數(shù)設(shè)定過程中,較長時間無操作,則自動恢復為正常顯示方式。。

4.其它自選的擴展功能。

三、總體方案設(shè)計及說明

總體功能框圖:

 

硬件:

8個LED采用動態(tài)掃描以節(jié)約驅(qū)動成本;

走時采用內(nèi)部T0計時中斷;

4x4矩陣鍵盤掃描采用線反轉(zhuǎn)法,以中斷掃描計數(shù)防止抖動;

……

軟件:

采用C語言實現(xiàn)。

四、系統(tǒng)資源分配說明(接口、存儲器分配)

 

1.接口:

89S51的P1口接8個LED小燈;

89S51的P3_2接蜂鳴器(低電平鳴響);

外擴一片8255:

89S51單片機的P0口是低8位地址與數(shù)據(jù)復用的,現(xiàn)在我們用74HC373分離出地址,89S51高位地址的P2_0(A8)接8255的片選端(/CS), 低位地址Q1Q0(A1A0)與8255的A1A0連接,數(shù)據(jù)位P0_7~P0_0分別接8255的D_7~D_0。 以此得到的8255端口的地址分別為:

PA:xxxxxxx0 xxxxxx00取0x0fefc; PB:xxxxxxx0 xxxxxx01取0x0fefd;

PC:xxxxxxx0 xxxxxx10取0x0fefd; CTL:xxxxxxx0 xxxxxx11取0x0feff;

8255的PA口控制LED數(shù)碼管的8個顯示段;PB口分別接8個LED數(shù)碼管的共陽極;

PC口分別接4x4矩陣鍵盤的行線和列線。

2.存儲分配:

struct{ //鬧鐘時、分、秒 ,共設(shè)6個鬧鐘(初始狀態(tài)默認:00-00-F1)

uchar hour;

uchar minute;

uchar isON;

}alarm[6]={{0,0,0}};

uchar hour=12,minute=0,second=0;//時、分、秒

uchar temp_second; //用于立即切換顯示時間/日期

uint year=2011;// 年

uchar month=12;// 月

uchar day=1; // 日

uchar week=6;// 星期

uchar Mdays[]={0,31,28,31,30,31,30,31,31,30,31,30,31};//各月天數(shù)

uchar alarm_isON=1; //鬧鐘總開關(guān)

uchar alarm_station=0; //鬧鐘狀態(tài)

uchar ano; //鬧鐘號(當前時間到的鬧鐘號)

uchar start_minute;//開始響鈴的時間(也就是所定鬧鐘的時間)

uint count_ms25=0; //軟件計數(shù)器(計數(shù)40個25毫秒達1s)

uchar show_model=0; // 顯示模式:[0]切換顯示時間/日期 [1]切換顯示日期/時間

const uchar fixtime=0x00;//時間修正量

uchar key=0xff;//獲得的當前鍵值

uchar last_key=0xff; //最后一次掃描到的按鍵(非0xff)

uchar key_count=0;//掃描到同一按鍵的次數(shù)

uchar Edown=0; //鬧鐘開關(guān)鍵是否按下

uchar led_buf[8]={24,24,24,24,24,24,24,24}; //時間日期顯示緩沖區(qū)

uchar code led_table1[]={0x0c0,0x0f9,0x0a4,0x0b0, 0x99,0x92,0x82,0x0f8,0x80,0x90,

0x88,0x83,0x0C6,0x0a1,0x86,0x8e,0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,

0x08,0x03,0x46,0x21,0x06,0x0e,0x7f,0x0bf,0xff};//數(shù)碼管段碼

uchar code KBTable[] = {'1','2','3','F','4','5','6','E','7','8','9','C','0','A','B','D'};//鍵值(可有可無)

五、軟件流程圖及說明

1.流程圖:

 

2.主要程序段說明:

(1)顯示:

動態(tài)顯示:即各位數(shù)碼管輪流點亮,對于顯示器各位數(shù)碼管,每隔一段延時時間循環(huán)點亮一次。利用人的視覺暫留功能可以看到整個顯示,但須保證掃描速度足夠快,人的視覺暫留功能才可察覺不到字符閃爍。顯示器的亮度與導通電流、點亮時間及間隔時間的比例有關(guān)。調(diào)整參數(shù)可以實現(xiàn)較高穩(wěn)定度的顯示。動態(tài)顯示節(jié)省了驅(qū)動和I/O口,降低了能耗。

void LED_show(uchar buf[])

{

uchar i,num,pLED=0x80;

for(i=0;i<8;i++)

{

num=buf[i];

PA=led_table1[num]; /*送字段碼*/

PB=pLED; /*送字位碼*/

pLED>>=1; /*右移一位*/

Delay(1); /*延時*/

}

}

(2)鍵盤(本次設(shè)計對下面兩種掃描方式都進行了實現(xiàn)):

a. 行掃描法:依次從第一至最末行線上發(fā)出低電平信號, 如果該行線所連接的鍵沒有按下的話, 則列線所接的端口得到的是全“1”信號, 如果有鍵按下的話, 則得到非全“1”信號。

/*鍵盤掃描(行掃描法,延時消抖)********************************************************

uchar code KBTable[] = {

0xEE,'1',0xDE,'4',0xBE,'7',0x7E,'0',

0xED,'2',0xDD,'5',0xBD,'8',0x7D,'A',

0xEB,'3',0xDB,'6',0xBB,'9',0x7B,'B',

0xE7,'F',0xD7,'E',0xB7,'C',0x77,'D',

0x00,0xff};

uchar Get_key(void); // 獲取最終鍵值

{ uchar i;

uchar line, row, k_value;

static uchar lastkey=0xff;

CTL=0x88; //CH輸入,CL輸出 10001000

PC=PC & 0xf0; // PC0~PC3輸出0 , 輸入PC4~ PC7(默認1無鍵按下)

if ((PC & 0xf0) == 0xf0)

{

lastkey=0xff;

return 0xff; //無鍵按下

}

row = PC;

Delay(4); //延時,消除抖動

if (row != PC)

{

lastkey=0xff;

return 0xff; //判為抖動

}

line=0xFE;

for (i=0;i<4;i++)

{ PC = line; //輸出掃描信號

row=PC; //讀鍵盤口

if ((row & 0xf0) != 0xf0)

break;

line=(line<<1)+1;

}

if (i==4)

{ lastkey=0xff; return 0xff; }

k_value = (row & 0xf0) | (line & 0x0f) ;

for (i=0; i<32; i+=2)

if (k_value == KBTable[i])

break;

if(lastkey==KBTable[i+1])

return 0xff;

lastkey=KBTable[i+1];

return KBTable[i+1];

}

b.線反轉(zhuǎn)法:線反轉(zhuǎn)法也是識別閉合鍵的一種常用方法, 該法比行掃描速度快, 但在硬件上要求行線與列線外接上拉電阻。先將行線作為輸出線, 列線作為輸入線, 行線輸出全“0”信號, 讀入列線的值, 那么在閉合鍵所在的列線上的值必為0;然后從列線輸出全“0”信號,再讀取行線的輸入值,閉合鍵所在的行線值必為 0。這樣,當一個鍵被按下時, 必定可讀到一對唯一的行列值。再由這一對行列值可以求出閉合鍵所在的位置。

//一次鍵盤掃描(線反轉(zhuǎn)法,中斷掃描計數(shù)去抖)*********************************************************

uchar code KBTable[] = {'1','2','3','F','4','5','6','E','7','8','9','C','0','A','B','D'};

//key_index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

uchar key_scan(void) //返回 '0','1','2'...'E','F',0xff

{ uchar key_index,temp=0;

CTL=0x88; //CH輸入,CL輸出 10001000

PC=PC & 0xf0; //將低四位置0

if(PC!=0xF0) //判斷按鍵是否按下 如果按鈕按下 會拉低CH其中的一個端口

{

temp=PC; //讀PC口

temp=temp&0xf0; //屏蔽低四位

temp=~((temp>>4)|0xf0); // 高四位取反值 ~(1111****)

if(temp==1) // pC.4 被拉低 1110 第一行

key_index=0;

else if(temp==2) // PC.5 被拉低 1101 第二行

key_index=4;

else if(temp==4) // PC.6 被拉低 1011 第三行

key_index=8;

else if(temp==8) // PC.7 被拉低 0111 第四行

key_index=12;

else //沒有鍵按下

return 0xff;

CTL=0x81; //CL輸入,CH輸出 10000001

PC=PC & 0xf0; //CH輸出0000 初始: 00001111 若有鍵按下,根據(jù)低4位哪一位被拉低確定第二維坐標

temp=PC; //讀PC口

temp=temp&0x0f;

temp=~(temp|0xf0); // 低四位取反值 ~(1111****)

if(temp==1) //PC.0 被拉低 1110 第一列

key_index+=0;

else if(temp==2) //PC.1 被拉低 1101 第二列

key_index+=1;

else if(temp==4) //PC.2 被拉低 1011 第三列

key_index+=2;

else if(temp==8) //PC.3 被拉低 0111 第四列

key_index+=3;

else //沒有鍵按下

return 0xff;

return KBTable[key_index];

}

else return 0xff;

}

c.消抖方法:

(1)硬件消抖法:就是在鍵盤中附加去抖動電路,從根上消除抖動產(chǎn)生的可能性。右圖所示電路實際上是由R-S觸發(fā)器構(gòu)成的單脈沖電路。當按鈕開關(guān)按下時Q端輸出低電平,當開關(guān)松開時Q端恢復高電平,即輸出一個負脈沖,以此消除抖動。

(2)軟件消抖法:鍵按下的時間與操作者的按鍵動作有關(guān),約為十分之幾到幾秒不等。而鍵抖動時間與按鍵的機械特性有關(guān),一般為5~10ms不等。軟件消抖法即是采用延時(一般延時10~20ms)的方法,以避開按鍵的抖動,即在按鍵已穩(wěn)定地閉合或斷開時才讀出其狀態(tài)。

(3)定時中斷掃描計數(shù)消抖法:把掃描函數(shù)放在定時中斷里調(diào)用,掃描有鍵值則保存至kast_key并計數(shù)(key_count++),下次中斷時再掃描,鍵值與上次相同則計數(shù)累加,若鍵值不同則計數(shù)從1開始,……,直到相同的鍵值被計數(shù)>=6,則認為該鍵按下。

假設(shè)1s最快可以按5下,則每下約持續(xù)200ms, 每次25ms中斷時就掃描一次鍵盤,在這8次(或更多)里若有6次以上檢測到有同一鍵則判為有效鍵。

uchar Get_key(void)//返回 '0','1','2'...'E','F',0xff

{

if(key_count>=6)

{

key_count=0;

return last_key;

}

else return 0xff;

}

(3)中斷服務(wù)子程序

/*25ms中斷子函數(shù)**********************************************************************/

void Time_over(void) interrupt 1 using 2

{

uchar key_temp;

TH0 = 0x0A6; //晶振頻率11.0592MHz下,定時25ms的計數(shù)初值

TL0 = 0x00+fixtime;//(2^16-X)*(12*1/11.0592)=25ms; -->X=42496=A600H

count_ms25++;

if(count_ms25>=40) //達到1秒

{

DateTime_change(); //走時更新時分秒等

count_ms25=0;

}

//每次25ms中斷就掃描一次按鍵

key_temp=key_scan();

if((last_key==0xff)&&(key_temp!=0xff))

{

last_key=key_temp;

key_count++;

}

else

{

if(key_temp==last_key)

{key_count++;}

else

if(key_temp!=0xff){last_key=key_temp;key_count=1;}

}

}

六、系統(tǒng)功能與操作說明;

1.系統(tǒng)功能:

時間、日期(包括星期)、鬧鐘顯示及其設(shè)置;

支持6個鬧鐘,可設(shè)置鬧鐘總開關(guān)及分開關(guān);

完善的設(shè)置容錯處理;

可自動/手動切換時間/日期顯示模式;

設(shè)置位閃爍標識;

設(shè)置過程30s無操作,自動恢復為正常顯示方式;

4x4矩陣鍵盤掃描采用線反轉(zhuǎn)法,以中斷掃描計數(shù)去抖;

開機自檢功能;

......

2.顯示格式說明:

時間顯示:12-30-02 12時-30分-02秒

日期顯示:11.01.01-6 2011年01月01日-星期六

鬧鐘設(shè)置顯示:-123456- 等待選擇要設(shè)置的鬧鐘號

06-00-E1 定時在06:00,鬧鐘1開(E開,F(xiàn)關(guān))

鬧鐘總開關(guān):時間顯示時最后一位小數(shù)點亮代表總開,否則為關(guān).

3.鍵盤操作說明:

 

(1)數(shù)字鍵“0”~“9”,用于設(shè)置時輸入時間和日期,正常工作時無效。

(2)“時間設(shè)置”、“日期設(shè)置”、“鬧鐘設(shè)置”,用于進入相應(yīng)功能的設(shè)置狀態(tài)。

(3)“鬧鐘開關(guān)”在鬧鐘設(shè)置狀態(tài)時,用于設(shè)置當前鬧鐘開或關(guān),正常工作時,用于手動關(guān)鬧鈴的聲音和設(shè)置鬧鐘總開關(guān)。

(4)“確認”用于設(shè)置參數(shù)的確認,并檢查參數(shù)是否合理,如果符合要求,則參數(shù)有效;否則本次修改無效,保持原值。正常工作情況下按該鍵無操作。

(5)“顯示切換”用于正常工作時手動切換時間、日期的顯示。

七、調(diào)試記錄(主要問題及解決方法)

1.在寫設(shè)置模塊特別是容錯處理時比較耗時,要求對各個細節(jié)都進行仔細地調(diào)試,修修改改在所難免,都是些小問題,略過;

2.在實現(xiàn)功能模塊的相互任意跳轉(zhuǎn)時,起初只是直接調(diào)用功能入口函數(shù)fun_key(),出現(xiàn)遞歸調(diào)用的警告,因為要堆棧保護現(xiàn)場,而在任意跳轉(zhuǎn)時多次遞歸調(diào)用必然會空間不足(即使通過reenstrant也不妥,也是這個問題),這樣就有可能會造成數(shù)據(jù)覆蓋使系統(tǒng)不穩(wěn)定。對此,我在主函數(shù)里采用以下方法避免遞歸調(diào)用:

while(1)

{

Show_control();

alarm_scan();

key=Get_key();

while(key=='A'||key=='B'||key=='C') fun_key(); //功能跳轉(zhuǎn)(可避免遞歸調(diào)用的處理)

fun_key();

}

3.在鍵盤的實驗過程中,我?guī)缀鯂L試了所有的鍵盤處理方法,包括行掃描法、線反轉(zhuǎn)法、延時消抖、定時中斷掃描計數(shù)消抖……其間曾導致LED動態(tài)刷新速度不足,閃爍嚴重,把LED_show()里的Delay(8)改為Delay(1)即可;條件key_count>=6判為有效也是針對我自己的程序根據(jù)實際情況調(diào)試得出的經(jīng)驗值。

聽說有一種類似的按鍵掃描,可以很方便識別出短按、短按抬起、長按、長按抬起共4種按鍵動作,大概思路如下:

用定時器中斷2mS掃描一次,如果按鍵被按下則計數(shù)累加,否則計數(shù)清零并記按鍵抬起動作。

1、累加到5次(10mS完成消斗)認為是短按;

2、累加超過5次且小于500次(1秒),并已產(chǎn)生按鍵抬起動作,認為是短按抬起;

3、累加超過500次,認為是長按;

4、累加超過500次,并已產(chǎn)生按鍵抬起動作,認為是長按抬起。

據(jù)此方法,我小試了下牛刀,具體實現(xiàn)未果,有空再試。

八、課程設(shè)計總結(jié)

其實以前就用Freescale單片機寫過一個帶溫度顯示和鬧鐘的數(shù)字時鐘,只是實現(xiàn)走時、溫度采集和鍵盤的基本功能,未考慮日期、容錯處理、功能的完善性等細節(jié)問題。這次原打算嘗試改用匯編實現(xiàn),但后來迫于形式,時間有限,只好先用C頂著,也算是對這一類小產(chǎn)品的改進與完善吧。

至于時間的精確性,如果只用晶振是很難做到精確的,即使嵌入?yún)R編精確計算或者調(diào)出一個最佳的時間偏差量、偏差函數(shù),也難以確保其精確性,畢竟每個晶振的振蕩頻率與標稱值存在誤差也會老化。所以這里我沒有做這方面的工作,目前普遍的做法是外接一個時鐘芯片,比如說DS1302,價格也不貴。

九、附錄:

1.Protel繪制的最小系統(tǒng)板原理圖:

 

2.程序清單及詳細注釋

/*****************************************************

描述:

全功能數(shù)字電子鐘

作者:Estrong 2011.12.12寫于福建工程學院.

平臺:Atmel 89S51 單片機

實現(xiàn)功能:

時間、日期(包括星期)、鬧鐘及其設(shè)置;

支持6個鬧鐘,可設(shè)置鬧鐘總開關(guān)及分開關(guān);

完善的設(shè)置容錯處理;

可自動/手動切換時間/日期顯示模式;

設(shè)置位閃爍標識;

設(shè)置過程30s無操作,自動恢復為正常顯示方式;

4x4矩陣鍵盤掃描采用線反轉(zhuǎn)法,以中斷掃描計數(shù)去抖;

開機自檢功能;

......

******************************************************/

#include

#include

#define PA XBYTE[0x0fefc]

#define PB XBYTE[0x0fefd]

#define PC XBYTE[0x0fefe]

#define CTL XBYTE[0x0feff]

#define uchar unsigned char

#define uint unsigned int

//PA、PB、PC、CTL分別為8255的控制口

sbit P3_2=P3^2; //蜂鳴器引腳定義

struct{ //鬧鐘時、分、秒 ,共設(shè)6個鬧鐘(初始狀態(tài)默認:00-00-F1)

uchar hour;

uchar minute;

uchar isON;

}alarm[6]={{0,0,0}};

uchar hour=12,minute=0,second=0;//時、分、秒

uchar temp_second; //用于立即切換顯示時間/日期

uint year=2011;// 年

uchar month=12;// 月

uchar day=1; // 日

uchar week=6;// 星期

uchar Mdays[]={0,31,28,31,30,31,30,31,31,30,31,30,31};//各月天數(shù)

uchar alarm_isON=1; //鬧鐘總開關(guān)

uchar alarm_station=0; //鬧鐘狀態(tài)

uchar ano; //鬧鐘號(當前時間到的鬧鐘號)

uchar start_minute;//開始響鈴的時間(也就是所定鬧鐘的時間)

uint count_ms25=0; //軟件計數(shù)器(計數(shù)40個25毫秒達1s)

uchar show_model=0; // 顯示模式:[0]切換顯示時間/日期 [1]切換顯示日期/時間

const uchar fixtime=0x00;//時間修正量

uchar key=0xff;//獲得的當前鍵值

uchar last_key=0xff; //最后一次掃描到的按鍵(非0xff)

uchar key_count=0;//掃描到同一按鍵的次數(shù)

uchar Edown=0; //鬧鐘開關(guān)鍵是否按下

uchar led_buf[8]={24,24,24,24,24,24,24,24}; //時間日期顯示緩沖區(qū)

uchar code led_table1[]={0x0c0,0x0f9,0x0a4,0x0b0, 0x99,0x92,0x82,0x0f8,0x80,0x90,0x88,0x83,0x0C6,0x0a1,0x86,0x8e,0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,0x08,0x03,0x46,0x21,0x06,0x0e,0x7f,0x0bf,0xff};

// 0 1 2 3 4 5 6 7 8 9 a b c d e f 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. A. B. C. D. E. F. . - 全滅

// 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

/*函數(shù)聲明*****************************************************************************/

void Init(void); //初始化

void fun_key(void); // 相應(yīng)按鍵功能入口

void test(void); // 開機自檢

void LED_show(uchar buf[]);// 8個數(shù)碼管動態(tài)顯示

void Show_time(void);// 時間送緩存

void Show_date(void); // 日期送緩存

void Show_alarm(uchar i);// 鬧鐘送緩存

uchar IsLeapYear(uint y); // 判斷閏年

void Update_Feb(void);// 更新2月天數(shù)

void Show_control(void);// 時間日期切換顯示控制

void DateTime_change(void);//走時改變時間和日期

void Delay(uchar k);// 延時約:60μs×k

uchar key_scan(void);// 鍵盤掃描(一次)

uchar Get_key(void); // 獲取最終鍵值

void alarm_scan(void);// 鬧鐘掃描與響鈴

void set_time(void);// 設(shè)置時間

void set_date(void);// 設(shè)置日期

void set_alarm(void);// 設(shè)置鬧鐘

/*主函數(shù)*****************************************************************************/

main()

{

Init();

Update_Feb(); //初始時更新2月天數(shù)

while(1)

{

Show_control();

alarm_scan();

key=Get_key();

while(key=='A'||key=='B'||key=='C') fun_key(); //功能跳轉(zhuǎn)(可避免遞歸調(diào)用的處理)

fun_key();

}

}

/*鍵盤功能入口********************************************************************/

void fun_key(void)

{

switch(key)

{

case 'A': //按'A'鍵 設(shè)置時間

set_time();

break;

case 'B': //按'B'鍵 設(shè)置日期

set_date();

break;

case 'C': //按'C'鍵 設(shè)置鬧鐘

set_alarm();

break;

case 'E': //按'D'鍵 鬧鐘總開關(guān),時間模式下最后一位小數(shù)點亮代表總開,否則為關(guān)

if(alarm_isON==0) alarm_isON=1;

else alarm_isON=0;

break;

case 'F': //按'F'鍵 時間/日期切換顯示

show_model++;

if(show_model>1) show_model=0;

temp_second=0;

break;

default:break;

}

}

/*初始化************************************************************************/

void Init(void)

{

int i;

for(i=0;i<400;i++) Delay(8); /*硬件準備工作*/

CTL=0x80; /*8255控制字 方式0 ABC口皆設(shè)為輸出*/

//計數(shù)器初始化:

TMOD=0x01; //GATE0=0,C/T=0,M1M0=01

TH0 = 0x0A6;//晶振頻率11.0592MHz下,定時25ms的計數(shù)初值

TL0 = 0x00+fixtime; //(2^16-X)*(12*1/11.0592)=25ms; -->X=42496=A600H

test(); //開機自檢

TR0=1; //啟動T0計數(shù)

IE=0x82; //開放TF0中斷

}

/*開機自檢**********************************************************************/

void test(void)

{

uchar i,j,num,pLED=0x80;

P3_2=0;

for(i=0;i<10;i++)Delay(250);

P3_2=1;

for(i=0;i<8;i++)

{

num=led_buf[i];

PA=led_table1[num]; /*送字段碼*/

PB=pLED; /*送字位碼*/

for(j=1;j<=5;j++)Delay(250);

pLED>>=1; /*右移一位*/

Delay(10); /*延時*/

}

}

/*LED顯示****************************************************************************/

void LED_show(uchar buf[]) //死循環(huán)里一般都要放該函數(shù)

{

uchar i,num,pLED=0x80;

for(i=0;i<8;i++)

{

num=buf[i];

PA=led_table1[num]; /*送字段碼*/

PB=pLED; /*送字位碼*/

pLED>>=1; /*右移一位*/

Delay(1); /*延時*/

}

}

/*更新顯示緩存************************************************************************/

/*時間送緩存(12-00-00 時-分-秒)*/

void Show_time(void)

{

if(alarm_isON==0) led_buf[7]=second%10;

else led_buf[7]=second%10+16;//鬧鐘開時間最后一位顯示有小數(shù)點

led_buf[6]=second/10;

led_buf[5]=33;

led_buf[4]=minute%10;

led_buf[3]=minute/10;

led_buf[2]=33;

led_buf[1]=hour%10;

led_buf[0]=hour/10;

}

/*日期送緩存(11.01.01-6,11年01月01日星期六)*/

void Show_date(void)

{ uint year_temp=year%100;

led_buf[1]=year_temp%10+16;

led_buf[0]=year_temp/10;

led_buf[3]=month%10+16;

led_buf[2]=month/10;

led_buf[5]=day%10;

led_buf[4]=day/10;

led_buf[6]=33;

led_buf[7]=week;

}

/*日期送緩存(2011.01.01,沒有星期)

void Show_date()

{ uint year_temp=year;

led_buf[3]=year_temp%10+16;

year_temp=year_temp/10;

led_buf[2]=year_temp%10;

year_temp=year_temp/10;

led_buf[1]=year_temp%10;

year_temp=year_temp/10;

led_buf[0]=year_temp;

led_buf[5]=month%10+16;

led_buf[4]=month/10;

led_buf[7]=day%10;

led_buf[6]=day/10;

}

*/

/*鬧鐘顯示送緩存(06-00-E1 鬧鐘1開,定時在06:00)*/

void Show_alarm(uchar i)

{

led_buf[7]=i;

if(alarm[i].isON) //鬧鐘開L6顯示'E'

led_buf[6]=14;

else led_buf[6]=15; //鬧鐘關(guān)L6顯示'F'

led_buf[5]=33;

led_buf[4]=alarm[i].minute%10;

led_buf[3]=alarm[i].minute/10;

led_buf[2]=33;

led_buf[1]=alarm[i].hour%10;

led_buf[0]=alarm[i].hour/10;

}

/*判斷是否為閏年************************************************************************/

uchar IsLeapYear(uint y)

{

if( (y%4==0)&&(y%100!=0)||(y%400==0) )

return 1;

else

return 0;

}

/*更新2月天數(shù)**************************************************************************/

void Update_Feb(void)

{

if(IsLeapYear(year)) Mdays[2]=29; //閏年2月29天

else Mdays[2]=28; //平年2月28天

}

/*顯示控制(時間日期切換顯示)************************************************************/

void Show_control(void)

{

if(show_model==0)

{

if(temp_second/10%2==0) Show_time(); //切換模式:每隔10秒切換顯示時間/日期

else Show_date();

}

else //if(show_model==1)

{

if(temp_second/10%2==0) Show_date(); //切換模式:每隔10秒切換顯示日期/時間

else Show_time();

}

LED_show(led_buf);

}

/*走時改變時間和日期**********************************************************************/

void DateTime_change(void) //放中斷,達1秒后執(zhí)行

{

second++;temp_second++;

if(second>=60)

{minute++;second=0;}

if(temp_second>=60)

{temp_second=0;}

if(minute>=60)

{hour++;minute =0;}

if(hour>=24)

{day++;hour=0;week++;if(week>7) week=1;}

if(day>Mdays[month])

{month++;day=1;}

if(month>12)

{year++;month=1;Update_Feb();} //年份改變時更新2月天數(shù)

}

/*延時約:60μs×k ******************************************************************/

void Delay(uchar k)

{ uchar i;

while((k--)!=0)

for(i=0;i<25;i++);

}

/*鍵盤掃描(行掃描法,延時消抖)********************************************************

uchar code KBTable[] = {

0xEE,'1',0xDE,'4',0xBE,'7',0x7E,'0',

0xED,'2',0xDD,'5',0xBD,'8',0x7D,'A',

0xEB,'3',0xDB,'6',0xBB,'9',0x7B,'B',

0xE7,'F',0xD7,'E',0xB7,'C',0x77,'D',

0x00,0xff};

uchar Get_key(void); // 獲取最終鍵值

{ uchar i;

uchar line, row, k_value;

static uchar lastkey=0xff;

CTL=0x88; //CH輸入,CL輸出 10001000

PC=PC & 0xf0; // PC0~PC3輸出0 , 輸入PC4~ PC7(默認1無鍵按下)

if ((PC & 0xf0) == 0xf0)

{

lastkey=0xff;

return 0xff; //無鍵按下

}

row = PC;

Delay(4); //延時,消除抖動

if (row != PC)

{

lastkey=0xff;

return 0xff; //判為抖動

}

line=0xFE;

for (i=0;i<4;i++)

{ PC = line; //輸出掃描信號

row=PC; //讀鍵盤口

if ((row & 0xf0) != 0xf0)

break;

line=(line<<1)+1;

}

if (i==4)

{ lastkey=0xff; return 0xff; }

k_value = (row & 0xf0) | (line & 0x0f) ;

for (i=0; i<32; i+=2)

if (k_value == KBTable[i])

break;

if(lastkey==KBTable[i+1])

return 0xff;

lastkey=KBTable[i+1];

return KBTable[i+1];

}

*/

//獲取最終鍵值*********************************************************

uchar Get_key(void)//返回 '0','1','2'...'E','F',0xff

{

if(key_count>=6) //假設(shè)1s最快可以按5下,則每下約持續(xù)200ms, 每次25ms中斷時就掃描一次鍵盤,在這8次(或更多)里若有六次以上檢測到有同一鍵則判為有效鍵。

{

key_count=0;

return last_key;

}

else return 0xff;

}

//一次鍵盤掃描(線反轉(zhuǎn)法,要求行列都要有上拉電阻,中斷掃描計數(shù)去抖)*********************************************************

uchar code KBTable[] = {'1','2','3','F','4','5','6','E','7','8','9','C','0','A','B','D'};

//key_index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

uchar key_scan(void) //返回 '0','1','2'...'E','F',0xff

{ uchar key_index,temp=0;

CTL=0x88; //CH輸入,CL輸出 10001000

PC=PC & 0xf0; //將低四位置0

if(PC!=0xF0) //判斷按鍵是否按下 如果按鈕按下 會拉低CH其中的一個端口

{

temp=PC; //讀PC口

temp=temp&0xf0; //屏蔽低四位

temp=~((temp>>4)|0xf0); // 高四位取反值 ~(1111****)

if(temp==1) // pC.4 被拉低 1110 第一行

key_index=0;

else if(temp==2) // PC.5 被拉低 1101 第二行

key_index=4;

else if(temp==4) // PC.6 被拉低 1011 第三行

key_index=8;

else if(temp==8) // PC.7 被拉低 0111 第四行

key_index=12;

else //沒有鍵按下

return 0xff;

CTL=0x81; //CL輸入,CH輸出 10000001

PC=PC & 0xf0; //CH輸出0000 初始: 00001111 若有鍵按下,根據(jù)低4位哪一位被拉低確定第二維坐標

temp=PC; //讀PC口

temp=temp&0x0f;

temp=~(temp|0xf0); // 低四位取反值 ~(1111****)

if(temp==1) //PC.0 被拉低 1110 第一列

key_index+=0;

else if(temp==2) //PC.1 被拉低 1101 第二列

key_index+=1;

else if(temp==4) //PC.2 被拉低 1011 第三列

key_index+=2;

else if(temp==8) //PC.3 被拉低 0111 第四列

key_index+=3;

else //沒有鍵按下

return 0xff;

return KBTable[key_index];

}

else return 0xff;

}

//鬧鐘掃描與響鈴*****************************************************************

void alarm_scan(void) //死循環(huán)里一般都要放該函數(shù)

{ uint i;

if(alarm_isON==0) alarm_station=0;//鬧鐘總開關(guān)

else // if((alarm_isON==1)

{ for(ano=0;ano<5;ano++)

{if((alarm[ano].isON==1)&&(hour==alarm[ano].hour)&&(minute==alarm[ano].minute))

{ start_minute=minute;

alarm_station=1;

break;

}

else alarm_station=0;

}

}

if(alarm_station==0)Edown=0;

while( (alarm_station==1)&&(Edown==0)&&(minute==start_minute) )

{ //鬧鐘開關(guān)鍵(E)被按下或者響過一分鐘就停

for(i=0;i<400;i++) /* 循環(huán)400次,嘀0.2秒鐘 */

{

P3_2=0; /* P3.4=0,晶體管導通 */

Delay(2); /* 調(diào)延時函數(shù),延時500μs */

P3_2=1; /* P3.4=1,晶體管截止 */

Delay(2); /* 調(diào)延時函數(shù),延時500μs */

P1=0x00;

}

P1=0xff;

for(i=0;i<=50;i++) //延時作用

{

Show_control();

key=Get_key();

if(key=='E') {Edown=1;break;}

else continue;

}

Show_control();

key=Get_key();

if(key=='E') Edown=1;

else continue;

}

}

/*時間設(shè)置*****************************************************************/

void set_time(void)

{ uchar i,time1,count=0;

uchar key_buf[8];

time1=second;

Show_time();

for(i=0;i<8;i++) key_buf[i]=led_buf[i];

i=0;time1=second;

while(1)

{ if (time1!=second){count++;time1=second;}

if(count>=30) break; //長時間(30S,可變)無操作自動恢復正常顯示

if(i==2) i=3;

else if(i==5) i=6;

if(count_ms25<20) led_buf[i]=key_buf[i]; //當前設(shè)置位閃爍(間隔0.5s)

else led_buf[i]=34;

key=Get_key();

if(key==0xff)

{LED_show(led_buf);alarm_scan();continue;}//沒有鍵按下

else count=0;

if(key>='0'&&key<='9')

{

if(i==0&&key>'2') i--; //輸入越界容錯處理

else if(i==1&&key_buf[0]==2&&key>'3') i--;

else if((i==3||i==6)&&key>'5') i--;

else key_buf[i]=key-'0';

led_buf[i]=key_buf[i];

i++;

if(i>=8) i=0;

}

else if(key=='D') //確定

{

hour=key_buf[0]*10+key_buf[1];

minute=key_buf[3]*10+key_buf[4];

second=key_buf[6]*10+key_buf[7];

break;

}

else break;

}

}

/*日期設(shè)置*****************************************************************/

void set_date(void)

{ uchar i,temp,time1,count=0;

uchar key_buf[8];

time1=second;

Show_date();

for(i=0;i<8;i++) key_buf[i]=led_buf[i];

i=0;

while(1)

{ if (time1!=second){count++;time1=second;}

if(count>=30) break; //長時間(30S,可變)無操作自動恢復正常顯示

if(i==6) i=7;

if(count_ms25<20) led_buf[i]=key_buf[i]; //當前設(shè)置位閃爍(間隔0.5s)

else led_buf[i]=34;

key=Get_key();

if(key==0xff) //沒有鍵按下

{LED_show(led_buf);alarm_scan();continue;}

else count=0;

if((key>='0')&&(key<='9'))

{ if(i==0) key_buf[0]=key-'0';

else if(i==1) key_buf[1]=key-'0'+16;

else if(i==2) //月 輸入越界容錯處理 2:monthH

{

if(key>'1')i--;

else key_buf[2]=key-'0';

}

else if(i==3) //3:monthL

{

if(key_buf[2]==0)

{

if(key=='0') key_buf[3]=17; //最少01月

else key_buf[3]=key-'0'+16;

}

else

{

if(key_buf[2]==1&&key>'2') i--; //最多12月

else key_buf[3]=key-'0'+16;

}

}

//日 輸入越界容錯處理 4:dayH 5:dayL

else if(i==4)

{

if((key_buf[2]==0)&&((key_buf[3]-16)==2))

{

if(key>'2') i--; //2月dayH<=2

else key_buf[4]=key-'0';

}

else

{

if(key>'3') i--; //其它月份dayH<=3

else key_buf[4]=key-'0';

}

}

else if(i==5)

{

if((key_buf[2]==0)&&((key_buf[3]-16)==2))

{

if( !IsLeapYear(year/100*100+key_buf[0]*10+key_buf[1]-16) ) //若設(shè)置年份為平年

{

if((key_buf[4]==2)&&(key=='9')) i--; //2月dayL<=2

else key_buf[5]=key-'0';

}

else key_buf[5]=key-'0';

}

else

{

temp=Mdays[key_buf[2]*10+key_buf[3]-16]%10;

if( (key_buf[4]==3) && (key>('0'+temp)) ) i--; //其它月份dayL<=3

else key_buf[5]=key-'0';

}

if((key_buf[4]==0)&&(key_buf[5]==0))

key_buf[5]=17; //最少01日

}

else // if(i==7) 星期容錯處理

{

if(key>'7') i--;

else if(key=='0') key_buf[7]=1;

else key_buf[7]=key-'0';

}

led_buf[i]=key_buf[i];

i++;

if(i>=8) i=0;

}

else if(key=='D') //確定

{

year=year/100*100+key_buf[0]*10+key_buf[1]-16;

month=key_buf[2]*10+key_buf[3]-16;

day=key_buf[4]*10+key_buf[5];

week=key_buf[7];

Update_Feb();

break;

}

else break;

LED_show(led_buf);alarm_scan();

}

}

/*鬧鐘設(shè)置*****************************************************************/

void set_alarm(void)

{ uchar i,a_no,time1,count=0;

uchar key_buf[8]={33,1,2,3,4,5,6,33};

while(1) //選擇要設(shè)置的鬧鐘號 -123456-

{

LED_show(key_buf);

key=Get_key();

if(key>='1'&&key<='6')

{

a_no=key-'1';

Show_alarm(a_no);

for(i=0;i<=7;i++) key_buf[i]=led_buf[i];

break;

}

else if(key>='A'&&key<='F'&&key!='E') break;

else continue;

}

i=0;

time1=second;

while(1)

{ if(key>='A'&&key<='F'&&key!='E') break;

if (time1!=second){count++;time1=second;}

if(count>=30) break; //長時間(30S,可變)無操作自動恢復正常顯示

if(i==2) i=3;

else if(i>=5) i=0;

if(count_ms25<20) led_buf[i]=key_buf[i]; //當前設(shè)置位閃爍(間隔0.5s)

else led_buf[i]=34;

key=Get_key();

if(key==0xff)

{LED_show(led_buf);alarm_scan();continue;} //沒有鍵按下

else count=0;

if(key>='0'&&key<='9')

{ if(i==0)

{

if(key>'2') i--; //輸入越界容錯處理

else key_buf[i]=key-'0';

}

else if(i==1)

{

if(key_buf[0]==2&&key>'3')i--;

else key_buf[i]=key-'0';

}

else if(i==3)

{

if(key>'5')i--;

else key_buf[i]=key-'0';

}

else if(i==4) key_buf[i]=key-'0';

led_buf[i]=key_buf[i];

i++;

if(i>=8) i=0;

}

else if(key=='E')

{if(key_buf[6]==14) key_buf[6]=15;

else key_buf[6]=14;

led_buf[6]=key_buf[6];

}

else if(key=='D') //確定

{

alarm[a_no].hour=key_buf[0]*10+key_buf[1];

alarm[a_no].minute=key_buf[3]*10+key_buf[4];

if(key_buf[6]==14) alarm[a_no].isON=1;

else alarm[a_no].isON=0;

break;

}

else break;

LED_show(led_buf);alarm_scan();

}

}

/*25ms中斷子函數(shù)**********************************************************************/

void Time_over(void) interrupt 1 using 2

{

uchar key_temp;

TH0 = 0x0A6; //晶振頻率11.0592MHz下,定時25ms的計數(shù)初值

TL0 = 0x00+fixtime;//(2^16-X)*(12*1/11.0592)=25ms; -->X=42496=A600H

count_ms25++;

//每次25ms中斷就掃描一次按鍵

key_temp=key_scan();

if((last_key==0xff)&&(key_temp!=0xff))

{

last_key=key_temp;

key_count++;

}

else

{

if(key_temp==last_key)

{key_count++;}

else

if(key_temp!=0xff){last_key=key_temp;key_count=1;}

}

if(count_ms25>=40) //達到1秒

{

DateTime_change(); //走時更新時分秒等

count_ms25=0;

}

}

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉