教你學(xué)單片機(jī) 2:用機(jī)器的視角思考
P.S. 因?yàn)檫@些工作確實(shí)很簡單,在網(wǎng)上找份教程看看就會了。
一般剛開始學(xué)一種單片機(jī)的時候,寫的第一個程序都是“點(diǎn)亮第一個LED”。這個程序很經(jīng)典,它代表你已經(jīng)成功學(xué)會操控單片機(jī)的IO端口,學(xué)51單片機(jī)亦是如此。代碼如下(我使用ATMEL 公司的AT89S52):
#include
sbit LED = P1^0 ;
void Delay(unsigned int t)
{
}
void main(void)
{
}
單片機(jī)會從main函數(shù)開始執(zhí)行,所以我們把思緒拉到main函數(shù)。
一開始用了“ LED = 1 ;”,初始化IO端口,讓它設(shè)定在某個狀態(tài)。接下來使用一個while大循環(huán)語句,調(diào)用Delay函數(shù),時間一到就把LED取反,再回到循環(huán),周而復(fù)始。在高級語言里面看起來這個過程貌似很簡單,小學(xué)生都會理解了。但是你知道把它翻譯成機(jī)器碼之后是什么樣的嗎?在這里我不想把產(chǎn)生的匯編代碼貼出來,免得你難以接受。不過我可以把執(zhí)行過程詳細(xì)地講給你聽。
首先,所有的CPU,它們在執(zhí)行指令的時候都是從程序段的0地址(也就是程序最開始的地方)開始的,而且CPU永遠(yuǎn)只做兩件事情,一是從程序區(qū)里取出指令,二是執(zhí)行這條指令,然后再回去取指令。。
這樣說很簡單嘛,把main函數(shù)的代碼從程序區(qū)的0地址開始一條條存放不就行了嗎。其實(shí)不是這樣的,一般0地址里存放的都不會是main函數(shù)的真實(shí)代碼,它會放一條跳轉(zhuǎn)指令(就是一條指令后面跟一個地址,告訴CPU要跳到那個地方去工作),這條指令跟著的是main函數(shù)的入口地址,把單片機(jī)指到main函數(shù)真正的地址去執(zhí)行。為什么要這樣做?看下面的圖:
程序存儲區(qū)的固定前面幾個地址是要用來存放中斷服務(wù)程序的地址的(中斷?后面會講,先不管),稱之為“中斷向量”。程序在執(zhí)行過程中遇到中斷的時候,它就會根據(jù)中斷信號的類型跳回到這些固定的地址,再由這些地址里面存儲的指令指引,跳轉(zhuǎn)到中斷服務(wù)程序真正開始的地方去執(zhí)行。所以,最開始的一系列地址是不能存放另的東西的,不然程序會亂掉。如果你把main函數(shù)定義在這里,沒遇到中斷之前當(dāng)然可以正常運(yùn)行,但如果你在程序中使用了中斷,后果就不堪設(shè)想了??偠灾琺ain不是放在最開始的地方的!
那它放在哪里?理論上只要避開了中斷向量地址的沖突,你可以放在任何地方,你用C語言編寫的時候編譯器會自動處理這個問題,不用你操心,如果以后你要用匯編寫了,你就必須自己定義main函數(shù)的地址了。
好了,那我們就進(jìn)入main函數(shù)里面看看吧。
根據(jù)這段代碼,你覺得第一句應(yīng)該執(zhí)行的語句應(yīng)該是“ LED = 1 ;”,然后是while循環(huán)。。BLA..BLA..
錯了,進(jìn)入main函數(shù)之后首先要做的事情是初始化相關(guān)寄存器。因?yàn)樾酒瑒傞_始工作的時候,寄存器都處于一種未知的狀態(tài),你必須首先賦給它一個初值才行,在這個main函數(shù)中沒有使用變量,所以可以不用初始化內(nèi)存區(qū),但至少CPU必須初始化一個很重要的寄存器:SP堆棧指針。關(guān)于這家伙以后再講,總之就是先初始化。
初始化完畢之后才開始執(zhí)行你的真正的代碼,先讓LED設(shè)置為1,然后進(jìn)入一個循環(huán)結(jié)構(gòu),調(diào)用延時函數(shù)Delay,等待它執(zhí)行完畢之后再回來把LED取反,然后返回繼續(xù)周而復(fù)始地執(zhí)行。
等等,有一個很重要的問題:單片機(jī)是怎么延時的?在延時的時間里它都在干什么?
我們知道,單片機(jī)的一個主要性能就是執(zhí)行速度,也就是一秒鐘能執(zhí)行多少條指令,一般速度越快代表性能越好,比如51單片機(jī)如果你在外面給它接上一個12M Hz的晶振,它的CPU就會以一百萬條/秒的速度工作,即是說它執(zhí)行一條指令要花費(fèi)一百萬分之一秒的時候即一微秒(是不是覺得很快?其實(shí)以現(xiàn)在的標(biāo)準(zhǔn)來說已經(jīng)算很慢了,慢得像烏龜)。按照這個道理,如果我們想要讓單片機(jī)延時,比如延時1ms,我們可以讓CPU空轉(zhuǎn)1000次,因?yàn)镃PU空轉(zhuǎn)一次也需要一條指令的時間。讓它轉(zhuǎn)上1000次之后結(jié)束,那么就相當(dāng)于它延遲了1ms,延時函數(shù)就是這樣寫出來的,工作原理如果下圖:
這就是Delay函數(shù)的作用,但是這樣的延時不能做到很精確,因?yàn)樵谶@段時間里CPU要執(zhí)行判斷、賦值等等一系列指令,在C語言寫的代碼里面你不能準(zhǔn)確預(yù)測到編譯器會把你這段代碼轉(zhuǎn)換為怎么樣的匯編指令,所以你也就無法計算出精確的變量值,只能在一個大概的范圍里面選取。雖然C語言寫起程序來很方便,但同時它的缺點(diǎn)也顯現(xiàn)在這里:無法控制編譯器寫出精確的延遲函數(shù),在有些對時間要求很高的場合里C語言無法勝任,只能用匯編來寫;同時用C語言寫出的程序產(chǎn)生的機(jī)器碼一般都比用匯編寫的要多,即不夠精簡,效率不高,在有些單片機(jī)里面程序存儲容量不高的話就比較麻煩,不過現(xiàn)在的單片機(jī)程序存儲容量普遍都比較高了。這樣說來用C語言來寫一些對時間性要求不高的程序是很有優(yōu)勢的,開發(fā)周期比匯編要高得多。
評論