春雨日記 about me tags

秋月の300円液晶というものが15年程前に流行りましたが,解析されたインタフェースがLTDCで使用できそうだったため,検証してみました.

はじめに

既に解析し尽くされた感のある300円液晶ですが,当時はまだマイコンの性能が低く,扱いづらさ故に動かすことがゴールと化している感がありました.(私の偏見です)

ですが,現代のマイコンはLCDコントローラを内蔵して,かつグラフィックアクセラレータを搭載した物でも安価で買えるようになっています.

例えば,Aliexpressで売っているWeAct製 STM32H7B0VBT6ボードは約$15でSTM32H7マイコン+液晶+カメラが揃っており激安と言えるレベルです. しかも280MHz駆動でRAMは1.4MBとそれなりの仕事はしれっとこなしてくれそう

400*96でRGB666なので512KBのD1ドメインにも収まります. つまりフレームバッファの外部RAMすら不要

安価マイコンでも外部デバイス無しで十分にこの手の液晶を駆動できる時代となったわけです.

というわけで,折角買ったH7ボードの動作試験も兼ねて現代のマイコンで動かしてみることにしました.

今回使用するのは上記ボードではなく,480MHzのSTM32H750VBT6(WeAct製)です. こちらも色々付いて$23だったので安いです.

300円液晶

本名をLTA042B010Fといい,アミューズメント用途で作られたモジュールらしいです.

秋月によると販売開始は2006年12月となっていますが,秋月に流れてきたということは保守期間が終わったか在庫が必要無くなったタイミングだと考えられます.

Toshiba Matsushita Displayとの記載から少なくとも2002年以後に作られた物のはずなので,まあ20年モノのディスプレイでしょうね…1

古いだけあって様々な人がこのモジュールを手にしており,なる研さんのHPに詳しい解析結果が掲載されています.

大体のスペックとしては,

  • RGBインタフェース
  • 解像度400*96
  • RGB666
  • DEは無さそう

で,微妙に角度は狭いですが発色は$3液晶よりも綺麗だと思います.

あと炎天下でもそれなりに見えるのが良いですね.

LTDC

STM32に搭載されている液晶コントローラで,LCD-TFT Controllerの略らしいです.

外部RAMや内部RAMから供給されたクロックに従って液晶を駆動してくれるペリフェラルで,同期のタイミングやエッジを細かく設定する事が可能です.

設定すれば後は勝手にRAMから読み出して描画を行い,同期信号の生成まで行ってくれるので凄いです.

私のボードはRGB666とRGB565で駆動できるようですが,H7B0VBT6はRGB888も追加で駆動できたり,F769NIH6はDSIも駆動できたりなどバリエーションがあります.

300円液晶はRGB666なの問題無いです.

CubeMX設定

それでは,設定していきます.

なる研さんのHPを見ながら試した結果,同期パラメータは以下の通りでうまく動作しました.

項目
水平バックポーチ 107ドット
水平フロントポーチ 53ドット
垂直バックポーチ 16ライン
垂直フロントポーチ 25ライン
垂直同期 最低153クロック→1ライン

というわけで,LTDCペリフェラルを以下の通り設定します.

なお,これらの項目が意味する所はこちらの記事が詳しいです.

次に,描画するレイヤの設定をします.

背景とオブジェクトといった重ね合わせ描写も可能らしいですが,今回は内蔵SRAMのみなので1層としました.

注意して欲しいのがLayer 0 - Color Frame Buffer Start Adressです.これは,フレームバッファを格納するメモリアドレスを指定する箇所ですが,MX_LTDC_Init(void)で起動時に設定されてそのままLTDCが動作してしまうため,(CubeMXに従うなら)正しい値を決め打ちする必要があります.

今回は面倒だったのでdata領域に直接確保する事で0x24000000に用意しています.(後述)

次に,数MHzの高速なデータ転送を行う事になるためGPIOの動作モードをVery Highにしておきます.

また,クロックを適当に調整しておきます.

色々試した感じ,8MHz付近が良い感じでした.

早くしすぎると応答速度は早くなりますが色が薄くなります.

また,何かの基準の整数倍にしないといけないのか? 6MHzなどにするといまいち同期が取れなくなります.

25MHzでは辛うじてマトモに動いてくれました.この辺が上限だと思います.

以上でLTDCの設定は終了です.

この時V-Sync時の動作は次の通りとなります.

(H-Sync時はここからVSがLOWでDEがHIGHとなる)

よってなる研さんの示した要件を満たします.

main.c

次はプログラムをいじっていきます.

