在MAXQ8913微控制器中从RAM执行应用程序

Abstract

MAXQ8913及其它MAXQ®微控制器采用的Harvard存储器映射架构为用户提供了极大的灵活性,可根据需要将不同的物理内存(例如数据SRAM)映射为程序或数据内存空间。在特定环境下,从数据SRAM执行一个程序的部分代码能够提升性能并降低功耗。这些优势都是以应用程序的更加复杂为代价。

概述

MAXQ8913和其它许多MAXQ微控制器一样,也包含了一个基于SRAM的内置数据存储区域,该存储区域可被映射为数据内存空间,或者选择映射为程序内存空间。内置SRAM通常用作数据存储器,而在程序闪存或掩膜ROM中执行大部分程序代码。然而,在特定环境下,从内部SRAM执行有限的部分代码非常有用。

本应用笔记介绍如何配置、装载汇编程序,以便从内部SRAM正确运行,本文还讨论了这种方法的优势和缺点。本应用笔记给出的例程针对MAXQ8913编写,使用基于汇编的MAX-IDE环境。用户可下载本文所涉及的应用程序代码和项目文件。

本文讨论的代码都特别针对MAXQ8913微控制器编写,所介绍的原理和方法也同样适合其它含有可映射为程序空间的内部SRAM的MAXQ微控制器。能够以这种方式执行代码的其它MAXQ微控制器包括:MAXQ2000MAXQ2010MAXQ3210/MAXQ3212

该代码能很好地运行在任何基于MAXQ8913并为MAXQ8913的串口0提供一路串行接口(RS-232或USB至串口转换)的硬件。将一个终端模拟器连接到该串口,并设置为9600波特率、8个数据位、1个停止位、无奇偶校验,即可查看例程的代码输出。

用户可免费下载MAX-IDE环境的最新安装程序包和文档资料。

在RAM中执行代码的优势

通常情况下,MAXQ微控制器的绝大多数应用代码都被设计为在主程序空间执行,主程序空间通常是利用一片大的内部闪存或(对于掩膜ROM器件)用户指定的应用ROM来实现。主程序空间为非易失存储器,所以大多数情况下可用来保存应用程序代码。内部SRAM被用来存储变量、软件栈,以及器件被关闭时不需要保存的类似数据。

然而,对于特定应用,在数据SRAM中执行某些代码具有一定优势。

降低功耗

在大多数MAXQ微控制器中,当在内部SRAM (或固定用途ROM)中执行代码时,相对于程序闪存而言,电源电流会减小。因为闪存在不被存取时可被动态断电,所以这种情况下就能节省功率。如果某个应用程序通常在大部分活动时间内执行非常小的代码量,在SRAM中执行就能大大降低总体功耗。

直接访问主程序空间存储器

通常,从主程序闪存执行的代码不能直接读取保存在主程序闪存中的数据。这种类型的数据可以包括随应用程序数据一起的常量字符串和数据表。若要读取该数据,应用程序必须调用固定用途ROM中的专用数据传递函数。在RAM中执行代码则避开了这一限制,允许利用标准的数据指针直接读取闪存中包含的数据。这就加快了存取操作。若一个小的算法花费大量的时间遍历闪存中存储的查找表或其它常量数据,那么在RAM中执行该算法则能够在非常短的时间内完成运算。

可重写整个闪存

和大多数基于闪存的MAXQ微控制器一样,MAXQ8913中的固定用途ROM含有在应用程序控制下擦除和重写程序闪存的标准函数。该过程能够使用户装载器通过用户指定接口(例如串口、SPI或I²C)重新装载部分或全部应用程序。然而,若用户装载程序位于闪存内,则不能擦除或重写自身所占用的闪存。在RAM中执行用户装载器,可以擦除整个闪存程序空间并重新写入新的代码,包括用户装载器本身。

在RAM中执行代码的缺点

在RAM中执行应用程序代码也存在缺点和限制。有些缺点与具体工作相关,而有些缺点则是MAXQ架构所固有的。

有限的代码空间

RAM一般比程序闪存小得多,这意味着在任何给定时间只能执行少量代码。但有可能在RAM中运行一个例程,然后将其擦除并装载第二个例程,随后再运行第二个例程,依此类推。

代码映射

在RAM中执行代码之前,必须将其复制到RAM。这一过程需要时间和代码空间。此外,代码必须从某个位置复制,所以代码实际上被存储两次:一次在闪存或程序ROM,一次在RAM。即使该代码不是为了在闪存中执行,也必须被存储于其中,从而消耗了额外的空间。

不可直接存取RAM

当在RAM中执行代码时,RAM就不再是可见的数据存储空间。这意味着不能利用数据指针直接从RAM存储单元读取或写入数据。按照在闪存中运行应用代码相同的方式,有可能避开这种限制。利用固定用途ROM数据传递函数(UROM_moveDP0和类似的函数)可对RAM进行读取,以及通过在闪存中写入类似的函数,可直接对RAM进行写操作。然而,这种迂回方法也占用额外的时间和应用程序空间。

