应用笔记 4238

在MAXQ2000数据存储器中实现软堆栈


摘要 : 本应用笔记介绍了汇编应用中在数据存储器内实现软堆栈的简单方法。该方法使用了MAXQ2000和其他基于MAXQ20的微控制器。采用了MAX-IDE的宏预处理特性,在Maxim的MAXQ®系列工程应用开发和调试环境中编写了实例代码。

引言

MAXQ2000微控制器与Maxim的RISC微控制器系列MAXQ器件一样,都是基于MAXQ20内核。基于MAXQ20的微控制器通常可实现一个16位宽硬堆栈,其深度固定不变(MAXQ2000为16),存储在与数据和程序空间分开的专用内部存储器中。通过子程序调用和中断,这一硬堆栈可用于保存和恢复微控制器的操作状态。

虽然非常适合紧凑型应用,但是在规模较大的汇编应用中,使用深度嵌套的子程序(或者使用了在堆栈中保存和恢复多个工作寄存器的子程序),硬堆栈会很快耗尽空间。利用数据存储器中的“软堆栈”,以C编程语言(使用IAR的Embedded Workbench®等编译器)或者Rowley Associates的Crossworks for MAXQ编写应用程序可避免这一问题。这一软堆栈存储子程序的调用/返回地址以及本地工作变量。然而,MAXQ20内核没有内置机制来定位数据存储器中的堆栈,这些数据存储器只用在汇编应用中。

本应用笔记介绍了汇编应用中在数据存储器中实现软堆栈的简单方法。应用笔记中的代码可以用于MAXQ2000和其他基于MAXQ20的微控制器。采用了MAX-IDE的宏预处理特性,在Maxim的MAXQ系列工程应用开发和调试环境中编写了实例代码。

可以免费下载MAX-IDE环境最新的安装包和文档:

MAXQ20内核中的硬堆栈工作

MAXQ20内核使用了两种不同类型的堆栈工作模式:
  • PUSH工作(它包括操作代码PUSH、LCALL和SCALL)被用于在堆栈中存储数据。这些操作将堆栈指针SP预增1,然后,把数据存储在SP指针(@SP)指向的堆栈位置。
  • POP工作(它包括操作代码POP、POPI、RET和RETI)被用于从堆栈中恢复数据。这些操作从SP指向的堆栈位置恢复数据,然后,将堆栈指针减1。
硬堆栈的一项主要功能是在调用子程序时保存并恢复地址,因此,堆栈含有16位(字)位置。这一位宽支持在一次push或者pop操作中保存或者恢复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 <reg>

该宏的使用方式和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 <reg>

该宏的使用方式和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 <address>
mret

mcall宏的使用方式和CALL操作代码执行子程序相同。一旦执行完成,子程序必须使用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
上面的代码导致在推入或者弹出(或者call,或者ret)操作之前检查当前的堆栈位置。如果探测到下溢或者上溢错误,不同的应用程序有不同的响应。一般情况下,这类错误只会出现在应用程序开发阶段;如果代码编写正确,不会出现这类错误。如果的确出现了错误,一般将其考虑为重大错误,就像使用硬堆栈时出现下溢/上溢错误。对这类错误可能的响应包括暂停,发送错误消息,或者闪烁LED。在开发阶段,一个好的方法是两个程序中的每一个都在MAX-IDE中设置断点(例如,在“Error handler should be implemented here”行中),如果出现下溢或者上溢,立即发出反馈。

但是,应用程序有时候也可以从堆栈下溢或者上溢中恢复(例如,重新装入和从头重新启动应用程序子任务)。在这种情况下,需要简单地设置一个标志,指示出现了堆栈错误。可选的这类标志包括位于PSF寄存器中的两个通用标志(GPF0和GPF1)。由于有两个位标志,因此,其中一个可以用于指示上溢,另一个用于指示下溢。

结论

MAX-IDE强大的宏预处理功能实现了MAXQ2000以及其他MAXQ20微控制器数据存储器软堆栈的直接替换。这一软堆栈使子程序更加模块化,可重复使用,有助于大规模汇编应用程序的开发。堆栈还支持堆栈错误的探测。