AVR32 C Project from templateのintc.c
AVR32 StudioでAVR32 C Project from templateからStandalone版のアプリケーションを指定すると、C言語ファイルsrc/SOFTWARE_FRAMEWORK/DRIVERS/INTC/intc.cが作成される。
INTCについてはこちらを。
お勉強としてintc.cを眺めてみる。行番号は私が付加したもので、実際には存在せず、行番号が飛んでいるのは不要と判断して削除したため。
53: //! Values to store in the interrupt priority registers for the various interrupt priority levels. 54: extern const unsigned int ipr_val[AVR32_INTC_NUM_INT_LEVELS];
AVR32_INTC_NUM_INT_LEVELSは(1<<2)=4であり、ipr_valは4要素の配列である。
ipr_valはexception.xで定義されている。ダンプ内容は次の通り。INTLEVELはbit31〜30、AUTOVECTORはbit13〜0。
要素番号 | 値(4byte) | INTLEVEL | AUTOVECTOR |
---|---|---|---|
0 | 00 00 01 04 | 0 | 0x0104 |
1 | 40 00 01 12 | 1 | 0x0112 |
2 | 80 00 01 20 | 2 | 0x0120 |
3 | c0 00 01 2e | 3 | 0x012e |
56: //! Creates a table of interrupt line handlers per interrupt group in order to optimize RAM space. 57: //! Each line handler table contains a set of pointers to interrupt handlers. 58: #if __GNUC__ 59: #define DECL_INT_LINE_HANDLER_TABLE(GRP, unused) \ 60: static volatile __int_handler _int_line_handler_table_##GRP[Max(AVR32_INTC_NUM_IRQS_PER_GRP##GRP, 1)]; 61: #elif __ICCAVR32__ 62: #define DECL_INT_LINE_HANDLER_TABLE(GRP, unused) \ 63: static volatile __no_init __int_handler _int_line_handler_table_##GRP[Max(AVR32_INTC_NUM_IRQS_PER_GRP##GRP, 1)]; 64: #endif 65: MREPEAT(AVR32_INTC_NUM_INT_GRPS, DECL_INT_LINE_HANDLER_TABLE, ~); 66: #undef DECL_INT_LINE_HANDLER_TABLE
AT32UC3B0256におけるavr32-gccの定義済みマクロによると、__GNUC__が定義済みなので、60行と65,66行目が有効である。
60行目は、割込ライン毎のハンドラ・テーブルを宣言する関数型マクロ定義DECL_INT_LINE_HANDLER_TABLEである。第1引数はグループ番号で、宣言されるテーブル名の一部に使われ、またテーブル・サイズの計算に利用される。第2引数は使用されない。
65行目の関数型マクロMREPEATの定義は以下のようになっている。C言語プリプロセッサにはマクロをループ展開する機能がないから、こういう手段を使うことになる。
ヘッダファイル mrepeat.h 64: #define MREPEAT(count, macro, data) TPASTE2(MREPEAT, count)(macro, data) 65: #define MREPEAT0( macro, data) 66: #define MREPEAT1( macro, data) MREPEAT0( macro, data) macro( 0, data) 67: #define MREPEAT2( macro, data) MREPEAT1( macro, data) macro( 1, data) 以下、同様にMREPEAT_LIMIT(=256)まで続く。 ヘッダファイル tpaste.h 59: #define TPASTE2( a, b) a##b
AVR32_INTC_NUM_INT_GRPSは18なので以下のように展開される。
DECL_INT_LINE_HANDLER_TABLE( 0, ~ );
DECL_INT_LINE_HANDLER_TABLE( 1, ~ );
...
DECL_INT_LINE_HANDLER_TABLE( 18, ~ );
更に
static volatile __int_handler _int_line_handler_table_0[Max(AVR32_INTC_NUM_IRQS_PER_GRP0, 1)];
static volatile __int_handler _int_line_handler_table_1[Max(AVR32_INTC_NUM_IRQS_PER_GRP1, 1)];
...
static volatile __int_handler _int_line_handler_table_18[Max(AVR32_INTC_NUM_IRQS_PER_GRP18, 1)];
へ展開される。AVR32_INTC_NUM_IRQS_PER_GRP???(uc3b0256.hで定義)のいくつかは0なんだけど、そのままだとエラーになるので、最低でも1を確保するようにしている。
ということで、割込グループ毎のハンドラ・テーブルを確保している(初期値無しなので0でクリア)。
66行目で、使い終わったマクロDECL_INT_LINE_HANDLER_TABLEを削除している。
68: //! Table containing for each interrupt group the number of interrupt request 69: //! lines and a pointer to the table of interrupt line handlers. 70: static const struct 71: { 72: unsigned int num_irqs; 73: volatile __int_handler *_int_line_handler_table; 74: } _int_handler_table[AVR32_INTC_NUM_INT_GRPS] = 75: { 76: #define INSERT_INT_LINE_HANDLER_TABLE(GRP, unused) \ 77: {AVR32_INTC_NUM_IRQS_PER_GRP##GRP, _int_line_handler_table_##GRP}, 78: MREPEAT(AVR32_INTC_NUM_INT_GRPS, INSERT_INT_LINE_HANDLER_TABLE, ~) 79: #undef INSERT_INT_LINE_HANDLER_TABLE 80: };
58〜65行目で作成した割込グループ毎のハンドラ・テーブルは個別に定義されているので、これらをまとめる全体のテーブル_int_handler_tableを定義している。
70〜74行目でデータ構造(割込数とハンドラ・テーブルへのポインタ)を定義し、76行目で割込グループ数分の配列で_int_handler_tableを定義し、75〜80行目で初期値を設定している。
このテーブルはconst宣言されているので、固定データ領域(FlushROM)に割り付けられる一方、割込グループ毎のハンドラ・テーブルは書き換え可能なSRAM領域となる。もし_int_handler_tableの初期化部分に割込グループ毎のハンドラ・テーブルをコードしてしまうと、ここも固定データ領域に確保されてしまい、後で書き換えられなくなる。
83: /*! \brief Default interrupt handler. 84: * 85: * \note Taken and adapted from Newlib. 86: */ 87: #if __GNUC__ 88: __attribute__((__interrupt__)) 89: #elif __ICCAVR32__ 90: __interrupt 91: #endif 92: static void _unhandled_interrupt(void) 93: { 94: // Catch unregistered interrupts. 95: while (TRUE); 96: }
デフォルトのハンドラ定義。ダイナミック・ストップしている。
ハードから直接呼ばれる訳ではないが、この関数に割込の標「__attribute__((__interrupt__))」(GNUの場合)を付ける。
詳細を調べていないが、少なくともrete命令で返る必要があるからだ。
106: * \note Taken and adapted from Newlib. 107: */ 108: __int_handler _get_interrupt_handler(unsigned int int_lev) 109: { 110: // ICR3 is mapped first, ICR0 last. 111: // Code in exception.S puts int_lev in R12 which is used by AVR32-GCC to pass 112: // a single argument to a function. 113: unsigned int int_grp = AVR32_INTC.icr[AVR32_INTC_INT3 - int_lev]; 114: unsigned int int_req = AVR32_INTC.irr[int_grp]; 161: return (int_req) ? _int_handler_table[int_grp]._int_line_handler_table[32 - clz(int_req) - 1] : NULL; 162: }
割込グループ毎に、どの割込要因があったか判定し、該当割込のハンドラをR12に入れて返す関数_get_interrupt_handlerの定義。ハンドラがないときNULLを返す。
ということはAVR32-gccの戻り値は、デフォルトでR12に格納するのか。知らなかった。
それから、この関数は先の関数_unhandled_interruptよりも先に呼び出されるが、ここでは「__attribute__((__interrupt__))」を付けてはいけない。rete命令でいきなり割込から抜けてしまうからだ。
処理内容は、引数の割り込み優先レベルint_levがINT番号と逆順なのでひっくり返した上で、INTCの該当レジスタICRから割込グループ番号(変数int_grp)、割込グループ番号から割込ライン(変数int_irq、ビットフラグ)に取得している。そしてint_grpとint_irqを元に_int_handler_tableからハンドラ・アドレスを取得している。int_irqが0の場合を考慮しているのは、割込要因発生からEVBAで登録されたベクタへ飛んでくるまでの間に、割込要因がクリアされるケースがあるためだ。例えば、発生直後かつ割り込まれる前に通常処理で割込要因フラグをクリアとか、より優先度の高い割込処理でクリアした場合があるだろう。
H8やSHのように割込要因毎のベクタ・テーブルを持ち、ハードウェア処理で例外/割込ハンドラへ飛んでくれる方が高速なので、私は好きだ。
AVR32のような方式も有りだとは思うが、やはり本当の割込処理を開始するまでに時間が掛かりすぎる。さらに関数_get_interrupt_handler処理において113行目や161行目の配列インデックス内の余計な引き算、割込優先度(引数int_lev)、int_grp、int_reqの3つも情報が必要な点を見ると、ソフトを解っていない開発者が仕様を考えたんだなぁと残念に思う。
165: void INTC_init_interrupts(void) 166: { 167: unsigned int int_grp, int_req; 168: 169: // For all interrupt groups, 170: for (int_grp = 0; int_grp < AVR32_INTC_NUM_INT_GRPS; int_grp++) 171: { 172: // For all interrupt request lines of each group, 173: for (int_req = 0; int_req < _int_handler_table[int_grp].num_irqs; int_req++) 174: { 175: // Assign _unhandled_interrupt as default interrupt handler. 176: _int_handler_table[int_grp]._int_line_handler_table[int_req] = &_unhandled_interrupt; 177: } 178: 179: // Set the interrupt group priority register to its default value. 180: // By default, all interrupt groups are linked to the interrupt priority 181: // level 0 and to the interrupt vector _int0. 182: AVR32_INTC.ipr[int_grp] = ipr_val[AVR32_INTC_INT0]; 183: } 184: }
割込グループ毎のハンドラ・テーブルの初期化。デフォルトの関数_unhandled_interruptを設定する。また各IPRレジスタに割り込みレベルINT0を設定する。
187: void INTC_register_interrupt(__int_handler handler, unsigned int irq, unsigned int int_lev) 188: { 189: // Determine the group of the IRQ. 190: unsigned int int_grp = irq / AVR32_INTC_MAX_NUM_IRQS_PER_GRP; 191: 192: // Store in _int_line_handler_table_x the pointer to the interrupt handler, so 193: // that _get_interrupt_handler can retrieve it when the interrupt is vectored. 194: _int_handler_table[int_grp]._int_line_handler_table[irq % AVR32_INTC_MAX_NUM_IRQS_PER_GRP] = handler; 195: 196: // Program the corresponding IPRX register to set the interrupt priority level 197: // and the interrupt vector offset that will be fetched by the core interrupt 198: // system. 199: // NOTE: The _intx functions are intermediate assembly functions between the 200: // core interrupt system and the user interrupt handler. 201: AVR32_INTC.ipr[int_grp] = ipr_val[int_lev & (AVR32_INTC_IPR_INTLEVEL_MASK >> AVR32_INTC_IPR_INTLEVEL_OFFSET)]; 202: }
どこからも参照されず、ロード・モジュールに取り込まれない関数。
引数irq、int_levで指定される割込と割込レベルで引数handlerの割込ハンドラを登録する。
1つ前の関数INTC_init_interruptsにおける初期化と一見似ているが、配列要素へのアクセス方法が異なることに注意が必要だ。関数INTC_init_interruptsでは、単純に割込グループ番号とグループ内割込数でループを回している。一方、この関数では単独の割込番号(引数irq)から割込グループ番号(変数int_grp)とグループ内の割込ラインを求め、テーブルにアクセスする。
従ってこの関数の第2引数へ以下の計算結果を渡すことになる。
割込グループ番号 × AVR32_INTC_MAX_NUM_IRQS_PER_GRP + 割込ライン
つまり2つの情報を引数1つにまとめるため、呼び出し前に掛け算し、呼び出されたら割り算と剰余を実行するのだ。ハンドラ登録だし、AVR32では掛け算も割り算も1サイクルで終了するが、無駄があるなあ。
内部(ハードウェア)で割込グループと割込ラインを分けるにしても、AVR32_INTC_MAX_NUM_IRQS_PER_GRPを2のべき乗にして、末尾にギャップを設けて割込グループ毎のサイズを揃えれば、統一した割込番号を振ってソフト側から簡単に隠蔽できるはずだ。というか他社CPUはそうしているのだから、開発者の知識と経験が浅いのか?それともそんなにチップサイズに影響があるのかなあ。