编译在RAM中执行的代码

在编写将要在数据RAM中执行的应用代码时,必须要考虑一项主要因素。代码的每个字都将被编译至一个地址并被装载至该地址的闪存中,但是将在不同地址的RAM中执行。例如,如果一段代码被装载至以程序字地址0100h开始的闪存中,并被复制到以数据字地址0100h开始的RAM中,在RAM中就不可能跳至地址0100h来执行代码。在闪存中,地址0100h仍然是代码的地址。在程序空间中,RAM中代码的地址是其数据存储地址加上偏移量A000h,如图1所示。

图1. MAXQ8913在RAM中执行代码时的内存映射
图1. MAXQ8913在RAM中执行代码时的内存映射

为了执行复制到RAM中数据内存地址为0100h的应用程序,必须跳至程序地址A100h。

在RAM中执行代码会为MAX-IDE编译器造成困难。MAX-IDE并不知道将在与编译地址不同的地址执行代码。例如,假设一个例程调用了闪存地址为0080h的subOne,而另一个位于0300h的例程调用了第一个例程。其代码如下所示。

org 0080h

subOne:
   ....perform various calculations...
   ret

...

org 0300h

subTwo:
   call  subOne
   ...and so on...
   
如果两个例程均被复制到RAM并在此执行,将会发生什么? 假设例程均被复制到RAM中与其在闪存中占用的程序地址相同的数据内存地址,那么subOne将位于程序地址A080h,subTwo将位于A300h。

因为“call subOne”所在行与目标端标签subOne之间的距离超过了相对跳转距离(+127/-128个字),所以指令就必须被重新编译为绝对LCALL。然而,编译器所持有的subOne的唯一地址是0080h,所以指令将被编译为“LCALL 0080h”。当subTwo执行时,它将不调用位于RAM中的subOne副本,而是调用位于闪存中的版本。

有两种迂回方法可能解决这种困境。第一种方法也是最简单的方法,即强制编译器始终使用相对跳转和调用,并使例程在RAM中离得足够近,使其能够按照这一方式调用其它例程。总是使用SJUMP和SCALL,而不是JUMP和CALL机器码(使编译器可选择短或长跳转)。这将强制使用指令的相对跳转版本。

然而,这种方法也存在限制。如果在RAM中运行的代码量长于128个字,相对跳转就有可能不足以长到使RAM中的一个例程调用另一个例程。这种情况下的解决方法是通过ORG声明为不同的例程使用固定的地址,然后定义包含其在RAM中的正确地址的等价变量。这些等价变量可被用于LCALL和LJUMP声明中,如下所示。

subOne  equ  0A080h

org 0080h

; subOne
   ....perform various calculations...
   ret

...

org 0300h

subTwo:
   lcall  #subOne
   ...and so on...
这一过程强制编译器为LCALL使用正确的地址。

将代码复制至RAM

在RAM中执行代码之前,必须首先将其复制到RAM。将大量代码从闪存复制到RAM的最简单方式是使用应用ROM copyBuffer函数。该函数的输入参数为两个数据指针(DP[0]和BP[Offs])和一个长度值(LC[0])。它将指定数量的字节/字从源地址DP[0]复制到目标地址BP[Offs];一次可复制最多256个字节/字。

我们的示例应用程序将其开始的512个字从闪存复制到RAM,然后跳转至RAM中的副本开始执行代码。源指针(DP[0])指向程序闪存在应用ROM的内存映射中的地址,从8000h开始。请主意,为了避免无限循环,在复制RAM的代码之后,我们跳转至RAM中副本的部分。

org 0020h                    

copyToRAM:
   move    DPC,   #1Ch       ; Ensure all pointers are operating in word mode.
   move    DP[0], #8000h     ; Start of program flash from UROM's perspective.
   move    BP,    #0         ; Start of data memory.
   move    Offs,  #0         
   move    LC[0], #256       ; The Offs register limits us to a 256-word copy.
   lcall   UROM_copyBuffer

   move    DP[0], #8100h     ; Copy second half.
   move    BP,    #0100h
   move    Offs,  #0
   move    LC[0], #256
   lcall   UROM_copyBuffer

   ljump   #0A040h           ; Begin execution of code from RAM.


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;  Executing from RAM
;;

org 0040h

   move    LC[0], #1000
delayLoop:
   move    LC[1], #8000
   sdjnz   LC[1], $
   sdjnz   LC[0], delayLoop

;; Initialize serial port.

   move    SCON.6, #1        ; Set to mode 1 (10-bit asynchronous).
   move    SMD.1,  #1        ; Baud rate = 16 x baud clock
   move    PR, #009D4h       ; P = 2^21 * 9600/8.000MHz
   move    SCON.1, #0        ; Clear transmit character flag.

数据传递操作

