MAX-IDEでのデータセグメント値の自動的な初期化

要約

このアプリケーションノートは、MAXQ®マイクロコントローラのアプリケーションプログラミング用にMAX-IDEが提供している、コード/データセグメント機能について解説します。コード/データセグメントのメカニズムによって、データメモリ内の変数の位置を自動的に宣言して、それらの変数を開始時の値に初期化するための方法を提供します。その後、アプリケーションコードを使用してそれらの変数値をフラッシュメモリにキャッシュして、必要に応じて復元することができます。このアプローチによって、マイクロコントローラがJTAGデバッガに接続されている場合も接続されていない場合も一貫して動作する形で、MAX-IDEが提供するデータセグメントの自動ロード機能をアセンブリ言語ベースのアプリケーションで利用することが可能になります。MAXQ2000マイクロコントローラのEVキットを使用してこの方法の具体例を示し、本文中でコード例を示します。

概要

MAXQアセンブリ言語アプリケーションの変数は、作業レジスタ(アキュムレータA[0]~A[15]など)またはデータメモリ(SRAM)のいずれかに格納することができます。変数をデータメモリに格納することで、より大きな作業領域がアプリケーション変数に与えられますが、より多くのアクセスタイムが要求されます。

MaxQAsmアセンブラとMAX-IDE環境は、独立したコードセグメントとデータセグメントを宣言するメカニズムを備えており、それぞれのセグメントについて独立したhex出力ファイルが生成されます。実行時には、MAX-IDEが自動的にコードセグメントファイルをプログラムメモリ(通常はフラッシュ)に、データセグメントファイルをデータメモリ(通常はRAM)にロードします。しかし、データメモリは揮発性であるため、マイクロコントローラの電源をオフにした後にデータセグメントの内容がそのまま残ることはありません。

このアプリケーションノートでは、MAXQ2000のEV (評価)キットを使用して、第1にアプリケーションの初回実行時にこれらのあらかじめロードされたデータメモリ値をフラッシュに保存する方法を示し、第2にその後マイクロコントローラが再起動された時にフラッシュからデータセグメント値を復元する方法を示します。この2段階のプロセスによって、アプリケーションが開発中(JTAGアダプタとMAX-IDEに接続された状態)の場合も、フィールドで動作中の場合も、同一のデータセグメントメカニズムを使用して変数の宣言と初期化を行うことが可能になります。

このアプリケーションノートのデモ用コードはMAXQ2000マイクロコントローラとMAXQ2000のEVキット向けに記述されていますが、以下で示すコードと原則は、書換え可能なプログラムフラッシュメモリを備えたMAXQ20ベースの任意のマイクロコントローラに適用することができます。

MAX-IDE環境の最新のインストールパッケージとドキュメントを、無償でダウンロードすることができます。

変数と記憶域の位置

一般的な組込みアプリケーションは、状態に関する情報、設定内容、中間的な計算値、ループカウンタ、計算結果などを格納するための、一定量の作業スペースを必要とします。この作業スペースに格納される値は一般に変数と呼ばれ、以下のような共通の特性があります。

  1. 一時的であること。停電やリセットにっよってアプリケーションが中断された場合、保存の必要はありません。
  2. アクセスと更新が頻繁に行われること。読取りや書込みを素早く行うことができる場所に格納する必要があります。書込み回数に制限がある場所を使用することはできません。
  3. 初期値が定義されている場合が多いこと。アプリケーションの開始時に、ユーザーのコードで特定の値を設定する必要があります。
Cまたは他の高水準言語で記述され、アセンブリコードにコンパイルされるアプリケーションの場合、変数用のスペースの割当て(および変数をあらかじめ定義された開始値に初期化するプロセス)は一般的にコンパイラが自動的に管理します。この場合、ユーザーは変数とその型、および(必要に応じて)その初期値を宣言するだけでよく、後のことはコンパイラが処理してくれます。
   unsigned int c = 0x1234;
   
しかし、MAXQのアセンブリ言語で直接アプリケーションを記述する場合は、変数用のスペースの割当てと変数に対する初期値の設定を明示的に行う必要があります。この詳細な作業によって、MAXQマイクロコントローラで利用可能なリソースをより厳密に管理することができますが、システムの複雑さが多少増大することになります。

