本应用笔记演示了一种在数据存储器中实现软堆栈的简单方法,用于基于汇编的应用。该方法使用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的微控制器上直接实现数据存储器中的替换软堆栈。这种软堆栈通过允许子例程更加模块化和可重用来帮助开发更大的基于程序集的应用程序。堆栈还允许检测基于堆栈的错误。
审核编辑:郭婷
全部0条评论
快来发表一下你的评论吧 !