3. 内蔵RAMに配置したフレームバッファ
動機
このステップでは、内蔵RAMからディスプレイにピクセル・データを転送することで、ディスプレイが機能することを確認します。 ここでは、データをディスプレイに送信できること、およびディスプレイの内容を継続的に更新できることを確認します。
画像データをディスプレイに転送するだけでなく、ディスプレイに不具合が表示されることなく、継続的に新しいデータを送信できることも確認する必要があります。 転送速度も測定します。これはディスプレイで得られるフレームレートに影響を与えます。
フレームバッファは内部RAMに配置します。前のセクションで述べたように、このRAMは読出しと書込みの両方が可能だからです。 このフレームバッファを繰り返し更新し、ディスプレイに転送します。
フレームバッファのサイズは次式で計算されることを思い出してください。
幅 x 高さ x ビット/ピクセル
したがって、たとえば解像度480 x 272の一般的な16bitディスプレイでは、480 x 272 x 16/8バイト = 261120バイトを占有します。
フレームバッファが大きくなりすぎて、内部RAMに格納できないようなディスプレイ・サイズでも、このステップをスキップしないでください。 その場合は、ディスプレイの一部のみを更新するようにディスプレイ・コントローラを設定します。 この方法により、フレームバッファに必要なRAMの容量を内部RAMに合わせて調整できます。
ディスプレイ・インタフェースのタイプは、フレームバッファの転送に必要な設定とコードに大きな影響を与えます。 このセクションでは、まずLTDCに接続するディスプレイをターゲットにします。 たとえばSPIディスプレイを使用する場合、コードは大きく変わりますが、タスクや目標は同じです。
目標
このセクションの目標は、フレームバッファをディスプレイに転送することです。 フレームバッファの内容を継続的に変更してディスプレイに再送信できることも検証する必要があります。
検証
次の表に、このセクションの検証ポイントを示します。
検証ポイント | 検証内容 |
---|---|
フレームバッファが表示される | ディスプレイ・コントローラまたはSPIが設定され動作していること。 |
更新されたフレームバッファが表示される | フレームバッファを繰り返し送信する仕組みがわかること。 |
色が正しい | GPIOが正しい(LTDC)、またはディスプレイのデータ・フォーマットがフレームバッファと一致すること。 |
フレームレートが正しい | ピクセル・クロックとポーチが必要なフレームレートを得られるように設定されていること。 |
前提条件
以下に、このステップの前提条件を示します。
- ディスプレイに関する情報(通常はデータシート)
- マイクロコントローラとディスプレイの接続に関する情報
作業内容
必要な設定は、ディスプレイのタイプによって異なります。 しかし、どのタイプのディスプレイでも、内部RAMにフレームバッファが必要です。 このメモリを割り当てる簡単な方法は、単に適切なサイズのグローバル配列を宣言することです。
main.c
uint16_t framebuffer[480*272]; //16 bpp framebuffer
内部RAMがこの配列を保持するのに十分な大きさではない場合は、480 x 200など低い解像度に対応する配列を宣言します。
フレームバッファをディスプレイに転送する方法はディスプレイのタイプによって異なります。 次の今度はこの点について見てみましょう。
パラレルRGBディスプレイ
はじめに、マイクロコントローラのLTDCコントローラに接続されたパラレルRGBディスプレイについて説明します。
このようなディスプレイの設定作業は、次のとおりです。
- ディスプレイへのGPIO接続の設定
- LTDCコントローラの設定
- LTDCのピクセル・クロックの設定
- フレームバッファのアドレスの設定
- フレームレートの確認
説明例として、STM32F746Discovery評価キットを使用します。 このボードは、480 x 272のディスプレイを搭載しています。
ディスプレイのGPIO
このディスプレイは24BPPモードで動作するため、LTDCとディスプレイを接続するために24のGPIOを設定します。 最も簡単に設定するには、STM32CubeMXの[Multimedia]>[LTDC]>[GPIO Settings]を使用します。
ピクセル転送のための24のGPIO(LTDC_B0など)に加え、4つのディスプレイ制御信号も設定します。
信号 | 機能 |
---|---|
LTDC_CLK | ピクセル・クロック。 24本のラインからピクセルをサンプリングするタイミングをディスプレイに知らせる信号です。 |
LTDC_DE | データ・イネーブル。 この信号がアクティブの場合にピクセルが送信されます。 |
LTDC_HSYNC | 水平同期。 ディスプレイがピクセル・ラインの開始を検出できるようにします。 |
LTDC_VSYNC | 垂直同期。 ディスプレイがフレームの開始を検出できるようにします。 |
ハードウェア設計を確認し、対応する設定を行ってください。
LTDCの設定
LTDCの設定は、STM32CubeMXの[Multimedia]>[LTDC]>[Parameter Settings]にあります。
アクティブな幅と高さは、ディスプレイの解像度に対応します。 同期パルスの幅とポーチの幅は、ディスプレイのデータシートを確認してください。 信号の極性にも注意が必要です。 灰色で表示されている値は、他の値から計算されるものです。 これらの値は、LTDCのレジスタに書き込まれます(コード内で確認できます)。
LTDCレイヤの設定に移動します([Multimedia]>[LTDC]>[Layer Settings])。
このテストおよび一般的なTouchGFXで使用するのは、1レイヤのみです。 レイヤ0の解像度を、フレームバッファの寸法に合わせる必要があります。 フレームバッファのアドレスは後ほど設定する必要がありますが、ここでは変更しません。
ディスプレイの解像度よりも小さいフレームバッファ配列を宣言した場合は、その寸法に一致するようにレイヤのサイズを調整します。 LTDCは、フレームバッファで使用できないディスプレイ・ピクセルのバックグラウンドカラーを送信します。 バックグラウンドカラーは、赤(B: 0x00, G: 0x00, R: 0xFF) などの識別しやすい値に設定することを推奨します。
クロック設定
クロック設定も重要です。 すべてのGPIOとLTDCでクロックを有効化する必要があります。 ピクセル・クロックはディスプレイで許容できる範囲に収める必要があります。
LTDCは3つのクロック、HCLK、PCLK2、LCD_CLKを使用します。
フレームバッファのアドレス設定
STM32CubeMXで、レイヤ0のフレームバッファ・アドレスを0xC0000000に設定しました。 これを、内部RAMの配列のアドレスに変更する必要があります。 STM32CubeファームウェアのいずれかのHAL関数を使用すれば簡単です。
main.c
/* USER CODE BEGIN 2 */
HAL_LTDC_SetAddress(&hltdc, framebuffer, LTDC_LAYER_1);
/* USER CODE END 2 */
HAL関数ではレイヤの番号は1、2、ですが、STM32CubeMXでは0、1です。 LTDCの他の部分は、STM32CubeMXが生成するコードの関数MX_LTDC_Init(void)で完全に設定されます。
LTDCコントローラはフレームバッファをディスプレイに繰り返し送信します。 表示される画像は、フレームバッファ内の値で決まります。 フレームバッファ内の値またはパターンを変更してみてください。 たとえば、memsetを使用してフレームバッファを0xFFにクリアすると、白い画面が表示されます。
Note
フレームレートの確認
LTDCコントローラはフレームごとに割込みを発生させます。 この割込みをアプリケーションの駆動に使用します。
デバッガを使用して、この割込みが発生していることを確認してください。
割込み間隔は、すべてのピクセルとポーチのクロッキングの合計時間になります。 フレームレートを調整するには、ポーチを調整します。 ポーチはLTDC設定の一部でした。 習慣的に、垂直フロント・ポーチ幅を拡張してフレームレートを下げます。
フレームレートを簡単に測定するには、割込みハンドラでHAL_GetTick() を使用します。
stm32f7xx_it.c
volatile int last = 0;
volatile int diff = 0;
void LTDC_IRQHandler(void)
{
/* USER CODE BEGIN LTDC_IRQn 0 */
int now = HAL_GetTick();
diff = last - now;
last = now;
/* USER CODE END LTDC_IRQn 0 */
HAL_LTDC_IRQHandler(&hltdc);
...
毎秒60フレームの場合、フレーム間の時間は1000ms / 60 = 16msになることを覚えておいてください。
SPIディスプレイ
今度はSPIバスで接続されたディスプレイについて説明します。
このようなディスプレイの設定作業は、次のとおりです。
- SPIペリフェラルとGPIOの設定
- クロックの確認
- 必要なドライバ・コードの作成または入手
SPIの設定
STM32CubeMXで開始し、SPIを有効化します。 下図は、STM32G081のプロジェクトの例です。
使用するSPIのフォーマット(データ・サイズとビット順) については、ディスプレイのデータシートを確認してください。 フレームバッファでは、16bitのワードはリトル・エンディアンのバイト順で格納されています。 このフォーマットを受け付けるようにディスプレイを設定できるかどうかを確認します。 設定できない場合は、送信時にデータを変換する必要があります。
クロックの極性と位相についても注意してください。 これらも、ディスプレイのデータシートに規定されています。
SPIクロック(ビット・レート) は、FCLKの分周回路で制御します。 最小分周比は2です。 マイクロコントローラがたとえば64MHzで動作する場合、SPIの最大ビット・レートは32Mb/sになります。
[GPIO]タブで、SPIペリフェラル用のGPIOの選択を確認できます。
[Pinout]ビューの右側にあるGPIOを選択します。
残る作業は、ディスプレイの設定と、SPIチャネルでのフレームバッファの転送です。 そのためのコードはディスプレイに大きく依存するため、STM32CubeMXでは自動生成されません。
多くのディスプレイで、コマンド・シーケンスの送信と、固有の電源投入シーケンスの実行が必要になります。 その後、通常はカラー・モードを設定し、ディスプレイをオンにします。 これはすべて、データシートまたはベンダが提供するサンプルに示されています。
STM32Cubeファームウェアには、SPI通信を使用したサンプルが含まれています。 STM32Cube HALには各種ヘルパ関数があります。 データ送信の基本的な関数は次のとおりです。
stm32g0xx_hal_spi.h
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
通信が安定して動作するまで、これらの関数を使用することを推奨します。 安定した後は、専用の関数を作成することで性能が向上する場合があります。
SPIディスプレイ・ドライバを作成する工程では、SPI対応のオシロスコープまたはSPI-USBロガーが非常に役立ちます。
Note
ディスプレイの色の確認
ディスプレイにフレームバッファを送信できるようになったこの段階で、ディスプレイの色を徹底的に確認することを推奨します。
フレームバッファに色を書き込み、目視でディスプレイを確認するということです。 以下にいくつかの例を示します。
テスト | 説明 |
---|---|
赤 | フレームバッファに赤色を設定します。 ディスプレイも赤くなる必要があります。 |
緑 | フレームバッファに緑色を設定します。 ディスプレイも緑色になる必要があります。 |
青 | フレームバッファに青色を設定します。 ディスプレイも青くなる必要があります。 |
暗色 | 50%赤などの暗色(0x8000など)は、ディスプレイに暗く表示される必要があります。 |
変化する色 | フレームバッファを毎秒変更して、ディスプレイも更新されることを確認します。 |
RGB565フレームバッファに色を設定するには、次の方式を使用できます。
uint8_t r = 0xff, g = 0x00, b = 0x00; // Solid red
uint16_t col = ((r>>3)<<11) | ((g>>2)<<5) | (b>>3); // Convert colors to RGB565
// put colors into the framebuffer
for(int i = 0; i < W*H; i++) {
framebuffer[i] = col;
}
24BPPディスプレイの場合、バイト・ポインタを使用すると、コードがより適切に構築されます(色はBGRの順に格納されます)。
uint8_t* framebuffer[480*272*3]; //24 bit framebuffer
...
uint8_t *fb = framebuffer;
while(fb < (uint8_t*)(framebuffer + (480*272*3))) {
*fb++ = 0x00; // Write blue color and increment pointer by one byte
*fb++ = 0x00; // Write green color
*fb++ = 0xFF; // Write red color
}