小規模なアセンブリ言語ベースのアプリケーションや、大量の作業スペースを必要としないアプリケーションの場合、内部レジスタを使用してすべてのアプリケーション変数を格納することができます。このアプローチには、2つの重要なメリットがあります。

  1. コンパクトで高速なコード。レジスタ変数の読取り、書込み、または他のレジスタ変数へのコピーは、最小1命令サイクルで実行可能です(レジスタの位置に依存します)。MAXQ20ベースのマイクロコントローラでは、通常はワーストケースでも最大2命令サイクルしか必要とされません。
  2. 変数に対する直接演算。一部の内部レジスタ位置は、直接演算が可能です。たとえば、16の作業アキュムレータA[0]~A[15]は、いずれもアクティブなアキュムレータAccとして(APレジスタを使用して)選択することができます。すなわち、これらのレジスタの1つに格納されている変数に対して演算を実行する必要が生じた場合、値をレジスタの外部にコピーして、演算を実行し、また値をレジスタ内にコピーする必要はなく、そのレジスタに対して直接演算を実行することができます。同様に、LC[0]およびLC[1]レジスタに格納されている変数は、djnz命令を実行することによって直接ループカウンタとして使用することができます。
より大規模なアプリケーションや、多数の作業変数を必要とするアプリケーションの場合、変数の一部またはすべてをSRAMベースのデータメモリに格納することにメリットがあります。この方法によって、最大ではデータメモリのサイズによる限界まで、大幅に多数の変数を作ることができます。この方法で格納された変数に対するアクセスは、バイトサイズまたはワードサイズの変数の読み書きに使用可能な、MAXQ20コアの標準データポインタの1つを使用して行います(注:このアプリケーションノートのコード例は、すべてDP[0]がワードモードで動作する設定になっていることを前提としています)。
   move   DP[0], #0010h      ; Location of variable in data memory
   move   Acc, @DP[0]        ; Read variable
   add    #1                 ; Increment variable value by 1
   move   @DP[0], Acc        ; Store variable back in data memory
   
変数に対して一連の長い計算を実行する必要がある場合、上のコード例で示すように、その変数の値を作業レジスタにコピーすると便利です。途中の演算はすべてその作業レジスタを使用して行い、計算が完了した時点で値を元の変数にコピーします。

MAX-IDEでのセグメント宣言

アプリケーション変数をSRAMベースのデータメモリに格納することに決定した場合、変数をどこに格納するかの決定はどのように行うのでしょうか?

一般的には、デバッガが使用する最上位の32バイトを除いて、すべてのデータメモリをアプリケーションで利用することができます。したがって、変数の宣言は、単にデータメモリ中でその変数用の場所を定義することに相当します。その後は、変数の読み書きが行われるごとに、この場所がコードによって使用されることになります。#defineマクロを使用して、変数の位置にシンボル名を結び付けることができます。

#define VarA  #0020h
#define VarB  #0021h
#define VarC  #0022h

   move   DP[0], VarA        ; Point to VarA variable
   move   Acc, @DP[0]        ; Read value of variable
   move   DP[0], VarB        ; Point to VarB variable
   move   @DP[0], Acc        ; Copy VarA to VarB
   move   DP[0], VarC        ; Point to VarC variable
   move   @DP[0], #1234h     ; Set VarC = 1234h

このアプローチは十分に役立ちますが、いくつか問題もあります。
  • 各変数の位置を前もって決定しておく必要があります。この作業には時間がかかり、特に、後からすべての変数をデータメモリ内の別の領域に移動することになった場合が問題です。
  • 誤って同じ場所を2つ以上の変数に使用しないように注意する必要があります。この誤りを犯すと、バグの追跡が困難になります。
  • 変数の初期値(開始時の値)がある場合は、上の例の最後の行のように、アプリケーションコードで明示的にロードする必要があります。この方法で初期化する変数が多数存在する場合、この処理によって大量のコードスペースが消費される可能性があります。
より効率的なアプローチとして、独立したコードセグメントとデータセグメントを宣言するMAX-IDEのメカニズムを利用する方法があります。この方法では、アセンブリコードファイルのどの部分をコードスペース用に使い、どの部分をデータスペース用にするかを、アプリケーションの作成者が指定することが可能になります。
segment code

   move  DP[0], #VarA       ; Point to VarA
   move  Acc, @DP[0]        ; Get current value of VarA
   add   #1                 ; Increment it
   move  @DP[0], Acc        ; Store value back in VarA

