アプリケーションノート 4458

マイクロコントローラMAXQ8913のRAMからのアプリケーションコード実行


要約: MAXQ8913および他のMAXQ®マイクロコントローラが使用しているハーバードメモリマップアーキテクチャは、異なる物理メモリセグメント(データSRAMなど)を必要に応じてプログラムまたはデータメモリ空間にマッピングすることができる柔軟性をユーザーに提供します。特定の状況下では、アプリケーションの一部をデータSRAMから実行することによって、性能の向上と消費電力の削減が可能になります。これらのメリットには、アプリケーションの複雑性の増大という代償が伴います。

概要

他の多くのMAXQマイクロコントローラ同様、MAXQ8913はデータメモリ空間または(必要に応じて)プログラムメモリ空間にマッピング可能なSRAMベースの内部データメモリ領域を内蔵しています。この内蔵SRAMは一般にはデータメモリとして使用され、コード実行の大部分はプログラムフラッシュまたマスクROM内で行われます。しかし、特定の状況下では、アプリケーションがコードの一定の部分を内蔵SRAMから実行することが有益な場合があります。

このアプリケーションノートは、アセンブリ言語ベースのコードを内蔵SRAMから適切に実行するための設定とロードの方法を説明します。この実行方式のメリットとデメリットについて解説します。このアプリケーションノートのデモ用コードは、アセンブリ言語ベースのMAX-IDE環境を使用して、MAXQ8913向けに記述されています。このアプリケーションノートで取り上げるデモアプリケーションのコードとプロジェクトファイルをダウンロードすることができます。

このアプリケーションノートで解説するコードは、マイクロコントローラMAXQ8913を特に対象としていますが、以下で示す原理と技法は、プログラム空間にマッピング可能な内蔵SRAMを備えた他の任意のMAXQベースのマイクロコントローラにも同じように適用可能です。この方式でコードを実行することができる他のMAXQマイクロコントローラには、MAXQ2000MAXQ2010、およびMAXQ3210/MAXQ3212があります。

このコードは、MAXQ8913のシリアルポート0用のシリアルポートインタフェース(RS-232またはUSB-シリアル)を備えた、MAXQ8913ベースの任意のハードウェア上で正しく実行することができます。端末エミュレータを、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から1つのルーチンを実行した後、それを消去して第2のルーチンをロードする、ということを繰り返すことが可能です。

コードのミラーリングが必要

コードをRAMから実行するためには、その前にRAMにコピーする必要があります。この手順の実行には、時間と、実装のためのコードスペースが必要です。さらに、コードのコピー元が必要であるため、実際には2回(フラッシュまたはROMに1回とRAMに1回)コードを格納することになります。フラッシュからの実行を意図していないコードでもそこに格納する必要があるため、余分なスペースを消費することになります。

RAMへの直接アクセスが不可能

内部RAMからコードを実行する場合、そのRAMはもうデータメモリ空間では可視ではなくなります。すなわち、データポインタを使用してRAM内の位置に対する直接の読取りまたは書込みを行うことができません。この制約は、フラッシュからアプリケーションコードを実行する場合と同様の方法によって回避することが可能です。ユーティリティROMのデータ転送関数(UROM_moveDP0およびそれと類似の関数)を使用してRAMからの読取りを行い、同様の関数をフラッシュに書き込むことによって、RAMに対する間接的な書込みを行ってください。しかし、この回避策には余分な時間とアプリケーションスペースが必要になります。

RAMから実行するコードのアセンブル

データRAMから実行されるアプリケーションコードを記述する場合、1つの重要な要素を理解する必要があります。コードの各ワードがそれぞれ1つのアドレスにアセンブルされ、フラッシュ内のそのアドレスにロードされますが、RAM内ではそれと異なるアドレスで実行されることになるという点です。たとえば、アプリケーションコードの一部がプログラムワードアドレス0100hで始まるフラッシュにロードされ、データワードアドレス0100hで始まるRAMにコピーされる場合、RAM内でコードを実行するためにアドレス0100hにジャンプすることは不可能です。アドレス0100hは、依然としてフラッシュ内のコードのアドレスです。次の図1に示すように、プログラム空間のRAM内のコードのアドレスは、そのデータメモリアドレスにオフセットA000hを加えたものになります。

