# ディスプレイインタフェース — LCD・NTSCビデオドライバ層

> interface/graphlib_lcd.c (ILI9341/ILI9488/ST7789 SPI LCD) と interface/graphlib_ntsc.c (RP2040 PWM 生成 NTSC 信号) の共通 graphlib.h API、display.c による描画ライブラリディスパッチ、editor.c / editor_ntsc.c のテキストエディタ描画ループ、フォントデータ (fontdata.c) の構造を解説します。

- Repository: machikania/phyllosoma
- GitHub: https://github.com/machikania/phyllosoma
- Human wiki: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c
- Complete Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/llms-full.txt

## Source Files

- `display.c`
- `interface/graphlib_lcd.c`
- `interface/graphlib_ntsc.c`
- `interface/graphlib.h`
- `interface/ili9341_spi.c`
- `interface/rp2040_pwm_ntsc_textgraph.c`
- `interface/fontdata.c`

---

<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:
- [display.c](display.c)
- [display.h](display.h)
- [interface/graphlib.h](interface/graphlib.h)
- [interface/graphlib_lcd.c](interface/graphlib_lcd.c)
- [interface/graphlib_ntsc.c](interface/graphlib_ntsc.c)
- [interface/ili9341_spi.c](interface/ili9341_spi.c)
- [interface/LCDdriver.h](interface/LCDdriver.h)
- [interface/rp2040_pwm_ntsc_textgraph.c](interface/rp2040_pwm_ntsc_textgraph.c)
- [interface/rp2040_pwm_ntsc_textgraph.h](interface/rp2040_pwm_ntsc_textgraph.h)
- [interface/videodriver.h](interface/videodriver.h)
- [interface/fontdata.c](interface/fontdata.c)
- [editor.c](editor.c)
- [editor_ntsc.c](editor_ntsc.c)
- [config.h](config.h)
</details>

# ディスプレイインタフェース — LCD・NTSCビデオドライバ層

MachiKania BASIC システム (phyllosoma リポジトリ) は、同一の描画 API を **2 種類のハードウェア出力バックエンド** に対して提供する。一方は SPI 接続 LCD (Phyllosoma ビルド)、他方は RP2040 の PWM + DMA で合成する NTSC ビデオ信号 (Puerulus ビルド) である。共通インタフェース `interface/graphlib.h` が両バックエンドの「契約」を定義し、BASIC コンパイラ側の `display.c` がその API を呼び出すディスパッチャとして機能する。フォントデータ `interface/fontdata.c` はどちらのバックエンドでも同じバイナリテーブルとして共有される。

このページでは、各ファイルの責務、データ構造、API シグネチャ、信号生成の仕組み、そして `editor.c`/`editor_ntsc.c` がどのように描画ループを構成しているかを解説する。

---

## システム全体のアーキテクチャ

```text
┌─────────────────────────────────────────────────┐
│          BASIC コンパイラ (display.c)            │
│  lib_display() / display_statements()           │
│  DISPLAY_CLS / DISPLAY_PSET / DISPLAY_LINE ...  │
└──────────────────┬──────────────────────────────┘
                   │ graphlib.h API (共通)
       ┌───────────┴────────────┐
       ▼                        ▼
┌─────────────┐        ┌──────────────────────┐
│graphlib_lcd │        │   graphlib_ntsc.c    │
│    .c       │        │  (TVRAM + GVRAM 直書)│
│ palette[]   │        │  color_tbl[] (NTSC)  │
└──────┬──────┘        └──────────┬───────────┘
       │                          │
       ▼                          ▼
┌─────────────┐        ┌──────────────────────┐
│ili9341_spi.c│        │rp2040_pwm_ntsc_       │
│LCDdriver.h  │        │textgraph.c            │
│SPI 転送層   │        │PWM + DMA  NTSC 合成  │
└─────────────┘        └──────────────────────┘

共有: interface/fontdata.c (FontData[256*8])
```

ビルド選択は `config.h` の `MACHIKANIA_PHYLLOSOMA` / `MACHIKANIA_PUERULUS` マクロ、および `interface/videodriver.h` の `#ifdef` 分岐によって行われる。

Sources: [config.h:9-22](), [interface/videodriver.h:1-9]()

---

## 共通 API — interface/graphlib.h

`graphlib.h` はバックエンド中立のヘッダで、テキスト操作とグラフィック操作の両方を宣言する。

### テキスト系 API