segment data

VarA:
   dw    0394h              ; Initial value for VarA

上のアプローチの場合、データセグメント内で宣言した変数のアドレスは、コードスペースのラベルに対するアドレスの付与に使用されるのと同じ方法で、ファイルをパースする際にアセンブラによって自動的に決定されます。これらの変数アドレスにシンボル名を付与するためにラベルが使用され、dwおよびdbステートメントを使用してワードサイズとバイトサイズの変数を開始時の値で初期化することができます。この場合、このアセンブリファイルにはこれより前にsegment dataディレクティブがなかったと仮定すると、アセンブラはアドレス0000hからこのデータセグメントを開始します。したがって、VarAはワードアドレス0000hに格納されることになります。コードスペースの場合と同様に、orgステートメントを使用して、指定したアドレスの先頭に強制的に変数を配置することも可能です。

データセグメントの初期化

前出のコードリストで、変数VarAは(dwステートメントを使用して)初期値が0394hになるように定義されています。しかしこの値は、コード中ではVarAにロードされていません。それでは、この値はどうやって初期化されるのでしょうか?データセグメントの初期化は、プロジェクトのコンパイルと実行の際に、MAX-IDEによって自動的に行われるというのがその答えです。

MaxQAsmアセンブラは、segment dataディレクティブに対して、第2のhex出力ファイルを生成します。通常は、コードデータを含むプロジェクトに対してhexファイルが生成されます。たとえば、「example.prj」というプロジェクトをコンパイルした場合、プロジェクトファイルをアセンブルすることによって生成されたコードデータを含んだ「example.hex」というhexファイルが生成されます。データセグメントが定義されている場合、そのセグメントでアセンブルされたデータが含まれた「example_d.hex」という追加のhexファイルが生成されることになります。

プロジェクトの実行時、MAX-IDEはプロジェクトのコンパイル中にデータセグメントファイル(末尾が_d.hexのもの)が生成されていないか調べます。データセグメントファイルが存在する場合、MAX-IDEは標準のJTAGローダを使用して、このセグメントのデータをデバイスのデータSRAMにロードします。これは、標準のhexファイルがプログラムメモリにロードされた後で行われます。

デバイスがJTAGアダプタに接続されている開発サイクルでは、この方法がうまく機能して、MAX-IDEはアプリケーションの実行前に毎回コードとセグメントデータをリロードします。しかし、デバイスの電源をオフ/オンした後、単独で(デバッガを接続せずに)動作させた場合、MAX-IDEは実行ごとにデータセグメントに適切な値をロードすることができなくなります。以後は変数に所定の値が設定されなくなり、その結果アプリケーションが誤動作する可能性があります。もう一度デバイスをデバッガに接続すると、MAX-IDEは実行前のデータセグメントのロードを再開し、問題が即座に消滅してしまうため、この種の障害は分析が困難な場合があります。

データセグメントの保存と復元

残る問題は、どのようにすれば、アプリケーションがデバッガに接続されていても(毎回MAX-IDEがコードとデータを再ロードする場合も)、自由に動作していても(起動時に特定のRAMの内容が保証されていなくても)、一貫してアプリケーションが動作するようにできるかということです。明らかな解決策として、アプリケーションに変数の値を(初期化が終わった後で)フラッシュメモリに保存させ、リセットまたは起動ごとに値を復元させるという、2段階のプロセスが考えられます。

第1のステップとして、アプリケーションがフラッシュメモリに値を保存する必要があります。この動作は、マスター消去またはコードのロードサイクルの後、アプリケーションが最初に実行される時点で行われます。

  1. アプリケーションは「フラグ」位置をチェックして、変数がまだフラッシュにコピーされていないことを確認します。このフラグは、変数以外の専用の位置を使用するか、または変数の初期値が非ゼロである限り(空のRAM位置と区別するため)その変数と共用することが可能です。
  2. アプリケーションは、個々の変数の値をデータRAMからフラッシュメモリにコピーします。書換え可能なフラッシュを備えたほとんどのMAXQマイクロコントローラ(MAXQ2000など)では、これはUROM_flashWrite関数を使用して行われます。
  3. アプリケーションは、変数が格納されたことを示すために、フラッシュメモリにフラグを書き込みます。
