Skip to main content

FMC & SPIディスプレイ・インタフェース

このシナリオは、LCDをFMCに接続するかSPIを介して使用し、TouchGFX GeneratorでCustomディスプレイ・インタフェースを選択した場合の、一般的なTouchGFXドライバ作成手順を説明するものです。

Further reading
touchGFX Designerで提供されているSTM32L496-DISCOアプリケーション・テンプレートはFMCを使用しています。このテンプレートを調べることで、TouchGFXディスプレイ・ドライバの実装方法に関するヒントが得られます。

ディスプレイ・コントローラが内蔵されていないマイクロコントローラ向けにTouchGFXディスプレイ・ドライバを作成するプロセスは、FMCまたはSPIのいずれを使用する場合でも同じです。 このセクションで説明するシナリオは、例としてST7789H2 LCDコントローラを使用します。

CubeMXで、ボード仕様に沿ったFMCまたはSPIの設定が完了すれば、TouchGFX Generatorを使用し、ディスプレイ・インタフェースとしてCustom を選択すると、HALを生成できます。これにより開発者は、アプリケーション・フレームバッファの更新された部分を接続されたディスプレイに転送するカスタム・コードを作成できるようになります。

下図は、ディスプレイ・インタフェースとしてCustom が選択された、TouchGFX Generatorの設定です。 この設定は、TouchGFX Generatorに対して、開発者が手動で設定してピクセルをフレームバッファ・メモリからディスプレイに転送しようとしていることを知らせるとともに、それを実現するためのハンドラを生成します。

Note
このセクションに示したST7789H2用のドライバ・コードは、ボードの立ち上げフェーズで開発されたものであり、機能すれば、多かれ少なかれTouchGFX Generatorによって生成されるHALクラスにコピーできます。

ドライバは、ピクセルをディスプレイに転送し、ディスプレイのメモリ書込み位置を制御できる必要があります。 ディスプレイのデータシートで、該当するコマンド(後述)と詳細を確認してください。

TouchGFX Generatorの設定

一般的に、8080やSPIディスプレイなどのGRAMを内蔵したディスプレイの場合、ドライバは次のように動作します。

  1. 再描画されるフレームバッファの領域に基づいて、この領域に対応するGRAM内の場所に、「ディスプレイ・カーソル」および「アクティブ・ウィンドウ」を移動します。
  2. 送られてくるピクセル・データをGRAMに書き込む準備を整えます。
  3. ピクセル・データを送信します。

フレームバッファの転送

フレームバッファの領域が更新されると、TouchGFXエンジンはHAL::flushFrameBuffer(Rect r)を呼び出します。 [Custom]ディスプレイ・インタフェース用のドライバを実装する必要がある場合は、この関数を上書きできます。

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 */
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);
}

The following function TouchGFXHAL::copyFrameBufferBlockToLCD is a private function that sends one line of the updated area (Rect) at a time, ensuring to progress the framebuffer pointer accordingly.

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) * BSP_LCD_GetXSize();
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;
}

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
prepare();

//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);
}

TouchGFXドライバ / ティアリング効果信号

上記TouchGFX Generatorの設定では、[Application Tick Source]も[Custom]に設定されています。これは、TFTコントローラを内蔵していないマイクロコントローラでは一般的な設定です。

「抽象化レイヤ・アーキテクチャ」のセクションで述べたように、TouchGFXエンジンのメイン・ループは、通常、ディスプレイから信号が送られた時点で、OSWrappers::signalVSync()を呼び出すことでブロック解除されます。

シリアルまたは8080ディスプレイ・インタフェースを備えたディスプレイの場合、通常は内蔵ディスプレイ・コントローラがティアリング効果(TE) の信号を周期的に生成します。この信号は、マイクロコントローラのGPIOに接続できます。 その場合、通常は、GPIOに信号が送られると割込みが発生するようにマイクロコントローラを設定します。 この「ティアリング効果」割込みにより、TouchGFXエンジンのメイン・ループのブロックが解除され、次のフレームがレンダリングされます。 CubeMXで、必ずこの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などに転送できます。

ディスプレイ・インタフェースとして[Custom] を選択する場合、TouchGFXエンジンのメイン・ループのブロックを解除する OSWrappers::signalVSync()の信号を生成するカスタムのTouchGFXアプリケーション・ティック・ドライバの実装も必要になります。 通常、TFTコントローラを搭載していないマイクロコントローラとともに使用されるディスプレイは、マイクロコントローラに接続されるティアリング効果信号を出力できます。