メイン・コンテンツまでスキップ

バックエンド通信

ほとんどのアプリケーションでは、UIを何らかの方法でシステムのその他のコンポーネントに接続し、データを送受信する必要があります。 このために、ハードウェア・ペリフェラル(センサ・データ、A/D変換、シリアル通信など)とのインタフェースや、他のソフトウェア・モジュールとのインタフェースを行うことが考えられます。

この記事では、この接続を実装するために推奨されるソリューションについて説明します。

最初の方法はクイック・アンド・ダーティなアプローチで、主にプロトタイプを目的とするものです。一方で、2番目の方法は、現実世界のアプリケーションでUIをその他のコンポーネントと正しく接続するための堅実なアーキテクチャーに基づくものです。

この記事の最後には、両方の方法を紹介するサンプルへのリンクがあります。

モデル・クラス

すべてのTouchGFXアプリケーションにはモデル・クラスがあります。これは、UIの状態情報を保存するのとは別に、周辺システムへのインタフェースとして機能する目的も持っています。 モデル・クラスによって、両方のハードウェア・ペリフェラルを参照するだけでなく、システム内の他のOSタスクとも通信します。 通常、他のソフトウェア・モジュールやハードウェアに、個別のビュー・クラスでアクセスするのは良い設計ではありません。

Further reading
モデルの詳細については、「MVPのパターン」を参照してください。

以下の理由から、モデル・クラスはこうしたインタフェース・コードの配置によく適しています。

  1. モデル・クラスには tick() 関数があります。これは毎フレームで自動的に呼び出され、他のサブモジュールからのイベントを探して反応するために実装されます。
  2. モデル・クラスには現在アクティブなプレゼンタへのポインタがあります。これにより、受信するイベントをUIに通知することができます。

システムとのインタフェース

周辺システムとのインタフェースには2つの方法があります。GUIタスクから直接サンプリングする方法と、セカンダリ・タスクからサンプリングする方法です。

GUIタスクからのサンプリング

周辺システムとのインタフェースのために最適な方法は、サンプリングが必要とされる頻度、それにかかる時間の長さ、かかる時間がどれほど重要かによって決まります。

これらの点での要件が緩やかな場合、最も単純なアプローチは、 Model::tick 関数で周辺システムを直接サンプリングすることです。

サンプリングの実行頻度がフレーム・レート(通常は60Hz前後)よりも低い場合は、単純にカウンタを追加し、N番目のティックごとにサンプリングを実行します。 この方法で実行する場合、サンプリング操作をある程度高速(通常は1ms以下)で行う必要があり、そうでないとフレーム・レートに支障が出始めます。このサンプリングはGUIタスクのコンテキストで実行され、フレームの描画を遅延させるからです。

セカンダリ・タスクからのサンプリング

あるいは、GUIタスクのコンテキスト内で周辺システムと直接インタラクションを実行するのが望ましくない場合、サンプリングを実行する新しいOSタスクを作成することができます。

このタスクは、特定のシナリオで必要とされる正確な時間間隔で実行するように設定できます。 また、ユーザのニーズに応じて、この新しいタスクの優先順位をGUIタスクよりも低く、または高く設定することもできます。

優先順位を高くすると、実行中のGUIタスクの内容に関わらず、指定した正確な時間にこちらのタスクが実行されることが保証されます。 ただし欠点として、それがCPUの負荷が高いプロセスである場合、UIのフレーム・レートに影響する恐れがあります。

一方、サンプリングが時間的に重要でない場合は、GUIタスクより低い優先度を割り当て、UIフレームレートが周囲のシステムのサンプリングに影響されないようにできます。 描画中は、GUIタスクのスリープ時間が長く(DMAベースのピクセル転送の完了を待つ場合など)、優先度の低いタスクでもかなりの頻度で実行可能になるため、大半のアプリケーションにとってはこれで十分です。

セカンダリ・タスクのアプローチを使用する場合には、RTOSでサポートされるタスク間メッセージング・システムを利用することをお勧めします。 すべてではありませんが、ほとんどのRTOSはキュー / メール・メカニズムを備えているので、タスク間でデータ(通常はユーザ定義のC構造体、バイト配列、または単純な整数)の送信が可能です。 新しいデータをGUIタスクに伝送するには、UIタスク用のメールボックスまたはメッセージ・キューをセットアップし、このメッセージング・システムを使用してデータをGUIタスクに送信します。 次に、 Model::tick でGUIタスクのメールボックスをポーリングして、新着データがないかチェックします。 その場合、データを読み取り、それに応じてUIを更新します。

