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

MIPI-DSIコマンド・モード

このセクションでは、コマンド・モード用のMIPI DSIインタフェースの設定方法と、TouchGFX Generatorでこの設定を使用する方法について説明します。 この記事で使用する例は、16ビットRGB565のフレームバッファ・フォーマットに対応するもので、STM32CubeMXでは一般的に以下の設定で実行されます。また、コード例を示します。

  • LTDC IP
  • DSI-HOST IP
  • TouchGFX Generator

STM32CubeMX - ハードウェア設定

LTDC

  • モード
    • Display TypeをRGB565(16 bits)- DSI Modeに設定
  • レイヤの設定
    • Number of layersを1 layerに設定
    • スクリーン解像度をWindows PositionFrame Buffer Line Length、およびFrame Buffer Number of Linesに設定
    • Layer 0 - Pixel FormatをRGB565に設定
    • Layer 0 - Alpha constant for blendingを255に設定

      LTDCの設定

  • NVICの設定
    • LTDC global interruptLTDC global error interruptは両方とも不要なため、無効にする必要があります。

      LTDC NVICの設定

DSIHOSTの設定

  • モード
    • DSIHostをAdapted Command Mode with TE Pinに設定
  • ディスプレイ・インタフェース
    • Color CodingをRGB565(16 bits)- DSI modeに設定
    • Maximum Command Sizeをディスプレイ幅相当の数値に設定
    • The Refresh of the Display Frame Buffer is Triggeredをmanually by Enabling the LTDCに設定
    • 残りの設定は選択したLCD HWに応じて異なる

      DSIHOSTの設定

  • NVICの設定
    • DSI global interruptを有効にする

      DSIHOST NVICの設定

STM32CubeMX - TouchGFX Generator

  • モード
    • Graphics Applicationを有効にする
  • TouchGFX Generator
    • Display / InterfaceをParallel RGB(LTDC)に設定(これはアプリケーションが通信する必要があるコントローラであるため)
    • Application Tick SourceをCustomに設定

      TouchGFX Generatorの設定

DSIHOST / LTDCの初期化シーケンス

  • MX_DSIHOST_DSI_Init()の呼出しは、MX_LTDC_Init()の前に行う必要があります。 これはCubeMXで処理します。

  • 使用しているLCDコントローラに固有の必要な初期化コードをMX_LTDC_Init()関数の最後に追加する必要があります。これは通常、DSI HAL APIのHAL_DSI_ShortWrite()およびHAL_DSI_LongWrite()に基づいています。

    /**
    * @brief LTDC Initialization Function
    * @param None
    * @retval None
    */
    static void MX_LTDC_Init(void)
    {
    ...

    /* USER CODE BEGIN LTDC_Init 2 */

    __HAL_LTDC_DISABLE(&hltdc);
    HAL_DSI_Start(&hdsi);

    // Specific LCD controller's initialization code
    ...

    // Turn display off until first frame
    HAL_DSI_ShortWrite(&hdsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, DSI_SET_DISPLAY_OFF, 0x00);

    __HAL_LTDC_ENABLE(&hltdc);

    /* USER CODE END LTDC_Init 2 */

    ...
    }

DSIコマンド・モード用に更新されたTouchGFXHALクラス

アプリケーションで最初のフレームの描画を完了するまでMIPI DSIディスプレイがオンになるのを防ぐ1つの方法は、関数TouchGFXHAL::endFrameをガードして、TouchGFXが最初のフレームを描画するまでディスプレイをオフにしておくことです。 TouchGFXHAL::endFrame()を以下のように更新すると、PWM出力用に設定されたHWタイマを通して、LCDとそのバックライトを有効にできます。

void TouchGFXHAL::endFrame()
{
if (!display_on)
{
display_on = true;
/* Enable the LCD, Send Display on DCS command to display */
HAL_DSI_ShortWrite(&hdsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, DSI_SET_DISPLAY_ON, 0x00);
/* Start PWM Timer channel */
(void)HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
/* Enable Backlight by setting Brightness to 100% */
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, 2U * 100);
}

TouchGFXGeneratedHAL::endFrame();
}

DSIコマンド・モードでは、ディスプレイの内部GRAMは、LTDCを使用してウィンドウをそれに書き込むことにより更新されます。 ウィンドウのサイズはスクリーン領域全体に設定できます。

void LCD_SetUpdateRegion()
{
uint8_t pCols[4] = {0x00, 0x00, 0x01, 0xDF}; /* 0 -> 480 */
uint8_t pRows[4] = {0x00, 0x00, 0x03, 0x3F}; /* 0 -> 800 */

HAL_DSI_LongWrite(&hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 4, OTM8009A_CMD_CASET, pCols);
HAL_DSI_LongWrite(&hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 4, OTM8009A_CMD_PASET, pRows);
}

ディスプレイでは、GRAMのピクセル値に基づいてピクセルを設定することで、スクリーンが1行ずつ更新されます。 スクリーンでのティアリングを防ぐには、GRAMの各ピクセル値を更新してからスクリーンに描画する必要があります。 更新がスキャンラインに追いつけるかぎり、ディスプレイの更新中に値をGRAMに書き込むことができます。 GRAMの更新を安全に開始できる場合は、ディスプレイからティアリング効果信号が発せられます。 TE信号を有効にしてディスプレイの最終行に設定するコードは、高さが800ピクセルのスクリーンの場合、次のようになります。

/**
* Request TE at scanline 800.
*/
void LCD_ReqTear(void)
{
static uint8_t ScanLineParams[2];

uint16_t scanline = 800;
ScanLineParams[0] = scanline >> 8;
ScanLineParams[1] = scanline & 0x00FF;

HAL_DSI_LongWrite(&hdsi, LCD_OTM8009A_ID, DSI_DCS_LONG_PKT_WRITE, 2, 0x44, ScanLineParams);
// set_tear_on
HAL_DSI_ShortWrite(&hdsi, LCD_OTM8009A_ID, DSI_DCS_SHORT_PKT_WRITE_P1, 0x35, 0x00);
}

TE信号は割込みをトリガし、これはLTDC経由での転送の開始に使用されます。 転送が完了したら、別の割り込みがトリガされます。 この例では、ダブル・フレームバッファを使用して、一方のバッファに描画しながら、もう一方をGRAMに転送できるようにします。

void HAL_DSI_TearingEffectCallback(DSI_HandleTypeDef* hdsi)
{
GPIO::set(GPIO::VSYNC_FREQ);
HAL::getInstance()->vSync();
OSWrappers::signalVSync();

if (HAL::getInstance())
{
// Swap frame buffers immediately instead of waiting for the task to be scheduled in.
// Note: task will also swap when it wakes up, but that operation is guarded and will not have
// any effect if already swapped.
HAL::getInstance()->swapFrameBuffers();
}

// Transfer frame buffer data
HAL_DSI_Refresh(hdsi);
}

void HAL_DSI_EndOfRefreshCallback(DSI_HandleTypeDef* hdsi)
{
//Point LTDC base address to the new frame buffer:
__HAL_DSI_WRAPPER_DISABLE(hdsi);
LTDC_LAYER(&hltdc, 0)->CFBAR = ((uint32_t)currFbBase);
__HAL_LTDC_RELOAD_IMMEDIATE_CONFIG(&hltdc);
__HAL_DSI_WRAPPER_ENABLE(hdsi);

GPIO::clear(GPIO::VSYNC_FREQ);
}