令和元年 12月
受注システムの概要
新規開発または既存システムを参考にしたシステムの再構築でも、ユーザーからの明確な要求仕様があれば極力それに従います。
しかし、ユーザーが制御システムの開発に不慣れであったり、専門的な知識・経験が不十分な場合を想定して推奨する最小規模のシステム構成を用意しています。
このシステム構成は現在の小規模システムとして最低限の市場要求を満たすものです。
1.推奨するシステム構成
以下に推奨するシステムの構成例を示します。この例ではPIC32MM_CPU基板を例にとって説明しています。
CPUボードにはPC端末を接続するためのRS-232Cポートとログ内容を記録するための不揮発性メモリが実装されています。
・ PC端末はユーザーのプログラムと平行して動作するPC端末プログラムの入出力として使用します。
・ 不揮発性メモリは装置のログ情報を記録し、PC端末からの要求に応じて記録を表示・削除します。

オペレータが操作する操作パネルは基本的にPLC用のタッチパネルを使用します。タッチパネルのメリットはプログラムの変更のみで操作ボタンや数値設定の項目を追加・削除できる点です。例外として非常停止ボタンのように安全上の理由からタッチパネルに移せないもの、および操作の利用頻度が高いためタッチパネル外に設置した方が良い設定操作がある場合は別途操作パネルを追加します。
2.ソフトウェアの概要
推奨システムにおけるソフトウェアは以下の要素で構成されます。
・ 複数の処理(タスク)を平行して実行する擬似マルチタスクシステム
・ タスク実行の優先度を管理するタスク優先度管理
・ PC端末に対する操作を処理するためのターミナルタスク
・ 不揮発性メモリにログを記録するためのログタスク
・ ユーザーのアプリケーションプログラム
プログラムは全体として擬似マルチタスクを前提に動作します。これにより複数の処理内容を平行して実行することが出来ます。
つまり、ユーザーのアプリケーションを実行しながら、それ以外の処理も同時並行して実行されます。
最小限のシステムではPC端末を処理するターミナルタスクとログを記録管理するログタスクの二つがユーザーのプログラムに追加されます。
各タスクはタスク優先度管理システムの要求するIFに対応して製作されている必要がありますが、ここでは詳細は省略します。
3.簡単な動作の説明
これらの機能を使ってプログラムを組んでいくのですが、実際の様子を具体例で示します。
ここではまだシステムの詳細についての説明が出来ていないので、説明の途中で新しい機能が出てくることがあります。
細部にこだわらずにコメントを参考に大まかな動作を理解してください。なお、マルチタスクを実現するコルーチンについてはこちら(処理キーワードが変更になっていますが内容は変わりません)を参考にしてください。使用言語はC++を使用しています。
3.1 通常動作時の動作
以下のリストは簡単なユーザープログラムを示しています。システムは各タスクのexec()関数を順番に実行していきます。
//task_test_fatal_error.cpp CTaskTestFatalError TaskTestFatalError; volatile uint8_t test_sel = 0; //volatile uint8_t test_sel = 1; void exec1(CCoroutineBase& co){ CoStart(co); static uint8_t flag_first; //static変数であることに注意 if(!flag_first) { flag_first = 1; StdMsg("exec1() message\n"); //PC端末に表示を出力 CoSusp(); } if(test_sel) //test_selの値によって処理を変える fatalError("test"); else CoSusp(); CoFinish(); } void CTaskTestFatalError::exec(){ CoStart(m_co); TaskControl.startTaskMesure(); //タスク実行時間計測開始 static uint8_t flag_first; //static変数であることに注意 if(!flag_first) { flag_first = 1; StdMsg("start exec()\n"); //PC端末に表示を出力 while(StdUart.isSending()) //StdMsg送信中 ; CoSusp(); //ここが37行目(スライス実行時間が最大の位置) } exec1(m_co); CoFinish(); TaskControl.endTaskMesure(); //タスク実行時間計測終了 }このプログラムの実行結果はPC端末上で以下のような表示となります。
start exec() exec1() message最初の1巡目の実行時のみメッセージを表示しますが、2順目以降の実行では単に無限ループとなって何の表示も行われません。
ただし、新規の表示は行われなくても、実行自体は続いている状態です。
この状態でPC端末の改行キーを入力すると '>' が表示されキー入力待ちとなります。ここで 'task_info'と入力します。
>task_info TaskControl LoopCount:31044 LoopMaxTime:6558us TaskNo 0 Slice Count:31099 MaxTime:289us ../src/terminal/cmd_task_info.cpp cmdTaskInfo 24 TaskLoop Count:0 MaxTime:0us TaskNo 1 Slice Count:0 MaxTime:0us TaskLoop Count:2 MaxTime:462us TaskNo 2 Slice Count:34809 MaxTime:5570us ../src/ztst/test_task/task/user/task_test_fatal_error.cpp exec 37 TaskLoop Count:34927 MaxTime:6401ustask_infoはPC端末用のコマンドで、マルチタスクシステムによる各タスクの実行時間の測定値を示します。
TaskControlは各タスク実行を一巡した回数と、そのときに要した最大実行時間を示しています。この例では3個あるタスクを0から2までを順に実行したときに回数が+1されます。回数は16ビット長のためオーバーフローを起こします。
TaskNo n は各タスク毎の実行情報です。
Slice Countはタスクの実行単位(スライス)を実行した回数とその実行に要した時間の最大値およびその時のプログラム上の実行位置を示します。
各タスクのスライス実行時間は極端に長い時間にならないように注意が必要ですが、これを実測するためのコードは上記のプログラムには現れていません。タスクシステムが自動的に計測しています。
TaskLoopは 関数TaskControl.startTaskMesure()から TaskControl.endTaskMesure()までを実行した回数とそれに要した時間です。
左記の二つの関数は関数exec()内に表れています。
TaskLoopで計測される実行時間は上記のようにタスクの開始時からタスクの終了時までの実行時間であるため、マルチタスクとして動作する他のタスク数やその実行時間によって変動します。
この実行時間は特定の処理時間はXXms以内に終了することといったように時間指定されているシステムでは特に重要です。
今回の例では上記リストで示したTaskTestFatalErrorはタスク番号2になります。
タスク番号2におけるタスクスライスの最大実行時間は5,570usとなっています。こんな簡単なプログラムで5ms以上もの処理時間が必要でしょうか?
この時間を要した実行位置はメッセージからファイル名task_test_fatal_error.cpp、関数名exec、行番号が37行目であることが分かります。
ファイル名task_test_fatal_error.cppは上記のプログラムリストで、関数exec()内のCoSusp()が37行目になります。
プログラムは StdMsg("start exec()\n")でPC端末に表示を出力後、while(StdUart.isSending()) でStdMsgの送信終了を確認後にタスクスライスを終了しています。
Uartの送信終了を待っているため多くの時間が掛かっていることが分かります。Uartの送信終了を待たずに処理を終了していれば実行時間は大きく短縮されるでしょう。
TaskLoopの最大実行時間は6,401usとなっています。先のUart送信実行時の処理時間が最大となりますのでタスクスライスの実行時間5,570usにそれ以外の処理時間を加算すれば順当な時間でしょう。なお、加算される時間には本タスク以外の他タスクのスライス実行時間も含まれることには注意が必要です。これはマルチタスクにより並行動作しているので、このようになります。
次にはキー入力待ちに対して'intrpt_info'と入力してみます。PC端末には以下のような表示が現れます。
>intrpt_info StdTimer Count:2817 MaxTime:29us StdUart Count:77 MaxTime:106us'intrpt_info'は割り込み処理に関する情報を表示するPC端末用のコマンドです。
最初の行はシステム用タイマでの割り込み発生回数とその時に要した最大実行時間の値です。実行回数は16ビット長のためオーバーフローが発生します。システム用Uartについても同様の情報が表示されています。割り込み処理関数の処理時間は出来るだけ短いに越したことはないので、その実行時間を把握しておくことは重要です。
今回のサンプルではユーザープログラムでは割り込みを使用していないのでシステム標準の割り込みに関する情報のみが表示されています。
ユーザーアプリケーションで割り込みを使用する場合もシステムの標準的な割り込み処理関数の記述ルールに従うことで、ここに示す情報をを簡単に追加取得することが出来ます。
以上のように通常の動作時にはPC端末よりシステムの動作状態を確認することが出来ます。
PC端末用のコマンドは他にもありますが、説明が煩雑になるので割愛します・
3.2 エラー発生時の動作
システム内の何処かで致命的なエラーが発生した場合の動作を説明します。
上記プログラムリストで変数 test-sel がリスト外のプログラムから値を0以外に変更された場合を考えます。
(実際にはこのような使い方はしないのですが、短いサンプルリストで動作を説明するためです。)
プログラムは従来の無限ループから外れて関数fatalErro()が呼び出されます。関数fatalErro()はシステムに継続実行が不可能な程の致命的なエラーが発生したときに呼び出す専用の関数です。関数fatalErro()の標準仕様では、以下の処理を行います。
・ 次に実行されるリセット動作の前にリセット後に必要な情報をバックアップする。
・ システムの安全を確保する目的でCPUにリセット命令を実行させる。
これによりCPUのIOポートはリセット状態に移行する(CPUのリセット状態でシステムは安全であることが前提です)。
・ PC端末にエラーの発生要因とプログラム上の実行位置に関する情報を出力し、同じ内容を装置ログとして不揮発性メモリに記録します。
この処理中はユーザーのアプリケーションタスクは実行されません。
・ 再度CPUにリセット命令を実行させシステムを再起動させます。再起動後システムは通常動作に入るかまたは無限ループに入ります。
この時のPC端末上の表示は以下のようになります。
start exec() exec1() message fatal error at ../src/ztst/test_task/task/user/task_test_fatal_error.cpp/exec1/test TaskNo 2 ../src/ztst/test_task/task/user/task_test_fatal_error.cpp exec CoroutineNest ../src/ztst/test_task/task/user/task_test_fatal_error.cpp exec1 CoroutineNest start exec() exec1() message通常動作を開始してstart exec()の表示が終了後、exec1() message の表示か始まります。この表示開始以降いずれかのタイミングで関数fatalErrror()か実行されます。関数fatalErrror()の実行によって、その実行位置情報が表示されています。次の行ではファイル名 task_test_fatal_error.cpp 内の関数exec1にある識別文字 'test' を引数とするfatalError()関数が実行されたことを示しています。
2行目以降は関数fatalErrror()を呼び出すまでのコルーチンの呼出しネスト情報です。関数fatalErrror()はタスク番号2のタスクから呼び出されファイル名task_test_fatal_error.cpp 内の関数exec() 内でコルーチンを実行しています。'CoroutineNest'はコルーチンの実行示すキーワードです。3行目は2行目の関数exec()内のコルーチンで呼び出された下位のコルーチン情報です。このようにコルーチンの呼び出しはエラーの発生位置まで順次追跡できます。
その後、再びCPUがリセットされて通常動作を始めたため、再びstart exec()が表示されています。
通常、このようなソースリストに対応した情報はデバッガを接続した状態であれば簡単に入手できますが、実機単独での動作状態では入手が困難です。実機単独ではスタックダンプと呼ばれるバイナリーデータを表示し、それを解析する手法がとられることが多いのですが、こちらの方法の方が明らかに分かりやすい表示になります。
これらの表示を行うプログラムは、システム側にありユーザープログラムからは隠蔽されています。
次は、再びPC端末に改行を入力して'elog_read'コマンドを実行してみます。'elog_read'コマンドは不揮発性メモリに記録した情報を表示するコマンドです。通常は多量の表示が出てくるのですが、ここでは今回の操作に関連する表示部分のみ切り出して以下に示します。
>elog_read 33 20/12/15 13:40:31 system start 34 20/12/15 13:40:51 fatal error at ../src/ztst/test_task/task/user/task_test_fatal_error.cpp/exec1/test 35 20/12/15 13:40:51 TaskNo 2 36 20/12/15 13:40:51 ../src/ztst/test_task/task/user/task_test_fatal_error.cpp exec CoroutineNest 37 20/12/15 13:40:51 ../src/ztst/test_task/task/user/task_test_fatal_error.cpp exec1 CoroutineNest 38 20/12/15 13:40:51 system start >表示は左からインデックス番号、年月日、時刻、ロストマーク、メッセージテキストとなっています。
'system start'はシステムがリセットから通常動作に入ったときに記録されるログで、その次の行からの数行は先のPC端末上のfatalError()による表示と同じものです。PC端末への表示は非常に便利でなものですが、表示を出力するタイミングでPCが接続されていないとメッセージの記録が残らないため、同じ内容を不揮発性メモリにも記録するようにしてあります。不揮発性メモリへの記録は一定量を超えるまで記録が残り、その後は古い記録から順に上書きされていきます。記録されたログはPC端末から自由に表示できるため、エラーの発生後でもその時の情報を得ることが出来るメリットがあります。
組み込みシステム装置にPC端末を操作するターミナルタスクとデータを記録するログタスクの二つを追加しただけの最小構成ですが、これらによってシステムの保守性や計測能力を大幅に引き上げることができことがお分かりいただけると思います。
従来は、装置の不調時の様子が単に”動かない”や"反応がない"といった言葉に集約されてしまい、修理担当者が現場に着くまで何が起きたのかすら分からないことが大半です。装置のログを確認すれば、装置が検出した異常を後日でも確認することが出来ます。ユーザーがPC端末の幾つかのコマンド操作を覚え、その結果を修理担当者に伝えることが出来れば、修理担当者が現地に出向く時には高い確率で予想される交換部品を持参することができるので故障発生から修理完了までの時間は大幅に短縮されます。