图1. MAXQ8913在RAM中执行代码时的内存映射
为了执行复制到RAM中数据内存地址为0100h的应用程序,必须跳至程序地址A100h。
在RAM中执行代码会为MAX-IDE编译器造成困难。MAX-IDE并不知道将在与编译地址不同的地址执行代码。例如,假设一个例程调用了闪存地址为0080h的subOne,而另一个位于0300h的例程调用了第一个例程。其代码如下所示。
org 0080h
subOne:
....perform various calculations...
ret
...
org 0300h
subTw
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
subTw
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.
;; 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)。