RTAI入門(9)[最終回]どう書くか?その3
この記事からの続きです。
とにかくテンプレートのように、コピペしてすぐ使える!という形式を目指しています。新たにRTAIアプリを組む時は、今回からの記事から必要な部分だけコピペして下さい。
なお、ここで使用するサンプルコードはすべてこちらの記事で構築した環境にて確かめています。
今回の記事で出来るようになる事は、次の通りです。
- 割り込み処理
割り込み処理
そもそも割り込みって何?という方は、IRQ 割り込み といったキーワードで検索してみてください。
以前の記事ではかなり省略して書きましたので、ここで割り込み処理について詳しくご説明します。
さて、割り込み処理は、カーネルモード、ユーザーモードそれぞれの場合で処理方法が異なります。まずは、それぞれの特徴についてご説明します。
カーネルモードにおける割り込み
カーネルモードでは、割り込み発生時に、割り込み関数が直接呼び出されます。
図1 割り込み発生時に、関数が呼び出される
これはマイコンプログラミングではおなじみの方法ですね。
手順1: どのIRQの割り込みが発生した時に、どの関数を呼び出したいか登録する
手順2: 割り込みが発生したら、その関数が呼び出される
こんな感じで、割り込みが発生したときに処理したいコードを、割り込み関数に用意しておきます。
ユーザーモードにおける割り込み
これはRTAIのとてもユニークな点なのですが、ユーザーモードの中でも割り込みを扱う事が出来ます。
図2 割り込み発生時に、ブロック解除される
これは、1ms毎に処理をする、といった場合のプログラミング方法に似ています。
手順1: どのIRQの割り込みが発生した時に、どのタスクのブロックを解除したいか登録する
手順2: タスクを実行する
手順3: タスクの中で、 rt_irq_wait( ); を呼び出す。すると、割り込みが来るまでブロックする。
手順4: 割り込みが発生したら、rt_irq_wait( ) でブロックしているタスクが、ブロック解除される
このような感じで、rt_irq_wait( ) 関数の下に、割り込み発生時に処理したいコードを書いておきます。すると、割り込みが発生したときにブロック解除が行われ、用意しておいたコードが実行されます。
ちなみに、この方式はユーザーモードだけでなく、カーネルモードでも有効です。ですから、こっちの方法で記述しておけば、ユーザーモード、カーネルモード、どちらでも実行可能です。
ユーザーモードで割り込みを使うために
ユーザーモードで割り込みを使用するには、RTAIインストール時に、RTAIのコンパイルオプションを変えないといけません。
こちらの記事では、make menuconfig したときにオプションを変えていません。しかし、このままではユーザーモードでの割り込みは使えません。
そこで、次のような設定に変更します。
図3 User-space interrupts を有効にする
make menuconfig時に、Base System => Other Features => User-space interrupts にチェックを入れます。
そしてSaveしてコンパイルすると、ユーザーモードでの割り込みが使えるようになります。
ユーザーモードの割り込みコード
rt_task_init_schmod でリアルタイムタスクを作ってから、rt_task_delete でそれを削除するまでの間に、次のようなコードを書きます。
/* Make this task realtime */ mlockall( MCL_CURRENT | MCL_FUTURE ); rt_make_hard_real_time(); rt_request_irq_task( 7 /* IRQ */, task1, RT_IRQ_TASK, 1 ); rt_startup_irq( 7 /* IRQ */ ); rt_enable_irq( 7 /* IRQ */ ); /* do realtime job */ while( 1 ) { rt_irq_wait( 7 /* IRQ */ ); /* do interrupt job here */ ; /* enable interrupt */ rt_ack_irq( 7 /* IRQ */ ); } rt_pend_linux_irq( 7 /* IRQ */ ); rt_release_irq_task( 7 /* IRQ */ );
ここで、次の点に注意して下さい。
- IRQは7としていますが、これは適宜書き変えて下さい。/* IRQ */ と書いてある所がそうです。
- rt_request_irq_taskの第二引数は、RT_TASK* の変数です。自分自身のタスクのRT_TASK*を渡して下さい。
- 割り込み発生時の処理は、rt_irq_wait と、 rt_ack_irq の間で行います。
- 上記の例ではwhile(1)で無限ループしていますが、適当な条件でループを抜けるようにしてください
サンプルプログラム
サンプルとして、パラレルポートを使用します。PCに付属のパラレルポートには、データの入出力をしたり、入力から割り込みを発生させたりする機能があります。
そこで、パラレルポートからの出力を、パラレルポートの割り込み入力に入れる事によって、割り込みを発生させるようにしました。
なお、本サンプルの作成にあたっては、Rio’s Home Page様の、こちらのページを参考にさせていただきました。貴重な情報がふんだんにある、とても素晴らしいサイトです。
パラレルポートの設定
実は、パラレルポートの制御がサッパリうまくいきませんでした。パラレルポートからの出力をオシロで見ていたのですが、Lowに張り付いたまま、うんともスンともいいません。
なんとか動かしてやろうと四苦八苦していたのですが、どうにもなりませんでした。
そんな時、ふと「パレるポートのI/Oアドレスを変えたら動いた」というような記述を見ましたので、それをマネてみました。
パラレルポートのアドレスというと、0x378 が有名です。しかし、これではなぜか動かないということで、0x3BC に変更しました。すると・・・なんと動くではありませんか!
原因は良く分かりませんが、とにかく動いたのでヨシとします。
そんなわけで、パラレルポートの設定は以下のような感じです。
図4 /proc/ioports の内容
図5 /proc/interrupts の内容
I/Oアドレスは 0x3BC, IRQは 7 です。
パラレルポート結線
パラレルポートの、
4番ピン: データ出力ビット2
10番ピン: 割り込み入力
を結線しました。
図6 4番ピンと10番ピンを結線
なぜこのピンをチョイスしたかというと、
4番ピン:右から4番目のピン
10番ピン:左から4番目のピン
ということで左右対称です。これで万が一、ピン番号を左右勘違いしていても大丈夫!
プログラムコード
ソースコードはこちらです。
ここには、2つのタスクがあります。
メインタスク: 割り込みカウンタの値を表示しつつ、パラレルポート出力をON/OFFします
割り込み用タスク: 割り込みがあるたびに、割り込みカウンタをインクリメントします。そして、パラレルポート出力をOFFします。
すると、次のような事が置きます。
メインタスクが、パラレルポート出力をONする → 割り込みがかかる → 割り込みタスクが起動する → パラレルポート出力をOFFする
こうして、パラレルポートはほんの一瞬だけONされる事になります。
これを実行すると、パラレルポートは次のような波形になります。
1ディビジョン 5マイクロ秒ですから、上記の例ではONしてからOFFするまで12マイクロ秒かかっています。
I/Oポートの制御にはおよそ1マイクロ秒かかります。それを差し引くと、割り込みが来てから割り込みハンドラが動き出すまでに11マイクロ秒かかっている、と言えます。
かなり遅いですが、所詮は汎用PC。まぁ、こんなものでしょう。
もっとタイムクリティカルな事をしたければ、マイコンをつかうか、いっそFPGAでも使った方が良いような気がします。
更なる情報源
今回のシリーズを書くにあたり、次の情報を参考にしました。
- RTAI User Manual
- <RTAIのソース>\doc\generated\html\api\index.html
- <RTAIのソース>\base 以下の、各サブフォルダにある README ファイル
- Captain’s UniverseのWebサイト
おわりに
9回にわたった記事のテーマは、「RTAIを使ってリアルタイムシステムを作る際に、ほんのちょっとでも工期を短縮出来るように」というものです。
しかし実は、これには裏のテーマがありました。それは、FreeHILSを作る際、RTAIの挙動をしっかり把握しておきたい。そうじゃないと、なんだか気持ち悪い!というものです。
実際、こうして記事にまとめていくうちに、私自身はとても勉強になりました。そして、RTAI周りの仕様がスッキリと理解できた気持ちがします。
ただし、FreeHILSへの適用を念頭においているため、APIのごくごく1部しかご紹介できませんでした。しかし、RTAIにはもっと沢山の機能があります。もしも、「こんな機能ないかなぁ?」と感じられたら、ぜひRTAIの各種ドキュメントをあたってみてください。