本應用筆記演示了一種在數據存儲器中實現軟堆棧的簡單方法,用于基于匯編的應用。該方法使用MAXQ2000和其他基于MAXQ20的微控制器。示例代碼使用MAX-IDE的宏預處理功能編寫,MAX-IDE是Maxim基于項目的MAXQ?系列應用開發和調試環境。
概述
MAXQ2000微控制器與MaximRISC微控制器系列中的其他MAXQ器件一樣,基于MAXQ20內核。基于MAXQ20的微控制器通常實現16位寬的硬件堆棧,其電平數固定(MAXQ2000為16級),存儲在獨立于數據和代碼空間的專用內部存儲器中。此硬件堆棧用于在子例程調用和中斷操作中保存和恢復微控制器的操作狀態。
雖然完全適合小型、緊密集中的應用程序,但在較大的裝配應用程序中使用深度嵌套的子例程(或在堆棧上保存和恢復多個工作寄存器的子例程)時,硬件堆棧很快就會耗盡空間。用C編程語言編寫的應用程序(使用IAR的嵌入式工作臺?等編譯器)或Rowley Associates的MAXQ交叉工作通過利用數據存儲器中包含的“軟堆棧”來避免這個問題。此軟堆棧存儲子例程的調用/返回地址和本地工作變量。但是,MAXQ20內核上沒有內置機制來定位數據存儲器中的堆棧,可用于僅組裝應用。
本應用筆記演示了一種在數據存儲器中實現軟堆棧的簡單方法,用于基于匯編的應用。本應用筆記中的代碼可用于MAXQ2000和其他基于MAXQ20的微控制器。示例代碼使用MAX-IDE的宏預處理功能編寫,MAX-IDE是Maxim基于項目的MAXQ系列應用開發和調試環境。
最大 IDE 安裝 (ZIP)
MAXQ磁芯組裝指南 (PDF)
開發工具指南 (PDF)
MAXQ20內核中的硬件堆棧操作
MAXQ20內核使用兩種不同類型的堆棧操作:
PUSH 操作(包括操作碼 PUSH、LCALL 和 SCALL)用于在堆棧上存儲數據。這些操作將堆棧指針 SP 預遞增 1,然后將數據存儲在 SP 指針 (@SP) 指向的堆棧位置。
POP 操作(包括操作碼 POP、POPI、RE 和 RETI)用于從堆棧中檢索數據。這些操作從 SP 指向的堆棧位置檢索數據,然后將堆棧指針后減 1。
由于硬件堆棧的主要功能之一是在調用子例程時保存和恢復地址,因此堆棧由 16 位(字)位置組成。此寬度允許在單次推送或彈出操作中保存或恢復 16 位指令指針 (IP) 寄存器。即使使用 PUSH 或 POP 將 8 位寄存器(如 AP)保存到堆棧或從堆棧恢復,每個堆棧操作也始終使用整個 16 位字。
除了利用堆棧的各種操作碼外,微控制器還自動使用硬件堆棧的另外兩種情況:
當中斷被處理時,當前程序執行點在中斷服務例程(由中斷向量寄存器 IV 指向)開始執行之前被推送到堆棧上。
當調用某些調試命令(如讀寄存器和寫入數據存儲器)需要執行實用程序ROM中的代碼才能完成時,在將控制權傳輸到實用程序ROM之前,當前執行點被推送到堆棧上。實用程序 ROM 中的調試例程完成后,執行點將從堆棧中彈出,并恢復處理器的先前狀態。
向量到中斷服務例程或執行調試器命令將始終需要使用硬件堆棧。由于此行為嵌入在硬件中,因此無法解決它。但是,對于更常見的 PUSH/POP 和 CALL/RET 指令對,可以實現軟堆棧。
注意,當出現以下任一堆棧錯誤情況時,MAXQ2000(與其它基于MAXQ20內核的器件一樣)不提供任何錯誤檢測或警告:
堆棧溢出:當值被推送到已經滿的堆棧上時發生。此錯誤會導致堆棧中最舊的值被覆蓋。
堆棧下溢:當堆棧為空時,從堆棧中彈出值時發生。此錯誤會導致返回無效的數據值。例如,如果在堆棧為空時執行 RET,則執行將轉移到不正確(可能是隨機)的地址。
在數據存儲器中創建軟堆棧
在數據存儲器中創建軟堆棧的第一步是定義將使用數據存儲器的哪一部分。然后,必須定義用于跟蹤堆棧頂部當前位置的數據存儲器指針(DP[0]、DP[1]或BP[Offs])。注意:請注意,應用軟件不會將專用于堆棧的數據存儲器用于其他目的(例如,變量或緩沖區)。
定義和初始化這種軟堆棧的一種簡單方法涉及BP[Offs]寄存器對和一個等值。
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) 寄存器設置為SS_BASE位置,則可以使用 Offs 寄存器指向堆棧的當前頂部。由于 Offs 寄存器只有 8 位寬,因此硬件會自動將堆棧限制在范圍 (BP .(BP+255))在數據存儲器中。如果在 Offs 等于 255(溢出)時發生推送,或者在 Offs 等于 0(下溢)時發生彈出,則 Offs 寄存器將簡單地繞行到堆棧的另一端。此操作模擬硬件堆棧的運行方式,并允許簡單的軟堆棧實現;這不會檢測下溢和溢出情況。
在使用軟堆棧之前,主應用程序必須調用ss_init例程。它將 BP[Offs] 指針設置為字模式,必須這樣做,因為軟堆棧是作為 16 位寬堆棧實現的。它還將 BP[Offs] 寄存器對指向堆棧的開頭。
軟堆棧操作
使用堆棧的內置操作碼(PUSH、POP、CALL、RET等)不能重新定義為使用軟堆棧,因為它們的含義被硬連接到MAXQ匯編器中。此外,可能仍需要在應用程序的某些部分(如中斷服務例程)中使用標準硬件堆棧。由于這些原因,軟堆棧將由復制標準操作碼的宏訪問。
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 位寄存器,如上所示。請注意,如果推送 16 位值并將該值彈出到 8 位寄存器中,則寄存器中將僅存儲低字節。高字節將丟失。這與內置硬件堆棧的行為相同。
subroutine: mpush A[0] ; Save the current value of A[0] ... ; Code which destroys A[0] mpop A[1] ; Restore A[0] mret
mcall 宏的使用方式與 CALL op 代碼用于執行子例程的方式相同。此子例程必須使用 mret 宏(而不是標準 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
擴展軟堆棧的大小
某些應用需要大于 256 個級別的軟堆棧。通過使用其他數據指針(DP[0] 或 DP[1])之一,可以實現任何大小的堆棧(最多可達可用數據存儲器的限制)。
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
上面顯示的代碼保留數據存儲器中的位置 0000h 到 01FFh,從而創建一個最多可容納 511 個級別的堆棧。(堆棧內存空間中的一個位置未使用;這允許更短、更高效地實現 mpush/mpop/mcall/mret 宏)。
只需更改數據指針并將代碼中的其他所有內容保留相同即可擴展堆棧。盡管如此,由于DP[0]不限于數據存儲器的某個范圍,因此沒有什么可以阻止推送或彈出操作使DP[0]增加或減少到軟堆棧的指定邊界之外。為了避免這種情況,可以添加一些簡單的下溢/溢出檢查。
添加下溢和溢出檢查
為了使宏(每次使用宏時都會擴展為代碼)盡可能短,下溢和溢出檢查在子例程中執行,這些子例程由宏使用硬件堆棧調用。
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
上面的代碼導致在推送或彈出(或調用或重新)操作發生之前檢查當前堆棧位置。如果檢測到溢出或下溢錯誤,響應將因應用程序而異。通常,這種錯誤只應在應用程序開發期間發生;如果代碼編寫正確,則不應發生這種情況。如果確實發生錯誤,通常應將其視為致命錯誤,就像在使用硬件堆棧時發生下溢/溢出一樣。此錯誤的可能響應包括停止和傳輸錯誤消息或閃爍 LED。開發過程中一個有用的技巧是在這兩個例程中的每一個例程中(即,在“錯誤處理程序應在此處實現”行)中設置一個斷點,以便在發生下溢或溢出時立即反饋。
但是,有時應用程序可以從堆棧下溢或溢出中恢復(例如,通過從頭開始重新加載和重新啟動應用程序子任務)。在這種情況下,可能需要簡單地設置一個標志,指示發生了堆棧錯誤。此類標志的可能候選者包括位于 PSF 寄存器中的兩個通用標志(GPF0 和 GPF1)。由于有兩個位標志可用,其中一個可用于指示溢出,另一個用于指示下溢。
結論
MAX-IDE提供強大的宏預處理功能,允許在MAXQ2000和其他基于MAXQ20的微控制器上直接實現數據存儲器中的替換軟堆棧。這種軟堆棧通過允許子例程更加模塊化和可重用來幫助開發更大的基于程序集的應用程序。堆棧還允許檢測基于堆棧的錯誤。
審核編輯:郭婷
-
微控制器
+關注
關注
48文章
7651瀏覽量
152114 -
存儲器
+關注
關注
38文章
7528瀏覽量
164345 -
編譯器
+關注
關注
1文章
1642瀏覽量
49286
發布評論請先 登錄
相關推薦
評論