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環境の最新のインストールパッケージとドキュメントを、無償でダウンロードすることができます。
変数と記憶域の位置
一般的な組込みアプリケーションは、状態に関する情報、設定内容、中間的な計算値、ループカウンタ、計算結果などを格納するための、一定量の作業スペースを必要とします。この作業スペースに格納される値は一般に変数と呼ばれ、以下のような共通の特性があります。
- 一時的であること。停電やリセットにっよってアプリケーションが中断された場合、保存の必要はありません。
- アクセスと更新が頻繁に行われること。読取りや書込みを素早く行うことができる場所に格納する必要があります。書込み回数に制限がある場所を使用することはできません。
- 初期値が定義されている場合が多いこと。アプリケーションの開始時に、ユーザーのコードで特定の値を設定する必要があります。
unsigned int c = 0x1234;しかし、MAXQのアセンブリ言語で直接アプリケーションを記述する場合は、変数用のスペースの割当てと変数に対する初期値の設定を明示的に行う必要があります。この詳細な作業によって、MAXQマイクロコントローラで利用可能なリソースをより厳密に管理することができますが、システムの複雑さが多少増大することになります。
小規模なアセンブリ言語ベースのアプリケーションや、大量の作業スペースを必要としないアプリケーションの場合、内部レジスタを使用してすべてのアプリケーション変数を格納することができます。このアプローチには、2つの重要なメリットがあります。
- コンパクトで高速なコード。レジスタ変数の読取り、書込み、または他のレジスタ変数へのコピーは、最小1命令サイクルで実行可能です(レジスタの位置に依存します)。MAXQ20ベースのマイクロコントローラでは、通常はワーストケースでも最大2命令サイクルしか必要とされません。
- 変数に対する直接演算。一部の内部レジスタ位置は、直接演算が可能です。たとえば、16の作業アキュムレータA[0]~A[15]は、いずれもアクティブなアキュムレータAccとして(APレジスタを使用して)選択することができます。すなわち、これらのレジスタの1つに格納されている変数に対して演算を実行する必要が生じた場合、値をレジスタの外部にコピーして、演算を実行し、また値をレジスタ内にコピーする必要はなく、そのレジスタに対して直接演算を実行することができます。同様に、LC[0]およびLC[1]レジスタに格納されている変数は、djnz命令を実行することによって直接ループカウンタとして使用することができます。
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つ以上の変数に使用しないように注意する必要があります。この誤りを犯すと、バグの追跡が困難になります。
- 変数の初期値(開始時の値)がある場合は、上の例の最後の行のように、アプリケーションコードで明示的にロードする必要があります。この方法で初期化する変数が多数存在する場合、この処理によって大量のコードスペースが消費される可能性があります。
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のステップとして、アプリケーションがフラッシュメモリに値を保存する必要があります。この動作は、マスター消去またはコードのロードサイクルの後、アプリケーションが最初に実行される時点で行われます。
- アプリケーションは「フラグ」位置をチェックして、変数がまだフラッシュにコピーされていないことを確認します。このフラグは、変数以外の専用の位置を使用するか、または変数の初期値が非ゼロである限り(空のRAM位置と区別するため)その変数と共用することが可能です。
- アプリケーションは、個々の変数の値をデータRAMからフラッシュメモリにコピーします。書換え可能なフラッシュを備えたほとんどのMAXQマイクロコントローラ(MAXQ2000など)では、これはUROM_flashWrite関数を使用して行われます。
- アプリケーションは、変数が格納されたことを示すために、フラッシュメモリにフラグを書き込みます。
- アプリケーションはフラッシュ内のフラグ位置をチェックして、変数の値が格納されていることを確認します。
- アプリケーションは、UROM_copyBufferルーチンを使用して、変数の値をフラッシュメモリからデータRAM内の適切な位置にコピーします。
$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デバッガに接続されていても接続されていなくても一貫して動作させることが可能になります。