Raspberry Pi PicoでPicoMite(MMBasic)を使う〜CSなしST7789 LCDを動かす

# はじめに

I<sup>2</sup>C接続の白色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 用）](https://amzn.to/4kjbifb)を使用した。

このディスプレイは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   |      未接続      |

<!-- ここに配線図 -->

![TFT-LCD_ST7789_ブレッドボード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72921/795336a3-3ff1-4059-a9be-c492dcbfedf9.png)


# 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の初期化を行なっている。

<table>
<tr><th>処理</th><th>コマンド</th><th>データなど</th></tr>
<tr><td>ハードウエアリセット</td><td>なし</td><td>RESETピン（GP21）のハイローを繰り返す</td></tr>
<tr><td>ソフトウエアリセット(SWRESET)</td><td>&H01</td><td>なし</td></tr>
<tr><td>スリープ解除(SLPOUT)</td><td>&H11</td><td>なし</td></tr>
<tr><td rowspan="2">カラーモード設定(COLMOD)</td><td rowspan="2">&H3A</td><td>&H55,ビット6〜4の101で64Kカラー</td></tr>
<tr><td>下位3ビットの101で16ビット/ピクセルを指定</td><td>
<tr><td rowspan="7">メモリデータアクセス設定MADCTL</td><td rowspan="7">&H36</td><td>&H00（ビット7〜2）</td></tr>
<tr><td>ビット7（ページアドレス順序）0で上から下、1で下から上</td></tr>
<tr><td>ビット6（カラムアドレス順序）0で左から右、1で右から左</td></tr>
<tr><td>ビット5（ページ・カラム順序）0でノーマル、1で逆</td></tr>
<tr><td>ビット4（行アドレス順序）0で上から下に向かってリフレッシュ</td></tr>
<tr><td>ビット3（RGB/BGR順序）0でRGB、1でBGR</td></tr>
<tr><td>ビット2（表示データラッチ順序）0で左から右に向かってリフレッシュ、1はその逆</td></tr>
<tr><td>ディスプレイ反転(INVON)</td><td>&H21</td><td>描画時に指定した色が反転ししまったため指定している（本来は不要と思われる）</td></tr>
</table>

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）でメモリへの書き込みを受け付ける状態にする。

<!-- 説明の絵 -->
![ST7789-addresswindow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72921/329dea22-d5e7-4614-87e7-05e6f98d1215.png)


領域指定の位置指定では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

指定した色で画面を塗りつぶしてクリアするサブルーチン。

画面のクリアは次の手順で実施している。

1. コマンド&H28（DISPOFF：Display Off）でディスプレイをオフにする
2. ディスプレイの1行分のデータ（240ピクセル×2バイト＝480バイト）を格納する配列linebuf%()に塗りつぶす色のデータを格納する
3. 塗りつぶす1行分の領域アドレスを指定し、塗りつぶすデータをバルク転送する。この転送を240行分繰り返す。
4. コマンド&H29（DISPON：Display On）でディスプレをオンにすして塗りつぶされた状態を表示する。

ディスプレイをオフにしても表示データは表示用のメモリ（フレームメモリ）に格納され、ディスプレイをオンにすることで一気に表示される。

MMBasicのコードは次の通り。

```BASIC
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向けに書き直してて実行してみた。

```BASIC:hat-ST7789.bas
'===== 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
```

## 実行結果

上記プログラムの実行結果は次のとおり。このくらいの速度で描画される。

[実行動画](https://youtube.com/shorts/LhDO9AVxZic?feature=share)

# ライブラリとして保存

メキシカンハットのプログラムが動作したのでプログラムメモリ内をサブルーチンだけにして`LIBRARY SAVE`コマンドでライブラリとして保存した。


# 参考サイトなど

- [PicoMite User Manual](https://geoffg.net/Downloads/picomite/PicoMite_User_Manual.pdf)