引言
MAXQ2000微控制器與Maxim的RISC微控制器系列MAXQ器件一樣,都是基于MAXQ20內(nèi)核。基于MAXQ20的微控制器通常可實(shí)現(xiàn)一個(gè)16位寬硬堆棧,其深度固定不變(MAXQ2000為16),存儲(chǔ)在與數(shù)據(jù)和程序空間分開的專用內(nèi)部存儲(chǔ)器中。通過子程序調(diào)用和中斷,這一硬堆棧可用于保存和恢復(fù)微控制器的操作狀態(tài)。雖然非常適合緊湊型應(yīng)用,但是在規(guī)模較大的匯編應(yīng)用中,使用深度嵌套的子程序(或者使用了在堆棧中保存和恢復(fù)多個(gè)工作寄存器的子程序),硬堆棧會(huì)很快耗盡空間。利用數(shù)據(jù)存儲(chǔ)器中的“軟堆棧”,以C編程語言(使用IAR的Embedded Workbench?等編譯器)或者Rowley Associates的Crossworks for MAXQ編寫應(yīng)用程序可避免這一問題。(關(guān)于這些開發(fā)工具的詳細(xì)信息,請參考:開發(fā)工具)。這一軟堆棧存儲(chǔ)子程序的調(diào)用/返回地址以及本地工作變量。然而,MAXQ20內(nèi)核沒有內(nèi)置機(jī)制來定位數(shù)據(jù)存儲(chǔ)器中的堆棧,這些數(shù)據(jù)存儲(chǔ)器只用在匯編應(yīng)用中。
本應(yīng)用筆記介紹了匯編應(yīng)用中在數(shù)據(jù)存儲(chǔ)器中實(shí)現(xiàn)軟堆棧的簡單方法。應(yīng)用筆記中的代碼可以用于MAXQ2000和其他基于MAXQ20的微控制器。采用了MAX-IDE的宏預(yù)處理特性,在Maxim的MAXQ系列工程應(yīng)用開發(fā)和調(diào)試環(huán)境中編寫了實(shí)例代碼。
可以免費(fèi)下載MAX-IDE環(huán)境最新的安裝包和文檔:
- MAX-IDE安裝(ZIP)
- MAXQ內(nèi)核匯編指南(PDF)
- 開發(fā)工具指南(PDF)
MAXQ20內(nèi)核中的硬堆棧工作
MAXQ20內(nèi)核使用了兩種不同類型的堆棧工作模式:- PUSH工作(它包括操作代碼PUSH、LCALL和SCALL)被用于在堆棧中存儲(chǔ)數(shù)據(jù)。這些操作將堆棧指針SP預(yù)增1,然后,把數(shù)據(jù)存儲(chǔ)在SP指針(@SP)指向的堆棧位置。
- POP工作(它包括操作代碼POP、POPI、RET和RETI)被用于從堆棧中恢復(fù)數(shù)據(jù)。這些操作從SP指向的堆棧位置恢復(fù)數(shù)據(jù),然后,將堆棧指針減1。
除了利用堆棧的各種操作代碼之外,還有兩種其他的情況,在這些情況下,微控制器自動(dòng)使用硬堆棧:
- 當(dāng)處理中斷時(shí),開始執(zhí)行中斷服務(wù)程序(中斷矢量寄存器IV所指)之前,當(dāng)前程序執(zhí)行點(diǎn)被推入堆棧。
- 當(dāng)調(diào)用某些調(diào)試命令(例如,讀寄存器和寫數(shù)據(jù)存儲(chǔ)器)時(shí),需要執(zhí)行完程序ROM中的代碼,在控制被傳送給程序ROM之前,當(dāng)前的執(zhí)行點(diǎn)被推入堆棧。一旦執(zhí)行完程序ROM中的調(diào)試程序,執(zhí)行點(diǎn)從堆棧中彈出,恢復(fù)處理器以前的狀態(tài)。
注意,當(dāng)以下任一堆棧錯(cuò)誤狀態(tài)出現(xiàn)時(shí),MAXQ2000 (和其他的基于MAXQ20內(nèi)核的器件一樣)并不支持任何錯(cuò)誤探測或者報(bào)警:
- 堆棧上溢:將數(shù)值推入已經(jīng)滿的堆棧時(shí)發(fā)生。這一錯(cuò)誤導(dǎo)致堆棧中最早的數(shù)值被覆寫。
- 堆棧下溢:從空堆棧中彈出數(shù)值時(shí)發(fā)生。這一錯(cuò)誤導(dǎo)致返回?zé)o效數(shù)據(jù)值。例如,如果堆棧空時(shí)執(zhí)行RET,執(zhí)行后將返回一個(gè)不正確的(有可能是隨機(jī)的)地址。
在數(shù)據(jù)存儲(chǔ)器中建立一個(gè)軟堆棧
在數(shù)據(jù)存儲(chǔ)器中建立一個(gè)軟堆棧的第一步是定義要使用數(shù)據(jù)存儲(chǔ)器的哪一部分。然后,必須定義跟蹤堆棧頂當(dāng)前位置的數(shù)據(jù)存儲(chǔ)器指針(DP[0]、DP[1]或者BP[Offs])。注意:應(yīng)用軟件不會(huì)出于其他目的使用堆棧專用數(shù)據(jù)存儲(chǔ)器(例如,變量或者緩沖)。定義并初始化這類軟堆棧的一種簡單方法涉及到BP[Offs]寄存器對和一個(gè)公式。
SS_BASE equ 0100h ss_init: move DPC, #1Ch ; Set all pointers to word mode move BP, #SS_BASE ; Set base pointer to stack base location move Offs, #0 ; Set stack to start ret如果基指針(BP)寄存器被設(shè)置在SS_BASE位置,那么,可以采用Offs寄存器指向堆棧的當(dāng)前頂部。由于Offs寄存器只有8位寬,硬件會(huì)自動(dòng)把堆棧限制在數(shù)據(jù)存儲(chǔ)器的范圍內(nèi)(BP .. (BP+255))。如果當(dāng)Offs等于255 (上溢)時(shí)出現(xiàn)推入,或者Offs等于0 (下溢)時(shí)出現(xiàn)彈出,那么,Offs寄存器只會(huì)限制在堆棧的另一端。這一行為模仿硬堆棧工作方式,支持簡單的軟堆棧實(shí)現(xiàn);它不探測下溢和上溢狀態(tài)。
在使用軟堆棧前,主程序必須調(diào)用ss_init例程。它將BP[Offs]指針設(shè)置為字模式,由于軟堆棧以16位寬堆棧實(shí)現(xiàn),因此,必須完成這一步。它還將BP[Offs]寄存器對指向堆棧開始。
軟堆棧工作
不能重新定義使用堆棧的內(nèi)置操作代碼(PUSH、POP、CALL、RET等)以使用軟堆棧,這是因?yàn)檫@些代碼意味著MAXQ匯編器的硬線連接。而且,還有可能需要在應(yīng)用程序中的某些部分使用標(biāo)準(zhǔn)硬堆棧,例如,中斷服務(wù)例程等。出于這些原因,由復(fù)制標(biāo)準(zhǔn)操作代碼的宏來訪問軟堆棧。mpush MACRO Reg move @BP[++Offs], Reg ; Push value to soft stack endm mpop MACRO Reg move Reg, @BP[Offs--] ; Pop value from soft stack endm mcall MACRO Addr LOCAL return move @BP[++Offs], #return ; Push return destination to soft stack jump Addr return: endm mret MACRO jump @BP[Offs--] ; Jump to popped destination from soft stack endm下面將討論這些宏是怎樣工作的。
mpush
該宏的使用方式和PUSH操作代碼相同。它支持將8位或者16位寄存器,或者將立即值推入堆棧。 mpush A[0] ; Save the value of the A[0] register mpush A[1] ; Save A[1] mpush A[2] ; Save A[2] ... ; code which destroys A[0]-A[2] mpop A[2] ; Restore the value of A[2] (pop in reverse order) mpop A[1] ; Restore A[1] mpop A[0] ; Restore A[0]
mpop
該宏的使用方式和POP操作代碼相同。它支持從堆棧中裝入8位或者16位寄存器,如上所示。注意,如果推入了一個(gè)16位值,該值彈入到一個(gè)8位寄存器中,那么,只有低位字節(jié)被存儲(chǔ)在寄存器中。高位字節(jié)丟失。這與內(nèi)置硬堆棧的方式一致。 subroutine: mpush A[0] ; Save the current value of A[0] ... ; Code which destroys A[0] mpop A[1] ; Restore A[0] mret
mcall
mret
mcall宏的使用方式和CALL操作代碼執(zhí)行子程序相同。一旦執(zhí)行完成,子程序必須使用mret宏(而不是標(biāo)準(zhǔn)RET操作代碼)才能返回。 mcall mySub ... mySub: mpush A[0] ; Save A[0] mpush A[1] ; Save A[1] ... ; Perform calculations, etc. mpop A[1] mpop A[0] mret
擴(kuò)展軟堆棧的大小
某些應(yīng)用程序需要大于256級的軟堆棧。使用其他數(shù)據(jù)指針(DP[0]或者DP[1])之一可以實(shí)現(xiàn)任意大小的堆棧(最大達(dá)到數(shù)據(jù)存儲(chǔ)器的上限)。SS_BASE equ 0000h SS_TOP equ 01FFh ss_init: move DPC, #1Ch ; Set all data pointers to word mode move DP[0], #SS_BASE ; Set pointer to stack base location ret上面的代碼存儲(chǔ)在數(shù)據(jù)存儲(chǔ)器的0000h至01FFh,因此,產(chǎn)生的堆棧可以保持511級。(沒有使用堆棧存儲(chǔ)器空間中的某一位置;這樣可以更有效,以更短的代碼實(shí)現(xiàn)mpush/mpop/mcall/mret宏。)
只改變數(shù)據(jù)指針,而代碼中的其他部分保持不變可以擴(kuò)展堆棧。雖然如此,由于DP[0]并沒有限制在數(shù)據(jù)存儲(chǔ)器的一定范圍內(nèi),因此,沒有措施來防止推入或者彈出操作使DP[0]增加或者減小導(dǎo)致超出所指定的軟堆棧邊界。為避免這一點(diǎn),可以加入簡單的下溢/上溢檢查。
加入下溢和上溢檢查
為保持宏(每次使用時(shí)擴(kuò)展為代碼)盡量短,在子程序中完成下溢和上溢檢查,使用硬堆棧,由宏調(diào)用子程序。mpush MACRO Reg call ss_check_over ; Check for possible overflow move @++DP[0], Reg ; Push value to soft stack endm mpop MACRO Reg call ss_check_under ; Check for possible underflow move Reg, @DP[0]-- ; Pop value from soft stack endm mcall MACRO Addr LOCAL return call ss_check_over ; Check for possible overflow move @++DP[0], #return ; Push return destination to soft stack jump Addr return: endm mret MACRO call ss_check_under ; Check for possible underflow jump @DP[0]-- ; Jump to popped destination from soft stack endm ss_check_under: push A[0] push AP push APC push PSF move APC, #80h ; Set Acc to A[0], standard mode, no auto inc/dec move Acc, DP[0] ; Get current value of stack pointer cmp #SS_BASE jump NE, ss_check_under_ok nop ; < Error handler should be implemented here > ss_check_under_ok: pop PSF pop APC pop AP pop A[0] ret ss_check_over: push A[0] push AP push APC push PSF move APC, #80h ; Set Acc to A[0], standard mode, no auto inc/dec move Acc, DP[0] ; Get current value of stack pointer cmp #SS_TOP jump NE, ss_check_over_ok nop ; < Error handler should be implemented here > ss_check_over_ok: pop PSF pop APC pop AP pop A[0] ret上面的代碼導(dǎo)致在推入或者彈出(或者call,或者ret)操作之前檢查當(dāng)前的堆棧位置。如果探測到下溢或者上溢錯(cuò)誤,不同的應(yīng)用程序有不同的響應(yīng)。一般情況下,這類錯(cuò)誤只會(huì)出現(xiàn)在應(yīng)用程序開發(fā)階段;如果代碼編寫正確,不會(huì)出現(xiàn)這類錯(cuò)誤。如果的確出現(xiàn)了錯(cuò)誤,一般將其考慮為重大錯(cuò)誤,就像使用硬堆棧時(shí)出現(xiàn)下溢/上溢錯(cuò)誤。對這類錯(cuò)誤可能的響應(yīng)包括暫停,發(fā)送錯(cuò)誤消息,或者閃爍LED。在開發(fā)階段,一個(gè)好的方法是兩個(gè)程序中的每一個(gè)都在MAX-IDE中設(shè)置斷點(diǎn)(例如,在“Error handler should be implemented here”行中),如果出現(xiàn)下溢或者上溢,立即發(fā)出反饋。
但是,應(yīng)用程序有時(shí)候也可以從堆棧下溢或者上溢中恢復(fù)(例如,重新裝入和從頭重新啟動(dòng)應(yīng)用程序子任務(wù))。在這種情況下,需要簡單地設(shè)置一個(gè)標(biāo)志,指示出現(xiàn)了堆棧錯(cuò)誤。可選的這類標(biāo)志包括位于PSF寄存器中的兩個(gè)通用標(biāo)志(GPF0和GPF1)。由于有兩個(gè)位標(biāo)志,因此,其中一個(gè)可以用于指示上溢,另一個(gè)用于指示下溢。
評論
查看更多