UIへのデータの伝播

GUIタスクからのサンプリングセカンダリ・タスクからのサンプリングのどちらを使用する場合でも、 Model::tick 関数によって、GUIタスクはUIに表示すべき新しいデータを認識することになります。 周辺システムへのインタフェースとして機能するのとは別に、モデル・クラスには状態データを保存する役目もあることを思い出してください。つまり、いくつか状態変数を更新する必要もあるかもしれないということです。

温度センサがシステムに接続されているシンプルな例を考えてみましょう。ここでは現在の温度がUIに表示されます。 準備として、以下をサポートするようにモデル・クラスを拡張します。

Model.hpp
class Model
{
public:
// Function that allow your Presenters to read current temperature.
int getCurrentTemperature() const { return currentTemperature; }

// Called automatically by framework every tick.
void tick();
...
private:
// Variable storing last received temperature;
int currentTemperature;
...
};

上記によって、 Presenter が現在の温度をモデルに要求できるようになります。これで、温度を表示するスクリーンに入ると、Presenterがこの値をUI(ビュー)に設定できます。 次に必要なのは、新しい温度情報を受信したときに再びUIを更新できるようにすることです。 このためには、モデルが現在アクティブなプレゼンタへのポインタを持っていることを利用します。 このポインタのタイプはインタフェース(ModelListener)で、適切なアプリケーション固有のイベントを反映するように変更することができます。

ModelListener.hpp
class ModelListener
{
public:
// Call this function to notify that temperature has changed.
// Per default, use an empty implementation so that only those
// Presenters interested in this specific event need to
// override this function.
virtual void notifyTemperatureChanged(int newTemperature) {}
};

これで、このインタフェースに接続できるようになりました。あとは、受信する"新しい温度"のイベントの実際のサンプリングを行います。 Model::tick

Model.cpp
void Model::tick()
{
// Pseudo-code for sampling data
if (OS_Poll(GuiTaskMBox))
{
// Here we assume that you have defined a "Message" struct containing type and data,
// along with some event definitions.
struct Message msg = OS_Read(GuiTaskMBox);
if (msg.eventType == EVT_TEMP_CHANGED)
{
// We received information that temperature has changed.
// First, update Model state variable
currentTemperature = msg.data;

// Second, notify the currently active Presenter that temperature has changed.
// The modelListener pointer points to the currently active Presenter.
if (modelListener != 0)
{
modelListener->notifyTemperatureChanged(currentTemperature);
}
}
}
}

上記のアプローチでは次の2つが確実になります。

  1. この currentTemperature 変数は常に最新状態なので、Presenterはいつでも現在の温度を取得できます。
  2. この Presenterには 温度の変化が即座に通知され、適切なアクションを実行できます。

MVPパターンの1つの利点は、現在のスクリーンに応じて通知を個別に処理できることです。 たとえば、何かの設定メニューを表示しているとき (MainMenuPresenter/MainMenuViewがアクティブな場合など) に温度変化のイベントが発生した場合、そこに現在の温度は関係しません。

この notifyTemperatureChanged 関数はデフォルトで空実装なので、この通知は MainMenuPresenterによって単に無視されます。. 一方、 TemperatureControlPresenter がある場合は、このPresenterで notifyTemperatureChanged 関数をオーバーライドし、更新後の温度を表示するようにビューに通知することができます。

TemperatureControlPresenter.hpp
class TemperatureControlPresenter : public ModelListener
{
public:
// override the empty function.
virtual void notifyTemperatureChanged(int newTemperature) {
view.setTemp(newTemperature);
}
};

Viewクラス TemperatureControlViewでは、当然ながら setTemp メソッドを実装する必要があります。

UIから周辺システムへのデータの伝送

UIから周辺システムへのデータ / イベントの反対方向の伝送も、モデルを通してほぼ同じ方法で実行されます。 新しいターゲット温度を設定する機能を追加する必要が生じた場合、これまでの例を続行すると、モデルに以下を追加することになります。

