今回は以前にAmazonで購入したまま積んでいた フルカラーグラフィックLCDをPICで制御してみました。
激ムズというわけではありませんでしたが、いくつかつまずいたポイントがあったので備忘録がてら記事にしたいと思います。
今回使用したLCDはこちらです。
似たような商品は他にもいくらでもあると思いますが、制御チップがこれと同じST7789であれば恐らく同じように使用できると思います。
目次
付属資料の分析
Amazonの商品説明欄に ユーザーマニュアルのURL (Google Driveで共有するのね...) が記載されていたので、そこからzipファイルをダウンロード、解凍するといろいろ資料が入っていました。
当然のごとく中国語か英語なので頑張って雰囲気で読んでいきます。
まずは基板から
いきなり前言撤回しますが、資料の前に基板の情報を見てみましょう。
表面

裏面

7ピンでピンアサインは基板表面に書かれているので分かりやすいですね。解像度は240x240、けっこう細かい画像が表示できそうです。
裏面を見ると、制御チップはST7789で、通信方法はSPIのようです。SPIはI2Cに比べるととっつきにくいイメージですが、画像のような大量のデータを送信するにはスピードが必要になるので仕方ないですね。
ところでPICのデータシートとかにある用語ではSCL, SDAはI2Cの用語で、SPIだとSCK, SDI (, SDO) だったと思うけど、本当にSPIなんだよね?
ちなみにSPIについては解説記事を書いているので知りたい方はこちらも合わせてご覧ください。https://rikeden.net/?p=356付属資料にはピンアサインについて説明がついていました。中国語ですが雰囲気で読み取ります。
通信方法が4線SPIと書かれているのでこれは有益な情報ですね。

配線図
基板に出ている7ピンが制御チップのどのピンに繋がっているのか、どんな役割なのかを見ていきましょう。
資料が複数あるんですけど、どうやらこの液晶モジュールは2重のモジュールになっているようです。
まず12ピン、極小ピッチのモジュールがあって(基板裏面橙色部分)、それを7ピン、2.54 mmピッチの基板にモジュール化しているようです。

2.54 mmピッチなのは大変ありがたい限りですが、資料を眺めているとなんだか嫌な雰囲気が...
12ピンの中にあるCSが7ピンの中には含まれていなくGNDに繋がっています。CSってSPIで通信相手を指定するときのChip Selectだよね?なんで外部から制御できるようになってないの?
ちょっと受け入れ難かったのでテスターで確かめてみたんですけど、ばっちりGNDに繋がっていました。
まあここで悩んでてもしょうがないので先に進みましょう。
ST7789データシート
次は一番大事な、制御チップST7789のデータシートです。英語で書かれてて良かった、これが中国語だったらやる気がガタ落ちでした。
さて、初めに確認するべきはやっぱり通信方法ですかね。
このICはパラレル、シリアルともに多くの通信方法に対応しているようです。
シリアルインターフェイスの説明を見てみましょう。
The serial interface is either 3-lines/9-bits or 4-lines/8-bits bi-directional interface for communication between the micro controller and the LCD driver. The 3-lines serial interface use: CSX (chip enable), SCL (serial clock) and SDA (serial data input/output), and the 4-lines serial interface use: CSX (chip enable), D/CX (data/ command flag), SCL (serial clock) and SDA (serial data input/output). Serial clock (SCL) is used for interface with MCU only, so it can be stopped when no communication is necessary.

うんうん、なるほど。4線というのは クロック、データ、D/C、CSの4本のようです。私の知ってる4線SPIとは違うけど、データは一方通行でいいのでそこは置いておきましょう。DCというのはData/Commandの選択ピンのようです。パラレル接続のときにはよくあるやつですね。まあこれは大したことは無いでしょう。
でもやっぱりCS要るよね??
まあCSが常にGNDになっているということは常に受信態勢にあるわけなのでなんとかなるのかな?
でもパケットの先頭を知る術がないので1クロックでもずれたら崩壊しそうだけど大丈夫なのだろうか?
通信タイムチャートを見てみると、クロックはIdle Lowで、データの送信はIdle to Activeっぽいですね。(フラグ)
PICのSPIの設定をこれに合わせましょう。
初期化
こういうデバイスで一番厄介なのが初期化のプロセスですね。初期化が出来ないと通信が出来ているのかすら分からないので、壊れているんじゃないかという不安と闘いながら手がかりもなく原因を探すことになります。
付属資料の中に初期化プログラムが入っていました。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
void Initial_ST7789(void) { Write_Register(0x36); Write_Parameter(0x00); Write_Register(0x3A); Write_Parameter(0x05); (省略) Write_Register(0x29); } void address(void) { Write_Register(0x2A); Write_Parameter(0x00); Write_Parameter(0x00); Write_Parameter(0x00); Write_Parameter(0xEF); Write_Register(0x2B); Write_Parameter(0x00); Write_Parameter(0x00); Write_Parameter(0x00); Write_Parameter(0xEF); Write_Register(0x2C); } |
最初のコマンド (0x36) をデータシートで調べてみると、ディスプレイの表示方向の設定のようです。

