教你學(xué)單片機 2:用機器的視角思考
P.S. 因為這些工作確實(shí)很簡(jiǎn)單,在網(wǎng)上找份教程看看就會(huì )了。
一般剛開(kāi)始學(xué)一種單片機的時(shí)候,寫(xiě)的第一個(gè)程序都是“點(diǎn)亮第一個(gè)LED”。這個(gè)程序很經(jīng)典,它代表你已經(jīng)成功學(xué)會(huì )操控單片機的IO端口,學(xué)51單片機亦是如此。代碼如下(我使用ATMEL 公司的AT89S52):
#include
sbit LED = P1^0 ;
void Delay(unsigned int t)
{
}
void main(void)
{
}
單片機會(huì )從main函數開(kāi)始執行,所以我們把思緒拉到main函數。
一開(kāi)始用了“ LED = 1 ;”,初始化IO端口,讓它設定在某個(gè)狀態(tài)。接下來(lái)使用一個(gè)while大循環(huán)語(yǔ)句,調用Delay函數,時(shí)間一到就把LED取反,再回到循環(huán),周而復始。在高級語(yǔ)言里面看起來(lái)這個(gè)過(guò)程貌似很簡(jiǎn)單,小學(xué)生都會(huì )理解了。但是你知道把它翻譯成機器碼之后是什么樣的嗎?在這里我不想把產(chǎn)生的匯編代碼貼出來(lái),免得你難以接受。不過(guò)我可以把執行過(guò)程詳細地講給你聽(tīng)。
首先,所有的CPU,它們在執行指令的時(shí)候都是從程序段的0地址(也就是程序最開(kāi)始的地方)開(kāi)始的,而且CPU永遠只做兩件事情,一是從程序區里取出指令,二是執行這條指令,然后再回去取指令。。
這樣說(shuō)很簡(jiǎn)單嘛,把main函數的代碼從程序區的0地址開(kāi)始一條條存放不就行了嗎。其實(shí)不是這樣的,一般0地址里存放的都不會(huì )是main函數的真實(shí)代碼,它會(huì )放一條跳轉指令(就是一條指令后面跟一個(gè)地址,告訴CPU要跳到那個(gè)地方去工作),這條指令跟著(zhù)的是main函數的入口地址,把單片機指到main函數真正的地址去執行。為什么要這樣做?看下面的圖:

程序存儲區的固定前面幾個(gè)地址是要用來(lái)存放中斷服務(wù)程序的地址的(中斷?后面會(huì )講,先不管),稱(chēng)之為“中斷向量”。程序在執行過(guò)程中遇到中斷的時(shí)候,它就會(huì )根據中斷信號的類(lèi)型跳回到這些固定的地址,再由這些地址里面存儲的指令指引,跳轉到中斷服務(wù)程序真正開(kāi)始的地方去執行。所以,最開(kāi)始的一系列地址是不能存放另的東西的,不然程序會(huì )亂掉。如果你把main函數定義在這里,沒(méi)遇到中斷之前當然可以正常運行,但如果你在程序中使用了中斷,后果就不堪設想了??偠灾?,main不是放在最開(kāi)始的地方的!
那它放在哪里?理論上只要避開(kāi)了中斷向量地址的沖突,你可以放在任何地方,你用C語(yǔ)言編寫(xiě)的時(shí)候編譯器會(huì )自動(dòng)處理這個(gè)問(wèn)題,不用你操心,如果以后你要用匯編寫(xiě)了,你就必須自己定義main函數的地址了。
好了,那我們就進(jìn)入main函數里面看看吧。
根據這段代碼,你覺(jué)得第一句應該執行的語(yǔ)句應該是“ LED = 1 ;”,然后是while循環(huán)。。BLA..BLA..
錯了,進(jìn)入main函數之后首先要做的事情是初始化相關(guān)寄存器。因為芯片剛開(kāi)始工作的時(shí)候,寄存器都處于一種未知的狀態(tài),你必須首先賦給它一個(gè)初值才行,在這個(gè)main函數中沒(méi)有使用變量,所以可以不用初始化內存區,但至少CPU必須初始化一個(gè)很重要的寄存器:SP堆棧指針。關(guān)于這家伙以后再講,總之就是先初始化。
初始化完畢之后才開(kāi)始執行你的真正的代碼,先讓LED設置為1,然后進(jìn)入一個(gè)循環(huán)結構,調用延時(shí)函數Delay,等待它執行完畢之后再回來(lái)把LED取反,然后返回繼續周而復始地執行。
等等,有一個(gè)很重要的問(wèn)題:?jiǎn)纹瑱C是怎么延時(shí)的?在延時(shí)的時(shí)間里它都在干什么?
我們知道,單片機的一個(gè)主要性能就是執行速度,也就是一秒鐘能執行多少條指令,一般速度越快代表性能越好,比如51單片機如果你在外面給它接上一個(gè)12M Hz的晶振,它的CPU就會(huì )以一百萬(wàn)條/秒的速度工作,即是說(shuō)它執行一條指令要花費一百萬(wàn)分之一秒的時(shí)候即一微秒(是不是覺(jué)得很快?其實(shí)以現在的標準來(lái)說(shuō)已經(jīng)算很慢了,慢得像烏龜)。按照這個(gè)道理,如果我們想要讓單片機延時(shí),比如延時(shí)1ms,我們可以讓CPU空轉1000次,因為CPU空轉一次也需要一條指令的時(shí)間。讓它轉上1000次之后結束,那么就相當于它延遲了1ms,延時(shí)函數就是這樣寫(xiě)出來(lái)的,工作原理如果下圖:

這就是Delay函數的作用,但是這樣的延時(shí)不能做到很精確,因為在這段時(shí)間里CPU要執行判斷、賦值等等一系列指令,在C語(yǔ)言寫(xiě)的代碼里面你不能準確預測到編譯器會(huì )把你這段代碼轉換為怎么樣的匯編指令,所以你也就無(wú)法計算出精確的變量值,只能在一個(gè)大概的范圍里面選取。雖然C語(yǔ)言寫(xiě)起程序來(lái)很方便,但同時(shí)它的缺點(diǎn)也顯現在這里:無(wú)法控制編譯器寫(xiě)出精確的延遲函數,在有些對時(shí)間要求很高的場(chǎng)合里C語(yǔ)言無(wú)法勝任,只能用匯編來(lái)寫(xiě);同時(shí)用C語(yǔ)言寫(xiě)出的程序產(chǎn)生的機器碼一般都比用匯編寫(xiě)的要多,即不夠精簡(jiǎn),效率不高,在有些單片機里面程序存儲容量不高的話(huà)就比較麻煩,不過(guò)現在的單片機程序存儲容量普遍都比較高了。這樣說(shuō)來(lái)用C語(yǔ)言來(lái)寫(xiě)一些對時(shí)間性要求不高的程序是很有優(yōu)勢的,開(kāi)發(fā)周期比匯編要高得多。
評論