Model.hpp
void setNewTargetTemperature(int newTargetTemp)
{
// Pseudo-code for sending an event to a task responsible for controlling temperature.
struct Message msg;
msg.eventType = EVT_SET_TARGET_TEMP;
msg.data = newTargetTemp;
OS_Send(SystemTaskMBox, &msg);
}

ユーザがUIで新しいターゲット温度を設定した場合には、ビューが、モデル・オブジェクトへのポインタを保持するプレゼンタに通知できるようにし、その結果 setNewTargetTemperature関数を呼び出すことができます。 関数で周辺システムを直接サンプリングすることです。

以下の例は、Board Specific Demo(ボード固有のデモ: BSD)ですが、ここに示されているコードの多くは、他のデモ・ボードやカスタム・ハードウェアで再利用できます。 これらの例では、STM32CubeMXでタスクとキューを作成します。 次に、生成されたタスクを設定し、 サンプルのユーザ・コードをmain_user.cに実装します。. この例では、STM32CubeMX BSPライブラリを使用して、STM32評価キット上のLED、ユーザ・ボタン、その他のペリフェラルを制御します。

GUIタスクから

アプリケーション例のBSDは、TouchGFX Designerの最新バージョンにあります。GUIで、[Demos]->[Board Specific Demo]->[STM32F46G Discovery kit Control LED]の順に選択してください。

このアプリケーションは、ボタンのサンプリングおよびLEDの制御方法をデモンストレーションします。 モデル・クラスはボタンをサンプリングし、アプリケーションの状態と一致するようにLEDを更新します。

他のタスクから

アプリケーション例のBSDは、TouchGFX Designerの最新バージョンにあります。[Demos]->[Board Specific Demo]->[STM32H7B3I Evaluation Board Analog Sampler Task]の順に選択してください。

このアプリケーションは、別個のスレッドでアナログ入力をサンプリングする方法をデモンストレーションします。 この例では、MVPアーキテクチャを使用してアナログ値をビューに転送しています。

アプリケーション例のBSDは、TouchGFX Designerの最新バージョンにあります。[Demos]->[Board Specific Demo]->[STM32F46G Discovery kit Intertask Communication]の順に選択してください。

このアプリケーションは、タスク間の通信とUIとの間の伝播のデモを実行します。 独自のセットアップの際に、ヒントとして使用できます。 この例では、Cコードで実装されたバックエンド・システムとC++のTouchGFX GUIの間で通信が行われています。 この例はSTM32F746G-DISCOボードで、FreeRTOS上で実行されています。

複数タスクから

アプリケーション例のBSDは、TouchGFX Designerの最新バージョンにあります。[Demos]->[Board Specific Demo]->[STM32F769I Discovery Multitast Communication Demo]の順に選択してください。

このアプリケーションはボタンの状態をサンプリングし、ボタンが押されたら、GUIメッセージ・キューを通してメッセージを渡します。 これにより、ボタンを押したままアプリケーション内のアニメーションを進めることができます。

このアプリケーションでは3つのFreeRTOSタスクを使用しています。 1つはGUI用、残り2つは各ペリフェラル(LEDとUSER Button)用です。

タスクおよび外部の割込みラインから

アプリケーション例のBSDは、TouchGFX Designerの最新バージョンにあります。[Demos]->[Board Specific Demo]->[STM32F769I Discovery External Interrup Line Demo]の順に選択してください。

このアプリケーションはSTM32F769I-DISCOボード向けに設計されており、LEDおよびUSER BUTTONとのインタラクションによって、Cコードとハードウェア・ペリフェラルの両方をTouchGFXアプリケーションに統合する方法を示しています。

このアプリケーションでは、EXTIモードでボタンを設定します(外部割込みライン0)。 ボタンが押下されると割込みを受信し、その後割込みが消去されるという動作になっています。 GPIOと同じ動作はできず、代わりに1ステップずつアニメーションを進めることになります。割込みを受信するたびに、GUIメッセージ・キューを通してメッセージが送信されるのみだからです。

このアプリケーションでは2つのFreeRTOSタスクを使用しています。 1つはGUI用、もう1つはLED用です。 (このアプリケーションでは複数タスクのデモのボタン・タスクがアクティブなままになっており、ペリフェラル・インタラクションのコードが割込みハンドラに移動した例を示しています。)