はじめに
制御機器をコントロールするための環境として、リアルタイムOSであるINtime上のアプリケーションを作成することになりました。
販売代理店からいろいろ情報は発信しているのですが、細かいところはよくわからず、結局試行錯誤したため、その記録を共有します。
最終的な目的は、センサーからデータを取得し、アクチュエーターにデータを渡すアプリケーションを作成することなのですが、今回はそのアプリケーションを作成する準備を記述します。
今回の内容は、非常に簡単な内容です。
それ以外の細かいところ(大変なところ)は、別の記事にします。
https://qiita.com/mine820/items/1b2ffeb9789a5388acb9
https://qiita.com/mine820/items/e807e3ccf5373918bd8a
INtimeとは
INtimeとは、リアルタイムOSの一つで、Windowsと一緒に動作させられます。
イメージとしては、CPUコアの一部をINtimeで使用し、残りをWindowsで使用する感じです。
INtimeとWindowsでデータのやり取りもできるのですが、今回はここはやりません。
また、Windows側のアプリケーションではなく、INtime側のアプリケーションの開発のみを行います。
余談
I/Oボードメーカーは、Windows側のデバイスドライバを使用しないと問い合わせサポート対応してくれません。
つまり、デバイスドライバを使用しないINtime側での開発は対象外となるようです。
また、INtime販売代理店においても、個々のI/Oボードに関する問い合わせは有償サポートとなるそうです。
Traceable Controller
INtime事態にも実行環境や開発環境が用意されているのですが、それをより使いやすくしたのが「Traceable Contriller」です。
これのおかげで、INtimeアプリケーションをVisual Studioで開発することができます。
ただし言語はC/C++のみで、Visual Studioのバージョンも2015以前に限られています。
(2020年3月時点)
準備
INtimeとTraceable Controllerをインストールします。
※詳細は省略
なお、先にVisual Studioをインストールしておくことをお勧めします。
また、INtimeアプリケーションを実行する際には、必ずINtime OSを起動(Nodeを開始)しておきます。
INtimeアプリケーションの起動/停止は「INtime Explorer」から行います。
(「INtime Real-Time Application Loader」を呼び出すことで、エクスプローラからダブルクリックで直接起動できます)
開発
INtimeアプリケーションを開発する際には、Visual Studioのプロジェクトをテンプレートから指定して始めます。
(今回は、Visual Studio 2012を使用します)
アプリケーションを開発する場合は、「Application Wizard」を選択します。
すると、以下のように、大きく4種類のアプリケーションが選べます。
通常は、一番下の「ウィザードによりINtimeアプリケーションを構築」を選択します。
また、C++で開発を行う場合は「クラスを使用したC++コードの生成」をチェックします。
次の画面では、テンプレートで作成されたコードに、追加で入れ込む機能を選択します。
複数を組み合わせて設定することもできます。
以下で詳しく説明していきます。
メールボックス - セマフォを待機するスレッド(サーバスレッド) or Queue Thread
スレッド間のやり取りを、メールボックスを利用して行いたい場合に、これを指定します。
例えば、下記のポーリングスレッドでデータを取得し、別のスレッドでその内容をファイルに書き込みたい場合、間にメールボックスをかませたりします。
規則的周期で処理をするスレッド(ポーリングスレッド)
いわゆるスレッド処理を行います。
中で無限ループを回し、そこで定期的な処理を行います。
一定間隔でデータを取得/送出したりする際に使用します。
多くのINtimeアプリケーションは、この設定を使用します。
割り込み処理
スレッド処理中、デバイスからの割り込みを受け付けたい時などに、この設定を使用します。
共有メモリの確保
メールボックスはスレッド間でしたが、これはプロセス間でデータを共有したい時などに使用します。
要求を依頼するスレッド
他は送り側(サーバ側)のスレッドでしたが、これは受け側(クライアント側)のスレッドを作成します。
コード例
ポーリングスレッドのテンプレートを使用してプロジェクトを作成した場合、以下のようなコードが出力されます。
(主要な部分のみ)
int main(int argc, char* argv[])
{
SYSINFO sysinfo;
EVENTINFO eiEventInfo;
#ifdef _DEBUG
fprintf(stderr, "INtimeApp1 started\n");
#endif
// ルートプロセスを取得する(失敗しません)
hRootProcess = GetRtThreadHandles(ROOT_PROCESS);
// モジュール情報構造体をクリアします
memset(&gInit, 0, sizeof(gInit));
gInit.state = BEFORE_INIT;
// 低レベルティック値(マイクロ秒)を取得します
if (!CopyRtSystemInfo(&sysinfo))
Fail("Cannot copy system info");
dwKtickInUsecs = 10000 / sysinfo.KernelTickRatio;
if (dwKtickInUsecs == 0)
Fail("Invalid low level tick length");
// プロセス最大プライオリティを調整します(失敗は無視)
// TODO adjust 2番目のパラメータを数値的に小さくすることでより高いプライオリティスレッド生成を許容します
SetRtProcessMaxPriority(NULL_RTHANDLE, 150);
// メインスレッド(この関数)のハンドルを取得します
gInit.hMain = GetRtThreadHandles(THIS_THREAD);
gInit.state = INIT_BUSY;
// スレッドカタログを試みますが、失敗は無視します
Catalog(NULL_RTHANDLE, gInit.hMain, "TMain");
// ルートプロセスに本プロセスをカタログします
if (!Catalog(hRootProcess, GetRtThreadHandles(THIS_PROCESS), "INtimeApp1"))
Fail("Cannot catalog process name");
gInit.bCataloged = TRUE;
// ポーリングスレッドを生成します
if (BAD_RTHANDLE == CreateRtThread(170, (LPPROC)Poll1, 4096, 0))
Fail("Cannot create poll thread Poll1");
// 初期化が完了したことを示します
gInit.state = INIT_DONE;
#ifdef _DEBUG
fprintf(stderr, "INtimeApp1 finished initialization\n");
#endif
// イベントを待機します
while (RtNotifyEvent(RT_SYSTEM_NOTIFICATIONS | RT_EXIT_NOTIFICATIONS,
WAIT_FOREVER, &eiEventInfo))
{
switch(eiEventInfo.dwNotifyType)
{
case TERMINATE:
// TODO: 本プロセスは終了します
// 環境をクリーンアップします
Cleanup(); // ここから戻ることはありません
case NT_HOST_UP:
// TODO: RTクライアントにおいてNTホストの復帰対応時処理
break;
case NT_BLUESCREEN:
// TODO: Windowsのブルースクリーン検出対応処理
break;
case KERNEL_STOPPING:
// TODO: INtimeカーネルサービス停止時対応時処理
break;
case NT_HOST_HIBERNATE:
// TODO: Windowsホストの休止状態移行時対応処理
break;
case NT_HOST_STANDBY:
// TODO: Windowsホストのスタンバイ状態移行時対応処理
break;
break;
case NT_HOST_SHUTDOWN_PENDING:
// TODO: Windowsホストのシャットダウン処理時対応処理
break;
}
}
Fail("Notify failed");
return 0;
}
ポーリングスレッドを作成・保存し、ループでイベントを待ちます。
このコードはほとんど手を入れないです。
void Poll1(
void* param)
{
#ifdef _DEBUG
fprintf(stderr, "Poll1 started\n");
#endif
// 本スレッドが生存していることを登録
gInit.htPoll1 = GetRtThreadHandles(THIS_THREAD);
// スレッドカタログ、ただし失敗は無視
Catalog(NULL_RTHANDLE, gInit.htPoll1, "TPoll1");
while (!gInit.bShutdown)
{
RtSleep(1000);
#ifdef _DEBUG
fprintf(stderr, "Poll1 waking up\n");
#endif
// TODO: 1000 ミリ秒ごとに繰り返す処理を配置します
}
// 本スレッドの終了を通知
gInit.htPoll1 = NULL_RTHANDLE;
}
ループ内に定期的に行う処理を追加します。
ここが開発の中心になります。
まとめ
INtimeアプリケーションは、Visual Studioでテンプレートを使用することで、簡単に作成することができます。
おまけ
苦労したおかげで、いろいろ知見が溜まりましたので、同様にお困りに方がいらっしゃいましたらお気軽にご相談ください。
(できればお仕事として...(笑))