| 関数 | 説明 |
|------|------|
| `cls(void)` | テキスト画面消去、カーソルを先頭に移動 |
| `clearscreen(void)` | TVRAM をゼロクリアし LCD/映像を消去 |
| `printchar(unsigned char n)` | カーソル位置に 1 文字出力してカーソル前進 |
| `printstr(unsigned char *s)` | 文字列出力 |
| `setcursor(x, y, c)` | カーソル位置とカラー番号を設定 |
| `setcursorcolor(c)` | カーソルの色のみ変更 |
| `vramscroll(void)` | 1 行上スクロール |
| `vramscrolldown(void)` | 1 行下スクロール |
| `windowscroll(y1, y2)` | y1〜y2 行間のみスクロール |
| `textredraw(void)` | TVRAM 全体を画面に再描画 |
| `putcursorchar(void)` | カーソル位置の文字のみ再描画 |
| `startPCG(p, a)` | RAMフォント（PCG）開始 |
| `stopPCG(void)` | 標準フォントに戻す |

### グラフィック系 API

| 関数 | 説明 |
|------|------|
| `g_pset(x, y, c)` | 点描画 |
| `g_hline(x1, x2, y, c)` | 水平ライン（高速） |
| `g_gline(x1, y1, x2, y2, c)` | 直線（Bresenham 法） |
| `g_circle(x0, y0, r, c)` | 円の輪郭 |
| `g_circlefill(x0, y0, r, c)` | 塗りつぶし円 |
| `g_boxfill(x1, y1, x2, y2, c)` | 塗りつぶし矩形 |
| `g_putfont(x, y, c, bc, n)` | 8×8 フォント描画 |
| `g_printstr(x, y, c, bc, *s)` | 座標指定文字列描画 |
| `g_putbmpmn(x, y, m, n, bmp[])` | m×n ドット BMP 描画（カラー 0 = 透明） |
| `g_clearscreen(void)` | グラフィック画面クリア |
| `g_color(x, y)` | 座標の色値取得 |

### パレット・初期化 API

| 関数 | 説明 |
|------|------|
| `set_palette(n, b, r, g)` | パレット番号 n の色を設定（0–255） |
| `set_bgcolor(b, r, g)` | バックグラウンドカラー設定 |
| `init_palette(void)` | デフォルトパレット 16 色＋拡張 初期化 |
| `init_textgraph(align)` | LCD テキスト・グラフィック初期化 (LCD 用) |
| `set_lcdalign(align)` | 縦横回転設定 |
| `video_init(void)` | BASIC 実行開始時の映像初期化 |
| `set_gvram(draw, disp)` | 描画用/表示用 GVRAM ポインタ設定 (NTSC 用) |

Sources: [interface/graphlib.h:1-130]()

---

## LCD バックエンド — graphlib_lcd.c と ili9341_spi.c

### TVRAM とパレット

```c
// interface/graphlib_lcd.c
unsigned char TVRAM[ATTROFFSET*2+1] __attribute__ ((aligned (4)));
unsigned char *fontp;   // フォント格納アドレス（初期化時は FontData）
unsigned int bgcolor;   // バックグラウンドカラー（RGB565）
unsigned short palette[256]; // 256 エントリ RGB565 パレット
int WIDTH_X;  // 横方向文字数
int WIDTH_Y;  // 縦方向文字数
```

TVRAM のレイアウトは二部構成で、前半 `[0..ATTROFFSET-1]` がキャラクタコード、後半 `[ATTROFFSET..2*ATTROFFSET-1]` がカラーパレット番号 (attr) である。`ATTROFFSET` は `LCD_COLUMN_RES * LCD_ROW_RES / 64` で計算される (`LCDdriver.h` 参照)。

パレットは `set_palette()` で 8 ビット R/G/B をそれぞれ RGB565 に変換して格納する:

```c
void set_palette(unsigned char n, unsigned char b, unsigned char r, unsigned char g){
    palette[n] = ((r>>3)<<11) + ((g>>2)<<5) + (b>>3);
}
```

Sources: [interface/graphlib_lcd.c:24-32](), [interface/LCDdriver.h:46]()

### テキスト描画フロー (LCD)

`textredraw()` は TVRAM 全体を走査し、縦表示モードと横表示モードで異なるループ構造を持つ。縦モードでは行→スキャンライン→列の順、横モードでは列→ビット→行の順に `LCD_WriteDataColor_notfinish()` を呼び出し、最後に `checkSPIfinish()` でフラッシュする。