第2のステップとして、以後の実行時には、アプリケーションが変数の値をフラッシュメモリからデータRAM内の所定の位置に復元する必要があります。
  1. アプリケーションはフラッシュ内のフラグ位置をチェックして、変数の値が格納されていることを確認します。
  2. アプリケーションは、UROM_copyBufferルーチンを使用して、変数の値をフラッシュメモリからデータRAM内の適切な位置にコピーします。
以下に示すコードリストは、MAXQ2000のEVキットでこの保存/復元方式を例示したものです。このコードでは、変数の値をフラッシュメモリのアドレス7000h~71FFhに格納しています。
$include(maxQ2000.inc)

;; Code memory (flash) : 0000h-7FFFh  (word addr)
;; Data memory (RAM)   : 0000h-03FFh  (word addr)

org 0000h

   ljump   start             ; Skip over password area

org 0020h

start:
   move    DPC, #1Ch         ; Set all pointers to word mode
   move    DP[0], #0F000h    ; Check first variable value (flag)
   lcall   UROM_moveDP0      ; 'move GR, @DP[0]' executed by Utility ROM
   move    Acc, GR
   cmp     #1234h
   jump    NE, copyToFlash

;; This is the "free-running" code, executed on subsequent power-ups, that copies
;; values from the flash back into their proper data segment locations.

   move    DP[0], #0F000h    ; Source: Flash location 7000h
   move    BP,    #0         ; Dest:   Start of RAM
   move    Offs,  #0
   move    LC[0], #100h      ; Copy 256 words
   lcall   UROM_copyBuffer

   jump    main

;; This is the first-pass code. A bit of a trick here; because MAX-IDE enters
;; and exits the loader separately when loading the code and data segment files,
;; the application is allowed to execute briefly before the data segment file
;; has been loaded. The first four lines under copyFlash ensure that the 
;; application waits for MAX-IDE to load the data segment file before continuing.

copyToFlash:
   move    DP[0], #0h        ; Wait for flag variable to be loaded by MAX-IDE.
   move    Acc, @DP[0]       ;    Note that this will reset the application; the
   cmp     #1234h            ;    data segment is not loaded while the application
   jump    NE, copyToFlash   ;    is still running.

   move    DP[0], #0         ; Start of RAM variable area
   move    A[4],  #7000h     ; Location in flash to write to
   move    LC[0], #100h      ; Store 256 words in flash 7000h-70FFh

copyToFlash_loop:
   move    DP[0], DP[0]      ; Refresh the data pointer to read values correctly,
                             ;    because calling UROM_flashWrite changes memory
                             ;    contexts and affects the cached @DP[0] value
   move    A[0], A[4]        ; Location to write
   move    A[1], @DP[0]++    ; Value to write (taken from RAM)
   lcall   UROM_flashWrite
   move    Acc, A[4]
   add     #1
   move    A[4], Acc
   djnz    LC[0], copyToFlash_loop

main:
   move    PD0,   #0FFh      ; Set all port 0 pins to output
   move    PO0,   #000h      ; Drive all port 0 pins low (LEDs off)

   move    DPC,   #1Ch       ; Set pointers to word mode
   move    DP[0], #varA
   move    Acc,   @DP[0]
   cmp     #1234h            ; Verify that the variable is set correctly
   jump    NE, fail

pass:
   move    PO0, #55h
   sjump   $

fail:
   sjump   $

segment data

org 0000h

varA:   
   dw  1234h

org 00FFh

varB:
   dw  5678h

end

結論

MAX-IDEによって提供されるコード/データセグメント機能は、データメモリ内の変数の位置を自動的に宣言して、それらの変数を開始時の値で初期化する方法を提供します。その後、アプリケーションのコードを使用してそれらの変数の値をフラッシュメモリにキャッシュして、必要に応じて復元することができます。このアプローチによって、アセンブリ言語ベースのアプリケーションでMAX-IDEが提供するデータセグメントの自動ロードを利用するとともに、マイクロコントローラがJTAGデバッガに接続されていても接続されていなくても一貫して動作させることが可能になります。