FMC & SPIディスプレイ・インタフェース
このシナリオは、FMCに接続されている、またはSPI経由で接続されているLCDにピクセルを転送する際の手順について説明するものです。この2つの方法はいくつかの要素を共有しています。 このセクションで説明するシナリオは、例としてST7789H2 LCDコントローラを使用します。
STM32CubeMXで、ボード仕様に沿ったFMCまたはSPIの設定が完了すれば、TouchGFX Generatorを使用してTouchGFX HALを生成できます。これにより開発者は、アプリケーション・フレームバッファの更新された部分を接続されたディスプレイに転送するコードを作成できるようになります。
下図は、ディスプレイ・インタフェースとしてCustomが選択された、TouchGFX Generatorの設定です。 この設定は、TouchGFX Generatorに対して、開発者が手動で設定してピクセルをフレームバッファ・メモリからディスプレイに転送しようとしていることを知らせるとともに、それを実現するためのハンドラを生成します。
Tip
一般的に、GRAMを内蔵したディスプレイの場合、生成されたTouchGFX HALでユーザが記述するコードでは、以下の手順を実行する必要があります。
- 再描画されるフレームバッファの領域に基づいて、この領域に対応するGRAM内の場所に、「ディスプレイ・カーソル」および「アクティブ・ウィンドウ」を移動します。
- 送られてくるピクセル・データをGRAMに書き込む準備を整えます。
- ピクセル・データを送信します。
フレームバッファの転送
フレームバッファの領域が更新されると、TouchGFXエンジンはHAL::flushFrameBuffer(Rect r)
を呼び出します。 この関数は、SPIやFMCを使用する場合のように、開発者がピクセルをディスプレイに転送するためのコードを手動で実装する必要があるときにはオーバライドできます。 後述するように、FMCバンク経由でピクセルを転送する関数は、TouchGFX Generatorによって生成されます。
Note
ドライバは、ピクセルをディスプレイに転送し、ディスプレイのメモリ書込み位置を制御できる必要があります。 詳細については、ディスプレイのデータシートを参照してください。
void TouchGFXHAL::flushFrameBuffer(const Rect& rect)
{
/* Set Cursor */
ST7789H2_SetDisplayWindow(rect.x, rect.y, rect.width, rect.height);
/* Prepare to write to LCD RAM */
__ST7789H2_WriteReg(ST7789H2_WRITE_RAM, (uint8_t*)NULL, 0);
/* Send Pixels - User defined function */
this->copyFrameBufferBlockToLCD(rect);
}
以下の関数ST7789H2_SetDisplayWindow
は、GRAM内の仮想「カーソル」のx
およびy
座標を、特定のレジスタに書き込んで設定します。これはGRAMを使用するディスプレイでは一般的な方法です。
extern "C"
void ST7789H2_SetDisplayWindow(uint16_t Xpos, uint16_t Ypos, uint16_t Width, uint16_t Height)
{
uint8_t parameter[4];
/* CASET: Column Address Set */
parameter[0] = 0x00;
parameter[1] = Xpos;
parameter[2] = 0x00;
parameter[3] = Xpos + Width - 1;
ST7789H2_WriteReg(ST7789H2_CASET, parameter, 4);
/* RASET: Row Address Set */
parameter[0] = 0x00;
parameter[1] = Ypos;
parameter[2] = 0x00;
parameter[3] = Ypos + Height - 1;
ST7789H2_WriteReg(ST7789H2_RASET, parameter, 4);
}
この例では、次に示す関数TouchGFXHAL::copyFrameBufferBlockToLCD
は、更新された領域(Rect
)を一度に1ラインずつ送信し、それに応じてフレームバッファのポインタが進んでいることを確認するユーザ定義の関数です。
void TouchGFXHAL::copyFrameBufferBlockToLCD(const Rect& rect)
{
__IO uint16_t* ptr;
uint32_t height;
// This can be accelerated using regular DMA hardware
for (height = 0; height < rect.height ; height++)
{
ptr = getClientFrameBuffer() + rect.x + (height + rect.y) * HAL::DISPLAY_WIDTH;
LCD_IO_WriteMultipleData((uint16_t*)ptr, rect.width);
}
}
ptr
を手動で進めるのではなく、TouchGFX Generatorが生成する関数advanceFrameBufferToRect
が、フレームバッファ内のRect
の位置に従ってptr
を進めます。
inline uint8_t* TouchGFXGeneratedHAL::advanceFrameBufferToRect(uint8_t* fbPtr, const touchgfx::Rect& rect) const
{
// Advance vertically Advance horizontally
fbPtr += rect.y * lcd().framebufferStride() + rect.x * 2;
return fbPtr;
}
FMC
少なくとも1つのFMCバンクが正しく設定されていれば、TouchGFX GeneratorはFMCディスプレイ・インタフェースもサポートします。 この場合、TouchGFX Generatorによって生成されるコードはCustomディスプレイ・インタフェースのコードとよく似ていますが、ディスプレイに接続されているFMCバンクとやり取りするために、関数LCD_IO_WriteMultipleData
が生成される点が異なっています。 関数copyFrameBufferBlockToLCD
用に前に提示したコードをもう一度見直してみると、この生成された関数の使用を確認できます。
Tip
__weak void LCD_IO_WriteMultipleData(uint16_t* pData, uint32_t Size)
{
uint32_t i;
for (i = 0; i < Size; i++)
{
FMC_BANK1_WriteData(pData[i]);
}
}
次の図は、FMCバンク2の有効な16ビットの(必須)設定を示しています(どちらも使用できます)。
有効な設定が完了すると、TouchGFX Generatorでこのバンクを選択できるようになります。 使用するマイクロコントローラのFMCバンク・レジスタの開始アドレスを確認してください。
TouchGFX GeneratorはFMCバンクの設定を検証し、問題が見つかればそれを報告します。
HAL::flushFrameBuffer()から戻る
この関数から戻ると、TouchGFXエンジンはフレームの残りの部分の描画を続けます。 ディスプレイへのピクセルの転送にDMAを使用する場合、開発者はDMA完了割込み信号によるセマフォを待機するなどして、HAL::flushFrameBuffer(Rect& rect)
すぐに戻らないようにする必要があります。
次の擬似コードは、DMAを使用する場合のHAL::flushFrameBuffer()
の構成例を示したものです。 このコードは、FreeRTOSのセマフォscreen_frame_buffer_sem
を使用しています。
void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect)
{
uint16_t* fb = HAL::lockFrameBuffer();
//Prepare display: Set cursor, write to display gram as described previously in this scenario
//Try to take a display semaphore - Always free at this point
xSemaphoreTake(screen_frame_buffer_sem, portMAX_DELAY);
//Set up DMA
screenDMAEnable();
// Wait for the DMA transfer to complete
xSemaphoreTake(screen_frame_buffer_sem, portMAX_DELAY);
//Unlock framebuffer and give semaphore back
HAL::unlockFrameBuffer();
xSemaphoreGive(screen_frame_buffer_sem);
}
Caution
TouchGFXドライバ / ティアリング効果信号
上記TouchGFX Generatorの設定では、[Application Tick Source]も[Custom]に設定されています。これは、TFTコントローラを内蔵していないマイクロコントローラでは一般的な設定です。
「抽象化レイヤ・アーキテクチャ」のセクションで述べたように、TouchGFXエンジンのメイン・ループは、通常、ディスプレイから信号が送られた時点で、OSWrappers::signalVSync()
を呼び出すことでブロック解除されます。
シリアルまたは8080ディスプレイ・インタフェースを備えたディスプレイの場合、通常は内蔵ディスプレイ・コントローラがティアリング効果(TE) の信号を周期的に生成します。この信号は、マイクロコントローラのGPIOに接続できます。 その場合、通常は、GPIOに信号が送られると割込みが発生するようにマイクロコントローラを設定します。 この「ティアリング効果」割込みにより、TouchGFXエンジンのメイン・ループのブロックが解除され、次のフレームがレンダリングされます。 STM32CubeMXで、必ずこのGPIOを入力に設定し、そのピンの外部割込みを有効化してください。
extern "C"
void TE_Handler(void)
{
...
/* Unblock TouchGFX Engine Main Loop to render next frame */
OSWrappers::signalVSync();
...
}
結論
TouchGFX GeneratorでCustomディスプレイ・インタフェースを選択することは、アプリケーション・フレームバッファからディスプレイにピクセルを転送するコードを、開発者が手動で作成することを意味します。
TouchGFX Generatorは、関数 TouchGFXHAL::flushFrameBuffer(Rect& rect)
を生成します。この関数は、フレームバッファの領域のレンダリングが完了するとTouchGFXによって自動的に呼び出され、開発者は、これを使用して、影響を受けるピクセルをディスプレイ、SPI、FMCなどに転送できます。 いずれの場合でも、以下の手順を実行する必要があります。
- 再描画されるフレームバッファの領域に基づいて、この領域に対応するGRAM内の場所に、「ディスプレイ・カーソル」および「アクティブ・ウィンドウ」を移動します。
- 送られてくるピクセル・データをGRAMに書き込む準備を整えます。
- ピクセル・データを送信します。 FMCディスプレイ・インタフェースの場合は、この関数が生成され、
flushFrameBuffer(Rect& rect)
の内部で使用することができます(この記事の前半部を参照)。
ディスプレイ・インタフェースとしてCustomまたはFMCを選択する場合、TouchGFX Engineのメイン・ループのブロックを解除するOSWrappers::signalVSync()
の信号を生成するカスタムのTouchGFXアプリケーション・ティック・ドライバの実装も必要になります。 通常、TFTコントローラを搭載していないマイクロコントローラとともに使用されるディスプレイは、マイクロコントローラに接続されるティアリング効果信号を出力できます。