Raspberry Pi PicoでPicoMite(MMBasic)を使う〜LCDへの文字の表示

# はじめに

ST7789チップを採用したCS端子なしのLCDはPicoMite（MMBasic）ではサポートされないのでグラフィックスのコマンド類は自分で用意することになる。

[Raspberry Pi PicoでPicoMite(MMBasic)を使う〜CSなしST7789 LCDを動かす](https://qiita.com/ksakabe/items/a6b2042d4d34b24374c6)で1ピクセルの描画ができるようになったので直線、四角形などの図形の描画、文字の描画が可能になる。
今回もChatGPTの助けを借りて文字列を描画できるようになったのでそのコードなどを紹介する。

# 接続

Raspberry Pi PicoとLCDの接続は下図のとおりで、[Raspberry Pi PicoでPicoMite(MMBasic)を使う〜CSなしST7789 LCDを動かす](https://qiita.com/ksakabe/items/a6b2042d4d34b24374c6)と同じです。

![TFT-LCD_ST7789_ブレッドボード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72921/92ac3fa6-9827-43f6-991c-701375d26e5e.png)

# 文字の描画

PicoMite（MMBasic）がサポートしているLCDであれば用意されているフォントデータ、文字列描画コマンドを使用すればよいがサポートされていないLCDを使用しているので、これらを用意しなければならない。

## フォントデータ

LCDに文字を表示するには文字を$n\times m$のビットマップとして全ピクセルの色データを用意し、そのデータをLCDで表示する領域に対応するメモリへ書き込めばよい。

ChatGPTに相談して得られた回答のフォントデータは5×7と3センチ四方で240×240の解像度のLCDには小さ過ぎる。もう少し大きなフォントを要求したら回答制限を超えるため得られず、STM32 標準 BSP の fonts.c に含まれる定義を利用するコードを紹介されたのでSTM32のGithubのフォントデータを使った。

フォントのデータは文字のビットマップの1ピクセルに1ビットを割り当て、文字の線があるピクセルのビットには1、ないビットには0を割り当てる。
たとえば、1文字を$8\times 12$のビットマップとする場合、1バイトのデータが1行分になるので、12バイトで一文字分のデータになる。
記号、数字、英字大文字小文字であれば95文字分なので文字データを$95\times 12$の配列に格納し、表示用のプログラムで利用できるようにする。

```
' 空白のデータ
FNT8x12[0, 0] = &H00 : FNT8x12[0, 1] = &H00 : FNT8x12[0, 2] = &H00
FNT8x12[0, 3] = &H00 : FNT8x12[0, 4] = &H00 : FNT8x12[0, 5] = &H00
FNT8x12[0, 6] = &H00 : FNT8x12[0, 7] = &H00 : FNT8x12[0, 8] = &H00
FNT8x12[0, 9] = &H00 : FNT8x12[0,10] = &H00 : FNT8x12[0,11] = &H00
...
' !のデータ
FNT8x12[1, 0] = &H00 : FNT8x12[1, 1] = &H00 : FNT8x12[1, 2] = &H00
FNT8x12[1, 3] = &H10 : FNT8x12[1, 4] = &H10 : FNT8x12[1, 5] = &H10
FNT8x12[1, 6] = &H10 : FNT8x12[1, 7] = &H10 : FNT8x12[1, 8] = &H10
FNT8x12[1, 9] = &H00 : FNT8x12[1,10] = &H10 : FNT8x12[1,11] = &H00
...
' Bのデータ
FNT8x12[34, 0] = &H00 : FNT8x12[34, 1] = &H00 : FNT8x12[34, 2] = &H00
FNT8x12[34, 3] = &HF0 : FNT8x12[34, 4] = &H88 : FNT8x12[34, 5] = &H88
FNT8x12[34, 6] = &HF0 : FNT8x12[34, 7] = &H88 : FNT8x12[34, 8] = &H88
FNT8x12[34, 9] = &H88 : FNT8x12[34,10] = &HF0 : FNT8x12[34,11] = &H00
```

この配列のデータを使うと図に示すような文字が表示できる。

![フォントデータイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72921/d7d79195-0851-4e52-96db-9b72fc616020.png)


## 文字データの表示

文字をLCDに表示する手順は次の通り。

1. 表示する文字に対応するデータを配列から取り出し
2. 取り出したデータからピクセルごとに表示するかしないかを判定する
3. 判定結果から表示する場合またはしない場合の色データを配列に格納
4. 一文字分の判定が終わったら文字を表示する位置に対応したメモリへ色データを書き込む

文字列の表示は文字列から一文字ずつ取り出し、上記手順を繰り返せばよい。

## 文字の表示プログラム

### 一文字表示サブルーチン DrawChar8x12

1文字のビットマップ8×12に対応したサブルーチンを作った。

サブルーチンには表示開始座標（x、y）、表示する文字、前景色、背景色を引数として渡す。

1文字を表示する手順は以下の通り。
1. 表示する文字のビットマップデータを格納している配列要素FNT8x12から1行分のデータを取り出す
2. 取り出した1行分の各ビットが描画するかしないかを判定する
	1. 描画するときは前景色データを配列に保存する
	2. 描画しないときは背景色データを配列に保存する
3. 1行分のピクセルの色データを書き込むフレームメモリのアドレス範囲を設定する
4. 色データを保存している配列をフレームメモリへ書き込む

この手順を1文字分のすべての行に対して実行する。


```BASIC:
' Draw character
Sub DrawChar8x12(x%, y%, ch$, fg%, bg%)
  Local row, pix, mask                       ' 行、ビット位置、1行分のデータ
  Local cnt% = 0                             ' 配列カウンタ
  Local fgH = fg% \ 256, fgL = fg% Mod 256   ' 文字の前景色
  Local bgH = bg% \ 256, bgL = bg% Mod 256   ' 文字の背景色
  Local buf%(8*2*12-1)                       ' 1文字分の色データ格納

  For row = 0 To 11                          ' ビットマップデータを行単位でアクセス
    mask = FNT8x12(Asc(ch$) - 32, row)       ' ビットマップデータの1行分取得
    For pix = 0 To 7                         ' データを1ピクセルずつアクセス
      If mask And (1 << (7 - pix)) Then      ' 先頭ビットから順に描画するかしないかを判定
        buf%(cnt% * 2) = fgH : buf%(cnt% * 2 + 1) = fgL ' 描画時の前景色設定
      Else
        buf%(cnt% * 2) = bgH : buf%(cnt% * 2 + 1) = bgL ' 日描画時の背景色設定
      EndIf
      Inc cnt%                               ' 配列カウントをインクリメント
    Next pix
  Next row
  SetAddrWindow x%, y%, x% + 7, y% + 11      ' 色データ書き込み領域アドレス設定
  SendCmd &H2C                               ' データ書き込みモードに
  SendDatabulk buf%(), 8*2*12                ' 色データを一文字分書き込み
End Sub
```
演算子`\`は整数除算で`fg% \ 256`でfg%を256で割った商が得られ16ビットカラーの上位8ビットの値が得られる。`fg% Mod 256`は剰余なのでfg%を256で割ったあまりが得られるので16ビットカラーの下位8ビットが得られたことになる。

### 文字列表示サブルーチン

文字列を表示するには一文字表示サブルーチン`DrawChar8x12`を文字列のそれぞれの文字に対して実行すればよい。

文字列表示サブルーチンには文字列を表示する領域の左上座標、表示する文字列、文字列の前景色、文字列の背景色を引数として渡す。

1文字を表示するサブルーチンを使うため、表示する文字列の左から1文字ずつ取り出す必要があるので`Mid$`関数を使っている。
一文字表示したら表示開始位置のx座標を文字幅分増やす必要がある。

```
Sub DrawString8x12(px%, py%, txt$, fg%, bg%)
  Local k%
  For k% = 1 To Len(txt$)
    DrawChar8x12 px%, py%, Mid$(txt$, k%, 1), fg%, bg%
    px% = px% + 8
  Next k%
End Sub 
```

# 文字列描画テスト

文字列、文字表示サブルーチンをライブラリ化して下記のプログラムを実行してみた。ライブラリを含めてBASICで書かれていることなど決して高速な動作ではない。

このプログラムは文字列Hello Pico!とASCIIテーブルを8x12および16x24フォントで描画します。

以下のライブラリ化したサブルーチンを使っている。
- DrawString8x12 : 8x12フォントで文字列出力
- DrawString16x24 : 16x24フォントで文字列出力
- DrawChar8x12 : 8x12フォントで1文字出力
- DrawChar16x24 : 8x12フォントで1文字出力

```BASIC:character-test.bas
SetPin GP20, GP19, GP18, SPI
SetPin GP21, DOUT
SetPin GP22, DOUT
SPI OPEN 20000000, 3, 8
ST7789_Init
ClearScreen RGB565(0,0,0)
' Draw string "Hello Pico!" with 8x12 and 16x24 font.
DrawString8x12  0, 10, "Hello Pico!", RGB565(255,255,0),RGB565(64,64,64)
DrawString16x24 0, 25, "Hello Pico!", RGB565(255,255,0),RGB565(64,64,64)

Pause 1000

ClearScreen RGB565(0,0,0)

' Draw ASCII table with 16x24 and 8x12 font.
xx1=0:xx2=0:yy1=0:yy2=150
For i=&H20 To &H7E
  drawChar16x24 xx1, yy1, Chr$(i), RGB565(255,255,0), RGB565(64,64,64)
  drawChar8x12  xx2, yy2, Chr$(i), RGB565(255,255,0), RGB565(64,64,64))
  xx1 = xx1 + 16
  xx2 = xx2 + 8
  If (i -16)  Mod 16 = 15 Then
    xx1 = 0        : xx2 = 0
    yy1 = yy1 + 24 : yy2 = yy2 + 12
  EndIf
Next

ClearScreen RGB565(0,0,0)
End
```

この[プログラムの実行結果の動画](https://youtube.com/shorts/bfjT9U7jINY)です。

プログラムはMMBasicで書かれている、高速化の手法を組み込んでいないので低速です。描いている間があります。

# 参考サイトなど

- [STM32-Library-V3.5のASCII8x12フォント](https://github.com/codenuke/STM32-Library-V3.5/blob/master/Utilities/STM32_EVAL/Common/fonts.c)
- [PicoMite User Manual](https://geoffg.net/Downloads/picomite/PicoMite_User_Manual.pdf)