如上所述,在RAM中执行代码时,有两个与内存映射相关的事项发生了变化。第一,程序闪存现在被映射至数据内存。这意味着我们可通过任意数据指针直接从程序闪存读取数据,如下所示。

;; Read the banner string from flash and output it over the serial port.  Since
;; we are running from RAM, we can read from the flash directly without having
;; to use the Utility ROM data transfer functions (moveDP0inc, etc...).

   move    SC.4,  #0
   move    DPC,   #0                  ; Set pointers to byte mode.
   move    DP[0], #(stringData * 2)   ; Point to byte address of string data.

stringLoop:
   move    Acc, @DP[0]++
   sjump   Z, stringEnd
   lcall   #TxChar
   sjump   stringLoop
stringEnd:
   move    DPC, #1Ch         ; Set pointers to word mode.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;  This portion of the code (addresses 200h and higher) will remain in flash.

org 0200h

stringData:
   db      0Dh, 0Ah, "Executing code from RAM....", 00h
请注意,如图1所示,SC.4 (CDA0)位影响将哪一半程序闪存(上半页或下半页)以字节模式映射至数据内存。当使用字模式指针时,整个程序闪存被一次性映射至数据内存。

第二,现在虽然闪存在数据空间可存取,但SRAM不可直接存取。这意味着不能对SRAM的存储单元进行读或写操作,应用程序必须采取迂回的方法。从SRAM存储单元读取数据可按照在闪存中运行的代码从闪存存储单元读取数据相同的方式实现—利用应用ROM数据传递函数(moveDP0inc等)。然而,由于在应用ROM中没有类似的函数可实现直接写操作,所以应用程序必须提供一个小函数驻留在闪存中,该函数可被RAM中驻留的代码直接调用来执行写操作。

以下的代码演示用来读和写RAM变量varA的方法,其初始值随其它部分的应用程序被从闪存复制到RAM,地址范围为0000h-01FFh。

   scall   printVar
   scall   incrVar
   scall   printVar
   scall   incrVar
   scall   printVar
   scall   incrVar

   move    Acc, #0Dh
   lcall   #TxChar
   move    Acc, #0Ah
   lcall   #TxChar
   
   sjump   $


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;  Variables stored in RAM (program) space.  They can be read using the 
;;  Utility ROM data transfer functions (such as UROM_moveDP0) and written 
;;  using the writeDP0 function which remains in flash.
;;

varA:
   dw 'A'


;==============================================================================
;=
;=  printVar
;=
;=  Reads the varA RAM variable value and sends it over the serial port.
;=

printVar:
   move    DPC, #1Ch         ; Word mode
   move    DP[0], #varA      ; Variable's location in UROM data space
   lcall   UROM_moveDP0      ; Moves variable value into GR.
   move    Acc, GR
   lcall   #TxChar
   ret


;==============================================================================
;=
;=  incrVar
;=
;=  Reads the varA RAM variable value, adds 1 to it, and stores it back in RAM.
;=

incrVar:
   move    DPC, #1Ch         ; Word mode
   move    DP[0], #varA      ; Variable's location in UROM data space
   lcall   UROM_moveDP0      ; Moves variable value into GR.

   move    Acc, GR
   add     #1
   move    GR, Acc
   lcall   writeDP0

   ret



;==============================================================================
;=
;=  TxChar
;=
;=  Outputs a character to the serial port.
;=
;=  Inputs  : Acc.L - Character to send.
;=

org 01F0h
   move    SBUF, Acc         ; Send character.
TxChar_Loop:
   move    C, SCON.1         ; Check transmit flag.
   sjump   NC, TxChar_Loop   ; Stall until last transmit has completed.
   move    SCON.1, #0        ; Clear the transmit flag.
   ret


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;  This portion of the code (addresses 200h and higher) will remain in flash.

org 0200h

stringData:
   db      0Dh, 0Ah, "Executing code from RAM....", 00h


;==============================================================================
;=
;=  WriteRAM
;=
;=  This is a routine that can be called by code running in the RAM to load
;=  a new value into a byte or word location in the RAM.
;=
;=  Inputs  : DP[0] - Location to write (absolute starting at 0000h) in RAM.
;=            GR    - Value to write to the RAM location.
;=  
;=  Notes   : DP[0] must be configured to operate in word or byte mode as
;=            desired before calling this function.  Following a call to this
;=            function, DP[0] must be refreshed before it is used to read data.
;=            

writeDP0:
   move    @DP[0], GR
   ret
在执行时,示例代码通过串口输出以下的文字(图2)。

图2. 示例代码通过串口的输出文字
图2. 示例代码通过串口的输出文字

结论

利用MAXQ8913及其它MAXQ微控制器采用的Harvard内存映射架构,可以将不同的物理内存段(例如数据SRAM)映射为程序或数据内存空间。在数据SRAM中执行部分应用程序为性能提升和降低功耗提供了潜力。不过该过程也增加了应用程序的复杂性。