はじめに
I2C接続の白色OLEDを動かせたのでカラーLCDの動作を試みた。PicoMite(MMBasic)でサポートされているドライバチップST7789を搭載したTFT LCDをAmazonで購入した。購入したディスプレイにはCS(チップセレクト)端子がGNDに接続されており、Raspberry Pi Picoなどとの接続用端子そのものがないためPicoMite(MMBasic)ではサポートされていないものだった。そこでChatGPTの力を借りて動作するコードを作成・動作確認・修正してライブラリとして利用できるようにした。
使用部品
今回使用したのディスプレイはAmazonで購入したWINGONEER TFT LCDスクリーンディスプレイ1.3インチTFT LCDモジュール240 x 240 IPS 65Kフルカラー3.3V(SPIインターフェース付き)ST7789 ICドライバ(51 STM32 用)を使用した。
このディスプレイはSPIでRaspberry Pi Picoなどと接続できるがCS(チップセレクト)端子が外部に出ていないのでデバイスの選択ができないため他のSPIデバイスとの併用が難しい。
チップ自体は240×320x18ビットのフレームメモリがあるがLCDとしての表示は240×240ピクセルになっている。
色数は12ビットカラー、16ビットカラー、18ビットカラーに対応している。
配線図
TFT LCDとRaspberry Pi Picoと接続は次のようにした。
LCDのピン | Picoのピン(物理ピン) |
---|---|
GND | GND(38) |
VCC | 3V3(36) |
SCL | GP18(24) |
SDA | GP19(25) |
RES | GP21(27) |
DC | GP22(29) |
BLK | 未接続 |
CSなしST7789チップLCDディスプレイ用ライブラリ作成
ST7789のデータシートをChatGPTに読み込ませてMMBasicでST7789の初期化、LCDへの1ドットの描画サブルーチンを作成させてた。
作成されたサブルーチンをメキシカンハット描画プログラムに組み込んで動作確認して、修正を行なった。
作成したサブルーチンを動かすためには次のリストに示すSPIで使用するRaspberry Pi Picoのピン指定、RESピンとDCピンに接続するピンをデジタル出力するように設定する必要がある。
SetPin GP20, GP19, GP18, SPI ' SPI pin RX->GP20, TX->GP19, CLK->GP18
SetPin GP21, DOUT ' to RES
SetPin GP22, DOUT ' to DC
SPI OPEN 20000000, 3, 8 ' Open channel 1, 20MHz, mode 3, 8bit data
初期化のコード
作成・修正したコードでは下記の手順でST7789の初期化を行なっている。
処理 | コマンド | データなど |
---|---|---|
ハードウエアリセット | なし | RESETピン(GP21)のハイローを繰り返す |
ソフトウエアリセット(SWRESET) | &H01 | なし |
スリープ解除(SLPOUT) | &H11 | なし |
カラーモード設定(COLMOD) | &H3A | &H55,ビット6〜4の101で64Kカラー |
下位3ビットの101で16ビット/ピクセルを指定 | ||
メモリデータアクセス設定MADCTL | &H36 | &H00(ビット7〜2) |
ビット7(ページアドレス順序)0で上から下、1で下から上 | ||
ビット6(カラムアドレス順序)0で左から右、1で右から左 | ||
ビット5(ページ・カラム順序)0でノーマル、1で逆 | ||
ビット4(行アドレス順序)0で上から下に向かってリフレッシュ | ||
ビット3(RGB/BGR順序)0でRGB、1でBGR | ||
ビット2(表示データラッチ順序)0で左から右に向かってリフレッシュ、1はその逆 | ||
ディスプレイ反転(INVON) | &H21 | 描画時に指定した色が反転ししまったため指定している(本来は不要と思われる) |
MMBasicでのコードは次の通り。コマンド送信サブルーチンSendCmd
とデータ送信サブルーチンSendData8
は後述する。
Sub ST7789_Init
' Hardware Rest
Pin(GP21) = 1 : Pause 10
Pin(GP21) = 0 : Pause 10
Pin(GP21) = 1 : Pause 120
SendCmd &H01 : Pause 50 ' Software reset
SendCmd &H11 : Pause 120 ' SLEEP OUT
SendCmd &H3A : SendData8 &H55 ' Color mode=16bit
SendCmd &H36 : SendData8 &H00 ' MADCTL (Portrate)
SendCmd &H21 ' Display inversion ON
Pause 100
End Sub
コマンド送信サブルーチン SendCmd
ST7789にサブルーチンの引数の1バイトのコマンドデータを送信するサブルーチン。
ST7789はDCピン(PicoのGP22ピン)を0(ローレベル)にすると、Picoから書き込まれたデータをコマンドとして扱う。
Sub SendCmd(c%) ' Send command
Pin(GP22) = 0 ' D/C=0 -> command
SPI WRITE 1, c%
End Sub
8ビットデータ送信サブルーチンSendData8
ST7789に引数の1バイトのデータを送信するサブルーチン。
ST7789はDCピン(PicoのGP22ピン)を1(ハイレベル)にすると、Picoから書き込まれたデータを直前のコマンドに対するデータとして扱う。
Sub SendData8(d%) ' Send data
Pin(GP22) = 1 ' D/C=1 -> data
SPI WRITE 1, d%
End Sub
8ビットデータのバルク送信 SendDataBulk
ST7789に引数の配列から指定された個数分のデータを送信するサブルーチン。
SendData8サブルーチンと同様にST7789のDCピンを1にしてST7789が受信したデータをコマンドに対するデータとした扱う。
Sub SendDataBulk( buf%(), n%)
Pin(GP22) = 1 ' D/C=1 -> data
SPI WRITE n%, buf%() ' Send n% bytes from array buf%
End Sub
データ転送先アドレス指定 SetAddrWindow
LCDで描画用データを格納する領域を指定し、後続のデータをフレームメモリを書き込み可能な状態にするサブルーチン。
表示領域の指定にはコマンド&H2A(CASET:Column Address Set)で列(カラム)の開始位置(XS)と終了位置(XE)を、コマンド&H2B(RASET:Row Address Set)で行(ロー)の開始位置(YS)と終了位置(YE)を送信して指定する。
表示領域の指定完了後にコマンド&H2C(RAMWR:Memory Write)でメモリへの書き込みを受け付ける状態にする。
領域指定の位置指定では1つの位置を2バイト(16ビット)で送信する。そのため、位置指定のデータの256で整数除算(\)した値(上位8ビット)と、256との剰余(mod)の値(下位8ビット)を配列要素に格納している。
tmp%(0) = xs% \ 256 : tmp%(1) = xs% Mod 256
MMBasicのコードは次の通り。
Sub SetAddrWindow(XS%, YS%, XE%, YE%)
' XS : X start, XE : X end, YS : Y start, YE : Y end
Local tmp%(3)
SendCmd &H2A ' CASET(Column Address Set)
tmp%(0) = xs% \ 256 : tmp%(1) = xs% Mod 256
tmp%(2) = xe% \ 256 : tmp%(3) = xe% Mod 256
SendDataBulk tmp%(), 4
SendCmd &H2B ' RASET(Row Address Set)
tmp%(0) = ys% \ 256 : tmp%(1) = ys%' Mod 256
tmp%(2) = ye% \ 256 : tmp%(3) = ye%' Mod 256
SendDataBulk tmp%(), 4
SendCmd &H2C ' RAMWR(Memory write)
End Sub
ピクセルの16ビット色指定 RGB565
RGBそれそれを8ビットで指定された色(24ビットカラー)を16ビットカラーの値に変換する。
ST7789の初期設定で64Kカラー(16ビット/ピクセル)と設定したので256段階RGBの色指定(24ビットカラー RGB888)を16ビットカラー (RGB565)に変換する。
RGB888をRGB565にするにはRGBの各色を次のビット数と全体のビット位置にシフト演算とビット演算を使い、変換する
色 | 利用するビット | RGB565での位置 |
---|---|---|
R | 上位5ビット分 | 15〜11 |
G | 上位6ビット分 | 10〜5 |
B | 上位5ビット分 | 4〜0 |
MMBasicのコードは次の通り。
Function RGB565(R%, G%, B%)
RGB565 = ((R% >> 3) << 11) Or ((G% >> 2) << 5) Or (B% >> 3)
End Function
1ピクセル分の描画 DrawPixel
LCDの指定した座標の点を指定した色で塗りつぶすサブルーチン。
SetAddrWindowサブルーチンで描画する1ピクセルを指定し、そのピクセルの色データ(2バイト)を転送する。
MMBasicのコードは次の通り。
Sub DrawPixel(x%, y%, col%)
SetAddrWindow x%, y%, x%, y%
Local px%(2) ' Pixel color data
px%(0) = col% >> 8 : px%(1) = col% And &HFF
SendDataBulk px%(), 2
End Sub
スクリーンのクリア ClearScreen
指定した色で画面を塗りつぶしてクリアするサブルーチン。
画面のクリアは次の手順で実施している。
- コマンド&H28(DISPOFF:Display Off)でディスプレイをオフにする
- ディスプレイの1行分のデータ(240ピクセル×2バイト=480バイト)を格納する配列linebuf%()に塗りつぶす色のデータを格納する
- 塗りつぶす1行分の領域アドレスを指定し、塗りつぶすデータをバルク転送する。この転送を240行分繰り返す。
- コマンド&H29(DISPON:Display On)でディスプレをオンにすして塗りつぶされた状態を表示する。
ディスプレイをオフにしても表示データは表示用のメモリ(フレームメモリ)に格納され、ディスプレイをオンにすることで一気に表示される。
MMBasicのコードは次の通り。
Sub ClearScreen(col%)
Local linebuf%(480) '240pixels = 480bytes
Local i%
SendCmd &H28
For i% = 0 To 239
linebuf%(i% * 2) = col% \ 256
linebuf%(i% * 2 + 1) = col% Mod 256
Next
Local y%
For y% = 0 To 239
SetAddrWindow 0, y%, 239, y%
SendDataBulk linebuf%(), 480
Next
SendCmd &H29
End Sub
メキシカンハットプログラムで確認
SMC-777の777 BASICで動作するメキシカンハットのプログラムをMMBasic向けに書き直してて実行してみた。
'===== Initialize display =====
Sub ST7789_Init
' Hardware Rest
Pin(GP21) = 1 : Pause 10
Pin(GP21) = 0 : Pause 10
Pin(GP21) = 1 : Pause 120
SendCmd &H01 : Pause 50 ' Software reset
SendCmd &H11 ' SLEEP OUT
Pause 120
SendCmd &H3A : SendData8 &H55 ' COLMOD=16bit
SendCmd &H36 : SendData8 &H00 ' MADCTL (Portrate)
SendCmd &H21 ' Display inversion ON
Pause 100
ClearScreen(RGB(black))
End Sub
'===== Lowleve control =====
Sub SendCmd(c%) ' Send command
Pin(GP22) = 0 ' D/C=0 -> command
SPI WRITE 1, c%
End Sub
' Send 1 byte data
Sub SendData8(d%) ' Send data
Pin(GP22) = 1 ' D/C=1 -> data
SPI WRITE 1, d%
End Sub
' Send data in bulk
Sub SendDataBulk( buf%(), n%)
Pin(GP22) = 1 ' D/C=1 -> data
SPI WRITE n%, buf%() ' Send n% bytes from array buf%
End Sub
'===== Address setting =====
Sub SetAddrWindow(XS%, YS%, XE%, YE%)
' XS : X start, XE : X end, YS : Y start, YE : Y end
Local tmp%(3)
SendCmd &H2A ' CASET(Column Address Set)
tmp%(0) = xs% \ 256 : tmp%(1) = xs% Mod 256
tmp%(2) = xe% \ 256 : tmp%(3) = xe% Mod 256
SendDataBulk tmp%(), 4
SendCmd &H2B ' RASET(Row Address Set)
tmp%(0) = ys% \ 256 : tmp%(1) = ys%' Mod 256
tmp%(2) = ye% \ 256 : tmp%(3) = ye%' Mod 256
SendDataBulk tmp%(), 4
SendCmd &H2C ' RAMWR(Memory write)
End Sub
'===== Create 16bit coloe (RGB565) =====
Function RGB565(R%, G%, B%)
RGB565 = ((R% >> 3) << 11) Or ((G% >> 2) << 5) Or (B% >> 3)
End Function
'===== Draw 1dot =====
Sub DrawPixel(x%, y%, col%)
SetAddrWindow x%, y%, x%, y%
Local px%(2) ' Pixel color data
' px%(0) = col% \ 256 : px%(1) = col% Mod 256
px%(0) = col% >> 8 : px%(1) = col% And &HFF
SendDataBulk px%(), 2
End Sub
'===== Clear display =====
Sub ClearScreen(col%)
Local linebuf%(480) '240pixels = 480bytes
Local i%
SendCmd &H28
For i% = 0 To 239
linebuf%(i% * 2) = col% \ 256
linebuf%(i% * 2 + 1) = col% Mod 256
Next
Local y%
For y% = 0 To 239
SetAddrWindow 0, y%, 239, y%
SendDataBulk linebuf%(), 480
Next
SendCmd &H29
End Sub
Option EXPLICIT
'===== SPI setting =====
' SPI0 20 MHz, Mode-3 (CPOL=1, CPHA=1)
SetPin GP20, GP19, GP18, SPI '
SetPin GP21, DOUT ' RES
SetPin GP22, DOUT ' DC
SPI OPEN 20000000, 3, 8 ' channel 1, speed, mode, data width
ST7789_Init
ClearScreen RGB565(0, 0, 0) ' Clear the screen with Black
'===== Mexican Hat =====
Dim D(239)
Dim I As Integer
Dim DR As Float
Dim X As integer, Y As integer, Z As integer
Dim R As float
Dim SX As integer, SY As Integer
Dim Col As integer ' color
Dim cc%(10) ' colors
' Define colors
cc%(1) = RGB565( 0, 0, 255) : cc%(2) = RGB565( 0, 255, 0) ' Blue, Green
cc%(3) = RGB565( 0, 161, 233) : cc%(4) = RGB565(255, 0, 0) ' Cyan, Red
cc%(5) = RGB565(228, 0, 127) : cc%(6) = RGB565(255, 255, 0) ' Magenta, Yellow
cc%(7) = RGB565(128, 128, 128) : cc%(8) = RGB565(165, 42, 42) ' Gray, Brown
cc%(9) = RGB565(255, 165, 0) : cc%(10) = RGB565(255, 255, 255) ' Orange, White
For I = 0 To 239
D(I) = 200
Next I
DR = 3.14 / 180
For Y = -180 To 180 Step 6
For X = -180 To 180 Step 4
R = DR * Sqr(X^2 + Y^2)
Z = 100 * Cos(R)- 30 * Cos(3 * R)
SX = Int(120 + X / 3 - Y / 6)
SY = Int(120 - Y / 6 - Z / 4)
If (SX < 0) + (SX >= 240) Then GoTo JMP250
If D(SX) <= SY Then GoTo JMP250
COL = Int((Z + 100) * 0.035) + 1
If COL <= 0 Then GoTo JMP
DrawPixel SX, SY, cc%(COL)
D(SX) = SY
JMP250:
Next X
JMP:
Next Y
End
実行結果
上記プログラムの実行結果は次のとおり。このくらいの速度で描画される。
ライブラリとして保存
メキシカンハットのプログラムが動作したのでプログラムメモリ内をサブルーチンだけにしてLIBRARY SAVE
コマンドでライブラリとして保存した。