オペレーティング・システム
概要
このセクションでは、グラフィカル・ユーザ・インタフェースのアプリケーションにおけるオペレーティング・システムの使用について説明します。
組込みデバイスは進化を続けています。 ほとんどのシステムで、グラフィカル・ユーザ・インタフェースを処理するだけでなく、アルゴリズムやタスクの複雑な制御を行うことが多くあります。
これらのタスクの例には、モータ制御、データ収集、またはセキュリティ関連タスクなどがあります。 多くの最新デバイスには、データ・センターとの通信用のTCP/IPなどの通信プロトコル・スタックや、他のローカル・デバイスとの通信用のBluetoothなどの無線スタックが含まれています。
他のタスクへのユーザ・インタフェースのインタリーブ
グラフィカル・ユーザ・インタフェースと少数の単純なサポート・タスクによる、単純なデバイス(エッグ・タイマーなど)では、ユーザ・インタフェース・コードに基づいてアプリケーション全体を構築することができます。 定期的なユーザ・インタフェースの更新の他に、アプリケーションが実行するタスクはほとんどないので、他のタスクの実行は、ユーザ・インタフェース・コードにかなり上手く組み込むことができます。
ただし、モータの調節など、個別のタイミングで「バックグラウンドで実行」される非常に高度な機能がデバイスに含まれた途端に、そうした要件をサポートしながら2つのタスクを1つに統合することは困難になります。
前の記事で説明したように、グラフィック・エンジンは滑らかなユーザ・インタフェースをサポートするために、新しいフレームを描画し続ける必要があります。 他のタスクの実行中にグラフィック・エンジンがこれを一時停止すると、フレーム・レートが低下します。 同様に、他のタスクをフレームとフレームの間(アイドル時間)にのみ実行する場合は、ユーザ・インタフェースが複雑なシーンを描画していてアイドル時間が少なくなると、これらのタスクに支障が出ます。 こうした影響から、UIタスクに他の複雑なタスクを手動でインタリーブすることは困難です。
例
このセクションの残りの部分では、ディスプレイ付きのBluetoothスピーカを構築することにします。 次の3つの主なタスクを実行します。グラフィカル・ユーザ・インタフェースを実行する、音楽をスピーカに送る、他のデバイスと通信するためにBluetoothスタックを処理する、という3つです。
ユーザ・インタフェースを中心にしたアプリケーション・アーキテクチャが、このケースでは適していないことはすぐにわかるでしょう。たとえば、音楽のコードとユーザ・インタフェースをブレンドし、再生開始用のコードをユーザ・インタフェース内のボタン用のイベントハンドラに配置するようなことを想像してみてください。 音楽が始まるまでの時間、ユーザ・インタフェースがロックされてしまいます。 実行中のアニメーションがしばらくの間停止してしまうのです。
一般的に、ユーザ・インタフェースの応答性は、音楽のタスク(開始、停止、次へなど)の実行時間に依存することになります。 これは一般的な問題なので、これから説明します。
では、Bluetoothから音楽も開始できるようにする場合はどうなるでしょうか? 何らかの方法でユーザ・インタフェースを関与させる必要があるでしょうか?
また、音楽のタスクの優先度を上げて、音楽が途切れないようにするにはどうしたらよいでしょうか? 同時に、音楽のタスクが実行されていないときには、ユーザ・インタフェースが最高のパフォーマンスで実行されるようにする必要もあります。
これらすべては、タスク処理、通信手段、同期化を備えたオペレーティング・システムを、使用することで解決します。
RTOS
リアルタイム・オペレーティング・システム(RTOS)は、さまざまなサービスでアプリケーションをサポートし、アプリケーション内のタスクにコンピューティング・リソースを配布する小規模のソフトウェアです。
RTOSを使用すると、独立しながらも連携しあう多数のタスクで、アプリケーションを構築できます。 これらのタスクは、作業発生時に、RTOSによって優先度に従って同時実行されます。
1つのジョブを、優先度の高いタスクと優先度の低いタスクに分割することもできます。 Bluetoothデータが到着したら、バッファから非常に高速で読み取り、より大きいアプリケーション・バッファにそれを配置することを考えてみてください。 データの処理は、少し延期することができます。 このように、最終的には2つのBluetoothタスクになります。
この例では、メイン関数から4つのタスクを開始します。
int main() {
...
os_start_task(gui_task, medium_priority);
os_start_task(music_task, low_priority);
os_start_task(bt_comm_task, high_priority);
os_start_task(bt_appl_task, low_priority);
os_start_scheduler();
}
ミュージック・タスクでも、同じような分割を行うことができます。データをスピーカに送るタスクは優先度が高く、再生中の歌を制御し、ユーザ・インタフェースに通知を送るタスクは優先度が低くなります。
上記のように異なる優先度を使用した結果、処理すべきデータが存在するときにはbt_comm_taskが実行され、それ以外はユーザ・インタフェース・タスクが実行されることになります。 ユーザ・インタフェース・タスクが表示を待機している間に、2つの優先度の低いタスクを実行できます。 オペレーティング・システムのスケジューラが、この時間配分を自動的に処理します。
通常のTouchGFXアプリケーションでは、ユーザ・インタフェースはすべてのフレームの表示を待機し、グラフィック・アクセラレータ(ChromArt)が要素の描画を完了するのを必ず待機します。 つまり、優先度の低いタスクを実行できる、細切れの一時停止が多数発生するということです。 オペレーティング・システムのスケジューラは、優先度の高いタスクが待機中であれば、マイクロコントローラがこれらのタスクを実行するように自動的に変更します。
タスクの通信
複数のタスクを使用する場合は、これらのタスク間の通信が安全に行われるための方法も必要になります。 1つの簡単な例として、ユーザ・インタフェースからミュージック・タスクへの通信が挙げられます。 この例では特に、gui_taskによって歌の再生開始が要求されるまで、ミュージック・タスクが待機する必要があります。 これを実装するための簡単な方法は、メッセージ・キューを使用することです。 ミュージック・タスクは、キューにメッセージが入れられるまで、スリープ状態になっています。 キューにメッセージが入れられ、優先度の高いタスクがビジー状態でなければ、スケジューラがタスクをウェイクアップします。
...
music_task_input_queue = os_create_queue(10); //10 element queue
...
ユーザ・インタフェースで"Play"が押されたら、ミュージック・タスクのキューにメッセージを送ります。
void ScreenMusic::handlePlayPressed()
{
os_send_message(music_task_input_queue, play_message);
}
ミュージック・タスクは、キューを読み込みながらメッセージの到着を待ちます。 これにより、メッセージが到着するまでタスクがブロックされます。
...
Message message;
os_receive_message(music_task_input_queue, &message);
ミュージック・タスクのキューにメッセージを入れた後、ユーザ・インタフェースは実行を続け、できる限り高速でフレームを描画します。 再生のメッセージをすぐに処理するのに、時間を無駄にすることはありません。 しかし、その描画が完了し、次のフレームの描画の前にUIタスクが待機中であれば、スケジューラが実行対象をミュージック・タスクに変更し、着信メッセージが処理されるようになります。
同様に、ユーザ・インタフェースに入力キューを割り当てることもできます。 これでミュージック・タスクは、歌が終了したときなどに、通知メッセージを送ることができるようになります。 ユーザ・インタフェース・タスクはメッセージを待つのではなく、ブロックすることなくメッセージが着信しているかすばやくチェックし、着信していればこれを読み込みます。
このセットアップは、システム内のタスク間に非常に緩いつながりをもたらします。 実際にはユーザ・インタフェースを使用せずにミュージック・タスクをテストすることができ、Bluetoothタスクから音楽を簡単に開始することもできます。
割込みの処理
一部のタスクは、割込みへの応答として実行する必要があります。 ここでは、この例としてBluetooth通信タスクを挙げています。 このタスクは、Bluetoothチップに新しいパッケージが提供されたときに実行する必要があります。 この場合、割込みを受け取ったとき、割込みハンドラからメッセージを送ることになります。
void BT_DataAvailable_Handler(void)
{
os_send_message(bt_data_queue, data_available_message);
}
キュー以外の同期プリミティブも使用できます。 たとえば、セマフォやミューテックスなどが多くのオペレーティング・システムで見受けられます。
FreeRTOS
TouchGFXは開発時に、FreeRTOSオペレーティング・システムでテストされています。 TouchGFXは要件が非常に少なく、他の多くのオペレーティング・システム上で実行できますが、ユーザがOSに関して特定の要件を持っていなければ、FreeRTOSで開始するのがよいでしょう。
FreeRTOSは、市販のアプリケーションで無料で使用できるシンプルなオペレーティング・システムです。 STM32Cubeファームウェアと共にソース・コードで提供され、すべてのSTM32マイクロコントローラ向けにすぐ利用できるサンプルも提供されています。
FreeRTOSに関する詳細とライセンス条項については、freertos.orgを参照してください。
TouchGFX OS Wrappers
TouchGFXのデフォルト設定では、FreeRTOS上で実行され、1つのメッセージ・キューを使用してディスプレイ・コントローラおよびセマフォとの同期を取り、フレームバッファへのアクセスが保護されます。
これは、touchgfx/os/OSWrappers.cpp
で定義されているOSWrappersクラスで処理されます。 このクラスには以下のメソッドがあります。
メソッド | 説明 |
---|---|
signalVSync() | ディスプレイが次のフレームへの準備ができたら、ディスプレイ・ドライバから呼び出されるメソッドです。 |
waitForVSync() | 待機のために、グラフィック・エンジンによって呼び出されます。 signalVSyncが呼び出されるまで、戻りません。 |
isVSyncAvailable() | (オプション)VSyncが発出したらtrueを返します。 waitForVSyncでのブロックを防ぐために使用します。 |
signalRenderingDone() | (オプション)未処理のVSync信号をすべて削除します。 |
takeFrameBufferSemaphore() | フレームバッファにダイレクト・アクセスするために、グラフィック・エンジンとアクセラレータによって呼び出されます。 |
giveFrameBufferSemaphore() | ダイレクト・アクセスを再度解放するために呼び出されます。 |
デフォルトの実装では、VSync(フレーム)同期を実装するためにメッセージ・キューが使用されます。 グラフィック・エンジン・タスクは、次のVSyncが発出されるまでスリープ状態になります。
このOSWrapperクラスは、TouchGFX Generatorによって生成されます。 Generatorの詳細については、こちらを参照してください。
RTOSを使用しない場合
TouchGFXは、オペレーティング・システムなしでも実行できます。 この場合には、メイン関数内でグラフィック・エンジンのメイン・ループを直接開始する必要があります。
int main()
{
...
touchgfx::HAL::getInstance()->taskEntry();
//never returns
}
RTOSを使用しなくても、TouchGFXのパフォーマンスは低下しません。 ただ、マイクロコントローラの負荷が増える可能性があり、TouchGFXと一緒に他のタスクを実行することが難しくなります。
前述したように、メイン関数内でユーザ・インタフェースを実行中に、他のタスクを手動で駆動させる必要があります。
Model::tick
1つの方法は、フレームごとに1回ずつModelクラス内でタスク・チェックを実行することです。
Model.cpp
void Model::tick()
{
//run other tasks here
music_task_tick();
bluetooth_task_tick();
}
このメソッドを使用すると、フレームごとに1回ずつ、すべてのタスクが実行されるようになります。 ユーザ・インタフェースの描画時間に、タスクの実行にかかる時間が追加されます。 すべてのタスクがすばやく終了するような単純なシステムであれば、これはシンプルで許容できるソリューションです。
OSWrappers
もう1つのメソッドは、OSWrappersクラス内でフックを使用することです。 前述のとおり、グラフィック・エンジンは、イベントを待機する必要が生じると、このクラスでメソッドを呼び出します。 これを利用して、こうしたイベントを待機する間に別の作業を実行するのです。
OSWrappers.cpp
static volatile uint8_t vsync_sem = 0;
void OSWrappers::signalVSync()
{
vsync_sem = 1;
}
void OSWrappers::waitForVSync()
{
vsync_sem = 0; //clear the flag, so we wait for the next vsync
do {
// Perform other work while waiting
music_task_tick();
bluetooth_task_tick();
} while(!vsync_sem);
}
このメソッドを使用すると、フレーム間のアイドル・タスクを他のタスクによって完全に利用できます。ただし、タスクが取得する時間の長さはさまざまです。
もう1つのソリューションは、OSWrappers::isVSyncAvailable関数とOSWrappers::signalRenderingDone関数の使用です。 これによりアプリケーションは、複数のwhileループを持つことを回避できます。 これらの関数は、No-operating-system設定が選択されたときに、TouchGFXGeneratorによって使用されます。
重要なことは、タスクがその作業を、およそ1ミリ秒の小さなステップに分割できるようにすることです。 そうでないと、ユーザ・インタフェースのパフォーマンスが阻害されてしまいます。