關(guān)于單片機(jī)的using使用
一、中斷函數(shù)是一個(gè)特殊的函數(shù),沒有參數(shù),也沒有返回值;但是程序中允不允許使用return呢?答案是允許的,不過(guò)只能用"return;",不能用"return(z);";用在一些需要快速返回的地方,對(duì)應(yīng)的匯編會(huì)有多個(gè)ret語(yǔ)句,相對(duì)效率會(huì)高一些。
二、using的用法,using可以修飾任何函數(shù),不過(guò)個(gè)人建議只用來(lái)修飾中斷函數(shù);簡(jiǎn)單的說(shuō),“using”會(huì)指定工作寄存器組,由于中斷函數(shù)一般都是比較緊急的事情,有時(shí)一條語(yǔ)句都會(huì)斤斤計(jì)較,所以使用using切換寄存器組可以省去一些壓棧的動(dòng)作,由于51只有兩級(jí)中斷,同級(jí)中斷不能被打斷,因此,我們可以同級(jí)中斷設(shè)成同樣的寄存器組,從某種意義上來(lái)說(shuō),有一組寄存器是多余的。同時(shí)個(gè)人建議中斷函數(shù)應(yīng)該使用using這個(gè)關(guān)鍵字。
三、中斷中調(diào)用函數(shù),首先要討論中斷函數(shù)中調(diào)用函數(shù)的必要性,前天在論壇上我和別人爭(zhēng)論過(guò)這個(gè)問題,現(xiàn)在我還是這個(gè)觀點(diǎn):有些情況中斷中調(diào)用函數(shù)還是必要的,這個(gè)時(shí)候是不是該調(diào)用函數(shù),其實(shí)和普通函數(shù)差不多,首先是這個(gè)函數(shù)如果調(diào)用多次,或者要帶一些參數(shù)什么的就更加必要的;前天有人跟我叫勁,說(shuō)假如只調(diào)用一次且無(wú)參數(shù)無(wú)返回的函數(shù)要直接寫,因?yàn)槿绻煤瘮?shù),至少會(huì)增加CALL和RET兩條語(yǔ)句,我不敢茍同,我是實(shí)際調(diào)試發(fā)現(xiàn)的,當(dāng)你程序比較復(fù)雜時(shí),你將那部單獨(dú)拉出來(lái)做成函數(shù),可能代碼和時(shí)間都會(huì)更好。
四、中斷中調(diào)用的函數(shù)最好不要被中斷外的其它函數(shù)調(diào)用,因?yàn)闀?huì)出現(xiàn)“重復(fù)調(diào)用”的警告,有時(shí)這種調(diào)用是很致命的,有人說(shuō)這個(gè)函數(shù)可以用reentrant來(lái)修飾,是的,的確可以這樣解決,不過(guò)個(gè)人不建議這么做,也許這樣會(huì)跟你減少很多堆棧空間,并且整個(gè)程序的優(yōu)化要差很多,個(gè)人建議出現(xiàn)這種情況就把這個(gè)函數(shù)寫兩遍,分成兩個(gè)函數(shù)分別調(diào)用。
五、中斷調(diào)用了函數(shù),會(huì)出現(xiàn)一些莫名其妙的問題,一些數(shù)據(jù)不對(duì)(我現(xiàn)在遇到這個(gè)問題)其實(shí)一般是因?yàn)閰R編中使用了絕對(duì)寄存器引起的,有人說(shuō)中斷函數(shù)使用那個(gè)寄存器組,被中斷調(diào)用的函數(shù)就使用哪個(gè)寄存器組(我認(rèn)為好參考C51.PDF:Functions called from an interrupt procedure must function with the same register bank as the interrupt procedure. When the NOAREGS directive is not explicitly specified, the compiler may generate absolute register accesses using the register bank selected (by the using attribute or by the REGISTERBANK control) for that function. Unpredictable results may occur when a function assumes a register bank other than the one currently selected. Refer to “Register Bank Access” on page 124 for more information.),我認(rèn)為這樣不好:
這樣會(huì)增加額外的消耗,使用using會(huì)增加一下語(yǔ)句:
PUSH PSW
MOV PSW, #XX
....
POP PSW
更重要的是,使用using的函數(shù)不能有返回值(這個(gè)地方有問題,應(yīng)該可以有返回值,下文說(shuō)是不能不能返回bit類型的值),這是致命傷(所以這不是致命傷,可以使用using解決這個(gè)問題)
個(gè)人推薦的方法有兩種:
1、使用“#pragma NOAREGS”禁止使用絕對(duì)寄存器
2、使用“#pragme RB(x)”來(lái)指定本文件的工作寄存器組
六、一般說(shuō)來(lái),要求中斷函數(shù)盡可能的短,但也有特殊情況,有些前/后臺(tái)的系統(tǒng)中,就會(huì)把很多相對(duì)重要的事情放到定時(shí)中斷(這個(gè)定時(shí)中斷類似實(shí)時(shí)操作系統(tǒng)中的時(shí)鐘節(jié)拍)去做,而且程序很長(zhǎng)。我單獨(dú)提出來(lái)這點(diǎn)是想告訴大家,中斷函數(shù)也是一個(gè)函數(shù)而已,只要系統(tǒng)有必要,可以做一些看似不合理的事情,該出手時(shí)就出手,就像goto語(yǔ)句一樣。
舉個(gè)例子來(lái)說(shuō):
定義一個(gè)函數(shù)
void func(unsigned char i) {
...
}
有如下一個(gè)中斷函數(shù)
void int_0(void) interrupt 0 using 1 {
....
}
在默認(rèn)狀態(tài)下,func使用寄存器組0(BANK0),那么當(dāng)int_0調(diào)用func時(shí)是否存在當(dāng)傳遞參數(shù)時(shí)會(huì)造成參數(shù)傳遞錯(cuò)誤?
1、中斷服務(wù)函數(shù)使用using指定與主函數(shù)不同的寄存器組(主函數(shù)一般使用Register bank 0)。
2、中斷優(yōu)先級(jí)相同的ISR可用using指定相同的寄存器組,但優(yōu)先級(jí)不同的ISR必須使用不同的寄存器組,在ISR中被調(diào)用的函數(shù)也要使用using指定與中斷函數(shù)相同的寄存器組。(應(yīng)該是這樣的)
3、如果不用using指定,在ISR的入口,C51默認(rèn)選擇寄存器組0,這相當(dāng)于中斷服務(wù)程序的入口首先執(zhí)行指令:
MOV PSW #0
這點(diǎn)保證了,沒使用using指定的高優(yōu)先級(jí)中斷??梢灾袛嗍褂貌煌募拇嫫鹘M的低優(yōu)先級(jí)中斷。
4、使用using關(guān)鍵字給中斷指定寄存器組,這樣直接切換寄存器組而不必進(jìn)行大量的PUSH和POP操作,可以節(jié)省RAM空間,加速M(fèi)CU執(zhí)行時(shí)間。寄存器組的切換,總的來(lái)說(shuō)比較容易出錯(cuò),要對(duì)內(nèi)存的使用情況有比較清晰的認(rèn)識(shí),其正確性要由你自己來(lái)保證。特別在程序中有直接地址訪問的時(shí)候,一定要小心謹(jǐn)慎!至于“什么時(shí)候要用到寄存器組切換”,一種情況是:當(dāng)你試圖讓兩個(gè)(或以上)作業(yè)同時(shí)運(yùn)行,而且它們的現(xiàn)場(chǎng)需要一些隔離的時(shí)候,就會(huì)用上了。在ISR或使用實(shí)時(shí)操作系統(tǒng)RTOS中,寄存器非常有用。
寄存器組使用的原則:
1、8051的最低32個(gè)字節(jié)分成4組8寄存器。分別為寄存器R0到R7。寄存器組由PSW的低兩位選擇。在ISR中,MCU可以切換到一個(gè)不同的寄存器組。對(duì)寄存器組的訪問不可位尋址,C51編譯器規(guī)定使用using或禁止中斷的函數(shù)(#pragma disable)均不能返回bit類型的值。
2、主程序(main函數(shù))使用一組,如bank 0;低中斷優(yōu)先級(jí)的所有中斷均使用第二組,如bank 1;高中斷優(yōu)先級(jí)的所有中斷均使用再另外一組,如bank 2。顯然,同級(jí)別的中斷使用同一組寄存器不會(huì)有問題,因?yàn)椴粫?huì)發(fā)生中斷嵌套;而高優(yōu)先級(jí)的中斷則要使用與低優(yōu)先級(jí)中斷不同的一組,因?yàn)橛锌赡艹霈F(xiàn)在低優(yōu)先級(jí)中斷中發(fā)生高優(yōu)先級(jí)中斷的情況。編譯器會(huì)自動(dòng)判斷何時(shí)可使用絕對(duì)寄存器存取。
3、在ISR中調(diào)用其它函數(shù),必須和中斷使用相同的寄存器組。當(dāng)沒用NOAREGS命令做明確的聲明,編譯器將使用絕對(duì)寄存器尋址方式訪問函數(shù)選定(即用using或REGISTERBANK指定)的寄存器組,當(dāng)函數(shù)假定的和實(shí)際所選的寄存器組不同時(shí),將產(chǎn)生不可預(yù)知的結(jié)果,從而可能出現(xiàn)參數(shù)傳遞錯(cuò)誤,返回值可能會(huì)在錯(cuò)誤的寄存器組中。
舉一例子:當(dāng)需要在中斷內(nèi)和中斷外調(diào)用同一個(gè)函數(shù),假定按照程序的流程控制,不會(huì)出現(xiàn)函數(shù)的遞歸調(diào)用現(xiàn)象,這樣的調(diào)用會(huì)不會(huì)出現(xiàn)問題?若確定不會(huì)發(fā)生重入情況,則有以下兩種情況:
1、如果ISR和主程序使用同一寄存器組(主程序缺省使用BANK 0,若ISR沒有使用using為其指定寄存器區(qū),則缺省也使用BANK 0),則不需其他設(shè)置。
2、如果ISR和主程序使用不同的寄存器組(主程序缺省使用BANK 0,ISR使用using指定了其他BANK),則被調(diào)用函數(shù)必須放在:
#pragma NOAREGS
#pragma AREGS
控制參數(shù)對(duì)中,指定編譯器不要對(duì)該函數(shù)使用絕對(duì)寄存器尋址方式;或者也可在Options->C51,選中“Dont use absolute register accesses”,使所有代碼均不使用絕對(duì)寄存器尋址方式(這樣,執(zhí)行效率將稍有降低)。不論以上的哪一種情況,編譯器均會(huì)給出重入警告,需手工更改OVERLAY參數(shù),做重入說(shuō)明。
3、還有一種辦法:如果被調(diào)用函數(shù)的代碼不是很長(zhǎng),還是將該函數(shù)復(fù)制一份,用不同的函數(shù)名代替,這種情況適合ROM有足夠多余的空間。
因此,對(duì)using關(guān)鍵字的使用,如果沒把握,寧可不用,交給編譯系統(tǒng)自己去處理好了。
例子
用上using可以精簡(jiǎn)代碼,節(jié)省堆棧,不過(guò)有時(shí)會(huì)出現(xiàn)一個(gè)問題:
用上using ,在中斷服務(wù)程序里調(diào)用函數(shù)要小心一點(diǎn),因?yàn)?strong>keil C有時(shí)會(huì)產(chǎn)生依賴絕對(duì)地址的代碼,例如如下函數(shù),功能是從片外的存儲(chǔ)設(shè)備中讀取一個(gè)字節(jié):
uchar ReadByte(uchar address)
{
retrun PBYTE[address];
}
會(huì)被編譯成如下代碼:
MOVR0,0x07
MOVXA,@R0
MOVR7,A
這時(shí),如果在中斷服務(wù)程序里調(diào)用 ReadByte(0xAA); 就會(huì)發(fā)現(xiàn)讀出的數(shù)據(jù)根本不對(duì),因?yàn)閡sing 1使得中斷服務(wù)程序在調(diào)用函數(shù)時(shí)使用第一組寄存器傳遞參數(shù),編譯器生成的代碼如下:
MOVR7,#0xAA
LCALLReadByte
而ReadByte這個(gè)函數(shù)的代碼是使用絕對(duì)地址為0x07的第0組寄存器的R7來(lái)傳遞參數(shù)的,所以會(huì)出問題。
解決方法是在定義ReadByte這個(gè)函數(shù)的前面加上"#pragma noaregs",這樣編譯器就會(huì)生成不依賴于絕對(duì)地址的代碼了,函數(shù)ReadByte被編譯生成的代碼如下:
XCHA,R0
MOVA,R7
XCHA,R0
MOVXA,@R0
MOVR7,A
這樣就可以大膽的使用using了,使用using才是充分利用51架構(gòu)的使用方法。
評(píng)論