




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
這個(gè)我從eehome貼過(guò)來(lái)的。寫(xiě)的非常的好。我們用學(xué)單片機(jī)不要停在演示的基礎(chǔ)上。只能讓單片機(jī)完成
局部事。這樣我們永遠(yuǎn)不會(huì)走出流水燈地獄?。?!
學(xué)習(xí)單片機(jī)也已經(jīng)有幾年了,藉此機(jī)會(huì)和大家聊一下我學(xué)習(xí)過(guò)程中的一些經(jīng)歷和想法吧。也感謝一線工人
提供了這個(gè)機(jī)會(huì).
幾年前,和眾多初學(xué)者一樣,我接觸到了單片機(jī),立刻被其神奇的功能所吸引,從此不能自拔。很多個(gè)
日夜就這樣陪伴著它度過(guò)了.期間也遇到過(guò)非常多的問(wèn)題,也一度被這些問(wèn)題所困惑……等到回過(guò)頭來(lái),
看到自己曾經(jīng)走過(guò)的路,唏噓不已。經(jīng)常混跡于論壇?!百u弄,’也好,“吹噓,,也罷,我只是想認(rèn)真的寫(xiě)寫(xiě)我
這一路走來(lái)歷經(jīng)的總總,把其中值得注意,以及經(jīng)驗(yàn)的地方寫(xiě)出來(lái),權(quán)當(dāng)是我對(duì)自己的一個(gè)總結(jié)吧。而作
為看官的你,如果看到了我的錯(cuò)誤,還請(qǐng)一定指正,這樣對(duì)我以及其它讀者都有幫助,而至于你如果從中
能夠收獲到些許,那便是我最大的欣慰了。姑妄言之,姑妄聽(tīng)之。9里,也看到了很多初學(xué)者發(fā)的求助帖子,
看到他們走在自己曾走過(guò)的彎路上,忽然想到了自己的那段日子,心里竟然莫名的沖動(dòng),凡此總總,我總
是盡自己所能去回帖。很多時(shí)候,都想寫(xiě)一點(diǎn)什么東西出來(lái),希望對(duì)廣大的初學(xué)者有一點(diǎn)點(diǎn)幫助。但總是
不知從何處寫(xiě)起.今天借一線工人的臺(tái),唱一唱我的戲一路學(xué)習(xí)過(guò)來(lái)的過(guò)程中,幫助最大之一無(wú)疑來(lái)自于
網(wǎng)絡(luò)了。很多時(shí)候,通過(guò)網(wǎng)絡(luò),我們都可以獲取到所需要的學(xué)習(xí)資料。但是,隨著我們學(xué)習(xí)的深入,我們
會(huì)慢慢發(fā)現(xiàn),網(wǎng)絡(luò)提供的東西是有限度的,好像大部分的資料都差不多,或者說(shuō)是適合大部分的初學(xué)者所
需,而當(dāng)我們想更進(jìn)一步提高時(shí),卻發(fā)現(xiàn)能夠獲取到的資料越來(lái)越少,相信各位也會(huì)有同感,鋪天蓋地的
單片機(jī)資料中大部分不是流水燈就是LED,液晶,而且也只是僅僅作功能性的演示。于是有些人選擇了放
棄,或者是轉(zhuǎn)移到其他興趣上面去了,而只有少部分人選擇了繼續(xù)摸索下去,結(jié)合市面上的書(shū)籍,然后在
網(wǎng)絡(luò)上鍥而不舍的搜集資料,再?gòu)呐H说闹谎云Z(yǔ)中去體會(huì),不斷動(dòng)手實(shí)踐,慢慢的,也摸索出來(lái)了自己
的一條路子。當(dāng)然這個(gè)過(guò)程必然是艱辛的,而他學(xué)會(huì)了之后也不會(huì)在網(wǎng)絡(luò)上輕易分享自己的學(xué)習(xí)成果。如
此惡性循環(huán)下去,也就不難理解為什么初級(jí)的學(xué)習(xí)資料滿天飛,而深入一點(diǎn)的學(xué)習(xí)資料卻很少的原因了。
相較于其他領(lǐng)域,單片機(jī)技術(shù)的封鎖更加容易。盡管已經(jīng)問(wèn)世了很多年了,有價(jià)值的資料還是相當(dāng)?shù)那啡保?/p>
大部分的資料都是止于入門(mén)階段或者是簡(jiǎn)單的演示實(shí)驗(yàn)。但是在實(shí)際工程應(yīng)用中卻是另夕一回事。有能力
的高手無(wú)暇或者是不愿公開(kāi)自己的學(xué)習(xí)經(jīng)驗(yàn)。
很多時(shí)候,我也很困惑,看到國(guó)外爰好者毫不保留的在網(wǎng)絡(luò)上發(fā)布自己的作品,我忽然感覺(jué)到一絲絲的
悲哀。也許,我們真的該轉(zhuǎn)變一下思路了,幫助別人,其實(shí)也是在幫助自己.啰啰嗦嗦的說(shuō)了這么多,相
信大家能夠明白說(shuō)的是什么意思.在接下來(lái)的一段日子里,我將會(huì)結(jié)合曳壬工程師之家舉辦的主題周活動(dòng)
寫(xiě)一點(diǎn)自己的想法.盡可能從實(shí)用的角度去講述。希望能夠幫助更多的初學(xué)者更上一層樓。而關(guān)于這個(gè)主
題周的最大主題我想了這樣的一個(gè)名字“從單片機(jī)初學(xué)者邁向單片機(jī)工程師”。名字挺大挺響亮,給我的壓
力也挺大的,但我會(huì)努力,爭(zhēng)取使這樣的一系列文章能夠帶給大家一點(diǎn)幫助,而不是看后大跌眼鏡。這樣
的一系列文章主要的對(duì)象是初學(xué)者,以及想從初學(xué)者更進(jìn)一步提高的讀者。而至于老手,以及那些牛XX
的人,希望能夠給我們這些初學(xué)者更多的一些指點(diǎn)哈
我們首先來(lái)看第一章節(jié)
從這一章開(kāi)始,我們開(kāi)始邁入單片機(jī)的世界。在我們開(kāi)始這一章具體的學(xué)習(xí)之前,有必要給大家先說(shuō)明一
下。在以后的系列文章中,我們將以51內(nèi)核的單片機(jī)為載體,C語(yǔ)言為編程語(yǔ)言,開(kāi)發(fā)環(huán)境為KEILuv3。
至于為什么選用C語(yǔ)言開(kāi)發(fā),好處不言而喻,開(kāi)發(fā)速度快,效率高,代碼可復(fù)用率高,結(jié)構(gòu)清晰,尤其是
在大型的程序中,而且隨著編譯器的不斷升級(jí),其編譯后的代碼大小與匯編語(yǔ)言的差距越來(lái)越小。而關(guān)于
C語(yǔ)言和匯編之爭(zhēng),就像那個(gè)啥,每隔一段時(shí)間總會(huì)有人挑起這個(gè)話題,如果你感興趣,可以到網(wǎng)上搜索
相關(guān)的帖子自行閱讀。不是說(shuō)匯編不重要,在很多對(duì)時(shí)序要求非常高的場(chǎng)合,需要利用匯編語(yǔ)言和c語(yǔ)言
混合編程才能夠滿足系統(tǒng)的需求。在我們學(xué)習(xí)掌握C語(yǔ)言的同時(shí),也還需要利用閑余的時(shí)間去學(xué)習(xí)了解匯
編語(yǔ)言。
1.從點(diǎn)亮LED(發(fā)光二極管)開(kāi)始
在市面上眾多的單片機(jī)學(xué)習(xí)資料中,最基礎(chǔ)的實(shí)驗(yàn)無(wú)疑于點(diǎn)亮LED了,即控制單片機(jī)的I/O的電平的變化。
如同如下實(shí)例代碼一般
voidmain(void)
(
LedlnitO;
While⑴
(
LED=ON;
DelayMs(500);
LED=OFF;
DelayMs(500);
)
1
程序很簡(jiǎn)單,從它的結(jié)構(gòu)可以看出,LED先點(diǎn)亮500MS,然后熄滅500MS,如此循環(huán)下去,形成的效
果就是LED以1HZ的頻率進(jìn)行閃爍。下面讓我們分析上面的程序有沒(méi)有什么問(wèn)題。
看來(lái)看出,好像很正常的啊,能有什么問(wèn)題呢?這個(gè)時(shí)候我們應(yīng)該換一個(gè)思路去想了.試想,整個(gè)程序除
了控制LED=ON;LED=OFF;這兩條語(yǔ)句外,其余的時(shí)間,全消耗在了DclayMs(500)這兩個(gè)函數(shù)上。
而在實(shí)際應(yīng)用系統(tǒng)中是沒(méi)有哪個(gè)系統(tǒng)只閃爍一只LED就其它什么事情都不做了的。因此,在這里我們要想
辦法,把CPU解放出來(lái),讓它不要白白浪費(fèi)500MS的延時(shí)等待時(shí)間。寧可讓它一遍又一遍的掃描看有哪
些任務(wù)需要執(zhí)行,也不要讓它停留在某個(gè)地方空轉(zhuǎn)消耗CPU時(shí)間。
從上面我們可以總結(jié)出
(1)無(wú)論什么時(shí)候我們都要以實(shí)際應(yīng)用的角度去考慮程序的編寫(xiě)。
(2)無(wú)論什么時(shí)候都不要讓CPU白白浪費(fèi)等待,尤其是延時(shí)(超過(guò)IMS)這樣的地方。
下面讓我們從另夕1個(gè)角度來(lái)考慮如何點(diǎn)亮一顆LED.
先看看我們的硬件結(jié)構(gòu)是什么樣子的。
我手上的單片機(jī)板子是電子工程師之家的開(kāi)發(fā)的學(xué)習(xí)板。就以它的實(shí)際硬件連接圖來(lái)分析吧.如下圖所
示
一般的LED的正常發(fā)光電流為10~20MA而低電流LED的工作電流在2mA以下(亮度與普通發(fā)光管相同I
在上圖中我們可知,當(dāng)Q1~Q8引腳上面的電平為低電平時(shí),LED發(fā)光。通過(guò)LED的電流約為(VCC-Vd)
/RA2。其中Vd為L(zhǎng)ED導(dǎo)通后的壓降,約為1.7V左右。這個(gè)導(dǎo)通壓降根據(jù)LED顏色的不同,以及工作
電流的大小的不同,會(huì)有一定的差別.下面一些參數(shù)是網(wǎng)上有人測(cè)出來(lái)的,供大家參考。
紅色的壓降為1.82-1.88V,電流5-8mA,
綠色的壓降為1.75-1.82V,電流3-5mA,
橙色的壓降為1.7-1.8V,電流3-5mA
蘭色的壓降為3.1-3.3V,電流8-10mA,
白色的壓降為3-3.2V,電流10-15mA,
(供電電壓5V,LED直徑為5mm)
74HC573真值表如下:
通過(guò)這個(gè)真值表我們可以看出。當(dāng)OutputEnable引腳接低電平的時(shí)候,并目.LatchEnablc引腳為高電平的
時(shí)候,Q端電平與D端電平相同。結(jié)合我們的LED硬件連接圖可以知道LED_CS端為高電平時(shí)候,P0口
電平的變化即Q端的電平的變化,進(jìn)而引起LED的亮滅變化。由于單片機(jī)的驅(qū)動(dòng)能力有限,在此,74HC573
的主要作用就是起一個(gè)輸出驅(qū)動(dòng)的作用。需要注意的是,通過(guò)74HC573的最大電流是有限制的,否則可能
會(huì)燒壞74HC573這個(gè)芯片。
上面這個(gè)圖是從74HC573的DATASHEET中截取出來(lái)的,從上可以看出,每個(gè)引腳允許通過(guò)的最大電流為
35mA整個(gè)芯片允許通過(guò)的最大電流為75mA。在我們?cè)O(shè)計(jì)相應(yīng)的驅(qū)動(dòng)也跪時(shí)候,這些參數(shù)是相當(dāng)重要的,
而且是最容易被初學(xué)者所忽略的地方。同時(shí)在設(shè)計(jì)的時(shí)候,要留出一定量的余量出來(lái),不能說(shuō)單個(gè)引腳允
許通過(guò)的電流為35mA,你就設(shè)計(jì)為35mA,這個(gè)時(shí)候你應(yīng)該把設(shè)計(jì)的上限值定在20mA左右才能保證能夠
穩(wěn)定的工作。
(設(shè)計(jì)相應(yīng)驅(qū)動(dòng)電路時(shí)候,應(yīng)該仔細(xì)閱讀芯片的數(shù)據(jù)手冊(cè),了解每個(gè)引腳的驅(qū)動(dòng)能力,以及整個(gè)芯片的驅(qū)
動(dòng)能力)
了解了相應(yīng)的硬件后,我們?cè)賮?lái)編寫(xiě)驅(qū)動(dòng)程序。
首先定義LED的接口
#defineLEDP0
然后為亮滅常數(shù)定義?個(gè)宏,由硬件連接圖可以,當(dāng)P0輸出為低電平時(shí)候LED亮,P0輸出為高電
平時(shí),LED熄滅。
#defineLED_ON()LED=0x00//所有LED亮
#defineLED_OFF()LED=Oxff//所有LED熄滅
下面到了重點(diǎn)了,究竟該如何釋放CPU,避免其做延時(shí)空等待這樣的事情呢。很簡(jiǎn)單,我們?yōu)橄到y(tǒng)產(chǎn)
生一個(gè)IMS的時(shí)標(biāo)。假定LED需要亮500MS,熄滅500MS,那么我們可以對(duì)這個(gè)IMS的時(shí)標(biāo)進(jìn)行計(jì)數(shù),
當(dāng)這個(gè)計(jì)數(shù)值達(dá)到500時(shí)候,清零該計(jì)數(shù)值,同時(shí)把LED的狀態(tài)改變。
unsignedintg_ul6LedTimeCount=0;//LED計(jì)數(shù)器
unsignedcharg_u8LedState=0:〃LED狀態(tài)標(biāo)志,0表示亮,1表示熄滅
voidLedProcess(void)
if(0==g_u8LcdState)//如果LED的狀態(tài)為亮,則點(diǎn)亮LED
LED_ON();
)
else〃否則熄滅LED
(
LED_OFF();
)
voidLedStateChange(void)
(
if(g_bSystemTimelMs)〃系統(tǒng)IMS時(shí)標(biāo)到
(
g^bSystcmllnic1Ms=0;
g_u16LedTimeCount++;〃LED計(jì)數(shù)器力口一
if(g_ul6LedTimeCount>=500)〃計(jì)數(shù)達(dá)至lj500,即500MS到了,改變LED的狀態(tài)。
(
g_u16LedTimeCount=0;
g_u8LedState=!g_u8LedState;
}
)
}
上面有一個(gè)變量沒(méi)有提到,就是g_bSystemFmelMs。這個(gè)變量可以定義為位變量或者是其它變量,在我
們的定時(shí)器中斷函數(shù)中對(duì)其置位,其它函數(shù)使用該變量后,應(yīng)該對(duì)其復(fù)位(清0)。
我們的主函數(shù)就可以寫(xiě)成如下形式(示意代碼)
voidmain(void)
(
while(l)
(
LedProcess();
LedStateChange();
1
1
因?yàn)長(zhǎng)ED的亮或者滅依賴于LED狀態(tài)變量(g_u8LedState)的改變,而狀態(tài)變量的改變,又依賴于LED計(jì)數(shù)
器的計(jì)數(shù)值(§_"6民(11111?<。川1一只有計(jì)數(shù)值達(dá)到定后,狀態(tài)變量才改變)所以,兩個(gè)函數(shù)都沒(méi)有堵塞
CPU的地方。讓我們來(lái)從頭到尾分析?遍整個(gè)程序的流程。
程序首先執(zhí)行LedProcessO;函數(shù)
因?yàn)間_u8LedState的初始值為0(見(jiàn)定義,對(duì)于全局變量,在定義的時(shí)候最好給其一個(gè)確定的值)所以LED
被點(diǎn)亮,然后退出LedStateChange()函數(shù),執(zhí)行下一個(gè)函數(shù)LedStateChange。
在函數(shù)LcdStateChangeO內(nèi)部首先判斷IMS的系統(tǒng)時(shí)標(biāo)是否到了,如果沒(méi)有到就直接退出函數(shù),如果到了,
就把時(shí)標(biāo)清0以便下?個(gè)時(shí)標(biāo)消息的到來(lái),同時(shí)對(duì)LED計(jì)數(shù)器加、然后再判斷LED計(jì)數(shù)器是否到達(dá)我
們預(yù)先想要的值500,如果沒(méi)有,則退出函數(shù),如果有,對(duì)計(jì)數(shù)器清0,以便下次重新計(jì)數(shù),同時(shí)把LED
狀態(tài)變量取反,然后退出函數(shù)。
由上面整個(gè)流程可以知道,CPU所做的事情,就是對(duì)一些計(jì)數(shù)器加一,然后根據(jù)條件改變狀態(tài),再根據(jù)這
個(gè)狀態(tài)來(lái)決定是否點(diǎn)亮LED。這些函數(shù)執(zhí)行所花的時(shí)間都是相當(dāng)短的,如果主程序中還有其它函數(shù),則CPU
會(huì)順次往下執(zhí)行下去。對(duì)于其它的函數(shù)(如果有的話)也要采取同樣的措施,保證其不堵塞CPU,如果全部
基于這種方法設(shè)計(jì),那么對(duì)于不是非常龐大的系統(tǒng),我們的系統(tǒng)依舊可以保證多個(gè)任務(wù)(多個(gè)函數(shù))同時(shí)執(zhí)
行。系統(tǒng)的實(shí)時(shí)性得到了一定的保證,從宏觀上看來(lái),就是多個(gè)任務(wù)并發(fā)執(zhí)行。
好了,這一章就到此為止,讓我們總結(jié)一下,究竟有哪些需要注意的吧。
(1)無(wú)論什么時(shí)候我們都要以實(shí)際應(yīng)用的角度去考慮程序的編寫(xiě)。
(2)無(wú)論什么時(shí)候都不要讓CPU白白浪費(fèi)等待,尤其是延時(shí)(超過(guò)IMS)這樣的地方。
(3)設(shè)計(jì)相應(yīng)驅(qū)動(dòng)電路時(shí)候,應(yīng)該仔細(xì)閱讀芯片的數(shù)據(jù)手冊(cè),了解每個(gè)引腳的驅(qū)動(dòng)能力,
以及整個(gè)芯片的驅(qū)動(dòng)能力
(4)最重要的是,如何去糅放CPU(參考本章的例子),這是寫(xiě)出合格程序的基礎(chǔ)。
附完整程序代碼(基于電子工程師之家的單片機(jī)開(kāi)發(fā)板)
#include<reg52.h>
sbitLED_SEG=P1A4;〃數(shù)碼管段選
sbitLED_DIG=P1A5;〃數(shù)碼管位選
sbitLED.CSH=PIA6;//led控制位
sbitir=PIA7;
#defincLEDP0〃定義LED接口
bitg_bSystemTime1Ms=0;//IMS系統(tǒng)時(shí)標(biāo)
unsignedintg_u16LedTimeCount=0;//LED計(jì)數(shù)器
unsignedcharg_u8LedState=0;//LED狀態(tài)標(biāo)志,0表示亮,1表示熄滅
#dcfineLED_ON()LED=0x00;〃所有LED亮
#defineLED_OFF()LED=Oxff;〃所有LED熄滅
voidTimerOInit(void)
(
TMOD&=OxfO;
TMOD1=0x01;〃定時(shí)器0工作方式1
TH0=Oxfc;〃定時(shí)器初始值
TL0=0x66;
TRO=1;
ETO=1;
voidLedProcess(void)
if(O==g_u8LedState)〃如果LED的狀態(tài)為亮,則點(diǎn)亮LED
{
LED_ON();
)
else〃否則熄滅LED
(
LED_OFF();
J
voidLedStateChange(void)
(
if(g_bSystemTime1Ms)〃系統(tǒng)IMS時(shí)標(biāo)到
{
g_bSystemTime1Ms=0;
g_u!6LedTimeCount++;//LED計(jì)數(shù)器加一
if(g_u16LedTimeCount>=500)〃計(jì)數(shù)達(dá)到500,即500MS到了,改變LED的狀態(tài)。
(
g_u16LedTimeCount=0;
g_u8LedState=!g_u8LedState;
)
)
voidmain(void)
(
TimerOInit();
EA=1;
LED-CS11=1;〃74HC595輸出允許
LED_SEG=0;〃數(shù)碼管段選和位選禁止(因?yàn)樗鼈兒蚅ED共用P0口)
LED_DIG=0;
while(1)
{
LedProcess();
LedStateChange();
}
voidTimeOIsr(void)interrupt1
(
THO=Oxfc;〃定時(shí)器重新賦初值
TLO=0x66;
g_bSystemTimelMs=1;//IMS時(shí)標(biāo)標(biāo)志位置位
從單片機(jī)初學(xué)者邁向單片機(jī)工程師”
第三章--模塊化編程初識(shí)
好的開(kāi)始是成功的一半
通過(guò)上一章的學(xué)習(xí),我想你已經(jīng)掌握了如何在程序中釋放CPU了。希望能夠繼續(xù)堅(jiān)持下去。一個(gè)良好的
開(kāi)始是成功的一半。我們今天所做的一切都是為了在單片機(jī)編程上做的更好。
在談?wù)摻裉斓闹黝}之前,先說(shuō)卜.我以前的一些經(jīng)歷。在剛開(kāi)始接觸到C語(yǔ)言程序的時(shí)候,由于學(xué)習(xí)內(nèi)容所
限,寫(xiě)的程序都不是很大,?般也就兒百行而矣。所以所有的程序都完成在個(gè)源文件里面。記得那時(shí)候
大一參加學(xué)校里的一個(gè)電子設(shè)計(jì)大賽,調(diào)試了一個(gè)多星期,所有程序加起來(lái)大概將近1000行,長(zhǎng)長(zhǎng)的一個(gè)
文件,從上瀏覽下來(lái)都要好半天。出了錯(cuò)誤簡(jiǎn)單的語(yǔ)法錯(cuò)誤還好定位,其它一些錯(cuò)誤,往往找半天才找的
到。那個(gè)時(shí)候開(kāi)始知道了模塊化編程這個(gè)東西,也嘗試著開(kāi)始把程序分模塊編寫(xiě)。最開(kāi)始是把相同功能的
一些函數(shù)(譬如1602液晶的驅(qū)動(dòng))全部寫(xiě)在一個(gè)頭文件(.h)文件里面,然后需要調(diào)用的地方包含進(jìn)去,但是很
快發(fā)現(xiàn)這種方法有其局限性,很容易犯重復(fù)包含的錯(cuò)誤。
而且調(diào)用起來(lái)也很不方便。很快暑假的電子設(shè)計(jì)大賽來(lái)臨了,學(xué)校對(duì)我們的單片機(jī)軟件編程進(jìn)行了一些培
訓(xùn)。由于學(xué)校歷年來(lái)參加國(guó)賽和省賽,因此積累了?定數(shù)量的驅(qū)動(dòng)模塊,那些日子,老師每天都會(huì)布置?
定量的任務(wù),讓我們用這些模塊組合起來(lái),完成一定功能。而正是那些日子模塊化編程的培訓(xùn),使我對(duì)于
模塊化編程有了更進(jìn)一步的認(rèn)識(shí)。并且程序規(guī)范也開(kāi)始慢慢注意起來(lái)。此后的日子,無(wú)論程序的大小,均
采用模塊化編程的方式去編寫(xiě)。很長(zhǎng)一段時(shí)間以來(lái),一直有單片機(jī)愛(ài)好者在QQ上和我一起交流。有時(shí)候,
他們會(huì)發(fā)過(guò)來(lái)一些有問(wèn)題的程序源文件,讓我?guī)兔π薷囊幌隆M瑯邮情L(zhǎng)長(zhǎng)的一個(gè)文件,而且命名極不規(guī)范,
從頭看下來(lái),著實(shí)是痛苦,說(shuō)實(shí)話,還真不如我重新給他們寫(xiě)一個(gè)更快一些,此話到不假,因?yàn)槭诸^積累
了?定量的模塊,在完成?個(gè)新的系統(tǒng)時(shí)候,只需要根據(jù)上層功能需求,在底層模塊的支持下,可以很快
方便的完成。而不需要從頭到尾再?磚?瓦的重新編寫(xiě)。藉此,也可以看出模塊化編程的個(gè)好處,就是
可重復(fù)利用率高。下面讓我們揭開(kāi)模塊化神秘面紗,一窺其真面目。
C語(yǔ)言源文件*.c
提到C語(yǔ)言源文件,大家都不會(huì)陌生。因?yàn)槲覀兤匠?xiě)的程序代碼幾乎都在這個(gè)XX.C文件里面。編
譯器也是以此文件來(lái)進(jìn)行編譯并生成相應(yīng)的目標(biāo)文件。作為模塊化編程的組成基礎(chǔ),我們所要實(shí)現(xiàn)的所有
功能的源代碼均在這個(gè)文件里。理想的模塊化應(yīng)該可以看成是一個(gè)黑盒子。即我們只關(guān)心模塊提供的功能,
而不管模塊內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)。好比我們買了?部手機(jī),我們只需要會(huì)用手機(jī)提供的功能即可,不需要知曉
它是如何把短信發(fā)出去的,如何響應(yīng)我們按鍵的輸入,這些過(guò)程對(duì)我們用戶而言,就是是一個(gè)黑盒子。
在大規(guī)模程序開(kāi)發(fā)中,一個(gè)程序由很多個(gè)模塊組成,很可能,這些模塊的編寫(xiě)任務(wù)被分配到不同的人。而
你在編寫(xiě)這個(gè)模塊的時(shí)候很可能就需要利用到別人寫(xiě)好的模塊的借口,這個(gè)時(shí)候我們關(guān)心的是,它的模塊
實(shí)現(xiàn)了什么樣的接口,我該如何去調(diào)用,至于模塊內(nèi)部是如何組織的,對(duì)于我而言,無(wú)需過(guò)多關(guān)注。而追
求接口的單一性,把不需要的細(xì)節(jié)盡可能對(duì)外部屏蔽起來(lái),正是我們所需要注意的地方。
C語(yǔ)言頭文件*.h
談及到模塊化編程,必然會(huì)涉及到多文件編譯,也就是工程編譯。在這樣的一個(gè)系統(tǒng)中,往往會(huì)有多
個(gè)C文件,而且每個(gè)C文件的作用不盡相同。在我們的C文件中,由于需要對(duì)外提供接口,因此必須有一
些函數(shù)或者是變量提供給外部其它文件進(jìn)行調(diào)用。
假設(shè)我們有?個(gè)LCD.C文件,其提供最基本的LCD的驅(qū)動(dòng)函數(shù)
LcdPutChar(charcNewValue);〃在當(dāng)前位置輸出?個(gè)字符
而在我們的另外個(gè)文件中需要調(diào)用此函數(shù),那么我們?cè)撊绾巫瞿兀?/p>
頭文件的作用正是在此??梢苑Q其為一份接口描述文件。其文件內(nèi)部不應(yīng)該包含任何實(shí)質(zhì)性的函數(shù)代碼。
我們可以把這個(gè)頭文件理解成為一份說(shuō)明書(shū),說(shuō)明的內(nèi)容就是我們的模塊對(duì)外提供的接口函數(shù)或者是接口
變量。同時(shí)該文件也包含了一些很重要的宏定義以及一些結(jié)構(gòu)體的信息,離開(kāi)了這些信息,很可能就無(wú)法
正常使用接口函數(shù)或者是接口變量。但是總的原則是:不該讓外界知道的信息就不應(yīng)該出現(xiàn)在頭文件里,
而外界調(diào)用模塊內(nèi)接口函數(shù)或者是接口變量所必須的信息就一定要出現(xiàn)在頭文件里,否則,外界就無(wú)法正
確的調(diào)用我們提供的接口功能。因而為了讓外部函數(shù)或者文件調(diào)用我們提供的接口功能,就必須包含我們
提供的這個(gè)接口描述文件--即頭文件。同時(shí),我們自身模塊也需要包含這份模塊頭文件(因?yàn)槠浒四?/p>
塊源文件中所需要的宏定義或者是結(jié)構(gòu)體),好比我們平常所用的文件都是一式三份一樣,模塊本身也需要
包含這個(gè)頭文件。
卜面我們來(lái)定義這個(gè)頭文件,一般來(lái)說(shuō),頭文件的名字應(yīng)該與源文件的名字保持一致,這樣我們便可以清
晰的知道哪個(gè)頭文件是哪個(gè)源文件的描述。
于是便得到了LCD.C的頭文件LCD.h其內(nèi)容如下。
#ifndef_LCD_H_
#define_LCD_H_
externLcdPutChar(charcNewValue);
#endif
這與我們?cè)谠次募卸x函數(shù)時(shí)有點(diǎn)類似。不同的是,在其前面添加了extern修飾符表明其是一個(gè)外
部函數(shù),可以被外部其它模塊進(jìn)行調(diào)用。
#ifndef_LCD_H_
#define_LCD_H_
#endif
這個(gè)幾條條件編譯和宏定義是為了防止重復(fù)包含。假如有兩個(gè)不同源文件需要調(diào)用
LcdPutChar(charcNewValue)這個(gè)函數(shù),他們分別都通過(guò)#加111(10"Lcd.h”把這個(gè)頭文件包含了進(jìn)去。在第,
個(gè)源文件進(jìn)行編譯時(shí)候,由于沒(méi)有定義過(guò)_LCD_H_因此#ifndef_LCD_H_條件成立,于是定義_LCD_H_
并將下面的聲明包含進(jìn)去。在第二個(gè)文件編譯時(shí)候,由于第一個(gè)文件包含時(shí)候,已經(jīng)將_LCD_H_定義過(guò)了。
因此#ifndef_LCD_H_不成立,整個(gè)頭文件內(nèi)容就沒(méi)有被包含。假設(shè)沒(méi)有這樣的條件編譯語(yǔ)句,那么兩個(gè)
文件都包含了externLcdPutChar(charcNewValue);就會(huì)引起重復(fù)包含的錯(cuò)誤。
不得不說(shuō)的typedef
很多朋友似乎了習(xí)慣程序中利用如下語(yǔ)句來(lái)對(duì)數(shù)據(jù)類型進(jìn)行定義
#defineuintunsignedint
#defincucharunsignedchar
然后在定義變量的時(shí)候直接這樣使用
uintg_nTimeCounter=0;
不可否認(rèn),這樣確實(shí)很方便,而且對(duì)于移植起來(lái)也有一定的方便性。但是考慮下面這種情況你還會(huì)這
么認(rèn)為嗎?
#definePINTunsignedint*〃定義unsignedint指針類型
PINTg_npTimcCounter,g_npTimeState;
那么你到底是定義了兩個(gè)unsignedint型的指針變量,還是一個(gè)指針變量,一個(gè)整形變量呢?而你的
初衷又是什么呢,想定義兩個(gè)unsignedint型的指針變量嗎?如果是這樣,那么估計(jì)過(guò)不久就會(huì)到處抓狂
找錯(cuò)誤了。
慶幸的是C語(yǔ)言已經(jīng)為我們考慮到了這?點(diǎn)。typedef正是為此而生。為了給變量起?個(gè)別名我們可以
用如下的語(yǔ)句
typedefunsignedintuintl6;〃給指向無(wú)符號(hào)整形變量起一個(gè)別名uintl6
typedefunsignedint*puintl6;〃給指向無(wú)符號(hào)整形變量指針起一個(gè)別名puintl6
在我們定義變量時(shí)候便可以這樣定義了:
uint16g_nTimeCounter=0;〃定義?個(gè)無(wú)符號(hào)的整形變量
puint16g_npTimeCountcr;〃定義?個(gè)無(wú)符號(hào)的整形變量的指針
在我們使用51單片機(jī)的C語(yǔ)言編程的時(shí)候,整形變量的范圍是16位,而在基于32的微處理下的整形變
量是32位。倘若我們?cè)?位單片機(jī)下編寫(xiě)的"些代碼想要移植到32位的處理器匕那么很可能我們就需
要在源文件中到處修改變量的類型定義。這是一件龐大的工作,為了考慮程序的可移植性,在一開(kāi)始,我
們就應(yīng)該養(yǎng)成良好的習(xí)慣,用變量的別名進(jìn)行定義。
如在8位單片機(jī)的平臺(tái)下,有如卜一個(gè)變量定義
uint!6g_nTimeCounter=0;
如果移植32單片機(jī)的平臺(tái)下,想要其的范圍依舊為16位。
可以直接修改uiml6的定義,即
typedefunsignedshortintuintl6;
這樣就可以了,而不需要到源文件處處尋找并修改。
將常用的數(shù)據(jù)類型全部采用此種方法定義,形成一個(gè)頭文件,便于我們以后編程直接調(diào)用。
文件名MacroAndConst.h
其內(nèi)容如下:
#ifndef_MACRO_AND_CONST_H_
#define_MACRO_AND_CONST_H_
typedefunsignedintuinl!6;
typedefunsignedintUINT;
typedefunsignedintuint;
typedefunsignedintUINT16;
typedefunsignedintWORD;
typedefunsignedintword;
typedefintint16;
typedefintINT16;
typedefunsignedlonguint32;
typedefunsignedlongUINT32;
typedefunsignedlongDWORD;
typedefunsignedlongdword;
typedeflongint32;
typedeflongINT32;
typedefsignedcharint8;
typedefsignedcharINT8;
typedefunsignedcharbyte;
typedefunsignedcharBYTE;
typedefunsignedcharuchar;
typedefunsignedcharUINT8;
typedefunsignedcharuint8;
typedefunsignedcharBOOL;
#endif
至此,似乎我們對(duì)于源文件和頭文件的分工以及模塊化編程有那么一點(diǎn)概念了。那么讓我們趁熱打鐵,將
上一章的我們編寫(xiě)的LED閃爍函數(shù)進(jìn)行模塊劃分并重新組織進(jìn)行編譯。
在上一章中我們主要完成的功能是P0口所驅(qū)動(dòng)的LED以1Hz的頻率閃爍。其中用到了定時(shí)器,以及LED
驅(qū)動(dòng)模塊。因而我們可以簡(jiǎn)單的將整個(gè)工程分成三個(gè)模塊,定時(shí)器模塊,LED模塊,以及主函數(shù)
對(duì)應(yīng)的文件關(guān)系如下
main.c
Timer.hOTimcr.c—
Led.hOLed.c—
在開(kāi)始重新編寫(xiě)我們的程序之前,先給大家講一下如何在KEIL中建立工程模板吧,這個(gè)模板是我一直沿
用至今。希望能夠給大家?點(diǎn)啟發(fā)。
下面的內(nèi)容就主要以圖片為主了。同時(shí)輔以少量文字說(shuō)明。
我們以芯片AT89S52為例。
OK,到此一個(gè)簡(jiǎn)單的工程模板就建立起來(lái)了,以后我們?cè)傩陆ㄔ次募皖^文件的時(shí)候,就可以直接保存
至Ijsrc文件目錄下面了。
下面我們開(kāi)始編寫(xiě)各個(gè)模塊文件。
首先編寫(xiě)Timer,這個(gè)文件主要內(nèi)容就是定時(shí)器初始化,以及定時(shí)器中斷服務(wù)函數(shù)。其內(nèi)容如屋
#include<reg52.h>
bitg_bSystemTime1Ms=0;//IMS系統(tǒng)時(shí)標(biāo)
voidTimerOInit(void)
(
TMOD&=OxK);
TMOD1=0x01;〃定時(shí)器0工作方式1
THO=Oxfc;〃定時(shí)器初始值
TL0=0x66;
TRO=1;
ETO=1;
voidTimeOIsr(void)interrupt1
(
THO=Oxfc;〃定時(shí)器重新賦初值
TLO=0x66;
g_bSystemllmelMs=1;//IMS時(shí)標(biāo)標(biāo)志位置位
)
由于在Led.c文件中需要調(diào)用我們的g_bSystemTimelMs變量。同時(shí)主函數(shù)需要調(diào)用TimeiOInit。初始化函
數(shù),所以應(yīng)該對(duì)這個(gè)變量和函數(shù)在頭文件里作外部聲明。以方便其它函數(shù)調(diào)用。
Timer.h內(nèi)容如下。
#ifndef_TIMER_H_
#define_TIMER_H_
externvoidTimerOlnit(void);
externbitg_bSystemTime1Ms;
#endif
完成了定時(shí)器模塊后,我們開(kāi)始編寫(xiě)LED驅(qū)動(dòng)模塊。
Led.c內(nèi)容如下:
#include<reg52.h>
#include"MacroAndConst.h"
#include"Led.h"
#include"Timer.h"
staticuinl16g_u16LedTimeCount=0;//LED計(jì)數(shù)器
staticuint8g_u8LedState=0;〃LED狀態(tài)標(biāo)志,0表示亮,1表示熄滅
#defineLEDP0〃定義LED接口
#defineLED_ON()LED=0x00;〃所有LED亮
#defineLED_OFF()LED=Oxff;〃所有LED熄滅
voidLedProcess(void)
(
if(0==g_u8LcdState)〃如果LED的狀態(tài)為亮,則點(diǎn)亮LED
(
LED_ON();
)
else〃否則熄滅LED
(
LED_OFF();
)
}
voidLedStateChange(void)
if(g_bSystemTime1Ms)〃系統(tǒng)IMS時(shí)標(biāo)到
g_bSystemTime1Ms=0;
g_ul6LedTimeCount++;//LED計(jì)數(shù)器加一
if(g_u16LedTimeCount>=500)〃計(jì)數(shù)達(dá)至I」500,即500MS到了,改變LED的狀態(tài)。
(
g_u16LedTimeCount=0;
g_u8LedState=!g^u8LcdState;
)
)
這個(gè)模塊對(duì)外的借口只有兩個(gè)函數(shù),因此在相應(yīng)的Led.h中需要作相應(yīng)的聲明。
Led.h內(nèi)容:
#ifndef_LED_H_
#define_LED_H_
externvoidLedProcess(void);
externvoidLedStateChange(void);
#endif
這兩個(gè)模塊完成后,我們將其C文件添加到工程中。然后開(kāi)始編寫(xiě)主函數(shù)里的代碼。
如下所示:
#include<reg52.h>
#include"MacroAndConst.h"
#include"Timer.h"
#include”Led.h"
sbitLED_SEG=P1A4;〃數(shù)碼管段選
sbitLED_DIG=P1A5;//數(shù)碼管位選
sbitLED_CS11=P1A6;//led控制位
voidmain(void)
(
LED.CSll=1J/74HC595輸出允許
LED_SEG=0;〃數(shù)碼管段選和位選禁止(因?yàn)樗鼈兒蚅ED共用P0口)
LED.DIG=0;
TimerOInit();
EA=1;
while(1)
LedProcess();
LedStateChange();
)
整個(gè)工程截圖如下:
至此,第三章到此結(jié)束。
一起來(lái)總結(jié)?下我們需要注意的地方吧
1.C語(yǔ)言源文件(*.c)的作用是什么
2.C語(yǔ)言頭文件(*.h)的作用是什么
3.typedef的作用
4.工程模板如何組織
5.如何創(chuàng)建一個(gè)多模塊(多文件)的工程
“從單片機(jī)初學(xué)者邁向單片機(jī)工程師”之KEY主題討論
按鍵程序編寫(xiě)的基礎(chǔ)
從這一章開(kāi)始,我們步入按鍵程序設(shè)計(jì)的殿堂。在基于單片機(jī)為核心構(gòu)成的應(yīng)用系統(tǒng)中,用戶輸入是必不
可少的?部分。輸入可以分很多種情況,譬如有的系統(tǒng)支持PS2鍵盤(pán)的接口,有的系統(tǒng)輸入是基于編碼器,
有的系統(tǒng)輸入是基于串口或者USB或者其它輸入通道等等。在各種輸入途徑中,更常見(jiàn)的是,基于單個(gè)按
鍵或者由單個(gè)鍵盤(pán)按照一定排列構(gòu)成的矩陣鍵盤(pán)(行列鍵盤(pán))。我們這一篇章主要討論的對(duì)象就是基于單個(gè)
按鍵的程序設(shè)計(jì),以及矩陣鍵盤(pán)的程序編寫(xiě)。
◎按鍵檢測(cè)的原理
常見(jiàn)的獨(dú)立按鍵的外觀如3相信大家并不陌生,各種常見(jiàn)的開(kāi)發(fā)板學(xué)習(xí)板上隨處可以看到他們的身影。
總共有四個(gè)引腳,一般情況下,處于同一邊的兩個(gè)引腳內(nèi)部是連接在一起的,如何分辨兩個(gè)引腳是否處在
同一邊呢?可以將按鍵翻轉(zhuǎn)過(guò)來(lái),處于同一邊的兩個(gè)引腳,有一條突起的線將他們連接一起,以標(biāo)示它們
倆是相連的。如果無(wú)法觀察得到,用數(shù)字萬(wàn)用表的:極管擋位檢測(cè)一下即可。搞清楚這點(diǎn)非常重要,對(duì)于
我們畫(huà)PCB的時(shí)候的封裝很有益。
它們和我們的單片機(jī)系統(tǒng)的I/O口連接?般如下:
對(duì)于單片機(jī)I/O內(nèi)部有上拉電阻的微控制器而言,還可以省掉外部的那個(gè)上拉電阻。簡(jiǎn)單分析-嚇按鍵
檢測(cè)的原理。當(dāng)按鍵沒(méi)有按下的時(shí)候,單片機(jī)I/O通過(guò)上拉電阻R接到VCC,我們?cè)诔绦蛑凶x取該I/O的
電平的時(shí)候,其值為1(高電平);當(dāng)按鍵S按下的時(shí)候,該I/O被短接到GND,在程序中讀取該I/O的電平
的時(shí)候,其值為0(低電平).這樣,按鍵的按下與否,就和與該按鍵相連的I/O的電平的變化相對(duì)應(yīng)起來(lái)。
結(jié)論:我們?cè)诔绦蛑型ㄟ^(guò)檢測(cè)到該I/O口電平的變化與否,即可以知道按鍵是否被按下,從而做出相應(yīng)的
響應(yīng)。一切看起來(lái)很美好,是這樣的嗎?
◎現(xiàn)實(shí)并非理想
在我們通過(guò)上面的按鍵檢測(cè)原理得出上述的結(jié)論的時(shí)候,其實(shí)忽略了個(gè)重要的問(wèn)題,那就是現(xiàn)實(shí)中按鍵
按下時(shí)候的電平變化狀態(tài)。我們的結(jié)論是基于理想的情況得出來(lái)的,就如同下面這幅按鍵按下時(shí)候?qū)?yīng)電
平變化的波形圖一樣:
而實(shí)際中,由于按鍵的彈片接觸的時(shí)候,并不是一接觸就緊緊的閉合,它還存在一定的抖動(dòng),盡管這個(gè)時(shí)
間非常的短暫,但是對(duì)于我們執(zhí)行時(shí)間以u(píng)s為計(jì)算單位的微控制器來(lái)說(shuō),
它太漫長(zhǎng)了。因而,實(shí)際的波形圖應(yīng)該如下面這幅示意圖樣。
這樣便存在這樣一個(gè)問(wèn)題。假設(shè)我們的系統(tǒng)有這樣功能需求:在檢測(cè)到按鍵按下的時(shí)候,將某個(gè)I/O的狀
態(tài)取反。由于這種抖動(dòng)的存在,使得我們的微控制器誤以為是多次按鍵的按F,從而將某個(gè)I/O的狀態(tài)不
斷取反,這并不是我們想要的效果,假如該I/O控制著系統(tǒng)中某個(gè)重要的執(zhí)行的部件,那結(jié)果更不是我們
所期待的。于是乎有人便提出了軟件消除抖動(dòng)的思想,道理很簡(jiǎn)單:抖動(dòng)的時(shí)間長(zhǎng)度是一定的,只要我們
避開(kāi)這段抖動(dòng)時(shí)期,檢測(cè)穩(wěn)定的時(shí)候的電平不久可以了嗎?聽(tīng)起來(lái)確實(shí)不錯(cuò),而且實(shí)際應(yīng)用起來(lái)效果也還
可以。于是,各種各樣的書(shū)籍中,在提到按鍵檢測(cè)的時(shí)候,總也不忘說(shuō)道軟件消抖。就像下面的偽代碼所
描述的一樣。(假設(shè)按鍵按下時(shí)候,低電平有效)
If(O==io_KeyEnter)//如果有鍵按下了
(
Delayms(20);〃先延時(shí)20ms避開(kāi)抖動(dòng)時(shí)期
If(0==io_KeyEnter)〃然后再檢測(cè),如果還是檢測(cè)到有鍵按下
(
returnKeyValue;〃是真的按卜.了,返回鍵值
)
else
{
returnKEY_NULL〃是抖動(dòng),返回空的鍵值
1
while(0==io_KeyEnter);//等待按鍵釋放
I
乍看上去,確實(shí)挺不錯(cuò),實(shí)際中呢?在實(shí)際的系統(tǒng)中,一般是不允許這么樣做的。為什么呢?首先,這里
的Delayms(20),讓微控制器在這里白白等待了20ms的時(shí)間,啥也沒(méi)干,考慮我在《學(xué)會(huì)釋放CPU》一
章中所提及的幾點(diǎn),這是不可取的。其次while(0==io_KeyEnter)所以合理的分配好微控制的處理時(shí)間,
是編寫(xiě)按鍵程序的基礎(chǔ)。S;更是程序設(shè)計(jì)中的大忌(極少的特殊情況例外)。任何非極端情況卜一都不要使用
這樣語(yǔ)句來(lái)堵塞微控制器的執(zhí)行進(jìn)程。原本是等待按鍵釋放,結(jié)果CPU就?直死死的盯住該按鍵,其它事
情都不管了,那其它事情不干了嗎?你同意別人可不會(huì)同意
◎消除抖動(dòng)有必要嗎?
的確,軟件上的消抖確實(shí)可以保證按鍵的有效檢測(cè)。但是,這種消抖確實(shí)有必要嗎?有人提出了這樣的疑
問(wèn)。抖動(dòng)是按鍵按下的過(guò)程中產(chǎn)生的,如果按鍵沒(méi)有按下,抖動(dòng)會(huì)產(chǎn)生嗎?如果沒(méi)有按鍵按下,抖動(dòng)也會(huì)
在I/OI:出現(xiàn),我會(huì)立刻把這個(gè)微控制器錘了,永遠(yuǎn)不用這樣一款微控制器。所以抖動(dòng)的出現(xiàn)即意味著按
鍵已經(jīng)按下,盡管這個(gè)電平還沒(méi)有穩(wěn)定。所以只要我們檢測(cè)到按鍵按下,即可以返回鍵值,問(wèn)題的關(guān)鍵是,
在你執(zhí)行完其它任務(wù)的時(shí)候,再次執(zhí)行我們的按鍵任務(wù)的時(shí)候,抖動(dòng)過(guò)程還沒(méi)有結(jié)束,這樣便有可能造成
重復(fù)檢測(cè)。所以,如何在返回鍵值后,避免重復(fù)檢測(cè),或者在按鍵一按下就執(zhí)行功能函數(shù),當(dāng)功能函數(shù)的
執(zhí)行時(shí)間小于抖動(dòng)時(shí)間時(shí)候,如何避免再次執(zhí)行功能函數(shù),就成為我們要考慮的問(wèn)題了。這是一個(gè)仁者見(jiàn)
仁,智者見(jiàn)智的問(wèn)題,就留給大家去思考吧。所以消除抖動(dòng)的目的是:防止按鍵?次按下,多次響應(yīng)。
“從單片機(jī)初學(xué)者邁向單片機(jī)工程師''之KEY主題討論
基于狀態(tài)轉(zhuǎn)移的獨(dú)立按鍵程序設(shè)計(jì)
本章所描述的按鍵程序要達(dá)到的目的:檢測(cè)按鍵按下,短按,長(zhǎng)按,釋放。即通過(guò)按鍵的返回值我們可
以獲取到如下的信息:按鍵按下(短按),按鍵長(zhǎng)按,按鍵連一發(fā),按鍵釋放。不知道大家還記得小時(shí)候玩過(guò)
的電子鐘沒(méi)有,就是外形類似于CALL機(jī)(CALL)的那種,有一個(gè)小液晶屏,還有四個(gè)按鍵,功能是時(shí)鐘,
鬧鐘以及秒表。在調(diào)整時(shí)間的時(shí)候,短按+鍵每次調(diào)整值加一,長(zhǎng)按的時(shí)候調(diào)整值連續(xù)增加。小的時(shí)候很好
奇,這樣的功能到底是如何實(shí)現(xiàn)的呢,今天就讓我們來(lái)剖析它的原理吧。S機(jī),好像是很古老的東西了
狀態(tài)在生活中隨處可見(jiàn)。譬如早上的時(shí)候,鬧鐘把你叫醒了,這個(gè)時(shí)候,你便處于清醒的狀態(tài),馬上你就
穿衣起床洗漱吃早餐,這系列事情就是你在這個(gè)狀態(tài)做的事情。做完這些后你會(huì)去等車或者開(kāi)車去上班,
這個(gè)時(shí)候你就處在上班途中的狀態(tài)…中午下班時(shí)間到了,你就處于中午下班的狀態(tài),諸如此類等等,在每
一個(gè)狀態(tài)我們都會(huì)做一些不同的事情,而總會(huì)有外界條件促使我們轉(zhuǎn)換到另外一種狀態(tài),譬如鬧鐘叫醒我
們了,下班時(shí)間到了等等。對(duì)于狀態(tài)的定義出發(fā)點(diǎn)不同,考慮的方向不同,或者會(huì)有些許細(xì)節(jié)上面的差異,
但是大的狀態(tài)總是相同的。生活中的事物同樣遵循同樣的規(guī)律,譬如,用一個(gè)智能充電器給你的手機(jī)電池
充電,剛開(kāi)始,它是處于快速充電狀態(tài),隨著電量的增加,電壓的升高,當(dāng)達(dá)到規(guī)定的電壓時(shí)候,它會(huì)轉(zhuǎn)
換到恒壓充電。總而言之,細(xì)心觀察,你會(huì)發(fā)現(xiàn)生活中的總總都可以歸結(jié)為個(gè)個(gè)的狀態(tài),而狀態(tài)的變換
或者轉(zhuǎn)移總是由某些條件引起同時(shí)伴隨著一些動(dòng)作的發(fā)生。我們的按鍵亦遵循同樣的規(guī)律,下面讓我們來(lái)
簡(jiǎn)單的描繪一下它的狀態(tài)流程轉(zhuǎn)移圖。
下面對(duì)上面的流程圖進(jìn)行簡(jiǎn)要的分析。
首先按鍵程序進(jìn)入初始狀態(tài)SI,在這個(gè)狀態(tài)下,檢測(cè)按鍵是否按"如果有按"則進(jìn)入按鍵消抖狀態(tài)2,
在下?次執(zhí)行按鍵程序時(shí)候,直接由按鍵消抖狀態(tài)進(jìn)入按鍵按下?tīng)顟B(tài)3,在此狀態(tài)下檢測(cè)按鍵是否按下,
如果沒(méi)有按鍵按下,則返回初始狀態(tài)S1,如果有則可以返回鍵值,同時(shí)進(jìn)入長(zhǎng)按狀態(tài)S4,在長(zhǎng)按狀態(tài)下每
次進(jìn)入按鍵程序時(shí)候?qū)Π存I時(shí)間計(jì)數(shù),當(dāng)計(jì)茜arty??瓚匕兄凳焙潁?蝸著轎〈'詞錄?⑸???苯?加醇?琪發(fā)狀態(tài)
S5。如果按鍵鍵值為空鍵,則返回按鍵釋放狀態(tài)S6,否則繼續(xù)停留在本狀態(tài)。在按鍵連一發(fā)狀態(tài)下,如果
按鍵鍵值為空鍵則返回按鍵釋放狀態(tài)S6,如果按鍵時(shí)間計(jì)數(shù)超過(guò)連_發(fā)閾值,則返回連一發(fā)按鍵值,清零時(shí)
間計(jì)數(shù)后繼續(xù)停留在本狀態(tài)。
看了這么多,也許你已經(jīng)有?個(gè)模糊的概念了,下面讓我們趁熱打鐵,?起來(lái)動(dòng)手編寫(xiě)按鍵驅(qū)動(dòng)程序吧。
下面是我使用的硬件的連接圖。
硬件連接很簡(jiǎn)單,四個(gè)獨(dú)立按鍵分別接在P3A0--P3A3四個(gè)I/O上面。
因?yàn)?1單片機(jī)I/O口內(nèi)部結(jié)構(gòu)的限制,在讀取外部引腳狀態(tài)的時(shí)候,需要向端口寫(xiě)1.在51單片機(jī)復(fù)位后,
不需要進(jìn)行此操作也可以進(jìn)行讀取外部引腳的操作。因此,在按鍵的端口沒(méi)有復(fù)用的情況下,可以省略此
步驟。而對(duì)于其它一些真正雙向I/O口的單片機(jī)來(lái)說(shuō),將引腳設(shè)置成輸入狀態(tài),是必不可少的一個(gè)步驟。
下面的程序代碼初始化引腳為輸入。
voidKeyinit(void)
(
io_key_l=1;
io_key_2=1;
io_key_3=1;
io_key_4=1;
)
根據(jù)按鍵硬件連接定義按鍵鍵值
#defineKEY_VALUE_1OxOe
#defineKEY_VALUE_2OxOd
#defineKEY_VALUE_30x0b
#defineKEY_VALUE_40x07
#defineKEY_NULLOxOf
下面我們來(lái)編寫(xiě)按鍵的硬件驅(qū)動(dòng)程序。
根據(jù)第一章所描述的按鍵檢測(cè)原理,我們可以很容易的得出如卜?的代碼:
staticuint8KeyScan(void)
{
if(io_key_l=O)returnKEY_VALUE_1;
if(io_key_2=O)returnKEY_VALUE_2;
if(io_key_3==O)returnKEY_VALUE_3;
if(沁_(dá)key_4=0)retumKEY_VALUE_4;
returnKEY_NULL;
)
其中io_key」等是我們按鍵端口的定義,如下所示:
sbitio_key_l=P3A0;
sbitio_key_2=P3A1;
sbitio_key_3=P3A2;
sbitio_key__4=P3八3;
KeyScan。作為底層按鍵的驅(qū)動(dòng)程序,為匕層按鍵掃描提供一個(gè)接口,這樣我們編寫(xiě)的匕層按鍵掃描函數(shù)可
以幾乎不用修改就可以拿到我們的其它程序中去使用,使得程序復(fù)用性大大提高。同時(shí),通過(guò)有意識(shí)的將
與底層硬件連接緊密的程序和與硬件無(wú)關(guān)的代碼分開(kāi)寫(xiě),使得程序結(jié)構(gòu)層次清晰,可移植性也更好。對(duì)于
單片機(jī)類的程序而言,能夠做到函數(shù)級(jí)別的代碼重用已經(jīng)足夠了。
在編寫(xiě)我們的上層按鍵掃描函數(shù)之前,需要先完成些宏定義。
〃定義長(zhǎng)按鍵的TICK數(shù),以及連一發(fā)間隔的TICK數(shù)
#defineKEY_LONG_PERIOD100
#defineKEY_CONTINUE_PERIOD25
〃定義按鍵返回值狀態(tài)(按卜.,長(zhǎng)按,連一發(fā),釋放)
#defineKEY_DOWN0x80
#defineKEY_LONG0x40
#defineKEY.CONTINUE0x20
#defineKEY_UP0x10
〃定義按鍵狀態(tài)
#defineKEY_STATE_INIT0
#defineKEY_STATE_WOBBLE1
#defineKEY_STATE_PRESS2
#defineKEY_STATE_LONG3
#defineKEY_STATE_CONTINUE4
#defineKEY_STATE_RELEASE5
接著我們開(kāi)始編寫(xiě)完整的上層按鍵掃描函數(shù),按鍵的短按,長(zhǎng)按,連按,釋放等等狀態(tài)的判斷均是在此函
數(shù)中完成。對(duì)照狀態(tài)流程轉(zhuǎn)移圖,然后再看下面的函數(shù)代碼,可以更容易的去理解函數(shù)的執(zhí)行流程。完整
的函數(shù)代碼如下:
voidGetKey(uint8*pKeyValue)
(
staticuint8s_u8KeyState=KEY_S1ATE_INIT;
staticuint8s_u8KeyTimeCount=0;
staticuint8s_u8LastKcy=KEY_NULL;〃保存按鍵釋放時(shí)候的鍵值
uint8KeyTemp=KEY.NULL;
KeyTemp=KeyScan();〃獲取鍵值
switch(s_u8KeyState)
(
caseKEY_S1ATE」NIT:
(
if(KEY_NULL!=(KeyTemp))
(
s_u8KeyState=KEY_S7ATE_WOBBLE;
)
)
break;
caseKEY_STATE_WOBBLE:〃消抖
s_u8KeyState=KEY_STATE_PRESS;
)
break;
caseKEY_S1ATE_PRESS:
{
if(KEY_NULL!=(KeyTemp))
(
s_u8LastKey=KeyTfemp;〃保存鍵值,以便在釋放按鍵狀態(tài)返回鍵值
KeyTemp1=KEY.DOWN;〃按鍵按下
s_u8KeyState=KEY_S1ATE_LONG;
)
else
(
s_u8KeyStatc=KEY_S1ATE_INIT;
1
)
break;
caseKEY_S1ATE_LONG:
(
if(KEY_NULL!=(KeyTemp))
(
if(++s_u8KeyTimeCount>KEY_LONG_PERIOD)
(
s_u8KeyTimeCount=0;
KeyTemp1=KEY_LONG;〃長(zhǎng)按鍵事件發(fā)生
s_u8KeyState=KEY_STATE_CONTINUE;
1
)
else
(
s_u8KeyState=KEY_S1ATE_RELEASE;
}
)
break;
caseKEY_ST\TE_CONTINUE:
(
if(KEY_NULL!=(KeyTemp))
(
if(++s_u8KeyTimeCount>KEY_CONTINUE_PERIOD)
s_u8KeyTimeCount=0;
KeyTemp1=KEY_CONTINUE;
)
1
else
(
s_u8KeyState=KEY_STATE_RELEASE;
)
)
break;
caseKEY_S1ATE_RELEASE:
(
s_u8LastKey1=KEY_UP;
KeyTemp=s_u8LaslKey;
s_u8KeyState=KEY_STATE_INIT;
)
break;
default:break;
)
*pKeyValue=KeyTemp;〃返回鍵值
1
關(guān)于這個(gè)函數(shù)內(nèi)部的細(xì)節(jié)我并不打算花過(guò)多筆墨去講解。對(duì)照著按鍵狀態(tài)流程轉(zhuǎn)移圖,然后去看程序代碼,
你會(huì)發(fā)現(xiàn)其實(shí)思路非常清晰。最能讓人理解透徹的,莫非就是將整個(gè)程序自己看懂,然后想象為什么這個(gè)
地方要這樣寫(xiě),抱著思考的態(tài)度去閱讀程序,你會(huì)發(fā)現(xiàn)自己的程序水平會(huì)慢慢的提高。所以我更希望的是
你能夠認(rèn)認(rèn)真真的看完,然后思考。也許你會(huì)收獲更多。
不管怎么樣,這樣的個(gè)程序已經(jīng)完成了本章開(kāi)始時(shí)候要求的功能:按下,長(zhǎng)按,連按,釋放。事實(shí)上,
如果掌握了這種基于狀態(tài)轉(zhuǎn)移的思想,你會(huì)發(fā)現(xiàn)要求實(shí)現(xiàn)其它按鍵功能,譬如,多鍵按下,功能鍵等等,
亦相當(dāng)簡(jiǎn)單,在下一章,我們就去實(shí)現(xiàn)它。
在主程序中我編寫(xiě)了這樣的一段代碼,來(lái)演示我實(shí)現(xiàn)的按鍵功能。
voidmain(void)
(
uint8KeyValue=KEY_NULL;
uint8temp=0;
LED_CS11=1;〃流水燈輸出允許
LED_SEG=0;
LED.DIG=0;
HmerOInit();
Keylnit();
EA=1;
while(l)
(
TimerOMainLoopO;
KeyMainL()op(&KeyValue);
if(KeyValue==(KEY_VALUE_1IKEY_DOWN))PO=~1;
if(KeyValue==(KEY_VALUE_11KEY_LONG))P0=~2;
if(KeyValue==(KEY_VALUE_11KEY_CONTINUE))(POA=OxfO;}
if(KeyValue==(KEY_VALUE_11KEY_UP))PO=0xa5;
)
按住第一個(gè)鍵,可以清晰的看到PO口所接的LED的狀態(tài)的變化。當(dāng)按鍵按下時(shí)候,第一個(gè)LED燈亮,
等待2s后第二個(gè)LED亮,第一個(gè)熄滅,表示長(zhǎng)按事件發(fā)生。再過(guò)500ms第5~8個(gè)LED閃爍,表示連接
事件發(fā)生。當(dāng)釋放按鍵時(shí)候,P0口所接的LED的狀態(tài)為:
滅亮滅亮亮滅亮滅,這也正是P0=0xa5這條語(yǔ)句的功能。
第四章一一漸明漸暗的燈
看著學(xué)習(xí)板上的LED按照我們的意愿開(kāi)始閃爍起來(lái),你心里是否高興了,我相信你會(huì)的。
但是很快你就會(huì)感覺(jué)到太單調(diào),總是同一個(gè)頻率在閃爍,總是同一個(gè)亮度在閃爍。如果要是
能夠由暗逐漸變亮,然后再由亮變暗該多漂亮啊。嗯,想法不錯(cuò),可以該從什么地方入手呢。
在開(kāi)始我們的工程之前,首先來(lái)了解一個(gè)概念:PWM。
PWM(PulseWidthModulation)是脈沖寬度調(diào)制的英文單詞的縮寫(xiě)。下面這段話是通信百科中
對(duì)其的定義:
脈沖寬度調(diào)制(PWM)是利用微處理器的數(shù)字輸出來(lái)對(duì)模擬電路進(jìn)行控制的一種非常有效的
技術(shù),廣泛應(yīng)用在從測(cè)量、通信到功率控制與變換的許多領(lǐng)域中。脈寬調(diào)制是開(kāi)關(guān)型穩(wěn)壓電
源中的術(shù)語(yǔ)。這是按穩(wěn)壓的控制方式分類的,除了PWM型,還有PFM型和PWM、PFM
混合型。脈寬調(diào)制式開(kāi)關(guān)型穩(wěn)壓電路是在控制電路輸出頻率不變的情況下,通過(guò)電壓反饋調(diào)
整其占空比,從而達(dá)到穩(wěn)定輸出電壓的目的。
讀起來(lái)有點(diǎn)晦澀難懂。其實(shí)簡(jiǎn)單的說(shuō)來(lái),PWM技術(shù)就是通過(guò)調(diào)整一個(gè)周期固定的方波的占
空比,來(lái)調(diào)節(jié)輸出電壓的平均當(dāng)電壓,電流或者功率等被控量。我們可以用一個(gè)水龍頭來(lái)類
比,把1S時(shí)間分成50等份,即每一個(gè)等份20MSo在這20MS時(shí)間里如果我們把水龍頭水
閥一直打開(kāi),那么在這20Ms里流過(guò)的水肯定是最多的,如果我們把水閥打開(kāi)15MS,剩下
的5Ms關(guān)閉水閥,那么流出的水相比剛才20Ms全開(kāi)肯定要小的多。同樣的道理,我們可
以通過(guò)控制20MS時(shí)間里水閥開(kāi)啟的時(shí)間的長(zhǎng)短來(lái)控制流過(guò)的水的多少。那么在1S內(nèi)平均
流出的水流量也就可以被控制了。
當(dāng)我們調(diào)整PWM的占空比時(shí),就會(huì)引起電壓或者電流的改變,LED的明暗狀態(tài)就會(huì)隨之
發(fā)生相應(yīng)的變化,聽(tīng)起來(lái)好像可以通過(guò)這種方法來(lái)實(shí)現(xiàn)我們想要的漸明漸暗的效果。讓我們
來(lái)試一下吧。
大家都知道人眼有一個(gè)臨界頻率,當(dāng)LED的閃爍頻率達(dá)到一定的時(shí)候,人眼就分辨不出LED
是否在閃爍了。就像我們平??措娨曇粯?,看起來(lái)畫(huà)面是連續(xù)的,實(shí)質(zhì)不是這個(gè)樣子,所有
連續(xù)動(dòng)作都是一幀幀靜止的畫(huà)面在1S的時(shí)間里快速播放出來(lái),譬如每秒24幀的速度播放,
由于人眼的視覺(jué)暫留效應(yīng),看起來(lái)畫(huà)面就是連續(xù)的了。同樣的道理,為了讓我們的LED在
變化的過(guò)程中,我們感覺(jué)不到其在閃爍,可以將其閃爍的頻率定在50Hz以上。同時(shí)為了看
起來(lái)明暗過(guò)渡的效果更加明顯,我們?cè)谶@里定義
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年常德c1貨運(yùn)從業(yè)資格證考試內(nèi)容
- 兒童桌子采購(gòu)合同范本
- 鄉(xiāng)鎮(zhèn)飯店轉(zhuǎn)讓合同范本
- 公司房租轉(zhuǎn)租合同范本
- 倉(cāng)庫(kù)裝修合同范本版
- 上海廠房出售合同范本
- 茶器定制合同范本
- 中標(biāo)咨詢合同范本
- 農(nóng)村訂購(gòu)混泥土合同范本
- 代理代工合同范本
- 2025年春季學(xué)期學(xué)校工作計(jì)劃及安排表
- 第一課+追求向上向善的道德【中職專用】中職思想政治《職業(yè)道德與法治》高效課堂(高教版2023·基礎(chǔ)模塊)
- 生豬屠宰獸醫(yī)衛(wèi)生檢驗(yàn)人員理論考試題庫(kù)及答案
- 2024初中數(shù)學(xué)課程標(biāo)準(zhǔn)測(cè)試題(含答案)精華版
- 2024年陜西延長(zhǎng)石油集團(tuán)礦業(yè)公司招聘筆試參考題庫(kù)含答案解析
- 教師的五重境界公開(kāi)課教案教學(xué)設(shè)計(jì)課件案例試卷
- 人教版新教材高一上學(xué)期期末考試數(shù)學(xué)試卷及答案(共五套)
- 市場(chǎng)營(yíng)銷》教案
- 1-6年級(jí)美術(shù)知識(shí)點(diǎn)
- 大洋洲斐濟(jì)群島的成礦地質(zhì)背景_礦床類型及成礦期劃分_徐鳴
- 減數(shù)分裂過(guò)程圖
評(píng)論
0/150
提交評(píng)論