抽象化レイヤ・アーキテクチャ
前のセクションで説明したように、TouchGFX ALには一連の特定の役割があります。 こうした役割は、ALのハードウェア部分(HAL)か、通常はRTOSを介してTouchGFX Engineと同期するALの一部 (OSAL) のいずれかに実装されます。 次の表は、前のセクションで概説したこれらの役割をまとめたものです。
役割 | オペレーシング・システム / ハードウェア |
---|---|
TouchGFXエンジンのメイン・ループとディスプレイ転送の同期 | オペレーシング・システムとハードウェア |
タッチおよび物理ボタンのイベントのレポート | ハードウェア |
フレームバッファへのアクセスの同期 | オペレーティング・システム |
次に使用可能なフレームバッファ領域のレポート | ハードウェア |
レンダリング操作の実行 | ハードウェア |
ディスプレイへのフレームバッファの転送処理 | ハードウェア |
以降の各サブセクションでは、上記の役割を果たすために必要な事項に焦点を当てます。 カスタムのハードウェア・プラットフォームでは、STM32CubeMX内のTouchGFX GeneratorがほとんどのALとそれに付随するTouchGFXプロジェクトを生成できます。 残りの部分はAL開発者が手動で実装する必要がありますが、それらはTouchGFX Generatorからのコード・コメントや通知によって指示されます。 TouchGFX Generatorの詳細については、次のセクションを参照してください。
抽象化レイヤのクラス
TouchGFX ALには、具体的なサブクラスや、TouchGFX Engineで定義されたクラスのメンバー関数が実装されているクラス実装ファイル(.cpp)を通して、TouchGFX Engineがアクセスします。 これらのサブクラスとクラス実装ファイルは、TouchGFX Generatorによって生成されます。 TouchGFX Generatorは、STM32CubeMXからの設定を反映するHALの一部と、使用されたRTOSのOSALの両方を生成できます。 詳細については、TouchGFX Generatorに関するセクションを参照してください。 一般的に、HALのアーキテクチャは次の図のようになります。
TouchGFXエンジンのメイン・ループとディスプレイ転送の同期
このステップの主旨は、レンダリングの終了時にTouchGFX Engineのメイン・ループをブロックして、それ以上フレームが生成されないようにすることです。 ディスプレイの準備ができたら、フレームの生成を続行するように、OSALからブロックされたエンジンのメイン・ループに信号が送られます。
「抽象化レイヤの役割」で概説したように、TouchGFX ALはこの役割を果たすために、通常はエンジンのレンダリング終了フックとディスプレイ・レディ割込みを利用します。 OSALはOSWrappers::signalVSync
関数を定義します。開発者はこの関数で、コール時にエンジンが待機するセマフォを送出できます。
Tip
レンダリング終了
レンダリング終了フックのOSWrappers::waitForVSync
は、レンダリングの完了後にTouchGFXエンジンによって呼び出されます。
このOSALメソッドの実装時には、次のフレームをレンダリングする時が来るまで、抽象化レイヤがグラフィック・エンジンをブロックしておく必要があります。 このブロックを実装する標準的な方法は、メッセージ・キューからの読出しのブロッキングを実行することです。 この方法が実現可能でない場合、HAL開発者はブロックを実装するための任意の方法を使用できます。
Tip
OSWrappers::signalVSync
(または、OSWrappers::waitForVSync
で使用されるセマフォ / キュー)が送出されると、TouchGFXは次のアプリケーション・フレームのレンダリングを開始します。 CMSIS V2に基づく以下のコードによって、システムの別の部分(通常はディスプレイと同期した割込み)によって要素がキューに追加されるまでTouchGFX Engineはブロックを行います。
OSWrappers.cpp (CMSIS V2)
static osMessageQueueId_t vsync_queue = NULL; //Queue identifier is assigned elsewhere
void OSWrappers::waitForVSync()
{
uint32_t dummyGet;
// First make sure the queue is empty, by trying to remove an element with 0 timeout.
osMessageQueueGet(vsync_queue, &dummyGet, 0, 0);
// Then, wait for next VSYNC to occur.
osMessageQueueGet(vsync_queue, &dummyGet, 0, osWaitForever);
}
RTOSを使用しない場合、TouchGFX Generatorでは、volatile変数を使用してwaitForVSync
に対する以下の実装を提供します。
OSWrappers.cpp (No OS)
static volatile uint32_t vsync_sem = 0;
void OSWrappers::waitForVSync()
{
if(vsync_sem)
{
vsync_sem = 0;
// Signal TouchGFX to start rendering next frame
...
}
}
Tip
ディスプレイ・レディ
メイン・ループをブロック解除するディスプレイ・レディ信号は、ディスプレイ・コントローラからの割込み、ディスプレイそのもの、またはハードウェア・タイマから送出される必要があります。 信号の送出元はディスプレイのタイプによって異なります。
OSWrappers
クラスは、この信号の関数を定義します。: OSWrappers::signalVsync
. この関数の実装では、OSWrappers::waitForVSync
で使用される待機条件を満たすことで、メイン・ループをブロック解除する必要があります。
前述のCMSIS V2の例からの続きで、以下のコードは、TouchGFX Engineをブロック解除するメッセージをメッセージ・キューvsync_queue
に入れます。
OSWrappers.cpp (CMSIS V2)
void OSWrappers::signalVSync()
{
osMessageQueuePut(vsync_queue, &dummy, 0, 0);
}
このOSWrappers::signalVSync
メソッドは、LTDCなどの割込み、ディスプレイからの外部信号、またはハードウェア・タイマから、ハードウェア・レベルで呼び出す必要があります。
RTOSを使用しない場合は、変数を使用し、ゼロでない値を割り当ててwhileループから抜けます。
OSWrappers.cpp (No OS)
void OSWrappers::signalVSync()
{
vsync_sem = 1;
}
タッチおよび物理ボタンのイベントのレポート
新しいフレームをレンダリングする前に、TouchGFX EngineはTouchController
およびButtonController
インタフェースから外部入力を収集します。
タッチ座標
タッチ・コントローラからの座標は、TouchGFX エンジンによってクリック・イベント、ドラッグ・イベント、ジェスチャ・イベントに変換され、アプリケーションに渡されます。
タッチ・コントローラからのタッチの座標にTouchGFX エンジンからアクセスする方法は、実装を渡すことです。
TouchGFX Generatorによって以下のコードが生成されます。
TouchGFXConfiguration.cpp
static STM32TouchController tc;
...
static TouchGFXHAL hal(dma, display, tc, 390, 390);
TouchGFXエンジンのレンダリング・サイクルの間、入力の収集時に、エンジンはtc オブジェクトに対してsampleTouch()
関数を呼び出します。
bool STM32TouchController::sampleTouch(int32_t& x, int32_t& y)
AL開発者によって行われる実装では、読出しタッチ座標値をxおよびyに割り当て、タッチが検出されたかどうか(trueまたはfalse) を返す必要があります。
Tip
この関数の実装方法は複数あります。
- sampleTouch() でのポーリング: リクエストを送信し、結果のポーリングをすることで、ハードウェアのタッチ・コントローラ(通常はI2C) からタッチ・ステータスを読み出します。 この方法では、I2Cのラウンドトリップに最大で1 msかかり、その間グラフィックス・エンジンがブロックされるため、アプリケーションの全体的な描画時間に影響が生じます。
- 割込みベースもう1つの方法は、割込みの使用です。 I2Cの読出しコマンドは、タイマによって定期的に開始されるか、タッチ・ハードウェアからの外部割込みへの応答として開始されます。 I2Cデータが使用可能になると(別の割込み)、メッセージ・キューまたはグローバル変数を介してそのデータが
STM32TouchController
で使用可能になります。STM32TouchController.cpp
(TouchGFX Generatorによって生成)からの以下のコードは、sampleTouch
でRTOS搭載のシステムを検索する方法を示しています。
STM32TouchController.cpp
bool STM32TouchController::sampleTouch(int32_t& x, int32_t& y)
{
if (osMessageQueueGet(mid_MsgQueue, &msg, NULL, 0U) == osOK)
{
x = msg.x;
y = msg.y;
return true;
}
return false;
}
このファイルの場所は、TouchGFX Generatorに関する次の章で説明します。
その他の外部イベント
ボタン・コントローラ・インタフェース、touchgfx::ButtonController
を使用して、ハードウェア信号(ボタンなど)をアプリケーションに対するイベントにマッピングできます。 これらのイベントへの対応は、TouchGFX Designer内で設定できます。
このインタフェースの使用方法は前述のタッチ・コントローラとよく似ていますが、ButtonControllerが必須ではない点が異なります。 これを使用するには、ButtonController
インタフェースを実装するクラスのインスタンスを作成し、そのインスタンスへの参照をHALに渡します。
MyButtonController.cpp
class MyButtonController : public touchgfx::ButtonController
{
bool sample(uint8_t& key)
{
... //Sample IO, set key, return true/false
}
};
TouchGFXHAL.cpp
static MyButtonController bc;
void TouchGFXHAL::initialize()
{
...
TouchGFXGeneratedHAL::initialize();
setButtonController(&bc);
}
ButtonControllerクラス内のサンプル・メソッドは各フレームの前に呼び出されます。 Trueを返すと、現在の画面のhandleKeyEvent イベントハンドラにキー値が渡されます。
Further reading
フレームバッファへのアクセスの同期
複数のアクターがフレームバッファ・メモリへのアクセスに関わっている可能性があります。
1 | CPU | レンダリング時にピクセルを読み書きします。 |
2 | DMA2D* | ハードウェア支援のレンダリング時にピクセルを読み書きします。 |
3 | LTDC | パラレルRGBディスプレイへの転送時にピクセルを読み出します。 |
4 | DMA | SPIディスプレイへの転送時にピクセルを読み出します。 |
TouchGFXエンジンは、OSWrappers
インタフェースを介してフレームバッファへのアクセスを同期するので、フレームバッファにアクセスしようとするペリフェラル(DMA2Dなど)も同じことを行う必要があります。 通常の設計ではセマフォを使用してフレームバッファへのアクセスをガードしますが、その他の同期メカニズムを使用することもできます。
次の表は、OSWrappers
クラス(OSWrappers.cpp)内の関数の一覧を示しています。このクラスはTouchGFX Generatorによって生成するか、ユーザが手動で生成できます。
メソッド | 説明 |
---|---|
takeFrameBufferSemaphore | フレームバッファへの排他的アクセスを取得するために、グラフィック・エンジンによって呼び出されます。 DMA2Dが完了するまで(実行中の場合) エンジンをブロックします。 |
tryTakeFrameBufferSemaphore | ロックの実行を確認します。 このメソッドはブロックを行いませんが、takeFrameBufferSemaphoreへの次の呼び出しによって、その呼び出し元が確実にブロックされます。 |
giveFrameBufferSemaphore | フレームバッファのロックを解除します。 |
giveFrameBufferSemaphoreFromISR | 割込みコンテキストからフレームバッファのロックを解除します。 |
Tip
次に使用可能なフレームバッファ領域のレポート
レンダリング戦略に関係なく、TouchGFXエンジンは、ピクセルをレンダリングするメモリ領域をティックごとに把握する必要があります。 シングルまたはダブル・フレームバッファの戦略を使用して、TouchGFXエンジンは、フレームバッファの全幅、高さ、ビット深度に従ってピクセル・データをメモリ領域に書き込みます。 ダブル・バッファ設定では、グラフィック・エンジンが2つのバッファ間のスワッピングを管理します。
フレームバッファへのアクセスは一部分に制限できます。 HAL::getTFTCurrentLine()
メソッドをHALサブクラス内に再実装できます。 グラフィック・エンジンの描画用に保存される最後のライン番号が返されます。
部分的フレームバッファの戦略を使用して、開発者は、TouchGFXエンジンがレンダリング時に使用する1つ以上のメモリ・ブロックを定義します。 詳細については、こちらを参照してください。
Tip
レンダリング操作の実行
グラフィックスのレンダリングと表示だけを目的とするアプリケーションはほとんどありません。 その他のタスクもCPUを使用する必要があります。 TouchGFXの1つの目標は、できる限り少ないCPUサイクルを使用してユーザ・インタフェースを描画することです。 HALクラスは、多くのSTM32マイクロコントローラ(またはその他のハードウェア機能) にあるDMA2Dを抽象化し、グラフィック・エンジンで使用できるようにします。
ビットマップなどのアセットをフレームバッファにレンダリングするときに、TouchGFXエンジンは、HALにビットマップの一部または全部をフレームバッファに「blit」(転送) する機能があるかどうか確認します。 その機能がある場合、描画操作はCPUが処理するのではなく、HALに委譲されます。
エンジンは HAL::getBlitCaps()
メソッドを呼び出し、ハードウェアの機能の説明を取得します。 HALサブクラスはこれを再実装してその機能を追加できます。
エンジンはユーザ・インタフェースを描画するときに、HALクラスに対する操作(DMAの操作をキューに入れるHAL::blitCopy
,など)を呼び出します。 HALが必要な機能をレポートしない場合、グラフィック・エンジンはソフトウェアのレンダリング・フォールバックを使用します。
Tip
ディスプレイへのフレームバッファの転送処理
フレームバッファをディスプレイに転送するため、TouchGFX ALでは「領域レンダリングの完了」フックがよく利用されます。 フレームバッファの一部のレンダリングが完了すると、エンジンがALに信号を送ります。 ALはフレームバッファのこの部分をディスプレイに転送する方法を選択できます。
領域レンダリングの完了
コードでは、このフックは仮想関数HAL::flushFrameBuffer(Rect& rect)
になります。
LTDCコントローラ付きのSTM32マイクロコントローラでは、毎回のレンダリング後のフレームバッファの転送のために行うことはありません。 この転送は、LTDCの初期化後に一定の周期で継続的に実行されるので、このメソッドの実装は空のままにしておくことができます。
その他のディスプレイ・タイプ(SPIや8080など)では、フレームバッファを手動で転送する必要があります。
この関数の実装によって、開発者はGRAM搭載ディスプレイへの当該領域のフレームバッファの手動による転送を開始できます。
void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& r)
{
HAL::flushFrameBuffer(rect); //call superclass
//start transfer if not running already!
if (!IsTransmittingData())
{
const uint8_t* pixels = ...; // Calculate pixel address
SendFrameBufferRect((uint8_t*)pixels, r.x, r.y, r.width, r.height);
}
else
{
... // Queue rect for later or wait here
}
}