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

描述

本应用笔记演示了一种在数据存储器中实现软堆栈的简单方法,用于基于汇编的应用。该方法使用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系列应用开发和调试环境。

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

最大 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的微控制器上直接实现数据存储器中的替换软堆栈。这种软堆栈通过允许子例程更加模块化和可重用来帮助开发更大的基于程序集的应用程序。堆栈还允许检测基于堆栈的错误。

审核编辑:郭婷

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分