`printchar()` は 1 文字を TVRAM に書き込むと同時に `g_putfont()` でその位置だけ即座に LCD へ転送するため、フルリドローより高速である。

Sources: [interface/graphlib_lcd.c:378-440](), [interface/graphlib_lcd.c:467-480]()

### グラフィック描画 (LCD)

`g_pset()` は `drawPixel()` を経由して `LCD_setAddrWindow()` + `LCD_WriteData2()` を呼び出す。水平ライン `g_hline()` は `LCD_continuous_output()` を使い 1 回のアドレス設定で連続転送する。`g_boxfill()` / `g_circlefill()` は `#ifdef LCD_SPI_BAUDRATE2` ブロックで高速モードに切り替えてから実行する。

Sources: [interface/graphlib_lcd.c:33-44](), [interface/graphlib_lcd.c:222-252]()

### SPI トランスポート層 — ili9341_spi.c

`ili9341_spi.c` は Raspberry Pi Pico SDK の `hardware/spi` を使う。主要な低レベル関数:

| 関数 | 説明 |
|------|------|
| `LCD_WriteComm(comm)` | DC=Low でコマンド送信 |
| `LCD_WriteData(data)` | DC=High で 1 バイト送信 |
| `LCD_WriteDataColor_notfinish(color)` | バイトスワップ済み RGB565 を SPI FIFO に積む（完了待ちなし） |
| `checkSPIfinish()` | TX FIFO の排出＋BSY ビット待ち＋CS High |
| `LCD_setAddrWindow(x, y, w, h)` | CASET/PASET/RAMWR コマンドでウィンドウ設定 |
| `LCD_Init()` | ILI9341 初期化シーケンス（電源・ガンマ・色深度 0x55=RGB565） |

`lcd_display_init()` は SPI ピンの設定、CS/DC/RESET GPIO の初期化、`init_textgraph(HORIZONTAL)` の呼び出しを担う。

Sources: [interface/ili9341_spi.c:57-80](), [interface/ili9341_spi.c:190-230](), [interface/ili9341_spi.c:270-290]()

---

## NTSC バックエンド — graphlib_ntsc.c と rp2040_pwm_ntsc_textgraph.c

### ビデオメモリ構成

```c
// rp2040_pwm_ntsc_textgraph.c
uint8_t TVRAM[WIDTH_XMAX*WIDTH_Y*2+1] __attribute__ ((aligned (4)));
uint8_t *GVRAM = 0;         // グラフィック VRAM（描画用）
static uint8_t *GVRAM_DISP; // グラフィック VRAM（表示用）
```

`rp2040_pwm_ntsc_textgraph.h` で定義される解像度とモード:

| 定数 | 値 | 意味 |
|------|-----|------|
| `X_RES` | 336 | グラフィック横解像度 |
| `Y_RES` | 216 | グラフィック縦解像度 |
| `WIDTH_XCL` | 42 | カラーテキスト横文字数 |
| `WIDTH_Y` | 27 | 縦文字数 |
| `WIDTH_XBW` | 80 | モノクロテキスト横文字数 |
| `NUM_LINE_SAMPLES` | 908 (=227×4) | 1 走査線のサンプル数 |
| `NUM_LINES` | 262 | 総走査線数 |

Sources: [interface/rp2040_pwm_ntsc_textgraph.h:5-16]()

### ビデオモード

```c
#define VMODE_WIDETEXT  3  // カラーテキスト 42 文字
#define VMODE_MONOTEXT  5  // モノクロテキスト 80 文字
#define VMODE_WIDEGRPH  18 // グラフィック + テキスト 42 文字
```

`set_videomode()` を呼ぶことでモードを切り替えられる。`display.c` の `DISPLAY_WIDTH` / `DISPLAY_USEGRAPHIC` ハンドラがこれを呼び出す。

### NTSC 信号生成の仕組み

RP2040 の PWM を 11 カウント周期 (157.5 MHz / 11 ≒ 14.318 MHz) で動作させ、カラーバースト信号を 4 サンプル/周期 (3.579 MHz) で生成する。DMA 2 チャネルによるピンポンバッファで各走査線の波形データを割り込みハンドラ内で都度生成する。

