前回からの続きです。
このテーマを最初からご覧になる場合はこちらからどうぞ。
サンプルプロジェクトの説明
このページ(TOPPERS/ASPのビルドからデバッグまで~サンプルプロジェクトで遊ぼう)を参照してください。
ML62Q1000版カーネルについて
以下、このカーネルにおける備考です。
●OS上の割り込み優先度の扱い
このカーネルでは、割り込み優先度の設定はできません。
コンフィグレーションファイルなどにおいて、他のアーキテクチャからの移植性を考慮して、-1(優先度最低)から-6(優先度最高)を設定してもエラーが起きないようになっていますが、実際の動作に反映しません。
しかしながら、ML62Q1000に搭載されている割り込みコントローラーは、ちゃんと割り込み優先度を変更する機能を持っています。
だったら、対応しなさいよ!って言われちゃいそうですが、TOPPERS/ASP、および「μITRON4.0」の仕様に適合するカタチでの実装ができませんでした。
理由は以下の通りです。
少し小難しい話になってしまいますので、興味のない方は以下の青い部分は読み飛ばしてください。
複雑なので、箇条書きにします。
○「μITRON4.0」は、OSの仕様として割り込み優先度の操作をサポートするために割り込みマスクを管理する必要がある。
○これを行うには、割り込みハンドラ内でその割り込みが起こった直前の割り込みマスクの値を参照できる必要がある。
○大概のCPUの場合、ステータスレジスタに割り込みマスクのビットが含まれる。
○大概のCPUの場合、割り込みが発生するとその直前のステータスレジスタの値がスタックに退避される。
○大概のCPUの場合、割り込みハンドラ内でその割り込みが起こった直前の割り込みマスクの値は、スタックに退避されたステータスレジスタの値から知ることができる。
○ところが、ML62Q1000の場合、ステータスレジスタ「PSW」に割り込みマスクのビットがそもそも含まれない。「ELEVEL」という、それらしいビットは存在するが、これは「エミュレータ専用」、「ノンマスカブル」、「ソフトウェア」、「マスカブル」という4種類の大雑把な割り込みレベルを示すものであって、ここで言う割り込みマスクとは意味が異なる。
○ML62Q1000の場合、ここで言う割り込みマスクに近いものは、割り込みコントローラー内の現割り込みレベル管理レジスタ、すなわち「CIL」レジスタである。
○しかし、この「CIL」レジスタの値は割り込み発生時にスタックには退避されない。
○よって、ハンドラ内では割り込みが起こった直前の割り込みマスクの値を回収できず、「μITRON4.0」の仕様に沿った形での割り込みマスク管理が完全には実装できない。
…う~ん、無念!
その代わり、裏技的に割り込み優先度を変更する方法を後ほど紹介します。
その裏技を使用しない場合、ML62Q1000における割り込み優先度は、割り込み番号が若いほど優先度が高くなるように固定されています。
割り込み番号の定義は「..\target\rb_d62q1577tb100_u8dev\target_sil.h」にあり、以下の通りです。
- ...
- /*
- * 「ML62Q1577」割込み番号(intno)の定義
- */
- #define TINTNO_WDTINT 1U
- //#define TINTNO_ 2U
- #define TINTNO_VLS0INT 3U
- //#define TINTNO_ 4U
- #define TINTNO_EXI0INT 5U
- #define TINTNO_EXI1INT 6U
- #define TINTNO_EXI2INT 7U
- #define TINTNO_EXI3INT 8U
- #define TINTNO_EXI4INT 9U
- #define TINTNO_EXI5INT 10U
- #define TINTNO_EXI6INT 11U
- #define TINTNO_EXI7INT 12U
- #define TINTNO_CBUINT 13U
- #define TINTNO_DMACINT 14U
- #define TINTNO_MCSINT 15U
- #define TINTNO_SIU00INT 16U
- #define TINTNO_SIU01INT 17U
- //#define TINTNO_ 18U
- #define TINTNO_SADINT 19U
- //#define TINTNO_ 20U
- #define TINTNO_EXTXINT 21U
- //#define TINTNO_ 22U
- #define TINTNO_I2CM0INT 23U
- #define TINTNO_I2CM1INT 24U
- #define TINTNO_FTM0INT 25U
- #define TINTNO_FTM1INT 26U
- #define TINTNO_TM0INT 27U
- #define TINTNO_TM1INT 28U
- #define TINTNO_I2CU0INT 29U
- #define TINTNO_SIU10INT 30U
- #define TINTNO_SIU11INT 31U
- //#define TINTNO_ 32U
- #define TINTNO_FTM2INT 33U
- #define TINTNO_FTM3INT 34U
- #define TINTNO_TM2INT 35U
- #define TINTNO_TM3INT 36U
- #define TINTNO_SIU20INT 37U
- #define TINTNO_SIU21INT 38U
- #define TINTNO_CMP0INT 39U
- #define TINTNO_CMP1INT 40U
- #define TINTNO_FTM4INT 41U
- #define TINTNO_FTM5INT 42U
- #define TINTNO_TM4INT 43U
- #define TINTNO_TM5INT 44U
- #define TINTNO_SIU30INT 45U
- #define TINTNO_SIU31INT 46U
- #define TINTNO_SIU40INT 47U
- #define TINTNO_SIU41INT 48U
- #define TINTNO_FTM6INT 49U
- #define TINTNO_FTM7INT 50U
- #define TINTNO_TM6INT 51U
- #define TINTNO_TM7INT 52U
- #define TINTNO_SIU50INT 53U
- #define TINTNO_SIU51INT 54U
- #define TINTNO_LTB0INT 55U
- //#define TINTNO_ 56U
- #define TINTNO_LTB1INT 57U
- #define TINTNO_LTB2INT 58U
- #define TINTNO_RTCINT 59U
- //#define TINTNO_ 60U
- ...
●ソフトウェア割り込み
今回のTOPPERS/ASP ML62Q1000版においては、特別にサンプルプロジェクトにソフトウェア割り込みのサンプルコードを入れています。
サンプルプロジェクトの操作方法については前述したこのページ(TOPPERS/ASPのビルドからデバッグまで~サンプルプロジェクトで遊ぼう)を見ていただくとして、具体的には「i」を一文字ターミナルに対して入力すると、このソフトウェア割り込みが発生するようになっています。
「..\OBJ\sample1.c」の「main_task()」関数内を参照してください。
結構下の方、以下のソースコードに注目。
- ...
- case 'i':
- /*
- * ソフトウェア割込み(ソフトウェア割込み10番を発生させる)
- */
- #pragma ASM
- swi #10
- #pragma ENDASM
- break;
- ...
こんな感じで「i」の一文字をターミナルから入力された場合の処理を追記しています。
実際にソフトウェア割り込みを発生させているのは「swi #10」というインライン・アセンブラの命令です。
これは、ソフトウェア割り込みの10番を発生させろ!…という意味になります。
ソフトウェア割り込みでも、通常のマスカブル割り込みと同様に、正しく動作させるにはコンフィギュレーション・ファイルに宣言を行う必要があります。
このサンプルプロジェクトの場合は「..\OBJ\sample1.cfg」にその記述があります。
- ...
- /*
- * ソフトウェア割込み(ソフトウェア割込み10番を指定)
- */
- ATT_ISR({ TA_NULL, 0, (10 + 61), swi_isr, 1 });
- CFG_INT((10 + 61), { TA_NULL, -1 });
- ...
ここで指定する割り込み番号は、通常のマスカブル割り込みとソフトウェア割り込みを通して連番としています。
上記の割り込み番号の定義(「..\target\rb_d62q1577tb100_u8dev\target_sil.h」」)では、60番まで使われていましたよね?(実際は空番ですけど…。)
したがって、ソフトウェア割り込みはその次、すなわち61番から指定するようにしてください。
ちょっとややこしいですが、ソフトウェア割り込み番号0は61番、今回のソフトウェア割り込み番号10なら、(10 + 61)=71番ということになります。
ソフトウェア割り込み番号は、0~63番まで、合計64個も使えます!(何にそんなに使うんだ!?)
さて、上記のコンフィギュレーションでは、「swi_isr()」という割り込みハンドラを登録しています。
これは「..\OBJ\sample1.c」に実装しています。
一番下の方です。
- ...
- /*
- * ソフトウェア割込みハンドラ
- */
- bool_t debug = false;
- void swi_isr(intptr_t exinf)
- {
- debug = !debug;
- }
- ...
10番のソフトウェア割り込みが発生すると、この関数に処理が飛びます。
すなわち「debug = !debug;」の行にブレークを仕掛けて、サンプルプロジェクト動作中にターミナルに「i」を入力すると、この行で停止します。
ソフトウェア割り込みは、昔のCPUにはよく見られた実装ですが、最近ではあまり目にすることはありません。
しかし、上手く使うとプログラムの処理がスマートになり、ソースコードが読みやすくなったりするメリットがあります。
その分、他のアーキテクチャへの移植性は悪くなりますので、ここぞ!という時に使いましょう。
●OS管理外割り込み
今回のTOPPERS/ASP ML62Q1000版においては、OS管理外割り込みに対応しています。
通常「μITRON4.0」は割り込みをも管理するOSですが、OS管理下の割り込みは、純然たるベタな記述のOS管理外割り込みとして実装した場合と較べて、パフォーマンスは若干劣ります。
そのため、例えばAD変換などの応答性を重視される割り込みなどにおいては、OSの管理が足かせになる場合があります。
割り込みをOSの管理外として実装した場合、確かに応答性は向上しますが、割り込みハンドラ内でOSのシステムコールを呼ぶことは、原則できなくなります。
なにせOSの管理外ですから…。
さて、割り込みをOS管理外に設定する方法ですが、これはコンフィギュレーション・ファイルで宣言を行います。
具体的は、以下の条件で設定します。
1.CFG_INTの引数「intpri」を優先度最高(このML62Q1000版の場合は-6)以下にする
2.DEF_INHの引数「inhatr」に「TA_NONKERNEL」フラグを設定する
例えば、前述のソフトウェア割り込みをOS管轄外に変更してみましょう。
「..\OBJ\sample1.cfg」のソフトウェア割り込み10番の設定を以下のように変更します。
- ...
- /*
- * ソフトウェア割込み(ソフトウェア割込み10番を指定)
- */
- //ATT_ISR({ TA_NULL, 0, (10 + 61), swi_isr, 1 }); // コメントアウト!
- //CFG_INT((10 + 61), { TA_NULL, -1 }); // コメントアウト!
- CFG_INT((10 + 61), { TA_NULL, -7 }); // 追記!
- DEF_INH((10 + 61), { TA_NONKERNEL, swi_isr }); // 追記!
- ...
「CFG_INT」では、ML62Q1000版の優先度最高の値「-6」より、更に優先度が高い「-7」を設定しています。
元の「ATT_ISR」では「TA_NONKERNEL」フラグが設定できませんので「DEF_INH」を使います。
以上で前述の条件を満たせます。
このようにすると、このソフトウェア割り込み10番のハンドラは、OSの管轄から外れ、割り込みの応答性は向上しますが…。
繰り返します。
OS管理外割り込みハンドラ内で「iwup_tsk()」などのシステムコールを呼ぶことはできませんのでご注意を!
●割り込み優先度を変更する裏技
さて、前述した割り込み優先度を変更してしまう裏技についてです。
この裏技、実はすでにOS内で使用しています。
今回のTOPPERS/ASP ML62Q1000版においては、素の状態で3つの割り込みを使用しています。
1つはOSタイマーで、上記の割り込み番号の定義(「..\target\rb_d62q1577tb100_u8dev\target_sil.h」」)で言うところの「TINTNO_TM0INT(割り込み27番)」です。
2つ目は、デバッグ用のシリアル通信で、受信割り込みである「TINTNO_SIU00INT(割り込み16番)」です。
3つ目は、同じくシリアル通信で、送信割り込みである「TINTNO_SIU01INT(割り込み17番)」です。
ここで思い出してください。
「ML62Q1000における割り込み優先度は、割り込み番号が若いほど優先度が高くなる」と前述しましたね。
すなわち、普通に実装した場合、OSタイマーよりもシリアル通信のための割り込みの方が優先度が高いということになります。
TOPPERS/ASPはリアルタイムOSです。
タイムクリティカルが売りなのに、それを司るOSタイマーが、遅延の許されるデバッグ用のシリアル通信の割り込みよりも優先度が低いという状況になります。
それって、リアルタイムOSとしてどうなのよ?
なんとかしてOSタイマーの割り込み優先度を上げたいところですよね?
しかしながら、これも前述のように、このカーネルでは、割り込み優先度の設定はできません。
じゃあどうするか?
そこで裏技です。
「..\target\target_config.c」の「target_initialize()」関数を御覧ください。
この「target_initialize()」はOSが起動してから比較的早い時期に実行される関数です。
以下の部分に注目!
- ...
- /*
- * ターゲット依存の初期化
- */
- void
- target_initialize(void)
- {
- initVls_t initVls;
- /*
- * プロセッサ依存の初期化
- */
- prc_initialize();
- /*
- * 割込みレベル制御許可
- */
- write_reg8(ILEN, 0x01U);
- /*
- * 16 ビットタイマ0 割込み(TM0INT)のレベル設定
- * -> 11: レベル4(割込みレベル高)
- */
- ILTM0L = 1;
- ILTM0H = 1;
- ...
一番下の2行で「ILTM0L」と「ILTM0H」のレジスタにそれぞれ「1」を設定していますね?
これらは「割り込みレベル制御レジスタ」というレジスタの中のビットで、OSタイマーに使用している16ビットタイマ0割込み(TM0INT)用のものです。
この「割り込みレベル制御レジスタ」は全てのマスカブル割り込み要因ごとに用意されています。
これらは、今回の場合は「c:\U8Dev\Inc\ML621577.H」に定義されています。
「割り込みレベル制御レジスタ」のこれらのビットは、該当する割り込み要因のレベルを設定するものであり、「H(ハイ)」と「L(ロー)」の2ビットで構成されています。
2ビットだから、レベル1~4までの4段階を設定できます。
ここで言う「レベル」とは、その値が高い割り込みが優先されるという意味になります。
つまり、前述した「ML62Q1000における割り込み優先度は、割り込み番号が若いほど優先度が高くなる」という法則を無視することができます!
(同じレベルのものは、やはり上記の法則に則りますが…。)
「H(ハイ)」と「L(ロー)」の並びで、以下のようにレベルを設定できます。
○00: レベル1(割り込みレベル低)(初期値)
○01: レベル2
○10: レベル3
○11: レベル4(割り込みレベル高)
今回は「ILTM0L」と「ILTM0H」を共に「1」にしたので、OSタイマーに使用している16ビットタイマ0割込み(TM0INT)は「レベル4」に設定していることになります。
これにより、たとえ「割り込み番号が若いほど優先度が高くなる」という法則があろうとも、レベル1のシリアル通信系のものよりもレベル4のOSタイマーの割り込みが優先して実行されることになります。
前述の通り、割り込みマスクの問題から、割り込み優先度の操作についてこのカーネルでは完璧にはサポートできませんでしたが、この裏技を使えば、大抵の場合は対処できるようになるでしょう。
●例外ハンドラは未対応
コンフィグレーションファイルなどにおいて、他のアーキテクチャからの移植性を考慮して、例外ハンドラの作成はできるようになっているものの動作はしません。
理由はML62Q1000に例外処理が存在しないためです。
●一部サービスコールは未対応
性能評価用システム時刻取得のための「get_utm()」サービスコールは未実装です。
●拡張パッケージについて
例えば、アプリケーションによっては、メッセージバッファやミューテックスといった機能を使いたい場合が出てくると思います。
しかしながら、これらの機能は標準では実装されておらず、「..\extension」以下の該当する機能のソースコードをカーネルのソースコードディレクトリにコピーする必要があります。
ここで注意しなければならないのは、今回のTOPPERS/ASP ML62Q1000版においては、カーネルのソースコードディレクトリが2つ存在することです。
1つ目は、純正のカーネルソースコードディレクトリである「..\kernel」。
2つ目は、ML62Q1000専用の「..\kernel_u8dev」です。
なんで分かれているのか?
それは、ML62Q1000版で使用されることを想定しているコンパイラがいつものGCC系統ではなく、ラピステクノロジー社純正の「ccu8」だからです。
すなわち、GCC系統のコンパイラでビルドが通るように書かれた「..\kernel」以下のソースコードは、ラピステクノロジー社純正の「ccu8」コンパイラではビルドが通らなかったのです。
そこで「ccu8」コンパイラでもビルドが通るように「..\kernel」を書き換えたものが「..\kernel_u8dev」というわけです。
ヘッダファイルの格納ディレクトリである「..\include」と「..\include_u8dev」も同様の理由で分けています。
さて、例えば現在のカーネルにメッセージバッファの機能を加えるために、「..\extension\messagebuf」以下のソースコードやヘッダファイルを「..\kernel_u8dev」や「..\include_u8dev」に上書きすると仮定しましょう。
すると「..\extension」以下のソースコードやヘッダファイルは、GCC系統のコンパイラで通るように書かれているため、「ccu8」コンパイラではエラーが出ることが考えられます。
私が「ccu8」コンパイラ用の「..\extension_u8dev」といったものを用意すればいいだけの話ですが、そこまで手が回らず…すんません!
とはいえ、エラーが発生した場合に「ccu8」コンパイラで通るように修正するのは、それほど難しい作業ではありません。
ほとんどの場合、修正が必要なのはヘッダファイルであり、インラインの宣言の違いなど、C言語の方言にまつわる部分です。
この辺りは「..\kernel」と「..\kernel_u8dev」、および「..\include」と「..\include_u8dev」を比較して、それをヒントに移植してみてください。
ライセンスについて
このカーネルは「TOPPERSライセンス」で配布しております。
無償ですが、使用に関しては自己責任です。
このカーネルを商用利用するレアな方は、このリンク先の条項に従ってください。
さて、今回はラピステクノロジー社のML62Q1000マイコンを取り上げたわけですが、個人的には、大変気に入っております。
元々8ビットのアーキテクチャを16ビットに拡張した製品ではあるのですが、それが無理なくスマートに実現されており、命令系統も洗練されています。
コンパイラやデバッガも、純正品独特のクセはあるものの質実剛健といった印象で、好感が持てます。
それに加えてノイズに強くて低消費電力という売りを考えると、もう少し広く普及しても良いのでは?と思います。
TOPPERS/ASPに続いて、FreeRTOSも移植してみたいと思いました。
ML62Q1000マイコン、どうぞ機会があれば採用を検討してみてくださいね。
<終わり>