最後のコマンド (0x29) は表示をONにするコマンドのようなので、全部は見てられないけどこれで適切に初期化が出来ると信じましょう。
![]()
コマンドとパラメータを送信する関数の中身は書いてないですけど、きっとDCを切り替えて8bitをSPIで送信すれば大丈夫でしょう。address() はX, Yのアドレスの範囲を指定して、データ書き込み準備をしているようです。
これも初期化の後に呼んでおきましょう。
通信プログラム
ここでようやくPICの登場です。今回使用したのはPIC16F18326です。SPIを使うためのMSSPモジュールがあればいいと思います。
プログラムの全体は記事の最後に掲載するので、ここでは主要な部分だけ書きます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
char SPI_Exchange(char data){ char dumy; dumy = SSP1BUF; //Clear buffer SSP1BUF = data; //Send while(!SSP1STATbits.BF); return SSP1BUF; //Receive } void Write_Register(char c){ DC = 0; SPI_Exchange(c); } void Write_Parameter(char c){ DC = 1; SPI_Exchange(c); } void main(void) { Init(); SPI_Init(); Initial_ST7789(); address(); char c = 0; while(1){ Write_Parameter(c); c++; } } |
データの形式は分からないですけど、とりあえず適当な値を送れば通信が出来ているかどうかは分かるでしょう。
いざ実験!

映らない!
まあいきなり上手くいくとは思ってませんよ。
とりあえず配線を間違えてないかを確認する。とここでふと電源電圧を確認していないことに気付く。何も考えずに5Vをかけていたけど大丈夫だろうか?
資料で電源について探してみると...
![]()
あ、3.3Vだったわ。死んだわアイツ。(完)
でも今までの経験によると3.3Vのデバイスに5Vかけても案外死なないのでまだ生きてる可能性にかけて続けよう。
とりあえず電源を3.3Vに変更してもう一度チャレンジするも何も反応なし。SPIのデバイスを他に持っていないので、そもそもSPIが正しく通信出来ているかが分からない。I2Cは通信成功確認してるので多分大丈夫だと思うんだけど。
付属資料の中に他にもプログラムが入ってたんですが、そこではSPIをGPIOによってソフトウェア的に実装していたので、いったんこれでやってみることにしました。
|
1 2 3 4 5 6 7 8 9 10 11 |
void SPI_Exchange(char c){ for(char i=0; i<8; i++){ if(c & 0x80) SDA = 1; else SDA = 0; SCL = 0; SCL = 1; c <<= 1; } } |
結果

生きてた!!
ということはおそらくSPIの通信に問題がありそうです。また私の経験によると、SPIの通信が上手くいかないときは大抵通信モードが間違っているので、総当たり作戦に行きます。
クロックの極性をIdle Highに変更してみました。
結果

出来た!!
写真使いまわしてすみません。まあ表示されるもの同じなので許して。
ただしさっきのソフトウェアSPIに比べると何倍か速いです。やっぱりハードウェアで処理できるペリフェラルは速いんですねえ。
データ構造
先程はデータ構造も知らないまま適当なデータを送信して通信の成功を確認したわけですが、次はちゃんとした画像データを送信しましょう。
初期化コマンドの2つ目 (0x3A) を見てみると、これはデータの形式を設定するコマンドのようで、ここでは16bit/pixelのモードになっているようです。RGB interfaceについてはよく分かりません。

16bit/pixelのデータではR, G, Bがそれぞれ5, 6, 5 bitずつ割り当てられています。

ちなみにGに他よりも多くのビットが割り当てられているのは、人間の視覚特性として緑が最も感度が高いからです。
この16 bitを2回に分けて上位から送信します。
さて、データ形式も分かったところで表示させる画像を用意しましょう。
今回はひとまずこちらの私のアイコンを表示させてみることにしました。

この画像は512x512のpng画像なので、ひとまずペイントでサイズを240x240に変更します。16bitビットマップ に変換すればいいのですが、それでもヘッダーのデータが含まれてたり上下が逆だったりして多少手を加えなければ、使えるデータにはなりません。
データ変換ツール作成
というわけで小見出しの通りデータ変換ツールを自作しました。Visual StudioでC# を用いて作りました。
主なソースはこんな感じです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
if(openFileDialog1.ShowDialog() == DialogResult.OK) { Bitmap bmp = new Bitmap(openFileDialog1.FileName); var width = bmp.Width; var height = bmp.Height; byte[] data = new byte[width * height * 2]; int index = 0; for(int y = 0; y < height; y++) { for(int x = 0; x < width; x++) { Color c = bmp.GetPixel(x, y); UInt16 rgb16 = (UInt16)(((c.R << 8) & 0xF800) | ((c.G << 3) & 0x07E0) | ((c.B >> 3) & 0x001F)); BitConverter.GetBytes(rgb16).Reverse().ToArray().CopyTo(data, index*2); index++; } } if(saveFileDialog1.ShowDialog() == DialogResult.OK) { var fs = new FileStream(saveFileDialog1.FileName, FileMode.OpenOrCreate, FileAccess.Write); fs.Write(data, 0, data.Length); fs.Close(); } } |
17行目でそれぞれ8bitのR, G, Bの上位5, 6, 5ビットを取り出して16bitの変数にまとめています。
ちなみにWindowsはリトルエンディアンなので上位と下位を入れ替える必要があります。PCはPICと違って十分速いので、プログラムの効率とか考えずに適当に作ってます。
出来上がったデータは240x240x2 = 115200 byteある訳ですが、残念ながらPIC16F18326はプログラムメモリでも28 KBしかないので到底保存できません。
というわけで今回はPCからUSARTでデータを送信してそれをそのままディスプレイに流すという形にしたいと思います。
画像表示テスト
先程用意した画像データをTeraTermを利用して送信します。
ウィンドウ左上の ファイル>ファイル送信 から送信するファイルを選択することができます。

このときオプションのバイナリにチェックを入れておきます。

PCとPICの間には USB-シリアル変換モジュール を使用しています。このモジュールの出力は3.3VなのでPICにそのまま接続できます。
プログラムはこんな感じです。USARTで受信したデータをSPIで送信するだけです。
|
1 2 3 |
void __interrupt isr(){ Write_Parameter(EUSART_Receive()); } |
いざ実践!

なんか思ってたんと違う!
原因は割とすぐに見つかって、画像を変換するプログラムが間違ってました。(先程のプログラムは修正後のプログラムです。)
改めてデータ送信!

できた!大勝利!
ちなみにUSARTの速度は115200 bpsなので画像の描画には8秒かかることになります。実測はおよそ10秒でした。
多少は描画速度を上げられると思いますが、動画を表示させたりするのは厳しいでしょうね。まあパラレルじゃなくてシリアルなのでそこは諦めるしかないでしょう。
ソースコード
ソースコードは GitHub に載せてあります。一応ダウンロードも可能です。
エピローグ
というわけで無事にPICでフルカラーLCDに画像を表示させることに成功しました。
本当はスッキリまとめた記事を書こうと思ってたんですけど、試行錯誤の過程を書いていたらすっかりいい文量になってしまったので、また別の記事にしたいと思います。
私自身電子工作でつまづいたときにどこかの誰かの電子工作日記に助けられたことが何度かあるので、この徒然なる記事もいつか誰かの役に立つと信じます。
それでは最後までご覧いただきありがとうございました。







STLと申します。
現在、同じようなLCDを動作させようとしていて、こちらのサイトはとても参考にさせていただいています。
データ変換ツール作成のプログラムについて、C#やVisual studioをあまり使ったことがなくプログラムの内容がいまいち理解できないのですが、プログラムの全体を見せていただくことは可能でしょうか?
記事の閲覧とコメントありがとうございます。
すみませんが元のソースコードは見当たりませんでした。
C#の機能以外に自分で定義した関数などは一切使用していないので調べれば情報は出てくるかと思います。
どこが分からないのか教えてくれれば解説もしますが、今試したところChatGPTでこのコードの解説やほかの言語への変換をしてくれたので良かったら試してみてください。
ご返信ありがとうございました。
掲載されているコード以外の部分は、主にvisual studioが自動生成するコードでしょうか?
コードの分からない箇所はChatGPTで調べてみたいと思います。
もう一つお伺いしたいのですが、SPI通信のタイムチャートを見てみるとクロックは Idle Lowなのに、実際のクロックの設定をIdle Highにすると動作が上手くいったのでしょうか。私も同じドライバーICのLCDを使用していて、クロックの設定をIdle LowではなくIdle Highにすると動作したのですが、これがなぜなのか分かりますでしょうか?
>>掲載されているコード以外の部分は、主にvisual studioが自動生成するコードでしょうか?
そういうことにはなりますが、せいぜいボタンを押したらこのコードを実行するようにしてるぐらいのものだったと思います。openFileDialog1.FileName, saveFileDialog1.FileNameの部分を何かしらの方法で読み込み・保存先のファイルのパスにすれば、大抵の言語で画像クラスなど同じような機能は提供されてると思います。ただし18行目のバイト列への変換はC#の独自色が強いと思います。
uint8 rgb16_H = (c.R & 0xF8) | ((c.G >> 5) & 0x07);
uint8 rgb16_L = ((c.G << 3) & 0xE0) | ((c.B >> 3) & 0x1F);
のようにバイト単位で計算する方法ならどの言語でもできると思います。エンディアンも問題になりませんし。
>>クロックの設定をIdle LowではなくIdle Highにすると動作したのですが、これがなぜなのか分かりますでしょうか?
何故なのかはわかりません。当時は波形を見る手段がなかったのでデータシートのタイミングチャートと合っているかは確かめられていません。データシートが間違っている可能性があるかもしれませんね。
お答えいただきありがとうございました。
画像変換のコードに関しては大分理解が進んできたので、自分でも書いてみます。