```c
// rp2040_pwm_ntsc_textgraph.c (init より抜粋)
uint32_t freq_khz = 157500;  // CPUを157.5MHzで動作
uint32_t pwm_div  = 11;      // PWM周期11サイクル
set_sys_clock_khz(freq_khz, true);
gpio_set_function(n, GPIO_FUNC_PWM);
pwm_set_wrap(pwm_slice_num, pwm_div - 1);
```

IRQ ハンドラ `irq_handler()` が DMA 転送完了ごとに起動し、`makeDmaBuffer()` を呼んで次の走査線のサンプル列 `dma_buffer[0/1]` を構築する。

Sources: [interface/rp2040_pwm_ntsc_textgraph.c:381-430](), [interface/rp2040_pwm_ntsc_textgraph.c:288-307]()

### カラーパレットの NTSC エンコード

LCD の `palette[]` が RGB565 の 1 値を持つのに対し、NTSC では各パレットエントリが **4 位相サンプル** `color_tbl[c*4 + 0..3]` に展開される。

```c
// set_palette_main() の色変換式（簡略）
int32_t y = (150*g + 29*b + 77*r + 128) / 256;  // 輝度 Y
int32_t b_y_1 = (b-y)*441;   // Cb 成分 (位相0)
int32_t r_y_1 = (r-y)*1361;  // Cr 成分 (位相0)
// 以下 4 位相分を color_tbl[] に格納
```

`makeDmaBuffer()` 内のカラーテキストモード処理では、TVRAM から文字コードとカラー番号を取り出し、フォントビットが 1 のサンプルには `color_tbl[c*4]` を、0 のサンプルにはバックグラウンドカラー値を書き込む。

Sources: [interface/rp2040_pwm_ntsc_textgraph.c:239-265](), [interface/rp2040_pwm_ntsc_textgraph.c:149-175]()

### NTSC グラフィック描画

`graphlib_ntsc.c` の `g_pset()` は直接 `GVRAM[y*X_RES + x] = c` にパレット番号を書き込む。`g_hline()` は 4 バイト境界を揃えてから 32 ビット幅で一括書き込みする最適化を持つ。

```c
// graphlib_ntsc.c
void g_pset(int x, int y, unsigned char c){
    if((unsigned int)x >= X_RES) return;
    if((unsigned int)y >= Y_RES) return;
    GVRAM[y*X_RES + x] = c;
}
```

LCD バックエンドと異なり、グラフィックデータは GVRAM に蓄積するだけで、実際の映像出力は DMA / IRQ ループが次のフレームで読み取ることで実現する。

Sources: [interface/graphlib_ntsc.c:24-30](), [interface/graphlib_ntsc.c:181-200]()

### LCD との実装差分

| 項目 | LCD (graphlib_lcd.c) | NTSC (graphlib_ntsc.c) |
|------|----------------------|------------------------|
| `g_pset()` | `drawPixel()` → SPI 転送 | `GVRAM[y*X_RES+x] = c` |
| `textredraw()` | TVRAM 全走査 → SPI | ダミー関数 (no-op) |
| `putcursorchar()` | `g_putfont()` → SPI | ダミー関数 (no-op) |
| `set_lcdalign()` | MADCTL レジスタ設定 | ダミー関数 (no-op) |
| `set_gvram()` | ダミー関数 (no-op) | `GVRAM` / `GVRAM_DISP` 切替 |
| パレット | `palette[256]` (RGB565) | `color_tbl[256×4]` (NTSC 4 位相) |
| カラー取得 `g_color()` | SPI 読み返し (0x2E) | `GVRAM[y*X_RES+x]` 直読み |

Sources: [interface/graphlib_lcd.c:505-510](), [interface/graphlib_ntsc.c:511-515]()

---

## display.c — BASIC ステートメントから graphlib.h へのディスパッチ

`display.c` は KM-BASIC コンパイラの実行ライブラリ関数 `lib_display(r0, r1, r2)` を実装する。`r2` にはコマンド ID (`DISPLAY_*` 定数) が渡され、`switch` で各 API を呼び分ける。

```c
// display.c (抜粋)
switch(r2){
    case DISPLAY_CLS:     cls();                   break;
    case DISPLAY_PSET:    g_pset(x1,y1,gc);        break;
    case DISPLAY_LINE:
        if(y1==y2) g_hline(x1,x2,y1,gc);
        else       g_gline(x1,y1,x2,y2,gc);       break;
    case DISPLAY_BOXFILL: g_boxfill(x1,y1,x2,y2,gc); break;
    case DISPLAY_USEGRAPHIC:
        set_videomode(VMODE_WIDEGRPH, pgvram1);     break;
    // ...
}
```