図1. RAMからコードを実行する場合のMAXQ8913のメモリマップ
図1. RAMからコードを実行する場合のMAXQ8913のメモリマップ

RAMのデータメモリアドレス0100hにコピーしたアプリケーションコードを実行するには、プログラムアドレスA100hにジャンプする必要があります。

RAMからコードを実行する場合、MAX-IDEのアセンブラにとって問題が発生します。MAX-IDEは、アセンブルされたアドレスとは異なるアドレスでコードが実行されることを認識しません。たとえば、フラッシュアドレス0080hから始まるsubOneというルーチンと、0300hに位置してその第1のルーチンを呼び出すもう1つのルーチンがあるとします。このコードを次に示します。
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のコピーではなく、フラッシュ内に位置している方を呼び出すことになります。

このジレンマには、2つの回避策が考えられます。第1の、最も単純な方法は、アセンブラが常に相対ジャンプと相対コールを使用するよう強制するとともに、その形で相互に呼出しが可能なようにRAM内でのルーチン間の距離を短く保つというものです。JUMPおよびCALLオペコード(これらは、ロングジャンプかショートジャンプかの選択をアセンブラが行います)を使用せずに、常にSJUMPとSCALLを使用してください。これによって、強制的に相対ジャンプバージョンの命令が使用されます。

しかし、このアプローチには注意事項があります。RAMから実行するコードの大きさが128ワードよりも長い場合、RAM内の1つのルーチンがもう1つのルーチンを呼び出す上で相対ジャンプでは距離が不足する可能性があります。この場合の解決策としては、ORGステートメントを使用して様々なルーチンのアドレスを固定した上で、それらのルーチンのRAM内での修正済みアドレスを含んだEQUを定義します。これらのEQUを、次に示すように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関数を使用するのが最も簡単です。この関数は、2つのデータポインタ(DP[0]およびBP[Offs])と長さの値(LC[0])を入力として受け取り、指定されたバイト数/ワード数をコピー元DP[0]からコピー先BP[Offs]にコピーします。最大256バイト/ワードを一度にコピーすることが可能です。

デモ用アプリケーションは、それ自体の最初の512ワードをフラッシュからRAMにコピーした後、RAM内のコピーにジャンプしてコードの実行を開始します。コピー元ポインタ(DP[0])は、8000hから始まるユーティリティROMのメモリマップ内でのプログラムフラッシュの位置を指しています。無限ループを回避するために、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からコードを実行する場合にはメモリマップに関して2つの点が変化します。第1に、プログラムフラッシュがデータメモリ内にマッピングされます。したがって、次に示すように、任意のデータポインタを使用することによってデータを直接プログラムフラッシュから読取り可能になります。
;; 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)ビットで決まることに注意してください。ワードモードのポインタを使用する場合は、プログラムフラッシュ全体が一度にデータメモリにマッピングされます。

第2に、フラッシュメモリにはデータ空間内でアクセス可能になるのに対して、SRAMには直接アクセスすることができなくなります。すなわち、SRAMの位置に対するアプリケーションからの読取りまたは書込みは、間接的に行う必要があります。SRAMの位置からの読取りは、フラッシュ内で実行されるコードがフラッシュメモリの位置から読取りを行う場合と同じ方法、すなわちユーティリティROMのデータ転送関数(moveDP0incなど)を使用して行うことができます。しかし、間接書込みについてはユーティリティROMに類似の関数が存在しないため、フラッシュ内にとどまってRAM常駐コードからの呼出しに応じて書込みを行う小さな関数をアプリケーションに含めておく必要があります。

次のコードは、両方の方法を使用してRAM変数varAの読み書きを行う例を示しています。この変数の初期内容は、アドレス範囲0000h~01FFhに位置する残りのアプリケーションコードとともにフラッシュからRAMにコピーされます。
   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マイクロコントローラが使用しているハーバードメモリマップアーキテクチャは、異なる物理メモリセグメント(データSRAMなど)をプログラムまたはデータメモリ空間としてマッピングすることが可能です。アプリケーションの一部をデータSRAMから実行することによって、性能の向上と消費電力の削減の可能性が提供されます。この手順には、アプリケーションの複雑性の増大が要求されます。