avr32-gccの定義済みマクロ
Windows環境でgccの定義済みマクロ一覧を取得する方法。
コマンドプロンプトから以下のコマンドを実行する。
"%AVR32_HOME%\bin\avr32-gcc" -dM -E -xc - < nul
ファイルに出力したいなら、「> 出力ファイル・パス」を追加すればよい。
デバイス依存の値が定義されているため、各デバイス毎の正確な値を知るには更にオプションを追加する必要がある。次はAT32UC3B0256の場合である。
"%AVR32_HOME%\bin\avr32-gcc" -march=ucr1 -mpart=uc3b0256 -dM -E -xc - < nul
デバイス毎の-marchと-mpartの値は、AVR32 StudioでビルドしたときのConsole出力を参考にすればよい。
AT32UC3B0256の定義済みマクロ一覧は以下の通り(avr32-gcc (atmel.1.0.1-(mingw32_special)) 4.3.2の場合)。
マクロ名 | 値 |
---|---|
__DBL_MIN_EXP__ | (-1021) |
__FLT_MIN__ | 1.17549435e-38F |
__DEC64_DEN__ | 0.000000000000001E-383DD |
__CHAR_BIT__ | 8 |
__WCHAR_MAX__ | 2147483647 |
__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4 | 1 |
__DBL_DENORM_MIN__ | 4.9406564584124654e-324 |
__FLT_EVAL_METHOD__ | 0 |
__DBL_MIN_10_EXP__ | (-307) |
__FINITE_MATH_ONLY__ | 0 |
__GNUC_PATCHLEVEL__ | 2 |
__DEC64_MAX_EXP__ | 384 |
__SHRT_MAX__ | 32767 |
__LDBL_MAX__ | 1.7976931348623157e+308L |
__UINTMAX_TYPE__ | long long unsigned int |
__DEC32_EPSILON__ | 1E-6DF |
__CHAR_UNSIGNED__ | 1 |
__LDBL_MAX_EXP__ | 1024 |
__SCHAR_MAX__ | 127 |
__DBL_DIG__ | 15 |
__SIZEOF_INT__ | 4 |
__SIZEOF_POINTER__ | 4 |
__USER_LABEL_PREFIX__ | |
__STDC_HOSTED__ | 1 |
__LDBL_HAS_INFINITY__ | 1 |
__FLT_EPSILON__ | 1.19209290e-7F |
__LDBL_MIN__ | 2.2250738585072014e-308L |
__DEC32_MAX__ | 9.999999E96DF |
__SIZEOF_LONG__ | 4 |
__AVR32_HAS_RMW__ | 1 |
__DECIMAL_DIG__ | 17 |
__LDBL_HAS_QUIET_NAN__ | 1 |
__GNUC__ | 4 |
__FLT_HAS_DENORM__ | 1 |
__SIZEOF_LONG_DOUBLE__ | 8 |
__DBL_MAX__ | 1.7976931348623157e+308 |
__DBL_HAS_INFINITY__ | 1 |
__DEC32_MIN_EXP__ | (-95) |
__LDBL_HAS_DENORM__ | 1 |
__DEC128_MAX__ | 9.999999999999999999999999999999999E6144DL |
__USING_SJLJ_EXCEPTIONS__ | 1 |
__DEC32_MIN__ | 1E-95DF |
__DBL_MAX_EXP__ | 1024 |
__DEC128_EPSILON__ | 1E-33DL |
__LONG_LONG_MAX__ | 9223372036854775807LL |
__SIZEOF_SIZE_T__ | 4 |
__SIZEOF_WINT_T__ | 4 |
__GXX_ABI_VERSION | 1002 |
__FLT_MIN_EXP__ | (-125) |
__DBL_MIN__ | 2.2250738585072014e-308 |
__AVR32_AVR32A__ | 1 |
__AVR32_ELF__ | 1 |
__DEC128_MIN__ | 1E-6143DL |
__REGISTER_PREFIX__ | |
__DBL_HAS_DENORM__ | 1 |
__NO_INLINE__ | 1 |
__FLT_MANT_DIG__ | 24 |
__VERSION__ | "4.3.2" |
__AVR32_HAS_DSP__ | 1 |
__DEC64_EPSILON__ | 1E-15DD |
__DEC128_MIN_EXP__ | (-6143) |
__AVR32__ | 1 |
__SIZE_TYPE__ | long unsigned int |
__AVR32_UC__ | 1 |
__DEC32_DEN__ | 0.000001E-95DF |
__ELF__ | 1 |
__FLT_RADIX__ | 2 |
__LDBL_EPSILON__ | 2.2204460492503131e-16L |
__SIZEOF_PTRDIFF_T__ | 4 |
__FLT_HAS_QUIET_NAN__ | 1 |
__FLT_MAX_10_EXP__ | 38 |
__LONG_MAX__ | 2147483647L |
__FLT_HAS_INFINITY__ | 1 |
__DEC64_MAX__ | 9.999999999999999E384DD |
__DEC64_MANT_DIG__ | 16 |
__DEC32_MAX_EXP__ | 96 |
__DEC128_DEN__ | 0.000000000000000000000000000000001E-6143DL |
__LDBL_MANT_DIG__ | 53 |
__DBL_HAS_QUIET_NAN__ | 1 |
__WCHAR_TYPE__ | int |
__SIZEOF_FLOAT__ | 4 |
__DEC64_MIN_EXP__ | (-383) |
__FLT_DIG__ | 6 |
__INT_MAX__ | 2147483647 |
__FLT_MAX_EXP__ | 128 |
__DBL_MANT_DIG__ | 53 |
__DEC64_MIN__ | 1E-383DD |
__WINT_TYPE__ | unsigned int |
__SIZEOF_SHORT__ | 2 |
__LDBL_MIN_EXP__ | (-1021) |
__LDBL_MAX_10_EXP__ | 308 |
__DBL_EPSILON__ | 2.2204460492503131e-16 |
__SIZEOF_WCHAR_T__ | 4 |
__DEC_EVAL_METHOD__ | 2 |
__INTMAX_MAX__ | 9223372036854775807LL |
__FLT_DENORM_MIN__ | 1.40129846e-45F |
__FLT_MAX__ | 3.40282347e+38F |
__SIZEOF_DOUBLE__ | 8 |
__FLT_MIN_10_EXP__ | (-37) |
__INTMAX_TYPE__ | long long int |
__DEC128_MAX_EXP__ | 6144 |
__GNUC_MINOR__ | 3 |
__DEC32_MANT_DIG__ | 7 |
__DBL_MAX_10_EXP__ | 308 |
__LDBL_DENORM_MIN__ | 4.9406564584124654e-324L |
__STDC__ | 1 |
__AVR32_UC3B0256__ | 1 |
__PTRDIFF_TYPE__ | long int |
__DEC128_MANT_DIG__ | 34 |
__LDBL_MIN_10_EXP__ | (-307) |
__SIZEOF_LONG_LONG__ | 8 |
__LDBL_DIG__ | 15 |
__GNUC_GNU_INLINE__ | 1 |
__avr32__ | 1 |
AVR32のEIC(External Interrupts Controller)
ここではAVR32の例としてAT32UC3B0256を対象に、ドキュメント「AT32UC3B Series Preliminary」(現在はRev.G)を読んでいく。
外部割込端子(NMIやEXTINT)機能がEICの担当で、内蔵周辺モジュール全般をINTC(Interrupt Controller)が担当している。EICも周辺モジュールの1つであり、INTCから見るとNMIもEXTINTも優先度やマスクの扱いが異なるだけで、他のモジュールの割込と何ら変わらない。その辺は「Table 11-3. Interrupt Request Signal Map」からも判る(NMIは割込グループに属さない)。
外部割り込み端子がすべて多機能ピンに割り当てられていて、設定次第ではNMI,EXTINT,SCAN端子をすべて無くすことも出来る。多機能ピンの設定はGPIOの機能だ。ちなみにピン番号/名称と割り当て可能な信号名の組み合わせは、GPIOの章ではなく、Peripheralsの「11.8 Peripheral Multiplexing on I/O lines」にある(怒)。こういうのは「6. Package and Pinout」に載せて欲しいものだ。
どのスリープ・モードでも、すべての割込端子が動作可能だが、クロックが停止するスリープ・モードでは非同期割込を選択しなければならない(後述)。
外部割り込み端子EXTINTを有効にするには、レジスタENの該当ビットへ1を書き込まねばならない。同様に、無効にするにはレジスタDISの該当ビットへ1を書き込む。各割込信号線の状態はCTRLレジスタで観測される。(ブロック図の「Enable」と書かれた1段目の箱。)
割込生成の立ち上がり/立ち下がりエッジあるいはハイ/ロー・レベルをMODE、EDGEそしてLEVELレジスタにより環境設定できる。(ブロック図の「Polarity control」と「Edge/Level Detector」と書かれた箱。)
EDGEの該当ビット | LEVELの該当ビット | ||||
---|---|---|---|---|---|
0 | 1 | 0 | 1 | ||
MODEの該当ビット | 0 | 立ち下がり エッジ | 立ち上がり エッジ | don't care | |
1 | don't care | ロー・ レベル | ハイ・ レベル |
重大な注意が一点。
「16. External Interrupts Controller (EIC)」に記述されている「INT0〜INT7」はすべて「この章内部でのみ有効な名称」と考えること。
INTCで割り込み優先度を表すINT0〜3とは別物である。本来書きたいことは割込要因を識別する名称であり、「11.3 Interrupt Request Signal Map」の「Table 11-3. Interrupt Request Signal Map」にあるEIC0〜EIC7が相当するはずだ。
AVR32の周辺モジュール開発は独立性が高らしく、マニュアルにもモジュール毎にリビジョン番号が振られていて(16 EICには「Rev: 2.3.0.1」と記述有り)、ヘッダファイルもリビジョンに合わせてeic_230.hとなる。AVR32の別製品へ変更しても、モジュールのリビジョンが合っていれば同一動作を期待できる。
想像だが、これが災いして周辺モジュール開発者間の意思疎通が取れず、名称の衝突が起きたのではないか?
ということで、ヘッダファイルにもAVR32_EIC_CTRL_INT0というようなマクロ名が存在し、勝手に変える訳にもいかず、そのままINT0〜7という用語を使用するしかない。
INTCとEICのいずれを指しているか自分で注意する必要がある。
同様に、各割込は割り込み制御と状態のレジスタにそれぞれ対応するビットを持つ。IERレジスタの該当ビットに1を書き込むことは(INTCへの)割込を有効にし、IDRレジスタの該当ビットに1を書き込むことは割込を無効にする。
両レジスタともwrite-onlyなので、最後に1を書き込んだレジスタを覚えておく必要がありそうだが、IMRレジスタを読めば割込が有効かチェックできる。
割込トリガ発生時、ISRレジスタの対応するビットがセットされる。このフラグは、ICRレジスタの対応するビットに1が書き込まれるまでセットされ続ける(1が書かれればクリアされる)。
レベル・トリガ・モードではクロック周期より長いスパイク(訳注:「数クロック周期より短い」の誤りのようだ)を消去するため、レジスタFILTERに1を書くことで有効になるフィルタを含んでいる。
各割込信号は、レジスタASYNCの該当ビットに1を書き込むことにより非同期で生成される。これはEICモジュールの非同期パスを通って信号を送る(ブロック図中央の上側)。
外部割り込みの同期(レジスタASYNCの該当ビットが0)について。
EXTINT端子の取り込みは通常CPUクロックと同期しており、CPUクロック周期より短いスパイク(状のパルス)が割込生成することを保証しない。
ストップ・モードでは32kHz周期より短いスパイクが割込生成することを保証しない。
スタティック・モードでは、同期していない割込のみがアクティブで残っていて、この割込における短いスパイクがデバイスを(スリープから?)ウェイクアップする。
CPUは外部割り込みによりスリープ・モードからウェイクアップすることが出来る。ウェイクアップは2通りの解釈が出来る。レジスタIMRの対応するビットがセットされていれば、実行はこの割込のハンドラから開始する。レジスタIMRのビットがセットされていなければ、実行はsleep命令後の次の命令から開始する。(いずれでもsleep命令後の命令を実行するが、その前に割込ハンドラを実行するか否かの違い。)
NMI(None-Maskable Interrupt)は外部割り込み(EXTINT)と同じ機能をサポートし、同じレジスタを通してアクセスされる。
結局NMIの特徴は、CPU実行モードやSRレジスタのマスク・ビットからマスクできないということであり、EICのレジスタからマスクすることが可能だ。この辺は他社CPUと違う。
外部割込モジュールはキーパッド・スキャン・サポートを内蔵している。キー・パッド・スキャン機能は、キーが押されたときロウが接触したカラムとショートさせられるタイプのキー・パッドと互換性がある。
(ブロック図から読み取りづらいが)SCAN発生部はNMI/EXTINT部と独立していて、レジスタSCANのビットENのセットでSCAN0〜7端子からスキャン信号を発生する。スキャンのステップ周期は同レジスタのPRESCで制御する。使用したくないSCAN端子はGPIOで割り付けなければよい。
キーが押されたとき、プルアップされたロウがカラムによりロー・レベルにドライブされるので、外部割り込みがトリガされる。スキャンは停止し、割込ハンドラはレジスタISRとレジスタSCANのPINSから押されたキーを識別できる。割込ハンドラが割込要因をクリアするとスキャンは再開される。
うーん、キーパッド・スキャンは便利そうだけど本当に使える(実用的)かな?
キーパッド・スキャン機能は、64pinパッケージでのみサポート。割り付けられる多機能ピンも一部がパッケージの反対側にあったりして基板レイアウトには苦労しそう。
32kHzクロックと同期するので、同期モードを使用する。同期モードのフィルタを使用可能だが、32kHzでフィルタしてもチャタリング除去には役に立たない(周期が短い)。チャタリングが発生すると、前回のイベントからの経過時間を求めて...などという処理が必要になりそうだ。
実現したいキー・パッドの仕様に依るが、複数キー同時押しは検出&認識できるが、スキャン一周分の結果から判断したり、キーを離したことを検出しようとすると結局タイマを利用せざるをえなくなる。簡単な仕様なら十分だが。
割込要求をクリアする奨励手順
AVR32のINTC(Interrupt Controller)でドキュメントに
割込要求をクリアする奨励手順は、周辺レジスタを制御するstore命令の後に、同一レジスタからのダミーload命令を続ける。これはパイプライン・ストールを引き起こし、割込要求がクリアされる前に割込マスクがクリアされ割込ハンドラが返るようなケースにおいて、偶然に再度割込が発生することを防止する。
と書いてあるとした。この点についての考察。
問題点は、store命令とrete命令が連続していて、割込をクリア→実際にハードウェアがフラグ・クリアするより早く、割込ハンドラが返ってしまう可能性があることだ。これはパイプラインと関係している。store命令がパイプラインの実行ステージ(EX)にあるとき、rete命令がデコード・ステージ(ID)にいてデコードが完了している可能性が高い。
EXステージが完了する前に、IDステージの命令がEXへ送られることは無いだろうから、割込要因がクリアされてもINTCへ伝達されるまでにかなりの遅延があるのだろう。
一方、rete命令の処理は
だけなのでSRレジスタ(の各割込マスク・ビット)が復旧した時点で即割込要求が発生するかもしれない(2009/11/08追記:これはちょっと間違い。AT32UC3B0256はAVR32Aアーキテクチャなので、割込レベル3〜0のときLR,R12〜8レジスタの復旧が挿入される)。
紹介されているクリア奨励手順のように、同一アドレスへアクセスするld命令を実行すると、クリアが実行されるまでld命令がEXステージで待たされる(ストール)ので、各割込マスク・ビットが復旧しても割込要求が発生しない。
これで問題は発生しないが、気になるのはストールした後、無意味なレジスタ読み出しを実行する点だ。割込ハンドラはできるだけ高速化するべきで、当たり前だが余計な命令を実行したくない。
対処方法としては、割込の本体処理に取りかかる前(あるいは出来るだけ早い段階)に割込要求をクリアすればいいはず。レベル・マスクが掛かっていて、同一レベル以下の割込は入らない。
相違があるとすれば、割込要求クリアから割込ハンドラ終了までの時間が拡がるので、再度割込要求フラグが立つ可能性が高まるくらいか。
ドキュメントの奨励方法通りにしなくてもいいよといいたいのだが、改めて読み直してみると、組み込みでは当たり前の話。
AUTOVECTORの割込ハンドラ・アドレス
AVR32のINTC(Interrupt Controller)の続き。
固定ハンドラ・アドレスやEVBAで指し示す例外ハンドラ・テーブルを除き、その他の割込はINTCのIRPレジスタに格納されたAUTOVECTORから計算される。
AUTOVECTORから割込ハンドラへのアドレスは
EVBA | event_handler_offset
となる。event_handler_offsetはAUTOVECTORのことで、これは14bit(0〜16,383byte)の範囲しかない。このアドレスは、割込ハンドラ・テーブルではなく、ハンドラそのものの開始アドレスであることに注意が必要だ。最も遠くに配置するハンドラはともかく、それより近いハンドラはコンパクトに記述する必要がある。「割込グループから割込要因毎の処理へジャンプするなら、そんなに大きくならないでしょ」という開発者の意図かもしれない。
ハードウェアを簡潔にするためと思われるが、符号付き加算ではなく、論理和演算なので、EVBAが指す割込ハンドラ・テーブルと重なる可能性がある点も要注意だ。EVBAは4バイト・アライメントに乗っていればよいはずなので、極端な話EVBA=0x8000FFFCなどとすると、INT3〜0の割込ハンドラ本体がEVBAの指す割込ハンドラ・テーブルと重複する(リンク・エラーで気づく)。割込ハンドラ・テーブルとINT3〜0の割込ハンドラ本体を同一セクションに定義すると重複することは無いが、割込ハンドラが14bit範囲から外れる可能性がある(何らかのチェックが必要)。
安全を考えれば、割込ハンドラ・テーブルとINT3〜0の割込ハンドラのみ同一セクションに入れる。そして、4つの割込ハンドラの開始アドレスがセクション先頭アドレス | 0x3FFFより小さいことをリンク時に確認すればよい。
さて、そんなチェック、簡単にできるのかな?
AVR32のINTC(Interrupt Controller)
ここではAVR32の例としてAT32UC3B0256で説明する。
ドキュメント「AT32UC3B Series Preliminary」(現在はRev.G)を参照のこと。
「15.2 Block Diagram」を見ると判るように、大雑把に3段構成になっている(NMIを除く)。
1段目で各IRQ信号(IRR0〜nレジスタで参照可能)を、論理ORでグループ化する(interrupt group、割込グループ、信号GrpReg0〜N)。
2段目では、「Request masking」がGrpReg信号をIPRレジスタのINTLEVEL(2bit値)でINT0〜3へ変換する。そして、CPUのマスク信号(I3M〜I0MとGM)によりマスクされる(ブロック図との差異は後述)。
3段目でPrioritizerが、NMIも含め、最も優先度の高い割込を判定し、その割込グループのレベルINTLEVELとベクタAUTOVECTOR(IRPのAUTOVECTORのことで、自動ベクタ生成機能がある訳ではない。ややこしい)がCPUの割込ハンドラとコンテキスト・スイッチへ転送される。
結果的に、個々の割込に対するマスク(レベル)やハンドラは明示できず、割込グループのハンドラにジャンプした後、該当割込グループのIRRを参照して、割込要因を確認する必要がある(確認した上で、割込要求を該当周辺モジュールのレジスタでクリアする)。
ICR0〜3レジスタのCAUSEを読めば割込要因が判るが、このレジスタは割込レベル別である。多重割込への対応だろうが、割込レベルが変われば参照するレジスタも変える必要があり、バグの温床となりやすい(IRPにでも入れればいいのに)。現在の割込レベルを毎回取得するか、初期化登録時とハンドラ参照時で同じレベルで処理するようマクロ化などの配慮が必要である。
NMIのIRPは存在せず、当然AUTOVECTOR情報も無く、AVR32アーキテクチャの例外ベクタで固定されている。
注意:Request maskingはI3M〜I0Mでレベルによるマスクのため、IRPのINTLEVEL情報が必要である。この点でブロック図は誤りがあり、IRPのINTLEVELがRequest maskingへ入力されるべきだ。
実際の割込グループと各割込(ブロック図のINT)の関係は、Table 11-3. Interrupt Request Signal Mapに載っている。ブロック図のINTはこの表の「Line」に該当し、他社CPUのような割込要求信号ピンのことではない。AVR32ではEIC(External Interrupts Controller)のEXTINT端子が他社CPUのINT端子に該当する(NMIも同様)。EXTINT端子は、多重機能ピンで切り替えられるため、パッケージのピン情報ではなく、「11.8 Peripheral Multiplexing on I/O lines」を参照すること(これも他社のマニュアルと違うところだ)。
一度割込グループのハンドラに飛び、割込要因を確認した上で、本当の割込処理へ飛ぶ必要があるというのは、割り込み応答速度の低下になり、スピード命のような組み込みでは嫌だなぁ。
利点としては、割り込みベクタ・テーブルを小さくできることぐらいだが、これもFlushROMを使い切った経験がないので有り難みがない。
有効性があるとしたら、オペレーティング・システム、それもドライバ等のパーツが高度にモジュール化されている場合だろうか?
割り込みベクタ・テーブルを削った分、OSで利用できるし、DLLのハンドラを間接的に呼び出すときは良いかもしれない。
2009/11/08 追記
上記の話は誤り。EVBAはException Vector Base Addressの略なので、H8やSHのようなベクタ・テーブル(C言語的に言えば関数ポインタ配列)と勘違いして「EVBAはベクタ・テーブルを指す」というようなことを書いているが、正しくは「EVBAは例外ハンドラのベース・アドレス」であり、例えばNMIのハンドラ本体がEVBA+0x10に配置されている。次のハンドラ(Instruction Address)がEVBA+0x14 から始まるので、領域は僅か4バイトしかない。それなりの処理を記述するなら、別アドレスへジャンプすることになる。
あれ?そうすると、例外ハンドラ(割込ハンドラを除く)はアセンブラで記述するということか?まあ私はそのつもりなんだけど。
2009/11/04 追記
CPUがINTCから割込を受け取ったときの話が抜けていた。
CPUが割込要求を受け取ったとき、その他の例外が保留になっていないかチェックする。
もし優先度の高い例外が保留されていなければ、割込ハンドラが開始される。割込ハンドラを開始したとき、SRレジスタの対応する割込マスク・ビットが同等以下のレベルに対し自動的にセットされる(例えば割込レベル3なら、SRレジスタのI3M〜I0Mがすべてセットされる)。割込ハンドラ・オフセットは、AUTOVECTORとEVBAから計算され、このアドレスへ処理フローが変更される。
割込ハンドラが返る前に、割込要因の割込要求をクリアすることが、割込ハンドラ・ソフトの責務である。もし割込要因の条件がクリアされないと、割込要求がアクティブのまま残る(再度割込が発生する)。
対応する周辺モジュール・レジスタへの書き込みにより割込要求のクリアは実行され、それから対応するNMIREQ/IREQ信号をクリアする。
割込要求をクリアする奨励手順は、周辺レジスタを制御するstore命令の後に、同一レジスタからのダミーload命令を続ける。これはパイプライン・ストールを引き起こし、割込要求がクリアされる前に割込マスクがクリアされ割込ハンドラが返るようなケースにおいて、偶然に再度割込が発生することを防止する。
AVR32 C Project from templateのcrt0.x
AVR32 StudioでAVR32 C Project from templateからStandalone版のアプリケーションを指定すると、アセンブラ・ファイルsrc/SOFTWARE_FRAMEWORK/UTILS/STARTUP_FILES/GCC/crt0.xが作成される。trampoline.xのリセット・ハンドラ_trampoline→program_start→このcrt0.xの_stextへやってくる。
お勉強として、crt0.xを眺めてみる。行番号は私が付加したもので、実際には存在せず、行番号が飛んでいるのは不要と判断して削除したため。
54: .section .reset, "ax", @progbits
これ以降の定義はセクション.resetに配置される。allocatableとexecutableのフラグ、contains dataタイプが指定されている。
57: .global _start 58: .type _start, @function 59: _start: 60: // Jump to the C runtime startup routine. 61: lda.w pc, _stext 64: // _stext is placed outside the .reset section so that the program entry point 65: // can be changed without affecting the C runtime startup. 66: .section .text._stext, "ax", @progbits
関数_startが定義され、ほぼtrampoline.xの_trampolineとprogram_startと同様の内容であるから、本当はtrampoline.xは不要と思われる。実際のリセット・ハンドラは_trampolineであり、_startはロード・モジュールに取り込まれていない。これはリンク・オプションに「-Wl,-e,_trampoline」があるためらしい。「らしい」というのは、このオプションの正体がよく判らないから(ドキュメントにも無い)。後で調べてみよう。
個人的には複数ファイルにセクション.resetのグローバル関数を定義することは嫌いだ。どれが本当のリセット・ハンドラか判りづらいからだ。
69: .global _stext 70: .type _stext, @function 71: _stext:
続いてリセット・ハンドラからジャンプしてくる関数_stextの定義(関数といっても戻ってくることはないけど)。
73: lda.w sp, _estack
スタック・ポインタをシンボル_estack(00008000, 内蔵SRAM末尾+1)の位置に割り付ける。lda.wは仮想オペコードで、リンク時の評価により、異なるオペコードに展開される。セクション_estackがどこで定義されているのか判らない。たぶんリンク・オプションの「-Wl,--gc-sections」辺りが怪しいが、これも宿題。
75: // Set up EVBA so interrupts can be enabled. 76: lda.w r0, _evba 77: mtsr AVR32_EVBA, r0
例外ハンドラ・テーブルがシンボル_evbaに(たぶん)あり、このアドレスをレジスタEVBA(Exception Vector Base Address)に設定する。システム・レジスタにアクセスするためmtsr(Move To System Register)命令を使用する。
79: // Enable the exception processing. 80: csrf AVR32_SR_EM_OFFSET
例外ハンドラ・テーブルを設定したので、例外処理を有効化する。
82: // Load initialized data having a global lifetime from the data LMA. 83: lda.w r0, _data 84: lda.w r1, _edata 85: cp r0, r1 86: brhs idata_load_loop_end 87: lda.w r2, _data_lma 88: idata_load_loop: 89: ld.d r4, r2++ 90: st.d r0++, r4 91: cp r0, r1 92: brlo idata_load_loop 93: idata_load_loop_end:
C言語の初期値付きstatic/グローバル変数のような初期値つき変数領域を初期化する。内蔵SRAMの該当領域が_dataから_edata-1に存在する(今回の例では_data(0x00000000)、_edata(0x00000008))。85〜86行目は、もし_data≧edataなら初期値つき変数領域が無いので以後の処理を飛ばす。87〜92行目で、初期データが_data_lma(0x800024d0)に存在するのでr2に入れ、_dataを代入したr0の間でコピーする。コピー終了は_edataを保持するr1を越えるまで。ldとst命令のデータ・サイズが.d=4byte=64bitなので、データ転送にはR4だけでなくR5も使われている。記述としてはこうなるんだろうけど、「R4-R5」みたいに書けないとバグの温床になるから嫌だな。
ところで「LMA」とは何の略だろう?ダンプ・リストでは「VMA」と並んでアドレスを示しているので、「Local Memory Address」らしく、「Virtual Memory Address」とは異なり実アドレスを表しているようだが、それならPMAになりそうだ。Architecture Manualでは「Large Memory」という言葉も出てくるけど...うーん、判らない。
95: // Clear uninitialized data having a global lifetime in the blank static storage section. 96: lda.w r0, __bss_start 97: lda.w r1, _end 98: cp r0, r1 99: brhs udata_clear_loop_end 100: mov r2, 0 101: mov r3, 0 102: udata_clear_loop: 103: st.d r0++, r2 104: cp r0, r1 105: brlo udata_clear_loop 106: udata_clear_loop_end:
未初期化変数領域を0クリアしている。詳細は、1つ手前の初期化変数領域と似ているので省略。
108: #ifdef CONFIG_FRAME_POINTER 109: // Safety: Set the default "return" @ to the exit routine address. 110: lda.w lr, exit 111: #endif
マクロCONFIG_FRAME_POINTER(デフォルトでは未定義)が定義されているとLRレジスタが初期化される。LRレジスタはreturnするアドレスが格納されているが、コールせず(LRレジスタに戻り先アドレスが格納されない)にリターンすると明後日の方向に飛んでいってしまう。こういう不慮の事故を防ぐ安全策として予めexitへのアドレスを格納している。Fail to safeの考え方だが、exitに飛んだからといって安全か(問題無いか)というのは別の話。
113: // Start the show. 114: lda.w pc, main
最後にユーザ・プログラムのmainに飛んでいく。
あれ?!「_main」じゃない!!
C言語で作成されたシンボルはコンパイルされた時点(アセンブル記述に変換されたとき)で、シンボルの前に「_」が付く。これはUNIX以来の伝統だ。当初アセンブラでプログラムされていたところにUNIXのC言語が登場したが、ライブラリ(当然アセンブラで作成)のシンボルと衝突することがしばしばあった(シンボル名の長さが8文字(たぶん)という制限の影響が大きい)。これを自動的に回避する対処方法だった。だからアセンブラからC言語のシンボルを参照するには「_」を付加する必要があるし、C言語側から呼び出すアセンブラのシンボルには予め「_」を付けて定義するだが、AVR32-gccでは不要なのか?
ちなみにコンパイラ内部で利用するシンボルは、先頭に「__」(2文字)を付けるそうな。
このソースは、「C言語スタートアップ・ルーチン」なので、C言語のアプリケーションやライブラリに関する最低限の処理しか実施していない。ハードウェアの初期化など、例外ハンドラ・テーブルとの絡みがあるEVBAとLRレジスタのみ初期化する。
OS上で稼働するアプリケーションなら、main関数は直ちにアプリケーションの機能を実現するところだが、スタンドアローンのmain関数はまずハードウェアの初期化から実施する必要がある。
AVR32 C Project from templateのtrampoline.x
AVR32 StudioでAVR32 C Project from templateからスタンドアローン版のアプリケーションを指定すると、アセンブラ・ファイルsrc/SOFTWARE_FRAMEWORK/ASM/trampoline.xが作成される。他にもソース・ファイルが作成されるが、この「トランポリン」というファイル名が気になるので、自分の勉強のため中身を読んでみる。
trampoline.xの抜粋(先頭に行番号を付加)。
1: // This must be linked @ 0x80000000 if it is to be run upon reset. 2: .section .reset, "ax", @progbits 3: .global _trampoline 4: .type _trampoline, @function 5:_trampoline: 6: // Jump to program start. 7: rjmp program_start 8: 9: .org PROGRAM_START_OFFSET 10:program_start: 11: // Jump to the C runtime startup routine. 12: lda.w pc, _stext
2行目は、セクション.resetを定義。"ax"はallocatableとexecutableのフラグ、@progbitsは「contains data」タイプである。
3〜5行目は、関数_trampolineを定義。セクション.resetの先頭なので、これがリセット・ハンドラとなる。
7行目は、シンボルprogram_startへ相対ジャンプ。
9行目は、.orgディレクティブでロケーション・カウンタをPROGRAM_START_OFFSETに設定している。PROGRAM_START_OFFSETはsrc/SOFTWARE_FRAMEWORK/UTILS/conf_isp.hにあり、その値は0x00002000のため、ベースとなるセクション開始アドレスの0x80000000と合わせて0x80002000が実アドレスとなる(FlushROM内)。
10行目は、rjmp命令のオペランドになるシンボルprogram_startを定義する。
12行目のldaは正式なオペコードではなく仮想オペコードと呼ばれるもの。条件に応じてジャンプの仕方を変更する。ジャンプ方法はリンカに任せ、スタートアップ・ルーチンがあるcrt0.xの_stextへジャンプする。
このtrampoline.xは、7〜11行目まで本来不要であり直接スタートアップ・ルーチンへ飛んでいっても良いはずだが、たぶんフレームワークとしてこういう流儀にしているようだ。
ワン・クッションおいて飛んでいくところは、「トランポリン」というよりは「ロイター板(跳び箱の手前に置く体操用踏み切り板)」という感じだ。