`display.h` では 24 個のコマンド ID と、スタックからパラメータを取るコマンド群 (`DISPLAY_USE_STACK`) およびグラフィック VRAM が必要なコマンド群 (`DISPLAY_USE_GRAPHIC`) をビットマスクで管理する。

Sources: [display.c:70-260](), [display.h:1-60]()

---

## フォントデータ — interface/fontdata.c

`fontdata.c` は `const unsigned char FontData[256*8]` として 256 文字分、各 8 バイト（8 行 × 8 ビット）のフォントビットマップを定義する。上位ビットが左端に対応する。

```c
// interface/fontdata.c
const unsigned char FontData[256*8] = {
    // コード 0x00–0x1F: 制御文字 (全 0x00)
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 0x00
    ...
    // コード 0x21 ('!'):
    0x30,0x30,0x30,0x30,0x00,0x00,0x30,0x00,
    ...
};
```

`fontp` ポインタがこのテーブルを指すのがデフォルトで、`startPCG()` により任意の RAM アドレスへ差し替えることができる。差し替えた場合でも `FontData` は ROM 上に残るため、`stopPCG()` で即座に復元可能。

Sources: [interface/fontdata.c:18-25]()

---

## エディタの描画ループ — editor.c / editor_ntsc.c

`editor.c` (LCD 向け) と `editor_ntsc.c` (NTSC 向け) は基本的に同じロジックを持つが、NTSC バージョンには `drawcount` を使ったフレーム同期コードが含まれる。

### TVRAM への直接書き込み

エディタは `graphlib.h` の高レベル API ではなく、`TVRAM` と `attroffset` に直接アクセスすることで画面を更新する。変更のあったセルだけを書き換える差分更新を行い、最後に `textredraw()` を呼ぶ (LCD の場合)。

```c
// editor.c (テキスト描画ループ抜粋)
for(y=0; y<EDITWIDTHY; y++){
    for(x=0; x<WIDTH_X; x++){
        // テキストバッファから文字とカラーを TVRAM に書く
        *vp = ch;
        *(vp + attroffset) = cl;
        vp++;
    }
}
```

`EDITWIDTHY` は `WIDTH_Y - 1`（最下行はステータスバー用）として `editor.c:2248` で設定される。

### 範囲選択の表示

範囲選択中の領域は `COLOR_AREASELECTTEXT` というカラー番号を attr 領域に書き込むことで反転表示を実現している。BASIC 予約語のシンタックスハイライトも TVRAM の attr 書き換えのみで行うため、再描画コストを最小化している。

Sources: [editor.c:553-665](), [editor.c:2248]()

---

## データフロー — テキスト表示の流れ

```mermaid
sequenceDiagram
    participant BASIC as BASICランタイム<br/>(display.c)
    participant GL as graphlib_lcd.c<br/>or graphlib_ntsc.c
    participant TVRAM as TVRAM[]<br/>キャラクタ＋attr
    participant HW as LCD SPI<br/>or NTSC DMA/IRQ

    BASIC->>GL: printchar('A')
    GL->>TVRAM: TVRAM[pos]='A', TVRAM[pos+attroffset]=color
    alt LCDモード
        GL->>HW: g_putfont() → LCD_WriteDataColor_notfinish() × 64
        GL->>HW: checkSPIfinish()
    else NTSCモード
        Note over GL,HW: TVRAM書き込みのみ<br/>DMA IRQが次フレームで読み出す
    end

    HW-->>BASIC: 完了
```

---

## まとめ

phyllosoma リポジトリのディスプレイ層は、`interface/graphlib.h` を唯一の公開 API として、LCD SPI (Phyllosoma) と NTSC ビデオ (Puerulus) の 2 バックエンドを完全に交換可能な形で実装している。LCD 側はピクセル操作ごとに SPI トランザクションを発行するイミディエイト方式を採り、NTSC 側は TVRAM/GVRAM への書き込みを後から PWM + DMA + IRQ の走査線ループが読み出すダブルバッファ方式を採る。フォントは共通の `FontData[256*8]` テーブルを `fontp` ポインタ経由で参照し、PCG 機能で実行時に差し替えられる。`display.c` はその上位に位置し、BASIC 言語のステートメントを 24 種類のコマンド ID に変換してグラフライブラリへ橋渡しする。

Sources: [config.h:9-22](), [interface/videodriver.h:1-9]()