フレームバッファ

まず,前述のフレームバッファの確保です.

Private Definiationあたりで

1
volatile uint16_t framebuffer[400*96] __attribute__ ((section(".data"))) __attribute__ ((aligned (32)));

としました.液晶はRGB666駆動ですが,RGB565でレイヤを設定しているのでuint16_tです.できるだけコードの先端付近で宣言するのが良いと思います.

このセクション指定にはdataとbssの2種類あり

  • .dataに配置すると0x24000000からほぼ不変というメリットがありますが,Flashに初期化用の領域が確保されてしまうのでバイナリサイズが無駄に大きくなってしまうデメリットがあります.

  • .bssに配置すると初期化はされないのでFlashが余裕になりますが,プログラムを書く度にアドレスが変わるという大変面倒な状態になります.

おそらくリンカスクリプトをいじる事でもっとエレガントな設定を行えると思いますが,調べてもわからなかったのでこのような状態になっています. (分かる人教えて下さい…)

なお変数の位置はCubeIDEのBuild Analyzerで確認する事ができます.

LTDCの初期化自体は勝手に行われるため特に操作は必要無く,フレームバッファに直接手を加えれば自動的に描画されていきます.

描画してみる

テストパターンを作る例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  for(uint16_t x = 0;x < 96;x++){
	  for(uint16_t y = 0;y < 96;y++){
		  if(x == 0 || x == 95)
			  framebuffer[400*y+x] = RGB565(255,0,0);
		  if(y == 0 || y == 95)
			  framebuffer[400*y+x] = RGB565(0,255,0);
		  if(y == x || (95-y) == x)
			  framebuffer[400*y+x] = RGB565(0,0,255);
	  }
  }

*RGB565はこういうマクロです.

1
#define RGB565(r,g,b) ((uint16_t)((uint16_t)((r) >> 3) << 11 | (uint16_t)((g) >> 2) << 5 | (b) >> 3))

フレームバッファを直接触る事ができるためDMA等とも組み合わせる事ができ,直接描画できるわけですから大変便利です.

しかし… DMA2Dの使い方がよくわからなかったので,今回はLTDCのみとなっています.

正直DMA2Dが使えないLTDCは片手落ちも良いところなんですけどね…

色々表示してみた

どうもRGB888→RGB565に変換した+接続時にR5とB5を勝手にオミットしてRGB565接続となっている影響でグレーが正常に写らないみたいです.

勝手にオミットするのはやめようね!

これらのイメージは非圧縮で128KぐらいになるためFlashに格納できません.今回はとある手段(笑)を使って対処しました.

やっぱり解像度不足のせいで写真系はNintendo DSで見た感じになりますね…(わかりにくい例え)

ただ,炎天下でもそこそこ見えたのとサイズが大きい,そして安価というメリットがあるのでバイクに取り付けたら良い感じなんじゃないかと目論んでいます.

おわりに

H7ボードをとりあえず使ってみるという目的が果たせて良かったです.

棚ぼた的な感じでSDMMCにも触れられて良かった笑

本当は動画を再生してニコニコ動画にあるnnFPS達成!みたいなやつに挑戦したかったのですが,結構頑張ったにもかかわらずDMA2Dの使い方が分からなかったのでナシとなりました.

ところで,このスタイルの液晶が楽々駆動できるということはPSPの液晶だって駆動できるはずなのですよ.

300円液晶はちょっとイロモノ枠かもしれませんが,PSP液晶が使えるとなるとそれなりに夢が広がるのではないでしょうか.

まあSPI接続のLCD(OLED)が安価で大画面化しているので今更感ありますけどね…

おまけ: H7でSDMMC+FatFs

画像データを格納する為になんちゃらカードを使用しました.笑

H7ではDMA設定が現れなかったので使っていません. MPUのエラッタのせいかな?

まずCubeMXにて SDMMCをSD 4 bits Wide busとして,FATFSでSD Cardにチェックを入れます.

使用する際には

1
2
3
4
5
6
7
  HAL_SD_Init(&hsd1);
  HAL_SD_InitCard(&hsd1);
    result = f_mount(&SDFatFS, (TCHAR const*)SDPath,1);
  if(result != FR_OK){
	  memset(framebuffer,0xaa,400*96*2);
	  while(1);
  }

こんな感じで.

SDPathにドライブレターが入っているので複数ドライブを接続する際にはきちんと指定しましょう.

2024/5/13追記

DMA2Dの使い方はSTのサンプルに詳しく書かれています。 F7DISCOで合成などを行ってみた記事はコチラ