Agent-readable wiki
MachiKania Phyllosoma 開発者リファレンス Wiki
MachiKania Phyllosoma (type P) は Raspberry Pi Pico (RP2040/RP2350) 向けの ARM 用 BASIC コンパイラファームウェアです。LCD または NTSC ビデオ出力、SD カードファイルシステム、WiFi、USB キーボード、各種ペリフェラルをサポートします。
Pages
- 技術オリエンテーション — システム全体像とエントリポイントリポジトリの目的(RP2040/RP2350 向け BASIC コンパイラファームウェア)、2 つのファームウェアバリアント(phyllosoma / puerulus)、主要モジュールの責務、ビルド成果物 (.uf2) の種類、および開発者がこの Wiki を読み進める順序を解説します。
- ビルドシステムと設定プロファイルCMake ビルドシステムの構成、config.cmake による MACHIKANIA_BUILD 選択、LCD ドライバ・キーボード・WiFi ライブラリの自動切り替えロジック、RP2040/RP2350 向けリンカスクリプト、config/ ヘッダによるハードウェア定数の定義方法を解説します。
- BASICコンパイラコア — 字句解析・コード生成・CMPDATAcompiler.c が担う 1 パスコンパイル処理、statements.c によるステートメント解析、operators.c の演算子優先順位、cmpdata.c のコンパイル時メタデータ管理(変数名・ラベル・クラス情報の格納と検索)、ARMv6-M サムネイルオブジェクトコード生成の仕組みを解説します。
- ランタイム実行エンジン — run.c・library.c・api.crun.c の pre_run / run_code / post_run ライフサイクル、library.c のライブラリディスパッチテーブル (kmbasic_library)、api.c の表示プリミティブ (printchar/printstr 等)、error.c のエラー停止フロー、exception.c の ARM ハードウェア例外ハンドラを詳説します。
- メモリ管理・変数・クラスシステムmemory.c のブロックアロケータ(alloc_memory / garbage_collection)、variable.c の変数番号管理、value.c の型解決(整数・浮動小数点・文字列)、class.c のオブジェクト生成・フィールドアクセス・静的メンバ管理、globalvars.c のグローバル状態を解説します。
- ディスプレイインタフェース — 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) の構造を解説します。
- I/Oとペリフェラル制御 — GPIO・SPI・I2C・音楽・タイマー・RTCio.c の lib_gpio / lib_spi / lib_i2c / lib_serial / lib_pwm / lib_analog 実装、timer.c の割り込み駆動タイマー (INTERRUPT_TIMER / INTERRUPT_DRAWCOUNT 等)、music.c の PWM 音声合成とウェーブ再生、rtc.c のリアルタイムクロック API、MACHIKAP.INI による I/O 初期化設定を解説します。
- WiFiスタックとSDカードファイルシステムwifi/withwifi.c の CYW43 + lwIP + mbedTLS 統合、socket.c の TCP クライアント/サーバ/TLS API、wifi/picow_ntp_client.c の NTP 同期、file.c のファイル操作ステートメント群、interface/ff.c (FatFs) と interface/pico-sdmm.c (SPI MMC ドライバ)、withoutwifi.c のスタブ切り替えを解説します。
- エディタと入力ハンドリング — USBキーボード・PCシリアル・ファイル選択withkeyboard.c (TinyUSB ホスト HID キーボードモード) と withoutkeyboard.c (USB シリアル PC 接続モード) の分岐、interface/keyinput.c のキー入力バッファリング、interface/usbkeyboard.c の HID 仮想キーテーブル変換、interface/fileselect.c の SD カードファイルブラウザ、editor.c のインタラクティブ BASIC テキストエディタを解説します。
- 拡張ポイントと開発者リファレンス — auxcode・hexfile・デバッグ・helpauxcode/auxcode.c の補助コードライブラリ拡張インタフェース (aux_statements / aux_int_functions 等)、hexfile.c の Intel HEX 直接実行機能 (runHex)、pcconnect.c の PC シリアルプロトコル、debug.c のデバッグユーティリティ、help.c の組み込みヘルプ辞書、開発者が新規ライブラリを追加する際の手順と注意点をまとめます。
Complete Markdown
# MachiKania Phyllosoma 開発者リファレンス Wiki
> MachiKania Phyllosoma (type P) は Raspberry Pi Pico (RP2040/RP2350) 向けの ARM 用 BASIC コンパイラファームウェアです。LCD または NTSC ビデオ出力、SD カードファイルシステム、WiFi、USB キーボード、各種ペリフェラルをサポートします。
## Context Links
- [Agent index](https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/llms.txt)
- [Human interactive wiki](https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c)
- [GitHub repository](https://github.com/machikania/phyllosoma)
## Repository Metadata
- Repository: machikania/phyllosoma
- Generated: 2026-05-27T07:18:03.173Z
- Updated: 2026-05-27T07:18:08.881Z
- Runtime: Pi · Claude Code · claude-sonnet-4-6:high
- Format: Technical
- Pages: 10
## Page Index
- 01. [技術オリエンテーション — システム全体像とエントリポイント](https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/01-page-1.md) - リポジトリの目的(RP2040/RP2350 向け BASIC コンパイラファームウェア)、2 つのファームウェアバリアント(phyllosoma / puerulus)、主要モジュールの責務、ビルド成果物 (.uf2) の種類、および開発者がこの Wiki を読み進める順序を解説します。
- 02. [ビルドシステムと設定プロファイル](https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/02-page-2.md) - CMake ビルドシステムの構成、config.cmake による MACHIKANIA_BUILD 選択、LCD ドライバ・キーボード・WiFi ライブラリの自動切り替えロジック、RP2040/RP2350 向けリンカスクリプト、config/ ヘッダによるハードウェア定数の定義方法を解説します。
- 03. [BASICコンパイラコア — 字句解析・コード生成・CMPDATA](https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/03-basic-cmpdata.md) - compiler.c が担う 1 パスコンパイル処理、statements.c によるステートメント解析、operators.c の演算子優先順位、cmpdata.c のコンパイル時メタデータ管理(変数名・ラベル・クラス情報の格納と検索)、ARMv6-M サムネイルオブジェクトコード生成の仕組みを解説します。
- 04. [ランタイム実行エンジン — run.c・library.c・api.c](https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/04-run.c-library.c-api.c.md) - run.c の pre_run / run_code / post_run ライフサイクル、library.c のライブラリディスパッチテーブル (kmbasic_library)、api.c の表示プリミティブ (printchar/printstr 等)、error.c のエラー停止フロー、exception.c の ARM ハードウェア例外ハンドラを詳説します。
- 05. [メモリ管理・変数・クラスシステム](https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/05-page-5.md) - memory.c のブロックアロケータ(alloc_memory / garbage_collection)、variable.c の変数番号管理、value.c の型解決(整数・浮動小数点・文字列)、class.c のオブジェクト生成・フィールドアクセス・静的メンバ管理、globalvars.c のグローバル状態を解説します。
- 06. [ディスプレイインタフェース — LCD・NTSCビデオドライバ層](https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/06-lcd-ntsc.md) - 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) の構造を解説します。
- 07. [I/Oとペリフェラル制御 — GPIO・SPI・I2C・音楽・タイマー・RTC](https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/07-i-o-gpio-spi-i2c-rtc.md) - io.c の lib_gpio / lib_spi / lib_i2c / lib_serial / lib_pwm / lib_analog 実装、timer.c の割り込み駆動タイマー (INTERRUPT_TIMER / INTERRUPT_DRAWCOUNT 等)、music.c の PWM 音声合成とウェーブ再生、rtc.c のリアルタイムクロック API、MACHIKAP.INI による I/O 初期化設定を解説します。
- 08. [WiFiスタックとSDカードファイルシステム](https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/08-wifi-sd.md) - wifi/withwifi.c の CYW43 + lwIP + mbedTLS 統合、socket.c の TCP クライアント/サーバ/TLS API、wifi/picow_ntp_client.c の NTP 同期、file.c のファイル操作ステートメント群、interface/ff.c (FatFs) と interface/pico-sdmm.c (SPI MMC ドライバ)、withoutwifi.c のスタブ切り替えを解説します。
- 09. [エディタと入力ハンドリング — USBキーボード・PCシリアル・ファイル選択](https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/09-usb-pc.md) - withkeyboard.c (TinyUSB ホスト HID キーボードモード) と withoutkeyboard.c (USB シリアル PC 接続モード) の分岐、interface/keyinput.c のキー入力バッファリング、interface/usbkeyboard.c の HID 仮想キーテーブル変換、interface/fileselect.c の SD カードファイルブラウザ、editor.c のインタラクティブ BASIC テキストエディタを解説します。
- 10. [拡張ポイントと開発者リファレンス — auxcode・hexfile・デバッグ・help](https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/10-auxcode-hexfile-help.md) - auxcode/auxcode.c の補助コードライブラリ拡張インタフェース (aux_statements / aux_int_functions 等)、hexfile.c の Intel HEX 直接実行機能 (runHex)、pcconnect.c の PC シリアルプロトコル、debug.c のデバッグユーティリティ、help.c の組み込みヘルプ辞書、開発者が新規ライブラリを追加する際の手順と注意点をまとめます。
## Source File Index
- `api.c`
- `api.h`
- `auxcode/auxcode.c`
- `auxcode/auxcode.cmake`
- `class.c`
- `CMakeLists.txt`
- `cmpdata.c`
- `compiler.c`
- `compiler.h`
- `config.cmake`
- `config/pico_ili9341.h`
- `config/pico_ntsc.h`
- `core1.c`
- `debug.c`
- `display.c`
- `editor.c`
- `error.c`
- `exception.c`
- `file.c`
- `float.c`
- `functions.c`
- `globalvars.c`
- `help.c`
- `hexfile.c`
- `integer.c`
- `interface/ff.c`
- `interface/fileselect.c`
- `interface/fontdata.c`
- `interface/graphlib_lcd.c`
- `interface/graphlib_ntsc.c`
- `interface/graphlib.h`
- `interface/ili9341_spi.c`
- `interface/keyinput.c`
- `interface/pico-sdmm.c`
- `interface/rp2040_pwm_ntsc_textgraph.c`
- `interface/usbkeyboard.c`
- `io.c`
- `io.h`
- `library.c`
- `main.c`
- `memmap_machikania_pico2.ld`
- `memmap_machikania.ld`
- `memory.c`
- `music.c`
- `operators.c`
- `pcconnect/pcconnect.c`
- `README.md`
- `rtc.c`
- `run.c`
- `statements.c`
- `timer.c`
- `value.c`
- `variable.c`
- `wifi/socket.c`
- `wifi/wifi.h`
- `wifi/withoutwifi.c`
- `wifi/withwifi.c`
- `withkeyboard.c`
- `withoutkeyboard.c`
---
## 01. 技術オリエンテーション — システム全体像とエントリポイント
> リポジトリの目的(RP2040/RP2350 向け BASIC コンパイラファームウェア)、2 つのファームウェアバリアント(phyllosoma / puerulus)、主要モジュールの責務、ビルド成果物 (.uf2) の種類、および開発者がこの Wiki を読み進める順序を解説します。
- Page Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/01-page-1.md
- Generated: 2026-05-27T07:13:06.689Z
### Source Files
- `README.md`
- `CMakeLists.txt`
- `config.cmake`
- `main.c`
- `compiler.h`
<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:
- [README.md](README.md)
- [CMakeLists.txt](CMakeLists.txt)
- [config.cmake](config.cmake)
- [config.h](config.h)
- [main.c](main.c)
- [compiler.h](compiler.h)
- [compiler.c](compiler.c)
- [run.c](run.c)
- [globalvars.c](globalvars.c)
- [withkeyboard.c](withkeyboard.c)
- [withoutkeyboard.c](withoutkeyboard.c)
- [memmap_machikania.ld](memmap_machikania.ld)
- [config/pico_ili9341.h](config/pico_ili9341.h)
- [interface/](interface/)
</details>
# 技術オリエンテーション — システム全体像とエントリポイント
このページは **machikania/phyllosoma** リポジトリを初めて読む開発者向けに、プロジェクト全体の目的・構造・ビルド設計・実行モデルを解説します。どのファイルが何を担い、`.uf2` バイナリがどのような組み合わせで生成されるかを把握することで、以降の詳細な Wiki ページを効率よく読み進められるようになります。
## リポジトリの目的
このリポジトリは **KM-BASIC for ARM** の実装であり、Raspberry Pi Pico (RP2040) および Pico 2 (RP2350) 上で動作する BASIC コンパイラファームウェアを提供します。「コンパイラ」は PC 上で動くクロスコンパイラではなく、**マイコン本体上で BASIC ソースコードを ARM Thumb 機械語に変換して即実行するオンデバイスコンパイラ**です。BASIC プログラムは microSD カード (MMC/SPI) から読み込まれ、コンパイル後すぐに `kmbasic_object[]` 領域に書き込まれた Thumb コードとして実行されます。
ライセンスは LGPL 2.1(一部のファイルはそれ以外)。作者は Katsumi (kmorimatsu) および Kenken (kenkenMkIISR)。
Sources: [README.md](README.md)(冒頭の各バリアント説明)、[compiler.c:1-8](compiler.c)(ライセンスヘッダ)
---
## 2 つのファームウェアバリアント
リポジトリは単一のソースツリーから **2 種類のファームウェア名** を生成します。どちらが生成されるかは `config.cmake` の `MACHIKANIA_BUILD` 変数で決まります。
| バリアント名 | 型番称 | 映像出力 | 対象ボード例 | `MACHIKANIA_BUILD` 値 |
|---|---|---|---|---|
| **phyllosoma** | MachiKania type P | SPI 接続 LCD (ILI9341 / ILI9488 / ST7789 等) | Raspberry Pi Pico, Pico W, Pico 2, YD-RP2040 | `pico_ili9341`, `pico_ili9488`, `pico_st7789`, `pico_restouch`, `pico_picocalc`, `rp2350_lcd_1_47` |
| **puerulus** | MachiKania type PU | NTSC コンポジットビデオ | Raspberry Pi Pico, Pico W, XIAO RP2040, Tiny 2040, RP2040-Zero | `pico_ntsc`, `xiao_ntsc` |
`config.cmake` の末尾近くで、`MACHIKANIA_BUILD` が `pico_ntsc` または `xiao_ntsc` の場合に `MACHIKANIA_CODE_NAME=puerulus` が設定され、それ以外では `MACHIKANIA_CODE_NAME=phyllosoma` になります。`CMakeLists.txt` はこの変数をプロジェクト名・実行ファイル名・プリプロセッサマクロ (`MACHIKANIA_PHYLLOSOMA` / `MACHIKANIA_PUERULUS`) に使います。
Sources: [config.cmake:73-88](config.cmake)(`MACHIKANIA_CODE_NAME` の選択ロジック)、[CMakeLists.txt:164-170](CMakeLists.txt)(コンパイル定義の付与)
---
## ビルド成果物 (.uf2) の種類
各バリアントにつき **2 つの `.uf2`** が生成されます。
```text
phyllosoma_kb.uf2 ─── USB キーボード直結モード (USB HID ホスト)
phyllosoma.uf2 ─── USB シリアル接続モード (PC との REPL/ファイル転送)
puerulus_kb.uf2 ─── USB キーボード直結モード
puerulus.uf2 ─── USB シリアル接続モード
```
`_kb` 版は `withkeyboard.c` と `interface/usbkeyboard.c` を含み、USB を HID ホストとして初期化します。無印版は `withoutkeyboard.c` と `pcconnect.c` を含み、USB CDC シリアルとして PC に接続します。`CMakeLists.txt` でこの切り替えを明示しています。
```cmake
# _kb.uf2: キーボード版
add_executable(${MACHIKANIA_CODE_NAME}_kb
${MACHIKANIA_EDITOR}.c # editor.c または editor_ntsc.c
interface/${MACHIKANIA_KEYBOARD}.c # usbkeyboard.c / picocalc_keyboard.c
...
withkeyboard.c
)
# .uf2: シリアル版
add_executable(${MACHIKANIA_CODE_NAME}
withoutkeyboard.c
pcconnect.c
)
```
Sources: [CMakeLists.txt:95-120](CMakeLists.txt)(2 つの `add_executable`)、[withkeyboard.c:19-28](withkeyboard.c)、[withoutkeyboard.c:19-28](withoutkeyboard.c)(排他的コンパイルのコメント)
---
## システムアーキテクチャ図
```text
┌─────────────────────────────────────────────────────────┐
│ ファームウェア全体 │
│ │
│ ┌──────────────┐ ┌──────────────────────────────────┐ │
│ │ 入力 / I/O │ │ shared_files ライブラリ │ │
│ │ │ │ │ │
│ │ withkeyboard │ │ main.c ──► compiler.c │ │
│ │ または │ │ ├─ statements.c │ │
│ │withoutkeyboard│ │ ├─ functions.c │ │
│ │ + pcconnect │ │ ├─ integer.c │ │
│ └──────────────┘ │ ├─ float.c │ │
│ │ ├─ string.c │ │
│ ┌──────────────┐ │ ├─ value.c │ │
│ │ video_files │ │ ├─ operators.c │ │
│ │ │ │ └─ cmpdata.c │ │
│ │ graphlib_lcd │ │ run.c (ARM asm BX) │ │
│ │ または │ │ memory.c / class.c │ │
│ │graphlib_ntsc │ │ file.c (FatFS) │ │
│ │ + LCD/NTSC │ │ display.c / io.c │ │
│ │ driver │ │ timer.c / music.c │ │
│ └──────────────┘ │ rtc.c / hexfile.c │ │
│ └──────────────────────────────────┘ │
│ ┌──────────────┐ ┌──────────────────────────────────┐ │
│ │ wifi_files │ │ aux_files │ │
│ │ withwifi.c │ │ auxcode/auxcode.c │ │
│ │ (Pico W のみ)│ │ │ │
│ └──────────────┘ └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
▲ ▲
ハードウェア microSD (FatFS)
(LCD/NTSC/GPIO/ BASIC ソース
SPI/I2C/USB) クラスファイル
```
---
## 主要モジュールの責務
### コア — コンパイル・実行パイプライン
| ファイル | 責務 |
|---|---|
| `main.c` | エントリポイント。初期化シーケンス全体を制御し、`compile_file()` → `run_code()` を呼び出す |
| `compiler.c` | `init_compiler()` / `compile_line()` 等。BASIC ソースを ARM Thumb オブジェクトコードに変換する中核 |
| `run.c` | `run_code()` / `call_interrupt_function()`。インライン ASM で ARM レジスタ (R5/R6/R7/R8) をセットし `bx r1` でコンパイル済みコードへジャンプ |
| `globalvars.c` | `kmbasic_object[]`・`kmbasic_variables[]`・バッファなど全グローバル変数の実体定義 |
| `compiler.h` | エラーコード・ライブラリ番号 (LIB_*)・CMPDATA 型・全モジュールのプロトタイプ宣言。リポジトリ共通の「単一インクルード」ヘッダ |
### 言語要素のコンパイル
| ファイル | 責務 |
|---|---|
| `statements.c` | `GOTO` / `GOSUB` / `IF` / `FOR` 等のステートメント |
| `functions.c` | 組み込み関数のコンパイル (`argn_function` 等) |
| `integer.c` | 整数リテラル・式のコンパイル |
| `float.c` | 浮動小数点式のコンパイル |
| `string.c` | 文字列式のコンパイル |
| `value.c` | 汎用値取得 (`get_value()`) |
| `operators.c` | 演算子優先度・計算コード生成 |
| `cmpdata.c` | コンパイル時中間データ(ラベル、変数名、クラス情報)の管理 |
| `variable.c` | 変数番号の割り当て・参照 |
### ランタイムサポート
| ファイル | 責務 |
|---|---|
| `memory.c` | `alloc_memory()` / `garbage_collection()` — ALLOC_BLOCK_NUM (256) ブロック単位のヒープ |
| `class.c` | クラス定義・フィールド・メソッド・`NEW` / `DELETE` |
| `library.c` | `kmbasic_library()` — 実行時ライブラリディスパッチャ (LIB_* 番号で分岐) |
| `error.c` | コンパイル・実行時エラー表示 |
### ペリフェラル・インタフェース
| ファイル / ディレクトリ | 責務 |
|---|---|
| `file.c` + `interface/ff.c` | FatFS ラッパー。microSD からの BASIC / クラスファイル読み込み |
| `display.c` + `interface/graphlib_*.c` | LCD / NTSC の抽象表示レイヤ |
| `io.c` | GPIO / SPI / I2C / UART / ボタン / ADC / PWM |
| `timer.c` | タイマー割り込み・コアタイマー・フレームカウント |
| `music.c` | PWM 音楽・WAV 再生 |
| `rtc.c` | RTC (AON タイマー) |
| `wifi/` | Pico W 向け TCP/TLS/NTP クライアント・サーバ (lwIP + mbedTLS) |
| `hexfile.c` | `.HEX` バイナリの直接実行 |
| `interface/*.c` | LCD ドライバ (ILI9341/ILI9488/ST7789/NTSC)、USB キーボード HID、フォントデータ、FatFS 低レベル (pico-sdmm.c)、ファイル選択 UI |
### 入力モード切替
| ファイル | 定数 `g_active_usb_keyboard` | 動作 |
|---|---|---|
| `withoutkeyboard.c` | `0` | USB CDC シリアルで PC 接続。`connect2pc()` 呼び出し |
| `withkeyboard.c` | `1` | USB HID ホストとしてキーボードをポーリング。`usbkb_init()` + `start_core1()` |
---
## エントリポイントの制御フロー
`main()` は以下の順序で処理を行います。
```c
// main.c — main() の要点(簡略)
io_init(); // GPIO / SPI / ADC 初期化
timer_init(); // タイマー初期化
stdio_init_all(); // USB/UART stdio
display_init(); // LCD または NTSC 初期化
init_file_system(); // FatFS マウント
read_ini(); // MACHIKAP.INI 解析
connect_wifi(1); // Pico W のみ Wi-Fi 接続
post_inifile(); // キーボード or PC 接続確立
str = fileselect(); // ファイル選択 (または AUTOEXEC=)
init_compiler(); // コンパイラ初期化
compile_file(str, 0); // BASIC → ARM Thumb
post_compile(); // クラスのリンク
run_code(); // コンパイル済みコードを実行
// 終了後: RESETATEND 設定により software_reset() または無限ループ
```
実行後の「無限ループ」では START キーを押すと `software_reset()` が呼ばれ、`0xe000ed0c` (AIRCR) に `0x05FA0004` を書き込んでリセットします。
Sources: [main.c:94-160](main.c)(`main()` 関数全体)
---
## メモリレイアウト
カスタムリンカスクリプト `memmap_machikania.ld` で、コンパイル済み BASIC オブジェクトコードを格納する `kmbasic_object[]` を RAM 先頭 (`0x20000000`) に固定配置しています。
```
FLASH 0x10000000 (2 MB): ファームウェアコード・定数
RAM 0x20000000 (256 KB):
0x20000000 .kmbasicobject_section ← kmbasic_object[] (BASIC Thumb コード)
... .data / .bss
SCRATCH_X/Y (4 KB 各): Pico SDK スクラッチバッファ
```
RP2350 (Pico 2) 向けには別のリンカスクリプト `memmap_machikania_pico2.ld` が使われます (`CMakeLists.txt:143-148`)。
Sources: [memmap_machikania.ld:24-41](memmap_machikania.ld)(`MEMORY` / `.kmbasicobject_section` 定義)、[globalvars.c:19-21](globalvars.c)(`kmbasic_object[]` の `section` 属性宣言)
---
## ビルド設定のガイド
`config.cmake` の `MACHIKANIA_BUILD` を変更するだけで、グラフィックライブラリ・キーボードライブラリ・モニタライブラリ・ファームウェア名が一括で切り替わります。
```cmake
# 例: ILI9341 LCD (デフォルト) → phyllosoma
set(MACHIKANIA_BUILD pico_ili9341)
# 例: NTSC ビデオ出力 → puerulus
#set(MACHIKANIA_BUILD pico_ntsc)
```
Pico W / Pico 2 / Pico 2 W を対象にする場合は cmake 実行時に `-DPICO_BOARD` と `-DPICO_PLATFORM` を追加します。`PICO_BOARD=pico_w` 等が検出されると `MACHIKANIA_WIFI=withwifi` が自動設定され、`wifi/withwifi.c` 等が `wifi_files` ライブラリに含まれます。
Sources: [config.cmake:1-31](config.cmake)(ビルドターゲット一覧と選択方法)、[config.cmake:88-103](config.cmake)(Wi-Fi の自動有効化)
---
## Wiki 読み進めの推奨順序
1. **本ページ**(システム全体像)— 完了
2. **コンパイルパイプライン詳解** — `compiler.c` / `cmpdata.c` / `statements.c` を中心に BASIC → Thumb 変換の仕組みを解説
3. **実行モデルとライブラリディスパッチ** — `run.c` のアセンブリコンテキスト・`library.c` の `kmbasic_library()` ディスパッチ構造
4. **グラフィック・映像サブシステム** — `interface/graphlib_lcd.c` / `rp2040_pwm_ntsc_textgraph.c` / LCD ドライバ
5. **ペリフェラル API** — `io.c` / `timer.c` / `music.c` / `wifi/`
6. **クラスシステムとメモリ管理** — `class.c` / `memory.c`
7. **ビルド設定リファレンス** — `config/` 配下の各 `.h` ファイル
---
## まとめ
phyllosoma リポジトリは、RP2040/RP2350 マイコン上で自己完結するオンデバイス BASIC コンパイラ・実行環境です。`config.cmake` の `MACHIKANIA_BUILD` 変数が **phyllosoma (LCD)** と **puerulus (NTSC)** の分岐点であり、各バリアントについてさらに **USB シリアル版** と **USB キーボード版** の計 4 種の `.uf2` が生成されます。`main()` が初期化・INI 解析・ファイル選択・コンパイル・実行の全フェーズを順番に担い、コンパイル済み ARM Thumb コードは `run.c` のインラインアセンブリによって直接ジャンプ実行されます。
Sources: [CMakeLists.txt:130-170](CMakeLists.txt)(ライブラリ構成・コンパイル定義の全体像)
---
## 02. ビルドシステムと設定プロファイル
> CMake ビルドシステムの構成、config.cmake による MACHIKANIA_BUILD 選択、LCD ドライバ・キーボード・WiFi ライブラリの自動切り替えロジック、RP2040/RP2350 向けリンカスクリプト、config/ ヘッダによるハードウェア定数の定義方法を解説します。
- Page Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/02-page-2.md
- Generated: 2026-05-27T07:10:46.168Z
### Source Files
- `CMakeLists.txt`
- `config.cmake`
- `config/pico_ili9341.h`
- `config/pico_ntsc.h`
- `memmap_machikania.ld`
- `memmap_machikania_pico2.ld`
<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:
- [CMakeLists.txt](CMakeLists.txt)
- [config.cmake](config.cmake)
- [config.h](config.h)
- [config/pico_ili9341.h](config/pico_ili9341.h)
- [config/pico_ntsc.h](config/pico_ntsc.h)
- [config/pico_w_ili9341.h](config/pico_w_ili9341.h)
- [config/rp2350_lcd_1_47.h](config/rp2350_lcd_1_47.h)
- [config/xiao_ntsc.h](config/xiao_ntsc.h)
- [config/pico_picocalc.h](config/pico_picocalc.h)
- [memmap_machikania.ld](memmap_machikania.ld)
- [memmap_machikania_pico2.ld](memmap_machikania_pico2.ld)
- [auxcode/auxcode.cmake](auxcode/auxcode.cmake)
</details>
# ビルドシステムと設定プロファイル
Phyllosoma/Puerulus ファームウェアは **CMake** を中心に構成されており、ターゲットハードウェアの選択は `config.cmake` の 1 行を書き換えるだけで完結します。このページでは、`MACHIKANIA_BUILD` 変数がどのように LCD ドライバ・キーボード・WiFi ライブラリの組み合わせを自動的に確定させるか、RP2040/RP2350 向けにどちらのリンカスクリプトが選ばれるか、そして `config/` ディレクトリ内のハードウェア定数ヘッダがどのように C コードへ届くかを解説します。
---
## CMake ビルドの全体像
```text
CMakeLists.txt
├─ include(pico_sdk_import.cmake) # Pico SDK ルート設定
├─ include(config.cmake) # MACHIKANIA_BUILD → 各変数を決定
├─ project(${MACHIKANIA_CODE_NAME}) # phyllosoma / puerulus
└─ pico_sdk_init()
├─ add_library(shared_files) # 共通ランタイム
├─ add_library(video_files) # LCD/NTSC ドライバ (BUILD 依存)
├─ add_library(wifi_files) # WiFi / ダミースタブ (BOARD 依存)
├─ add_library(aux_files) # 補助コード (auxcode/auxcode.cmake)
├─ add_executable(*_kb) # キーボード付きビルド → *.uf2
└─ add_executable(*) # USB-CDC (PC 接続) ビルド → *.uf2
```
ビルドは TinyUSB が初期化されているときのみ実行されます。`if (TARGET tinyusb_device)` ブロックで全ライブラリ・実行ファイルが定義されています。
Sources: [CMakeLists.txt:14-17]()
---
## config.cmake による `MACHIKANIA_BUILD` の選択
ユーザーが編集すべきファイルは `config.cmake` だけです。先頭のコメント解除で使用するハードウェアプロファイルを 1 つ選びます。
### 選択可能なプロファイル一覧
| `MACHIKANIA_BUILD` 値 | 対象ハードウェア |
|---|---|
| `pico_ili9341` | Raspberry Pi Pico / YD-RP2040 + ILI9341 LCD(**デフォルト**)|
| `pico_st7789` | Raspberry Pi Pico / YD-RP2040 + ST7789 LCD |
| `pico_ili9488` | Raspberry Pi Pico / YD-RP2040 + ILI9488 LCD |
| `pico_restouch` | Waveshare Pico-ResTouch-LCD-3.5 |
| `pico_picocalc` | ClockworkPi PicoCalc |
| `rp2350_lcd_1_47` | Waveshare RP2350-LCD-1.47 |
| `pico_ntsc` | Raspberry Pi Pico / YD-RP2040 + NTSC ビデオ出力 |
| `xiao_ntsc` | Seeed XIAO RP2040 + NTSC ビデオ出力 |
WiFi 対応ボード(`PICO_BOARD=pico_w` または `pico2_w`)が指定された場合、CMake が自動的に `MACHIKANIA_BUILD` を `pico_w_ili9341`、`pico_w_ntsc` 等の WiFi 版に書き換えます。
Sources: [config.cmake:1-30](), [config.cmake:77-92]()
### 自動切り替えロジックの流れ
```mermaid
flowchart TD
A["MACHIKANIA_BUILD\n(ユーザーが config.cmake で設定)"]
A -->|"pico_ili9488\npico_picocalc"| G1["MACHIKANIA_GRAPH_LIB\n= ili9488_spi"]
A -->|"pico_st7789"| G2["MACHIKANIA_GRAPH_LIB\n= st7789_spi"]
A -->|"pico_restouch"| G3["MACHIKANIA_GRAPH_LIB\n= ws_pico_restouch"]
A -->|"rp2350_lcd_1_47"| G4["MACHIKANIA_GRAPH_LIB\n= rp2350_lcd_1_47"]
A -->|"pico_ntsc\nxiao_ntsc"| G5["MACHIKANIA_GRAPH_LIB\n= rp2040_pwm_ntsc_textgraph"]
A -->|"それ以外"| G6["MACHIKANIA_GRAPH_LIB\n= ili9341_spi(デフォルト)"]
A -->|"pico_picocalc"| K1["MACHIKANIA_KEYBOARD\n= picocalc_keyboard"]
A -->|"それ以外"| K2["MACHIKANIA_KEYBOARD\n= usbkeyboard"]
A -->|"pico_ntsc\nxiao_ntsc"| M1["MACHIKANIA_MONITOR_LIB=graphlib_ntsc\nMACHIKANIA_EDITOR=editor_ntsc\nMACHIKANIA_CODE_NAME=puerulus"]
A -->|"それ以外"| M2["MACHIKANIA_MONITOR_LIB=graphlib_lcd\nMACHIKANIA_EDITOR=editor\nMACHIKANIA_CODE_NAME=phyllosoma"]
B["PICO_BOARD"]
B -->|"pico_w / pico2_w"| W1["MACHIKANIA_WIFI=withwifi\nMACHIKANIA_BUILD を pico_w_* に変更"]
B -->|"それ以外"| W2["MACHIKANIA_WIFI=withoutwifi"]
```
Sources: [config.cmake:33-92]()
#### LCD ドライバ (`MACHIKANIA_GRAPH_LIB`)
`MACHIKANIA_GRAPH_LIB` は `interface/` 内のドライバ C ファイル名に対応します。`video_files` ライブラリは次のように構築されます。
```cmake
add_library(video_files
interface/${MACHIKANIA_GRAPH_LIB}.c
interface/${MACHIKANIA_MONITOR_LIB}.c
interface/fontdata.c
)
```
Sources: [CMakeLists.txt:51-60]()
#### WiFi ライブラリ (`MACHIKANIA_WIFI`)
WiFi が有効な場合はフル実装(`wifi/withwifi.c`、LwIP + mbedTLS)、無効な場合はスタブ(`wifi/withoutwifi.c`)のみがリンクされます。WiFi 有効時にはスタックサイズ・ヒープサイズに `4096` バイトが追加で割り当てられます。
```cmake
if (MACHIKANIA_WIFI STREQUAL "withwifi")
add_library(wifi_files
wifi/withwifi.c wifi/socket.c wifi/picow_ntp_client.c ...
)
target_link_libraries(wifi_files
pico_cyw43_arch_lwip_threadsafe_background
pico_lwip_mbedtls pico_mbedtls ...
)
else()
add_library(wifi_files wifi/withoutwifi.c)
endif()
```
Sources: [CMakeLists.txt:65-88]()
---
## 2 種類の実行バイナリ
`CMakeLists.txt` は常に 2 つの `.uf2` を生成します。
| バイナリ名 | 特徴 | USB 標準入出力 |
|---|---|---|
| `{CODE_NAME}_kb.uf2` | USB ホスト HID キーボードを使用 | 無効 |
| `{CODE_NAME}.uf2` | PC の USB-CDC 経由でコマンド入力 | USB CDC 有効 |
キーボード付きバイナリは `tinyusb_host` / `tinyusb_board` にリンクされ、キーなしバイナリは `pico_enable_stdio_usb` で USB CDC が有効になります。
Sources: [CMakeLists.txt:92-127]()
---
## コンパイル定義
`CMakeLists.txt` が付与する主要な `#define` を以下に示します。
| 定義名 | 値・条件 | 用途 |
|---|---|---|
| `MACHIKANIA_CONFIG` | `"./config/${MACHIKANIA_BUILD}.h"` | ハードウェアヘッダのインクルードパス |
| `PICO_CLOCK_ADJUST_PERI_CLOCK_WITH_SYS_CLOCK` | `1` | SPI 等の周辺クロック追従 |
| `PICO_PLATFORM_RP2350` | `rp2350-arm-s` 時のみ | プラットフォーム判別 |
| `MACHIKANIA_PHYLLOSOMA` / `MACHIKANIA_PUERULUS` | CODE_NAME による | システム種別判別 |
| `MACHIKANIA_DEBUG_MODE` | `config.cmake` で有効化 | デバッグモード |
| `PICO_STACK_SIZE` / `PICO_HEAP_SIZE` | WiFi 有効時 `4096` | スタック・ヒープ増量 |
Sources: [CMakeLists.txt:137-162]()
---
## リンカスクリプト
ターゲットプラットフォームに応じて、2 つのリンカスクリプトが切り替えられます。
```cmake
if(PICO_PLATFORM STREQUAL "rp2350-arm-s")
pico_set_linker_script(... memmap_machikania_pico2.ld)
else()
pico_set_linker_script(... memmap_machikania.ld)
endif()
```
Sources: [CMakeLists.txt:129-135]()
### メモリマップ比較
| 領域 | RP2040 (`memmap_machikania.ld`) | RP2350 (`memmap_machikania_pico2.ld`) |
|---|---|---|
| FLASH | `0x10000000` / 2048 KB | `0x10000000` / **4096 KB** |
| RAM | `0x20000000` / 256 KB | `0x20000000` / **512 KB** |
| SCRATCH_X | `0x20040000` / 4 KB | `0x20080000` / 4 KB |
| SCRATCH_Y | `0x20041000` / 4 KB | `0x20081000` / 4 KB |
両スクリプトとも先頭に `.kmbasicobject_section` を `0x20000000` に配置する点が共通です。これは BASIC インタープリタのオブジェクトコードを RAM の先頭固定アドレスに置くための設計です。
```ld
.kmbasicobject_section 0x20000000 (NOLOAD) : {
KEEP(*(.kmbasicobject_section))
} > RAM
```
Sources: [memmap_machikania.ld:23-26](), [memmap_machikania_pico2.ld:23-26]()
### RP2040 と RP2350 のスクリプト差異
- **boot2 セクション**: RP2040 版ではサイズが正確に 256 バイトであることをアサートしますが、RP2350 版では「256 バイト以下」のアサートに緩和されています(RP2350 では boot2 の役割が縮小)。
- **TLS サポート**: RP2350 版には `.tdata`・`.tbss` セクションと picolibc/LLVM-libc 向けの `PROVIDE` シンボルが追加されています。
- **ヒープ**: RP2350 版では `.heap` セクションが RAM 全体に拡張され、`__HeapLimit` が `ORIGIN(RAM) + LENGTH(RAM)` に設定されます。
- **`KMBASIC_OBJECT_KBYTES_EXTENDED`**: `config.h` が RP2350 時に `KMBASIC_OBJECT_KBYTES + 256` を設定し、拡張 RAM を BASIC オブジェクト領域に活用します。
Sources: [memmap_machikania.ld:36-39](), [memmap_machikania_pico2.ld:95-98](), [config.h:43-48]()
---
## `config/` ヘッダによるハードウェア定数の定義
### ヘッダファイル一覧
| ファイル名 | 対象ハードウェア |
|---|---|
| `pico_ili9341.h` | Pico + ILI9341 |
| `pico_ili9488.h` | Pico + ILI9488 |
| `pico_st7789.h` | Pico + ST7789 |
| `pico_restouch.h` | Waveshare Pico-ResTouch |
| `pico_picocalc.h` | PicoCalc |
| `rp2350_lcd_1_47.h` | Waveshare RP2350-LCD-1.47 |
| `pico_ntsc.h` | Pico + NTSC 映像出力 |
| `xiao_ntsc.h` | XIAO RP2040 + NTSC 映像出力 |
| `pico_w_ili9341.h` | Pico W + ILI9341(`pico_ili9341.h` を include + WiFi 用定数追加)|
| `pico_w_*.h` | 上記 WiFi 版バリアント(同様のパターン)|
### 定義カテゴリ
各ヘッダは以下のカテゴリのマクロを定義します。
**GPIO マスク**
```c
// pico_ili9341.h より
// 31 24 16 8 0
#define GPIO_ALL_MASK 0b00011100011100000000001111111111
```
**SPI / I2C / UART ピン**
```c
#define IO_SPI_CS 3
#define IO_I2C_SDA 6
#define IO_I2C_SCL 7
#define IO_UART_TX 4
#define IO_UART_RX 5
#define IO_UART_CH uart1
```
**ボタン GPIO**
```c
#define GPIO_KEYUP 8
#define GPIO_KEYLEFT 9
#define GPIO_KEYRIGHT 20
#define GPIO_KEYDOWN 21
#define GPIO_KEYSTART 22
#define GPIO_KEYFIRE 26
```
**LCD 設定(LCD 搭載モデルのみ)**
```c
#define LCD_CS 13
#define LCD_DC 10
#define LCD_COLUMN_RES 240
#define LCD_ROW_RES 320
#define LCD_SPI_BAUDRATE (50*1000*1000)
#define LCD_SPI_BAUDRATE_R (15*1000*1000)
```
**SD カード SPI 設定**
```c
#define SD_SPI_CS 17
#define SD_SPI_TX 19
#define SD_SPI_RX 16
#define SD_SPI_SCK 18
#define SD_SPICH spi0
#define SD_SPI_BAUDRATE (10*1000*1000)
```
NTSC モデルでは LCD 関連マクロが存在せず、`NTSC_VIDEO_OUT 15` のような映像出力ピン定義に置き換えられます。
Sources: [config/pico_ili9341.h:35-100](), [config/pico_ntsc.h:35-100]()
#### WiFi 版ヘッダの継承パターン
`pico_w_ili9341.h` のように WiFi 版ヘッダは非 WiFi 版ヘッダを `#include` し、差分定数(主に `KMBASIC_OBJECT_KBYTES` の縮小)だけを追加します。
```c
// pico_w_ili9341.h
#include "./pico_ili9341.h"
#define KMBASIC_OBJECT_KBYTES 176
```
WiFi スタックがメモリを消費するため、BASIC オブジェクト用 RAM を 192 KB → 176 KB に削減しています。
Sources: [config/pico_w_ili9341.h:1-8]()
---
## `config.h`: 共通ブリッジヘッダ
`config/` ヘッダを C コードに届けるのが **`config.h`** です。`MACHIKANIA_CONFIG` マクロを使ってコンパイル時にインクルードパスを解決します。
```c
// config.h
#ifdef MACHIKANIA_CONFIG
#include MACHIKANIA_CONFIG // "例: ./config/pico_ili9341.h"
#else
#error MACHIKANIA_CONFIG not defined
#endif
```
その後、ハードウェア非依存の共通定義が続きます。
| 定義 | 内容 |
|---|---|
| `PUERULUS` / `PHYLLOSOMA` マクロ | システム種別フラグ(0/1)|
| `MONITOR_TYPE` | `"LCD"` または `"NTSC"` |
| `SYSVER1`, `SYSVER2`, `BASVER` | バージョン文字列 |
| `RP2040` / `RP2350` | プラットフォームフラグ |
| `KMBASIC_OBJECT_KBYTES_EXTENDED` | RP2350 時は `+256` KB |
| `IO_SPI_TX/RX/SCK/CH` のフォールバック | 未定義時は SD カード SPI を共用 |
| `KEYUP`〜`KEYSMASK` | GPIO ビットマスク定数 |
| `rotate_buttons_data()` | LCD 回転時のボタン方向変換マクロ |
Sources: [config.h:1-89]()
---
## まとめ
Phyllosoma のビルドシステムは、`config.cmake` の `MACHIKANIA_BUILD` 変数を起点とした段階的な変数導出により、LCD ドライバ・キーボード・モニタ・WiFi の 4 軸を同時に制御します。WiFi 対応は `PICO_BOARD` フラグで自動判別され、WiFi 版ヘッダは非 WiFi 版を継承して差分だけを上書きするという簡潔な設計です。リンカスクリプトは RP2040(FLASH 2 MB / RAM 256 KB)と RP2350(FLASH 4 MB / RAM 512 KB)で切り替えられ、いずれも `.kmbasicobject_section` を RAM 先頭の固定アドレス `0x20000000` に配置します。C コードへのハードウェア定数の供給は `config.h` → `#include MACHIKANIA_CONFIG` の単一インダイレクションで完結しており、新しいハードウェアターゲットの追加は `config/` への新規ヘッダ追加と `config.cmake` への数行の追記だけで対応できます。
---
## 03. BASICコンパイラコア — 字句解析・コード生成・CMPDATA
> compiler.c が担う 1 パスコンパイル処理、statements.c によるステートメント解析、operators.c の演算子優先順位、cmpdata.c のコンパイル時メタデータ管理(変数名・ラベル・クラス情報の格納と検索)、ARMv6-M サムネイルオブジェクトコード生成の仕組みを解説します。
- Page Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/03-basic-cmpdata.md
- Generated: 2026-05-27T07:11:37.182Z
### Source Files
- `compiler.c`
- `compiler.h`
- `statements.c`
- `operators.c`
- `cmpdata.c`
- `functions.c`
<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:
- [compiler.c](compiler.c)
- [compiler.h](compiler.h)
- [cmpdata.c](cmpdata.c)
- [statements.c](statements.c)
- [operators.c](operators.c)
- [functions.c](functions.c)
- [variable.c](variable.c)
- [value.c](value.c)
- [integer.c](integer.c)
</details>
# BASICコンパイラコア — 字句解析・コード生成・CMPDATA
machikania/phyllosoma の BASIC コンパイラは、ソーステキストを **1パスで直接 ARMv6-M Thumb バイナリ**(`unsigned short` の配列として RAM 上に生成)へ変換するシステムです。字句解析・ステートメント分岐・演算子評価・コード出力がすべて C 関数の呼び出しチェーンとして実装されており、中間 AST は存在しません。コンパイル時に必要なメタデータ(変数名・ラベル・前方参照パッチ先・クラス情報)は **CMPDATA** と呼ばれる独自のコンパクトなレコード群で管理されます。
このページでは `compiler.c`・`statements.c`・`operators.c`・`cmpdata.c` が担う各責務、それらの連携方式、生成される Thumb 機械語の構造を解説します。
---
## 全体アーキテクチャ
```text
ソーステキスト (unsigned char*)
|
v
code2upper() ── 小文字→大文字、タブ→スペース、文字列リテラル内は保護
|
v
compile_line() ── 行番号の登録・取得、複数ステートメント(:区切り)ループ
|
v
compile_statement() ── instruction_is() でキーワードを判別し各ハンドラへ
/ \
/ \
let_statement() goto_statement() … (他多数)
|
v
get_integer() / get_float() / get_string()
|
v
get_value_sub() ←→ get_operator() / calculation() 演算子優先度付き再帰
|
v
object[] へ 16bit Thumb 命令を直接書き出す
|
v
CMPDATA ── 変数名・ラベル・前方参照BLアドレスを一時保存
```
Sources: [compiler.c:251-310](), [statements.c:1484-1585]()
---
## compiler.c — コンパイルパイプライン
### 初期化と終了処理
`init_compiler()` はコンパイル開始時に一度だけ呼ばれ、オブジェクト出力ポインタ `object`、CMPDATA 領域、変数テーブルを初期化します。
```c
// compiler.c
void init_compiler(void){
object=&kmbasic_object[0];
g_objmax=&kmbasic_object[(sizeof kmbasic_object)/2];
cmpdata_init();
variable_init();
g_class_id=0;
}
```
ファイル単位の初期化は `begin_file_compiler()` が担い、`IF`/`FOR` の深さカウンタや行番号をリセットします。コンパイル完了後は `end_file_compiler()` が未解決の前方参照(`CMPDATA_GOTO_NUM_BL`・`CMPDATA_GOTO_LABEL_BL` 等)の残留を検査しエラーを返します。
Sources: [compiler.c:37-52](), [compiler.c:55-112]()
### compile_line() — 行コンパイルの核
```c
int compile_line(unsigned char* code){
g_linenum++;
before=source=code2upper(code); // 正規化
e=get_positive_decimal_value(); // 行番号取得を試みる
if (0<=e) { register_line_number(e); handle_line_number(e); }
// ステートメントループ
while(1){
e=compile_statement();
if (e) break;
if (source[0]==0x00) break;
if (source[0]!=':') { e=ERROR_SYNTAX; break; }
source++; // ':' で次ステートメントへ
}
...
}
```
`code2upper()` は入力行を作業バッファ `g_compile_buffer` にコピーしながら英小文字を大文字へ変換します。文字列リテラル(`"..."` 内)は変換対象外です。行末 (`\r`/`\n`/`\0`) で変換を打ち切り、戻り値として消費バイト数を返します。
Sources: [compiler.c:176-230](), [compiler.c:251-310]()
### Thumb 命令出力ユーティリティ
| 関数 | 生成するコード | 用途 |
|---|---|---|
| `set_value_in_register(r, val)` | `movs rx, #val` / `ldr rx, [pc, #4]` + インライン定数 | 任意整数をレジスタへロード |
| `call_lib_code(lib_number)` | `movs r3, #lib` + `blx r8` | ランタイムライブラリ呼び出し |
| `update_bl(bl, dest)` | BL 命令 2 ハーフワードをパッチ | 前方参照の解決 |
`set_value_in_register` は値の範囲に応じて最小バイト数の命令列を選択します。`0–255` なら 1 命令(`movs`)、それ以外は PC 相対 `ldr` + インライン 32bit 定数(アライメントを考慮して NOP を挿入)を使います。
Sources: [variable.c:27-57](), [compiler.c:130-143]()
---
## statements.c — ステートメント解析
### compile_statement() — 大統一ディスパッチャ
`compile_statement()` は `instruction_is()` によるキーワード照合を順次試みます。
```c
int compile_statement(void){
// まず LET を試みる("LET" の明示省略も許容)
if (instruction_is("LET")) return let_statement();
e=let_statement();
if (!e) return 0;
rewind_object(bobj); source=bsrc;
// 以下、アルファベット順に全ステートメントを検査
if (instruction_is("BREAK")) return break_statement();
if (instruction_is("DATA")) return data_statement();
if (instruction_is("FOR")) return for_statement();
if (instruction_is("GOTO")) return goto_statement();
if (instruction_is("IF")) return if_statement();
if (instruction_is("LABEL")) return label_statement();
...
// サブモジュールへ委譲
e=io_statements();
if (e!=ERROR_STATEMENT_NOT_DETECTED) return e;
e=display_statements(); ...
e=wifi_statements(); ...
e=aux_statements(); ...
}
```
`instruction_is()` はソースポインタを前進させながらキーワードと比較し、次の文字が区切り文字(空白・`:`・`,`・`\0`)であることを確認します。マッチしない場合はポインタを戻します。
Sources: [statements.c:1484-1585](), [compiler.c:151-170]()
### GOTO / GOSUB と前方参照処理
ラベルへのジャンプは、コンパイル時点で宛先が未確定な場合に「プレースホルダ BL」を出力し、CMPDATA に BL のアドレスを記録します。ラベル定義が後から現れたとき `update_bl()` でパッチします。
```c
// 宛先未確定の場合
(object++)[0]=0xf000; // bl 仮エンコード
(object++)[0]=0xf800;
g_scratch_int[0]=(int)object-4;
cmpdata_insert(CMPDATA_GOTO_LABEL_BL, id, g_scratch_int, 1);
// ラベル定義時
while(data=cmpdata_findfirst_with_id(CMPDATA_GOTO_LABEL_BL, id)){
bl=(short*)data[1];
update_bl(bl, object); // BL を正しいオフセットで上書き
cmpdata_delete(data);
}
```
行番号による GOTO も同様で `CMPDATA_GOTO_NUM_BL` を使います。
Sources: [statements.c:467-530]()
### DATA / CDATA ステートメント
`DATA` はオブジェクトコード中に文字列・整数リテラルをインライン埋め込みます。BL 命令でデータブロック全体を読み飛ばすエピローグコードが先頭に置かれ、`MOV R5,R5`(opcode `0x462d`)がデータブロック開始のマーカーとなります。`CDATA` は 1 バイト単位のデータで `MOV R6,R6`(`0x4636`)がマーカーです。
Sources: [statements.c:201-260]()
---
## operators.c — 演算子優先順位と Thumb コード生成
### 優先順位テーブル
```c
const unsigned char g_priority[]={
0, // OP_VOID
1, // OP_OR
2, // OP_AND
3, // OP_XOR
4,4, // OP_EQ, OP_NEQ
5,5,5,5, // OP_LT, OP_LTE, OP_MT, OP_MTE
6,6, // OP_SHL, OP_SHR
7,7, // OP_ADD, OP_SUB
8,8,8 // OP_MUL, OP_DIV, OP_REM
};
```
数値が大きいほど優先度が高く(乗算・除算が最高)、`compiler.h` の `priority(x)` マクロでテーブルを参照します。
Sources: [operators.c:20-30]()
### 整数演算のコード生成
ほとんどの演算子は 1〜7 命令の直接 Thumb エンコードで処理されます。除算・剰余のみランタイムを呼び出します。
| 演算子 | 生成される Thumb | 備考 |
|---|---|---|
| `OR` | `orrs r0, r1` (`0x4308`) | 1命令 |
| `AND` | `ands r0, r1` (`0x4008`) | 1命令 |
| `XOR` | `eors r0, r1` (`0x4048`) | 1命令 |
| `ADD` | `adds r0, r1, r0` (`0x1808`) | 1命令 |
| `SUB` | `subs r0, r1, r0` (`0x1a08`) | 1命令 |
| `MUL` | `muls r0, r1` (`0x4348`) | 1命令 |
| `DIV` / `REM` | `call_lib_code(LIB_CALC)` | ランタイム委譲 |
| `EQ` | `subs`→`negs`→`adcs` | 3命令ブランチレス |
| `<` | 7命令の符号付き比較列 | |
| `SHL` | `lsls r1, r0` + `movs r0, r1` | 2命令 |
Sources: [operators.c:100-200]()
### 浮動小数点演算
浮動小数点では `get_float_operator()` が `XOR`・`%`・`<<`・`>>` を拒否し、残りを `LIB_CALC_FLOAT` ランタイムへ委譲します。
```c
int float_calculation(int op){
switch(op){
case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV:
case OP_EQ: case OP_NEQ: case OP_LT: case OP_LTE:
case OP_MT: case OP_MTE: case OP_OR: case OP_AND:
set_value_in_register(2, op);
return call_lib_code(LIB_CALC_FLOAT);
...
}
}
```
Sources: [operators.c:216-245]()
---
## cmpdata.c — コンパイル時メタデータ管理
### メモリレイアウト
CMPDATA はオブジェクトコード出力バッファの**上端**(`g_objmax`)から下方向へ伸びます。オブジェクトコードは下端(`kmbasic_object[0]`)から上方向へ伸びるため、両者が衝突したとき `ERROR_OBJ_TOO_LARGE` が返ります。
```text
kmbasic_object[]
低アドレス 高アドレス
+------------------+-----------+--------------------+
| オブジェクトコード→ | (空き) | ←CMPDATA レコード群 |
+------------------+-----------+--------------------+
object g_objmax
```
Sources: [cmpdata.c:55-65]()
### レコードフォーマット
```text
MSB LSB
+--------+--------+----------------+
| type | len | data16 | ← record[0] (32bit)
+--------+--------+----------------+
| record[1] | ← ペイロード先頭
+----------------------------------+
| record[2] |
+----------------------------------+
… (len-1 個)
```
- **type** (8bit): `CMPDATA_VARNAME`・`CMPDATA_LINENUM`・`CMPDATA_LABEL` などの種別定数
- **len** (8bit): ヘッダ含む 32bit ワード数(最小 1)
- **data16** (16bit): 汎用 16bit フィールド(変数番号・ラベル ID 等)
Sources: [cmpdata.c:15-30]()
### 主要な CMPDATA 型一覧
| 定数 | 用途 | ペイロード |
|---|---|---|
| `CMPDATA_VARNAME` | 変数名→変数番号のマッピング | hash + 変数名文字列 |
| `CMPDATA_LINENUM` | 行番号→オブジェクトアドレス | `(int)object` |
| `CMPDATA_LABEL` | ラベル ID→オブジェクトアドレス | `(int)object` |
| `CMPDATA_LABELNAME` | ラベル文字列→ラベル ID | hash + ラベル名文字列 |
| `CMPDATA_GOTO_NUM_BL` | 未解決行番号ジャンプ | 仮 BL のアドレス |
| `CMPDATA_GOTO_LABEL_BL` | 未解決ラベルジャンプ | 仮 BL のアドレス |
| `CMPDATA_IF_BL` | 未解決 IF 分岐 | 仮 BL のアドレス |
| `CMPDATA_ENDIF_BL` | 未解決 ENDIF | 仮 BL のアドレス |
| `CMPDATA_BREAK_BL` | 未解決 BREAK | 仮 BL のアドレス |
| `CMPDATA_CLASSNAME` | クラス名 | クラス名文字列 |
| `CMPDATA_CLASS` | クラス定義情報 | クラスメタデータ |
| `CMPDATA_METHOD` | メソッド情報 | — |
| `CMPDATA_FIELDNAME` | フィールド名 | フィールド名文字列 |
| `CMPDATA_STRSTACK` | 文字列スタック | 文字列データ |
| `CMPDATA_STATIC` | static 変数情報 | — |
Sources: [compiler.h:198-225]()
### 検索 API
文字列型レコードの検索は **ハッシュ** で高速化されています。
```c
// cmpdata.c
int cmpdata_nhash(const unsigned char* str, int num){
int hash=0;
for(i=0;i<num;i++){
hash = hash<<6 ^ hash>>26;
hash ^= str[i];
}
return hash;
}
int* cmpdata_nsearch_string(unsigned int type, unsigned char* str, int num){
int hash=cmpdata_nhash(str,num);
while(data=cmpdata_find(type)){
if (hash!=data[1]) continue; // ハッシュ不一致は即スキップ
// バイト列比較 ...
if (一致) return data;
}
return 0;
}
```
非文字列型の検索は `cmpdata_find()` / `cmpdata_findfirst()` / `cmpdata_findfirst_with_id()` が `type` フィールドと `data16`(ID)の組み合わせで線形スキャンします。
Sources: [cmpdata.c:210-255]()
### 挿入・削除
`cmpdata_insert()` は `g_cmpdata` ポインタを下方向へ `num+1` ワード移動させ、`g_objmax` を更新します。`CMPDATA_STRSTACK` 型だけは既存レコードの先頭に割り込む特別なスタックセマンティクスを持ちます。
`cmpdata_delete()` は削除対象レコードの上にある全レコードを `delnum` ワード分シフトダウンして穴を埋め、`g_cmpdata` を戻します。`rewind_object()` から呼ばれる `cmpdata_delete_invalid()` は、巻き戻されたオブジェクトポインタより先を指す前方参照レコードを一括削除します。
Sources: [cmpdata.c:87-190]()
---
## ARMv6-M Thumb オブジェクトコード生成
### レジスタ規約
| レジスタ | 役割 |
|---|---|
| R0 | 計算結果・第1引数 |
| R1 | 第2引数・一時 |
| R2 | 第3引数(ライブラリオプション) |
| R3 | ライブラリ番号(`call_lib_code` 時) |
| R5 | `&kmbasic_variables[0]`(変数配列ベース) |
| R6 | 引数スタックフレームポインタ |
| R7 | `&kmbasic_data[0]`(ランタイムデータ) |
| R8 | ライブラリ呼び出しエントリ(`blx r8`) |
Sources: [compiler.c:13-30]()
### ライブラリ呼び出し規約
```c
// call_lib_code() が生成するコード(2ハーフワード)
0x2300 | lib_number // movs r3, #lib_number
0x47c0 // blx r8
```
`lib_number` が 0–127 は「クイックライブラリ」(計算・関数)、128–255 は「ステートメントライブラリ」(ガベージコレクション・Break チェック付き)です。
Sources: [compiler.c:144-150](), [compiler.h:64-80]()
### 変数アクセスパターン
```c
// r0_to_variable(): R0の値を変数番号 vn に格納
// vn が 0-31 (A-Z 単文字) の場合
(object++)[0] = 0x6028 | (vn<<6); // str r0, [r5, #vn*4]
(object++)[0] = 0x2300; // movs r3, #0
(object++)[0] = 0x68ba; // ldr r2, [r7, #8] ← var_size 配列
(object++)[0] = 0x8013 | (vn<<6); // strh r3, [r2, #vn*2]
```
単文字変数(A–Z)はオフセット計算が即値でできるため高速です。長名変数(CMPDATA_VARNAME で管理)は `set_value_in_register(1, vn*4)` で動的に計算します。
Sources: [variable.c:80-100]()
### BL 前方参照パッチ (`update_bl`)
```c
void update_bl(short* bl, short* destination){
int i = (int)destination - ((int)bl + 4);
i >>= 1;
bl[1] = 0xf800 | (i & 0x7ff);
i >>= 11;
bl[0] = 0xf000 | (i & 0x7ff);
}
```
Thumb BL 命令は 2 つの 16bit ハーフワードで構成されます。`update_bl()` はオフセットをビットフィールドに分割し、すでにオブジェクトコード中に出力済みの仮 BL 命令(`0xf000`/`0xf800`)を正しいアドレスで上書きします。
Sources: [compiler.c:127-133]()
---
## 変数名の登録と解決フロー
```mermaid
sequenceDiagram
participant S as source ポインタ
participant V as variable.c<br/>get_var_number()
participant C as cmpdata.c
S->>V: 識別子を読む
V->>C: cmpdata_nsearch_string_first(CMPDATA_VARNAME, name, len)
C-->>V: 見つかった: data[0]&0xffff = 変数番号
V-->>S: 変数番号を返す
Note over V,C: 未登録の長名変数は<br/>CMPDATA_VARNAME に<br/>cmpdata_insert_string() で追加
```
単文字変数(A–Z)は 0–25 番に直接マップされ CMPDATA を介しません。2 文字以上の変数名は `CMPDATA_VARNAME` へ文字列とハッシュを格納し、`get_new_varnum()` で 26 番以降の番号が割り当てられます。
Sources: [variable.c:68-80]()
---
## まとめ
phyllosoma の BASICコンパイラは中間表現を持たない徹底したシングルパス設計です。`compile_line()` が行を正規化してトークン列を `source` グローバルポインタとして公開し、`compile_statement()` が `instruction_is()` によるキーワード照合で各ハンドラへ分岐、各ハンドラが `object` ポインタへ Thumb 命令を直接書き出します。前方参照はすべて CMPDATA に仮記録して `update_bl()` でパッチするため、データ構造の複雑さがランタイムメモリを消費せず、コンパイル完了後は `cmpdata_delete_all()` で解放されます。演算子優先順位は `g_priority[]` テーブルと再帰的な `get_value_sub()` で実現し、整数演算はほぼ全て直接 Thumb 命令列、浮動小数演算はランタイム委譲という明確な役割分担が取られています。
Sources: [compiler.c:251-310](), [cmpdata.c:55-65](), [operators.c:20-30]()
---
## 04. ランタイム実行エンジン — run.c・library.c・api.c
> run.c の pre_run / run_code / post_run ライフサイクル、library.c のライブラリディスパッチテーブル (kmbasic_library)、api.c の表示プリミティブ (printchar/printstr 等)、error.c のエラー停止フロー、exception.c の ARM ハードウェア例外ハンドラを詳説します。
- Page Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/04-run.c-library.c-api.c.md
- Generated: 2026-05-27T07:11:42.096Z
### Source Files
- `run.c`
- `library.c`
- `api.c`
- `api.h`
- `error.c`
- `exception.c`
<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:
- [run.c](run.c)
- [library.c](library.c)
- [api.c](api.c)
- [api.h](api.h)
- [error.c](error.c)
- [exception.c](exception.c)
- [compiler.c](compiler.c)
- [compiler.h](compiler.h)
- [globalvars.c](globalvars.c)
</details>
# ランタイム実行エンジン — run.c・library.c・api.c
phyllosoma は Raspberry Pi Pico (RP2040/ARM Cortex-M0+) 向け KM-BASIC インタプリタの実行基盤です。コンパイラが生成した Thumb-2 バイナリコード (`kmbasic_object`) を実際に走らせるための「実行エンジン」は、**run.c**(ライフサイクル制御・レジスタ初期化)、**library.c**(ランタイムライブラリのディスパッチテーブル)、**api.c / api.h**(出力プリミティブの二重出力レイヤ)、**error.c**(エラー停止フロー)、**exception.c**(ARM ハードフォルト例外ハンドラ)の 5 ファイルで構成されます。
これら 5 ファイルは密結合しており、BASIC プログラムの開始から終了まで、さらには予期しないハードウェア例外の後始末までを一貫して担います。ARMv6-M の制約(高レベルスタックフレームなし、Thumb-2 のみ)に対応するため、多くの箇所でインラインアセンブリが用いられています。
---
## run.c — 実行ライフサイクル
### ライフサイクル概観
```text
┌───────────────────────────────────────────────────────┐
│ pre_run() — メモリ・デバイス初期化 │
│ run_code() — ARM レジスタ設定 → kmbasic_object │
│ post_run() — リソース解放・クロック復元 │
└───────────────────────────────────────────────────────┘
```
### pre_run() — 実行前初期化
```c
// run.c
void pre_run(void){
init_memory();
g_read_point=&kmbasic_object[0];
g_rnd_seed=0x92D68CA2;
kmbasic_data[2]=(int)&kmbasic_var_size[0];
close_all_files();
video_init(); io_init(); init_music();
g_interrupt_code=0;
lib_display(0,0,RESET_STATIC_VARS);
lib_spi(0,0,RESET_STATIC_VARS);
handle_exception(1);
pre_run_wifi();
g_clock_hz_default=clock_get_hz(clk_sys);
g_cpu_voltage_default=vreg_get_voltage();
}
```
`pre_run()` は以下を順に実行します。
| 処理 | 説明 |
|------|------|
| `init_memory()` | メモリアロケータ初期化 |
| `g_read_point` 設定 | DATA/CDATA 読み出しポインタをオブジェクト先頭に初期化 |
| 乱数シード設定 | Xorshift 用初期値 `0x92D68CA2` をセット |
| `kmbasic_data[2]` | `&kmbasic_var_size[0]` を格納(変数サイズ管理配列) |
| デバイス初期化 | ファイル・ビデオ・I/O・音楽をリセット |
| 静的変数リセット | `RESET_STATIC_VARS (32767)` を使って `lib_display`/`lib_spi` の static 変数をクリア |
| 例外ハンドラ登録 | `handle_exception(1)` で HardFault ハンドラを差し替え |
| CPU ベースライン保存 | `g_clock_hz_default`, `g_cpu_voltage_default` を保存 |
Sources: [run.c:116-140]()
### run_code() — コード実行コア
`run_code()` は生成済みバイナリ (`kmbasic_object`) を呼び出す前に、BASICランタイムが前提とする **専用レジスタレイアウト** を構築します。
```c
// run.c — 専用レジスタの設定部分
asm("ldr r5,=kmbasic_variables"); // R5: 変数配列へのポインタ
asm("ldr r6,=g_r6_array"); // R6: 引数配列へのポインタ
asm("ldr r7,=kmbasic_data"); // R7: ランタイムデータ配列
asm("ldr r0,=kmbasic_library");
asm("mov r8,r0"); // R8: ライブラリディスパッチャ
asm("mov r0,sp");
asm("str r0,[r7,#0]"); // kmbasic_data[0] に SP を保存
asm("sub sp,#508");
asm("sub sp,#508"); // ライブラリ用スタック 1016 バイト確保
asm("ldr r1,=kmbasic_object+1");
asm("bx r1"); // kmbasic_object へジャンプ
```
呼び出しの前後で R0–R12 全レジスタをプッシュ/ポップし、C ランタイムの状態を保護します。
#### ARM レジスタ割り当て表
| レジスタ | 値 / 役割 |
|---------|-----------|
| R5 | `&kmbasic_variables[0]` — BASIC 変数値配列 |
| R6 | `g_r6_array` — 引数配列(R6[0]=オブジェクトポインタ, R6[1]=前の R6, R6[2]=引数数, R6[3+]=各引数) |
| R7 | `&kmbasic_data[0]` — ランタイムデータ配列(下表参照) |
| R8 | `kmbasic_library` 関数ポインタ |
#### kmbasic_data[] インデックス
| インデックス | 内容 |
|-------------|------|
| `[0]` | プログラム終了時に復元する SP 値 |
| `[1]` | プログラム終了時の復帰アドレス |
| `[2]` | `&kmbasic_var_size[0]` |
| `[3]` | ライブラリ関数呼び出し直前の PC(エラー表示でのライン特定に使用) |
| `[4]` | エラーハンドリング用 |
Sources: [run.c:18-57](), [compiler.c:18-31]()
### post_run() — 実行後クリーンアップ
```c
void post_run(void){
lib_system(200,1,0); // ビデオ出力を再開
reset_memory();
close_all_files();
io_init(); lib_keys(63,0,0);
timer_init(); stop_music(); stopPCG();
if (!g_active_usb_keyboard) stop_core1();
g_interrupt_code=0;
cancel_all_interrupts();
handle_exception(0); // 元のHardFaultハンドラに戻す
post_run_wifi();
// CPU クロック・電圧を実行前の値に復元
...
}
```
CPU 周波数と電圧を `g_clock_hz_default` / `g_cpu_voltage_default` と比較しながら安全に復元します。上げた場合と下げた場合で復元順を変えることで、不正な電圧・周波数の組み合わせを回避しています。
Sources: [run.c:142-183]()
### call_interrupt_function() と machikania_snprintf()
`call_interrupt_function(void* r0)` は割り込みコールバック用のラッパーで、`run_code()` と同様のレジスタセットアップを行いつつ `g_interrupt_code` フラグを保存・復元します。
`machikania_snprintf()` は `snprintf` の呼び出し前にスタックポインタをライブラリ専用スタック領域 (`kmbasic_data[0]`) に切り替え、割り込みを無効化してから実行します。これは `snprintf` が大きなスタックを消費するためのリエントラント対策です。
Sources: [run.c:63-110](), [run.c:199-215]()
---
## library.c — ライブラリディスパッチテーブル
### 二段階のディスパッチ構造
```mermaid
flowchart TD
A["kmbasic_library(r0,r1,r2,r3)"] --> B{"r3 < 128?"}
B -- "Yes (Quick Library)" --> C["lib_list1[r3](r0,r1,r2)"]
B -- "No (Statement Library)" --> D["statement_library(r0,r1,r2,r3)"]
D --> E["lib_list2[r3-128](r0,r1,r2)"]
E --> F["check_break() → lib_end() if Ctrl-Z"]
C --> G["戻り値 r0"]
F --> G
```
`kmbasic_library()` はエントリ時に LR を `kmbasic_data[3]` に格納します。これにより例外・エラー発生時にどのライブラリ呼び出し中に問題が起きたかを特定できます。
```c
int kmbasic_library(int r0, int r1, int r2, int r3){
asm("mov r4,lr");
asm("str r4,[r7,#12]"); // kmbasic_data[3] = LR
int (*f)(int r0, int r1, int r2) = lib_list1[r3];
if (r3<128) return f(r0,r1,r2);
else return statement_library(r0,r1,r2,r3);
}
```
Sources: [library.c:878-887]()
### クイックライブラリ (lib_list1, 0–32)
Break チェックやガベージコレクションフラグの設定を行わない高速パス。演算・文字列・数値変換など頻度の高い処理が入ります。
| インデックス | 定数 | 関数 | 役割 |
|-------------|------|------|------|
| 0 | `LIB_CALC` | `lib_calc` | 整数除算・剰余 |
| 1 | `LIB_CALC_FLOAT` | `lib_calc_float` | 浮動小数点演算 |
| 2 | `LIB_HEX` | `lib_hex` | 16進数文字列変換 |
| 3 | `LIB_ADD_STRING` | `lib_add_string` | 文字列連結 |
| 8 | `LIB_RND` | `lib_rnd` | 乱数(Xorshift) |
| 11 | `LIB_MATH` | `lib_math` | 数学関数群 (sin/cos/sqrt等) |
| 17 | `LIB_READ` | `lib_read` | DATA 読み出し |
| 22 | `LIB_DISPLAY_FUNCTION` | `lib_display` | グラフィック関数 |
| 23 | `LIB_INKEY` | `lib_inkey` | キー入力(非ブロッキング) |
Sources: [library.c:818-851](), [compiler.h:70-105]()
### ステートメントライブラリ (lib_list2, 128–157)
`statement_library()` 経由で呼ばれ、実行後に Break キー (`Ctrl-Z`) をチェックします。BASIC 文(PRINT, DIM, FOR等)および I/O・通信系が入ります。
| インデックス | 定数 | 関数 | 役割 |
|-------------|------|------|------|
| 129 | `LIB_PRINT` | `lib_print` | PRINT 文 |
| 131 | `LIB_END` | `lib_end` | プログラム終了・SP復元 |
| 132 | `LIB_LINE_NUM` | `lib_line_num` | GOTO/GOSUB ライン解決 |
| 133 | `LIB_DIM` | `lib_dim` | 配列確保 |
| 137 | `LIB_DISPLAY` | `lib_display` | グラフィック描画文 |
| 138 | `LIB_WAIT` | `lib_wait` | フレーム同期ウェイト |
| 139 | `LIB_SYSTEM` | `lib_system` | システム情報・設定 |
| 145 | `LIB_INTERRUPT` | `lib_interrupt` | 割り込み設定 |
| 152 | `LIB_MUSIC` | `lib_music` | 音楽・効果音 |
| 156 | `LIB_WIFI` | `lib_wifi` | TCP/IP通信 |
Sources: [library.c:853-876](), [compiler.h:109-133]()
### lib_end() — プログラム終了
`lib_end()` は例外なく最も重要な内部関数です。`kmbasic_data[0]` に保存した元の SP と `kmbasic_data[1]` の復帰アドレスを使い、`run_code()` の呼び出し元へ直接ジャンプして戻ります。
```c
int lib_end(int r0, int r1, int r2){
asm("ldr r7,=kmbasic_data");
asm("ldr r0, [r7, #0]"); // kmbasic_data[0] = 保存済みSP
asm("mov sp, r0");
asm("ldr r0, [r7, #4]"); // kmbasic_data[1] = 復帰アドレス
asm("bx r0");
return r0;
}
```
Sources: [library.c:205-213]()
### use_lib_stack マクロ
`lib_print`, `lib_fprint`, `lib_float_str`, `lib_sprintf` はスタックを多く消費する `snprintf` / `machikania_snprintf` を内部で呼ぶため、`use_lib_stack` マクロで実際の処理関数を呼び出します。これにより呼び出し規約を保ちつつ安全に処理できます。
```c
#define use_lib_stack(funcname) \
asm("push {r4,lr}"); \
asm("bl "funcname); \
asm("pop {r4,pc}")
```
Sources: [library.c:29-36]()
---
## api.c / api.h — 表示プリミティブの二重出力レイヤ
### アーキテクチャ
phyllosoma は LCD/ビデオ出力と USB シリアル出力を同時にサポートします。api.c はこの二重出力を実現するラッパー層です。
```text
BASIC コード
│
▼
_printchar() / _printstr() / _printnum() etc. [api.c]
│ │
▼ ▼
printchar() / printstr() putchar() / printf()
[interface/graphlib — LCD] [pico/stdlib — USB Serial]
│ │
g_disable_lcd_out で制御 g_disable_printf で制御
```
Sources: [api.c:37-50](), [api.h:35-56]()
### api.h のマクロリダイレクト
`api.h` では `graphlib.h` が宣言する `printchar`・`printstr` などを `#undef` してから、api.c が提供するアンダースコア付き実装にマクロで置き換えます。
```c
// api.h
#undef printchar
#undef printstr
#define printchar(a) _printchar(a)
#define printstr(a) _printstr(a)
```
これにより、コードベース全体で `printchar(c)` と書くだけで LCD と Serial の両方に出力されます。
Sources: [api.h:35-56]()
### 主要関数一覧
| 関数 | シグネチャ | 説明 |
|------|-----------|------|
| `_printchar` | `(unsigned char c)` | 1 文字出力。BS(0x08)/CR(0x0d)/LF(0x0a) を適切に処理し `g_cursor` を更新 |
| `_printstr` | `(unsigned char *s)` | NULL 終端文字列出力 |
| `_printnum` | `(unsigned int n)` | 符号なし整数を10進表示 |
| `_printnum2` | `(unsigned int n, unsigned char e)` | 幅 `e` 桁の右詰め整数表示 |
| `_cls` | `(void)` | 画面クリア(LCD + ANSI エスケープ 24 行分スクロール) |
| `_setcursor` | `(x, y, c)` | カーソル移動(LCD + ANSI ESC シーケンス) |
| `printint` | `(int i)` | 符号付き整数出力(`_printnum` のラッパー) |
| `printhex32` | `(unsigned int i)` | 32 bit 16進表示(`printhex16` × 2) |
Sources: [api.c:30-145]()
### カーソル位置管理
`g_cursor` は 80 桁幅の仮想端末上での絶対位置(列 + 行×80)として管理されます。`_setcursor()` は現在位置から目標位置への差を計算し、ANSI カーソル移動エスケープシーケンス(`ESC[A/B/C/D`)を発行します。
---
## error.c — エラー停止フロー
### エラーコードテーブル
`g_error_text[]` にはコンパイル時・実行時エラー 27 種のメッセージが格納されており、エラーコード値(負整数)の絶対値をインデックスとして参照します。
| エラーコード | メッセージ |
|------------|-----------|
| -1 | `Syntax error` |
| -9 | `Out of memory` |
| -11 | `DATA not found` |
| -24 | `Exception` |
| -26 | `Unsupported clock frequency` |
Sources: [error.c:12-38]()
### stop_with_error() — 実行時エラーの停止フロー
```mermaid
stateDiagram-v2
[*] --> 実行時エラー発生
実行時エラー発生 --> メッセージ表示: stop_with_error(e)
メッセージ表示 --> ライン番号検索: line_number_from_address(kmbasic_data[3])
ライン番号検索 --> ライン番号表示: 見つかった場合
ライン番号検索 --> アドレス表示: 見つからない場合
ライン番号表示 --> プログラム終了: lib_end(0,0,0)
アドレス表示 --> プログラム終了: lib_end(0,0,0)
プログラム終了 --> [*]
```
`line_number_from_address()` は `cmpdata` テーブル(`CMPDATA_LINENUM` 型エントリ)を走査し、`kmbasic_data[3]`(直前のライブラリ呼び出しの PC)に対応する BASIC ライン番号を探します。
```c
void stop_with_error(int e){
e=-e;
printstr(g_error_text[e]);
e=line_number_from_address(kmbasic_data[3]);
if (e<0) { printstr(" at "); printhex32(kmbasic_data[3]); }
else { printstr(" in line "); printint(e); }
lib_end(0,0,0);
}
```
Sources: [error.c:88-115]()
### コンパイル時エラー — throw_error() / show_error()
コンパイル時エラーは `throw_error(e, line, file)` でファイル名・行番号を保存し、`show_error(e, pos)` でソース位置とともに表示します。`MACHIKANIA_DEBUG_MODE` 定義時にはソースファイル名と行番号も出力されます。
---
## exception.c — ARM ハードウェア例外ハンドラ
### ハンドラのフロー
```mermaid
sequenceDiagram
participant CPU as ARM CPU (HardFault)
participant EH as exception_handler()
participant EHM as exception_handler_main()
participant SW as stop_with_error()
CPU->>EH: HardFault 割り込み
EH->>EH: MOV r0,sp / LDR r1,ICSR
EH->>EHM: B exception_handler_main
EHM->>EHM: EXCEPTION_MODE_RESTART? → software_reset()
EHM->>EHM: レジスタダンプ表示 (R0-R3,R12,LR,PC,SP,PSR,ICSR)
EHM->>EHM: スクリーンショット/ダンプ保存 (オプション)
EHM->>EHM: sp[0]=ERROR_EXCEPTION, sp[6]=stop_with_error
Note over EHM: 例外フレームの R0,PC を書き換え
EHM-->>CPU: 例外リターン
CPU->>SW: stop_with_error(ERROR_EXCEPTION) が呼ばれる
SW-->>CPU: lib_end() でプログラム終了
```
### exception_handler() — アセンブリエントリポイント
```c
void exception_handler(void){
asm("mov r0,sp");
asm("ldr r1,=0xE000ED04"); // ICSR (Interrupt Control and State Register)
asm("ldr r1,[r1]");
asm("b exception_handler_main");
}
```
ARM の HardFault 例外発生時、CPU はスタックに R0-R3, R12, LR, PC, PSR を自動的に積みます。この関数はその SP をそのまま `r0` として `exception_handler_main()` に渡します。
Sources: [exception.c:97-104]()
### exception_handler_main() — 本体処理
スタックフレームポインタ `sp` と ICSR の値を受け取り、以下を実行します。
1. `EXCEPTION_MODE_RESTART` フラグが立っている場合は即座に `software_reset()`
2. レジスタ情報(PSR, ICSR, SP, R0-R3, R12, LR, PC)を表示
3. フォルト発生地点前後の命令コードを 8 ハーフワード分表示
4. `EXCEPTION_MODE_SCREENSHOT` が設定されていれば TVRAM を SD カードに保存
5. `EXCEPTION_MODE_DUMP` が設定されていれば RAM (0x20000000〜) をファイルにダンプ
6. **スタックフレームの R0 と PC を書き換え**: `sp[0]=ERROR_EXCEPTION`, `sp[6]=stop_with_error`
ステップ 6 の手法が巧妙です。例外リターン後に CPU が復元するレジスタを改ざんすることで、フォルトの発生アドレスには戻らず `stop_with_error(ERROR_EXCEPTION)` が呼ばれます。
```c
// exception.c
sp[0]=(int)ERROR_EXCEPTION; // R0 = エラーコード
sp[6]=(int)stop_with_error; // PC = stop_with_error 関数
```
Sources: [exception.c:64-115]()
### ini_file_exception() — 動作設定
`.INI` ファイルの解析時に呼ばれ、例外発生時の挙動を設定します。
| キーワード | 設定内容 |
|-----------|---------|
| `EXCRESET` | 例外発生時に即座に再起動 |
| `EXCSCREENSHOT=ファイル名` | TVRAM の内容をファイルに保存 |
| `EXCDUMP=ファイル名` | RAM 0x20000000〜0x20042000 をファイルにダンプ |
Sources: [exception.c:16-50]()
### handle_exception() — ハンドラの差し替え
```c
void handle_exception(int set){
static exception_handler_t org_handler=0;
if (!org_handler)
org_handler=exception_get_vtable_handler(HARDFAULT_EXCEPTION);
if (set)
exception_set_exclusive_handler(HARDFAULT_EXCEPTION,exception_handler);
else
exception_restore_handler(HARDFAULT_EXCEPTION,org_handler);
}
```
`pre_run()` で `handle_exception(1)` を呼び BASIC 実行中専用のハンドラに差し替え、`post_run()` で `handle_exception(0)` を呼んで元のハンドラに戻します。これにより BASIC プログラムの外(メニュー画面など)でのフォルトには影響を与えません。
Sources: [exception.c:106-119]()
---
## まとめ
phyllosoma の実行エンジンは、BASIC プログラムの実行開始から終了までを `pre_run → run_code → post_run` の 3 段階ライフサイクルで管理します。`run_code()` が設定する ARM 専用レジスタレイアウト(R5=変数配列, R6=引数配列, R7=ランタイムデータ, R8=ライブラリディスパッチャ)は、コンパイラが生成するすべての Thumb-2 コードが前提とする固定プロトコルです。`kmbasic_library()` は番号ベースのディスパッチで 158 種のライブラリ関数を 2 段階(クイック/ステートメント)に分類して提供し、ステートメント層では Break キーチェックを自動実行します。`api.c` の二重出力レイヤは LCD とシリアルの同時出力を透過的に行い、`error.c` と `exception.c` は通常エラーとハードウェア例外の両方を `stop_with_error()` → `lib_end()` の共通フローに収束させます。
---
## 05. メモリ管理・変数・クラスシステム
> memory.c のブロックアロケータ(alloc_memory / garbage_collection)、variable.c の変数番号管理、value.c の型解決(整数・浮動小数点・文字列)、class.c のオブジェクト生成・フィールドアクセス・静的メンバ管理、globalvars.c のグローバル状態を解説します。
- Page Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/05-page-5.md
- Generated: 2026-05-27T07:13:45.320Z
### Source Files
- `memory.c`
- `variable.c`
- `class.c`
- `value.c`
- `globalvars.c`
- `integer.c`
- `float.c`
<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:
- [memory.c](memory.c)
- [variable.c](variable.c)
- [class.c](class.c)
- [value.c](value.c)
- [globalvars.c](globalvars.c)
- [integer.c](integer.c)
- [float.c](float.c)
- [compiler.h](compiler.h)
</details>
# メモリ管理・変数・クラスシステム
Phyllosoma(KM-BASIC for ARM)は、ARM Cortex-M(RP2040 など)の制約された環境向けに設計されたBASICコンパイラです。本ページでは、実行時メモリの確保と解放を担う `memory.c` のブロックアロケータ、コンパイル時の変数番号管理を行う `variable.c`、整数・浮動小数点・文字列の型解決を行う `value.c` / `integer.c` / `float.c`、クラスのオブジェクト生成・フィールドアクセス・静的メンバ管理を実装する `class.c`、そしてすべてのモジュールが共有するグローバル状態を保持する `globalvars.c` を解説します。
これらのモジュールは密接に連携しており、コンパイル時に ARM Thumb-2 マシンコードを直接生成しながら、実行時に必要なデータ構造を管理します。メモリアロケータはヒープ領域を直接管理し、標準の `malloc/free` を一切使用しないことで、予測可能なメモリレイアウトと組み込みデバイス向けの小フットプリントを実現しています。
---
## グローバル状態の概観(globalvars.c)
システム全体で共有される主要なグローバル変数は `globalvars.c` で定義されています。
| 変数 | 型 | 役割 |
|---|---|---|
| `kmbasic_variables[ALLOC_BLOCK_NUM]` | `int[256]` | 各ブロックのヒープポインタ |
| `kmbasic_var_size[ALLOC_BLOCK_NUM]` | `unsigned short[256]` | 各ブロックのサイズ(`int` 単位) |
| `kmbasic_object[]` | `unsigned short[]` | コンパイル済みコード領域 |
| `g_next_varnum` | `int` | 次に割り当てる変数番号(テンポラリブロック境界も兼ねる) |
| `g_class_id_list` | `unsigned short*` | 登録済みクラスIDの配列 |
| `g_class_list` | `int*` | 各クラスのクラス構造へのポインタ配列 |
| `g_empty_object_list` | `int*` | 各クラスの空オブジェクトへのポインタ配列 |
| `g_garbage_collection` | `char` | 次回 `alloc_memory` 呼び出し時に一時ブロックを解放するフラグ |
| `g_constant_value_flag` | `char` | コンパイル時定数最適化フラグ |
| `g_scratch_int` / `g_scratch_float` | `volatile int*/float*` | 同一領域を複数の型で参照するスクラッチバッファ |
`kmbasic_variables[]` と `kmbasic_var_size[]` は、ファイルバッファ・コンパイルバッファとしても共有使用されます。
Sources: [globalvars.c:20-70]()
---
## メモリ管理(memory.c)
### ヒープ領域の構造
ヒープ領域は `HEAP_BEGIN`(コンパイル済みコード末尾の直後、`object[1]` のアドレスを4バイト整合)から `HEAP_END`(`g_objmax[]` アドレスまで)に広がります。
```text
┌──────────────────────┬─────────────────────────────────────────┬──────────────────┐
│ kmbasic_object[] │ HEAP(動的割り当て領域) │ g_objmax[] │
│ (コンパイル済みコード) │ HEAP_BEGIN ←──── 確保済みブロック ────→ HEAP_END │ │
└──────────────────────┴─────────────────────────────────────────┴──────────────────┘
```
`init_memory` でヒープ境界を設定し、`reset_memory` では `g_heap_begin = g_heap_end` としてヒープ全体を使用済みとします(コンパイル再実行時の初期化に使用)。
Sources: [memory.c:44-70]()
### ブロックインデックスの3分割
`kmbasic_variables[]` の 256 エントリは、役割ごとに3つの領域に分割されます。
```text
インデックス 0 25 26 g_next_varnum g_next_varnum+10 255
├─────────────┼──────────────┼──────────────┼───────────────────┤
│ A-Z 変数 │ 名前付き変数 │ テンポラリ │ パーマネント │
│ (0-25) │ (26 〜) │ ブロック(10) │ ブロック │
└─────────────┴──────────────┴──────────────┴───────────────────┘
ALLOC_TEMP_BLOCK = g_next_varnum
ALLOC_PERM_BLOCK = g_next_varnum + TEMPVAR_NUMBER(=10)
```
- **変数ブロック**(0 〜 `ALLOC_TEMP_BLOCK-1`):A〜Zおよびユーザー定義変数。
- **テンポラリブロック**(`ALLOC_TEMP_BLOCK` 〜 `+TEMPVAR_NUMBER-1`):文字列演算などで一時的に確保され、`g_garbage_collection` フラグが立った次回 `alloc_memory` 呼び出し時に一括解放。
- **パーマネントブロック**(`ALLOC_PERM_BLOCK` 〜 255):クラスオブジェクトやDIM配列などの長期確保に使用。明示的な `delete_memory` 呼び出しまで解放されない。
Sources: [memory.c:38-43](), [compiler.h:13-14]()
### `alloc_memory` の空き領域探索
```c
void* alloc_memory(int size, int var_num)
```
`var_num < 0` の場合はテンポラリブロックを自動割り当て、`0 <= var_num < 256` の場合は指定ブロックに割り当てます。空き領域の探索は以下の優先順で行います。
1. **削除リスト**(`g_deleted_pointer[]`)から適合サイズのブロックを再利用(クラスオブジェクト高速再確保のため)。
2. **既存ブロック末尾の直後**(ヒープの末端方向への線形拡張)。
3. **既存ブロック間の隙間**(全ブロックのペアを全数チェックして重複しない空間を探索)。
4. 上記すべてで失敗した場合は `stop_with_error(ERROR_OUT_OF_MEMORY)`。
Sources: [memory.c:88-175]()
### ガベージコレクションと削除リスト
`garbage_collection(data)` は、特定ポインタが格納されているテンポラリブロックのサイズを 0 にクリアすることで、個別の一時データを解放します。`g_garbage_collection` フラグによる一括解放とは別の仕組みです。
`delete_memory(data)` はブロックのポインタとサイズを `0` に設定し、削除済み領域を `g_deleted_pointer[]` / `g_deleted_size[]`(最大10エントリ)のリストに追加します。このリストは `alloc_memory` の最初のパスで参照され、適合サイズがあればその領域を即座に再利用します。
```c
// 削除リストが満杯の場合、末尾エントリと比較して大きければ置き換え
if (DELETE_LIST_SIZE <= g_deleted_num) {
if (g_deleted_size[DELETE_LIST_SIZE-1] < size) {
g_deleted_pointer[DELETE_LIST_SIZE-1] = data;
g_deleted_size[DELETE_LIST_SIZE-1] = size;
}
}
```
Sources: [memory.c:185-240](), [memory.c:243-270]()
### `var2permanent` による昇格
`var2permanent(var_num)` は、変数ブロックが指すヒープ領域を、パーマネントブロックにコピー(または既存パーマネントブロックを確認してソースのみクリア)します。オブジェクトフィールドに配列などを格納する際、`var2obj` の中から呼び出されます。
Sources: [memory.c:287-316]()
---
## 変数番号管理(variable.c)
### 変数番号の割り当て
`variable_init()` では `g_next_varnum = 26` に初期化します。インデックス 0〜25 はアルファベット一文字変数 A〜Z に固定割り当て済みです。
```c
void variable_init(void){
g_next_varnum = 26;
}
```
長い名前の変数は `get_new_varnum()` で 26 以上の番号を採番し、`CMPDATA_VARNAME` レコードに名前文字列・ハッシュ・番号を登録します。採番上限は `ALLOC_BLOCK_NUM - TEMPVAR_NUMBER`(= 246)です。
`get_var_number()` はソースから変数名を読み取り、一文字の場合は `source[0] - 'A'`、長い名前の場合は `cmpdata_nsearch_string_first(CMPDATA_VARNAME, ...)` で既存レコードを検索して番号を返します。
Sources: [variable.c:22-76]()
### ARM Thumbコード生成
`r0_to_variable(vn)` と `variable_to_r0(vn)` は、変数番号 `vn` に対応する ARM Thumb-2 命令を `object[]` バッファに直接書き込みます。
変数領域はレジスタ R5(実行時フレームポインタ)相対のメモリに配置されます。番号が小さい(`vn < 32`)場合は即値オフセットによるワード `str`/`ldr`、それ以上は R1 にオフセットを設定してのインデックスストア/ロードを使います。
```c
// vn < 32 の場合(例: str r0, [r5, #vn*4])
(object++)[0] = 0x6028 | (vn << 6);
// vn < 256 の場合(R1にオフセットをセットしてからストア)
(object++)[0] = 0x5068; // str r0, [r5, r1]
```
Sources: [variable.c:86-130]()
---
## 型解決(value.c / integer.c / float.c)
### 値モードと型ディスパッチ
Phyllosoma のコンパイラは3つの値モードを定義します。
| モード定数 | 値 | 意味 |
|---|---|---|
| `VAR_MODE_INTEGER` | 0 | 整数(R0 に 32 bit 整数) |
| `VAR_MODE_STRING` | 1 | 文字列(R0 に文字列ポインタ) |
| `VAR_MODE_FLOAT` | 2 | 浮動小数点(R0 に IEEE754 ビット表現) |
Sources: [compiler.h:230-232]()
### 演算子優先度パーサー(value.c)
`get_value(vmode)` は再帰的な演算子優先度解析(Pratt parser 相当)を行うエントリポイントです。
```
get_value(vmode)
└─ get_value_sub(priority(OP_VOID), vmode)
├─ get_simple_value(vmode) // 単項値・リテラル・変数参照
└─ while(operator exists):
if current_priority >= operator_priority → return
value_push_r0() // R0 をスタックに退避 (str r0, [sp, #n])
get_value_sub(operator_priority, vmode) // 右辺再帰
value_pop_r1() // スタックから R1 に復元 (ldr r1, [sp, #n])
calculation(op, vmode) // 演算結果を R0 に
```
スタック使用量は `g_sdepth` / `g_maxsdepth` で追跡し、ネストが生じた場合のみ `sub sp, #n` / `add sp, #n` のコードが後から確定します。
Sources: [value.c:98-170]()
### 整数値の解析(integer.c)
`get_simple_integer()` は以下の順で解析を試みます。
1. `+` / `-` の符号処理(`-` は `negs r0, r0` を生成)
2. `$XXXX` または `0xXXXX` 形式の16進リテラル
3. `0〜9` で始まる10進リテラル
4. `A〜Z` で始まる識別子:
- クラス名と判断できる場合は `static_method_or_property()` へ委譲
- そうでなければ `get_var_number()` → `variable_to_r0(vn)` でロード
- `(` が続く場合は配列インデックス(`get_dim_value()`)
- `.` が続く場合はオブジェクトフィールド(`method_or_property(0)`)
5. `&` 演算子:変数のアドレス取得(`adds r0, r0, r5`)
6. それ以外は `integer_functions()` でビルトイン関数を検索
Sources: [integer.c:148-235]()
### 浮動小数点値の解析(float.c)
`get_simple_float()` は `strtof` で浮動小数点リテラルをパースし、その IEEE754 ビット表現を `g_scratch_float[0]` 経由で `g_scratch_int[0]` に取得してから `set_value_in_register(0, ...)` でコードを生成します。変数参照時はサフィックス `#` の存在を確認します。
```c
f = strtof((const char*)&source[0], (char**)&err);
g_scratch_float[0] = f; // float として書き込み
g_constant_float = f;
return set_value_in_register(0, g_scratch_int[0]); // int ビット列として ldr
```
Sources: [float.c:54-75]()
---
## クラスシステム(class.c)
### コンパイル時データ構造(CMPDATA)
クラスに関するコンパイル情報はすべて `CMPDATA` レコードとして管理されます。
```text
CMPDATA_CLASSNAME ─→ クラス名文字列 → クラスID (unsigned short)
CMPDATA_FIELDNAME ─→ フィールド/メソッド名文字列 → フィールドID
CMPDATA_CLASS ─→ クラスID → フィールド/メソッド情報配列
CMPDATA_CLASS_ADDRESS ─→ クラスID → (クラス構造アドレス, 空オブジェクトアドレス)
CMPDATA_METHOD ─→ メソッドID → メソッドコードアドレス
CMPDATA_STATIC ─→ クラスID → (フィールドID, 変数番号)
```
フィールド/メソッド情報の各エントリは 32bit で次のフラグを組み合わせます。
| フラグ定数 | ビット | 意味 |
|---|---|---|
| `CLASS_METHOD` | 0x00010000 | メソッド |
| `CLASS_FIELD` | 0x00020000 | フィールド |
| `CLASS_PUBLIC` | 0x00100000 | パブリックアクセス可 |
| `CLASS_STATIC` | 0x00200000 | 静的メンバ |
下位 16bit はフィールドID または変数番号、上位 8bit(`>>24`)はフィールドに対応する変数番号です。
Sources: [class.c:1-52](), [compiler.h:263-266]()
### オブジェクトのメモリ構造
実行時のオブジェクトは `lib_new` が確保したヒープ連続領域に格納されます。
```text
object[0] = クラス構造へのポインタ(class_structure*)
object[1] = フィールド値 または メソッドポインタ
object[2] = フィールド値 または メソッドポインタ
...
object[n] = フィールド値 または メソッドポインタ
```
クラス構造(`class_structure[]`)も同様のレイアウトで、`class_structure[0]` にフィールド/メソッド数、`class_structure[1..n]` にフラグ+IDのエントリが並びます。
Sources: [class.c:55-58]()
### オブジェクト生成(lib_new)
コンパイル時、`NEW(ClassName)` 式は `LIB_NEW` ライブラリ呼び出しにコンパイルされます。実行時は以下の手順でオブジェクトを生成します。
```c
int lib_new(int r0, int r1, int r2){
unsigned short class_id = r0;
// 1. g_class_id_list からクラスIDを検索
// 2. g_class_list[i] からクラス構造を取得
// 3. g_empty_object_list[i] からテンプレートを取得
// 4. get_permanent_block_number() でパーマネントブロックを確保
// 5. calloc_memory(num+1, i) でゼロ初期化済みメモリを確保
// 6. 空オブジェクトの内容をコピー
return (int)object; // オブジェクトポインタを R0 で返す
}
```
確保した後、`INIT` メソッドが存在する場合はコンストラクタとして自動呼び出しされます。
Sources: [class.c:154-182]()
### フィールドアクセス(lib_resolve_field_address)
フィールド読み書きは `LIB_OBJ_FIELD` ライブラリ経由で実行時に解決されます。
```c
int lib_resolve_field_address(int r0, int r1, int r2){
// r0: オブジェクトポインタ, r1: フィールドID
unsigned int* class_structure = (unsigned int*)object[0];
int num = class_structure[0];
for(i=1; 1<=num; i++){
if (field_id == (class_structure[i] & 0xffff)) break;
}
if (!(class_structure[i] & CLASS_PUBLIC)) stop_with_error(ERROR_NOT_PUBLIC);
return (int)(&object[i]); // フィールドのアドレスを返す
}
```
コンパイル側では `get_pointer_to_field()` が `set_value_in_register(1, fid)` でフィールドIDをR1に設定し、`call_lib_code(LIB_OBJ_FIELD)` でアドレスを取得した後、`ldr r0, [r0, #0]` で値を読み出します。
Sources: [class.c:109-131]()
### メソッド呼び出しとフレーム切り替え
```mermaid
sequenceDiagram
participant Caller as 呼び出し元コード
participant PreMethod as lib_pre_method
participant Method as メソッド本体
participant PostMethod as lib_post_method
Caller->>PreMethod: R1=R6(現フレーム)
PreMethod->>PreMethod: caller オブジェクトのフィールドをvar2obj()で保存
PreMethod->>PreMethod: 新オブジェクトのフィールドをobj2var()で変数に展開
PreMethod-->>Method: R0=戻り値そのまま
Method->>Method: 変数(R5相対)でフィールドを読み書き
Method->>PostMethod: R1=R6
PostMethod->>PostMethod: 現オブジェクトのフィールドをvar2obj()で保存
PostMethod->>PostMethod: caller オブジェクトのフィールドをobj2var()で復元
PostMethod-->>Caller: R0=メソッド戻り値
```
`obj2var` はクラス構造のフィールドエントリを走査し、`class_structure[i] >> 24` に格納された変数番号に対応する `kmbasic_variables[var_num]` を更新します。`var2obj` はその逆です。これにより、メソッド内部では通常の変数アクセス(R5相対)と同じコードでフィールドを操作できます。
Sources: [class.c:184-220]()
### 静的メンバ管理
静的フィールドは変数番号で管理され、オブジェクトインスタンスとは独立して存在します。
- コンパイル時:`register_class_static_field(var_number)` が `CMPDATA_STATIC` レコードに `(クラスID, フィールドID<<16 | 変数番号)` を登録。
- アクセス時(`ClassName::Field`):`static_method_or_property()` → `resolve_var_number_from_id()` で変数番号を取得 → `variable_to_r0(vn)` で通常の変数として読み書き。
- 静的フィールドは `CMPDATA_CLASS` エントリの `CLASS_STATIC` フラグが立っており、`post_compilling_a_class` で生成するクラス構造本体には含まれません(インスタンスのメモリレイアウトから除外)。
Sources: [class.c:331-370](), [class.c:65-75]()
---
## データフローの全体像
```text
ソースコード (.BAS)
│
▼ compile_file()
┌──────────────────────────────────────────────────┐
│ コンパイラ │
│ variable.c ─→ 変数番号採番 (g_next_varnum) │
│ value.c ─→ 演算子優先度解析 │
│ integer.c ─→ 整数リテラル・変数参照コード生成 │
│ float.c ─→ 浮動小数点リテラル・変数参照 │
│ class.c ─→ CMPDATA登録・クラス構造生成 │
│ memory.c ─→ alloc_memory でコンパイル時確保 │
└──────────────────────────────────────────────────┘
│ ARM Thumb-2 コード → kmbasic_object[]
▼ run_code()
┌──────────────────────────────────────────────────┐
│ ランタイム │
│ lib_new() → オブジェクト生成 (heap) │
│ lib_pre/post_method → obj2var / var2obj 切替 │
│ lib_resolve_field_address → フィールドアドレス解決 │
│ garbage_collection → 一時ブロック解放 │
│ delete_memory → パーマネントブロック解放 │
└──────────────────────────────────────────────────┘
```
---
## まとめ
Phyllosoma のメモリ・変数・クラスシステムは以下の設計原則に基づいています。
- **単一の `kmbasic_variables[]` 配列が変数ブロック・テンポラリブロック・パーマネントブロックを統合管理**する。`g_next_varnum` がコンパイル進行に伴って動的に境界を移動させる設計は、変数番号割り当てとメモリ管理の両方の役割を1つのインデックスで処理する点が特徴的です。
- **コンパイル時に ARM Thumb-2 コードを直接生成**することで、変数アクセスやフィールド解決のオーバーヘッドを最小化しています。`variable.c` の `r0_to_variable` / `variable_to_r0` はその核心です。
- **クラスシステムは CMPDATA レコードのコンパイル時メタデータと、ヒープ上のランタイム構造(クラス構造・空オブジェクト)の2層**で動作する。メソッド呼び出し時の `obj2var` / `var2obj` によるフレーム切り替えにより、メソッド内部のコードは通常変数と同一のアドレッシングでフィールドにアクセスできます。
- **ガベージコレクションは明示的なフラグ駆動**で、一時ブロックはライブラリ呼び出し後に `g_garbage_collection` フラグが立った次回のアロケーション時に一括解放されます。パーマネントブロックは `DELETE` 文による明示的な `delete_memory` 呼び出しで解放されます。
Sources: [memory.c:38-70](), [variable.c:22-24](), [class.c:184-220](), [compiler.h:263-266]()
---
## 06. ディスプレイインタフェース — 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) の構造を解説します。
- Page Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/06-lcd-ntsc.md
- Generated: 2026-05-27T07:15:14.507Z
### 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]()
---
## 07. I/Oとペリフェラル制御 — GPIO・SPI・I2C・音楽・タイマー・RTC
> io.c の lib_gpio / lib_spi / lib_i2c / lib_serial / lib_pwm / lib_analog 実装、timer.c の割り込み駆動タイマー (INTERRUPT_TIMER / INTERRUPT_DRAWCOUNT 等)、music.c の PWM 音声合成とウェーブ再生、rtc.c のリアルタイムクロック API、MACHIKAP.INI による I/O 初期化設定を解説します。
- Page Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/07-i-o-gpio-spi-i2c-rtc.md
- Generated: 2026-05-27T07:14:48.275Z
### Source Files
- `io.c`
- `io.h`
- `timer.c`
- `music.c`
- `rtc.c`
- `core1.c`
<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:
- [io.c](io.c)
- [io.h](io.h)
- [timer.c](timer.c)
- [music.c](music.c)
- [rtc.c](rtc.c)
- [core1.c](core1.c)
- [compiler.h](compiler.h)
- [PicoCalc/MACHIKAP.INI](PicoCalc/MACHIKAP.INI)
- [scripts/MACHIKAP.INI](scripts/MACHIKAP.INI)
- [config/pico_st7789.h](config/pico_st7789.h)
</details>
# I/Oとペリフェラル制御 — GPIO・SPI・I2C・音楽・タイマー・RTC
Phyllosoma(MachiKania Type P / Type PU)は Raspberry Pi Pico / Pico W / RP2350 上で動作する KM-BASIC インタプリタであり、RP2040/RP2350 のハードウェアペリフェラルをBASICコードから直接扱えるランタイムを備えています。本ページでは、GPIO・SPI・I2C・シリアル(UART)・PWM・ADC を束ねる `io.c`、60Hz 割り込み駆動のタイマーシステムを実装する `timer.c`、PWM音声合成とWAVファイル再生を提供する `music.c`、リアルタイムクロックAPIの `rtc.c`、Core1 の非同期コールバック管理を行う `core1.c`、そして全設定の起点となる `MACHIKAP.INI` の各実装を解説します。
これらのモジュールはすべてコンパイル時に決まる `LIB_*` 番号でディスパッチされ、コンパイラが生成した ARM Thumb-2 コードから呼び出されます。I/O 初期化パラメータは `MACHIKAP.INI` に記述された INI ファイルパーサが起動時に読み取り、グローバル変数を上書きすることで動作ピンを柔軟に切り替えられるようになっています。
---
## モジュール構成の概観
```text
┌─────────────────────────────────────────────────────────────────┐
│ MACHIKAP.INI (起動時設定) │
│ SPIMISO/MOSI/CLK UARTTX/RX I2CSDA/SCL PWM4-9 RTCFILE │
└────────────┬────────────────────────────────────────────────────┘
│ ini_file_io() / ini_file_rtc()
▼
┌────────────────────────────────────────┐ ┌──────────────────┐
│ io.c │ │ timer.c │
│ lib_gpio lib_spi lib_i2c lib_serial│ │ lib_timer │
│ lib_pwm lib_analog │ │ lib_interrupt │
│ io_init() │ │ timer_init() │
└───────────────┬────────────────────────┘ └────────┬─────────┘
│ │ 60Hz repeating
│ ┌──────────▼─────────┐
│ │ repeating_drawcount │
│ │ _callback() │
│ │ → musicint() │
│ │ → interrupt vector │
│ └─────────────────────┘
│
┌──────────┼───────────┐
▼ ▼ ▼
┌─────────┐ ┌────────┐ ┌────────┐
│ music.c │ │ rtc.c │ │core1.c │
│ PWM音声 │ │aon_tmr │ │Core1 │
│ WAV再生 │ │ISO8601 │ │コール │
│ │ │FATタイム│ │バック │
└─────────┘ └────────┘ └────────┘
```
---
## io.c — ペリフェラル実装
### GPIO (`lib_gpio`)
`lib_gpio` は `LIB_GPIO_IN`・`LIB_GPIO_OUT`・バルク8/16ビットバリアントを `r2` のオプション番号で選択します。ポート番号から実際の GPIO ピン番号への変換は `gpio_table[]` 静的配列で行い、上位8ビットと下位8ビットで物理ピン配置が異なる場合は `io_gpio_outh_conversion` / `io_gpio_inh_conversion` マクロがビット並び替えを吸収します。
```c
// io.h — 高位 8 ビット変換マクロ (デフォルト定義)
#define io_gpio_outh_conversion(a) (\
(((a)&0x03)<<8) |\
(((a)&0x1c)<<18) |\
(((a)&0xe0)<<21) \
)
```
ポートの方向(入力/出力)は `g_gpio_in` と `g_gpio_out` の 2 つのビットマスクで管理し、既に初期化済みのピンは `gpio_init` を省略します。SPI・I2C・UART に割り当てられたピンは対応する `g_gpio_in`/`g_gpio_out` ビットがクリアされ、GPIO 関数から除外されます。
Sources: [io.c:358-440](), [io.h:7-54]()
| ステートメント/関数 | 内部オプション定数 | 説明 |
|---|---|---|
| `OUT n, v` | `LIB_GPIO_OUT` | 1ピン出力 |
| `OUT8L v` | `LIB_GPIO_OUT8L` | GPIO0-7 まとめて出力 |
| `OUT8H v` | `LIB_GPIO_OUT8H` | GPIO8-15 まとめて出力 |
| `OUT16 v` | `LIB_GPIO_OUT16` | GPIO0-15 まとめて出力 |
| `IN(n)` | `LIB_GPIO_IN` | 1ピン読み取り |
| `IN8L()` | `LIB_GPIO_IN8L` | GPIO0-7 まとめて読み取り |
| `IN8H()` | `LIB_GPIO_IN8H` | GPIO8-15 まとめて読み取り |
| `IN16()` | `LIB_GPIO_IN16` | GPIO0-15 まとめて読み取り |
### PWM (`lib_pwm`)
BASICの `PWM n, freq, duty` 文は `lib_pwm` に転送されます。ポート 1〜3 はコンパイル時定数 `IO_PWM1`〜`IO_PWM3`(ボード設定ヘッダで定義)に固定され、ポート 4〜9 は `MACHIKAP.INI` の `PWM4=<GPIO番号>` 〜 `PWM9=<GPIO番号>` で実行時に変更できます。
```c
// io.c — PWM 周波数設定 (lib_pwm 抜粋)
if (g_clock_hz/100000 <= r1) {
pwm_set_clkdiv(slice, ((float)(g_clock_hz/1000)) / ((float)r1));
pwm_set_wrap(slice, 1000); // 1000 カウント周期
pwm_set_chan_level(slice, channel, r2); // デューティ 0–1000
} else {
pwm_set_clkdiv(slice, ((float)(g_clock_hz/1000/65)) / ((float)r1));
pwm_set_wrap(slice, 65000); // 低周波数用 65000 カウント
pwm_set_chan_level(slice, channel, r2*65);
}
```
Sources: [io.c:167-221]()
### ADC/アナログ (`lib_analog`)
`ANALOG(n)` 関数(n = 0〜3 または 13〜16 または直接 GPIO 番号)が `lib_analog` を呼び出します。初回呼び出し時に `adc_init()` を一度だけ実行し、プルアップを無効化した上で `adc_gpio_init` → `adc_select_input` → `adc_read` を行います。12ビット値(0〜4095)を返します。
Sources: [io.c:228-271]()
### SPI (`lib_spi`)
`SPI cs, freq, bits, mode` 文で初期化し、以降 `SPIWRITE`・`SPIREAD()`・`SPIWRITEDATA`・`SPIREADDATA`・`SPISWAPDATA` で通信します。8/16/32ビットのフレーム幅と 4 つの SPI モード(CPOL/CPHA の組み合わせ)に対応します。
SDカードと同一の SPI バスを共有する場合、MISO ピン切り替えを `spi_pre_read` / `spi_post_read` で行い、ユーザー I/O と SD カードの SPI レジスタ(SSPCR0)を `io_spi_sspcr[]` / `sd_spi_sspcr[]` の 2 配列で退避・復元します。
```c
// io.c — CS 制御と SSPCR0 レジスタの切り替え
g_io_spi_sspcr[0] = io_spi_sspcr[0]; // IO用レジスタ設定を復元
gpio_put(cs_port, 0); // CS アサート
// ... 通信処理 ...
gpio_put(cs_port, 1); // CS ネゲート
g_io_spi_sspcr[0] = sd_spi_sspcr[0]; // SD用レジスタ設定に戻す
```
Sources: [io.c:273-436]()
| 操作 | オプション定数 | 説明 |
|---|---|---|
| `SPI cs, freq, bits, mode` | `LIB_SPI_SPI` | 初期化・CS設定 |
| `SPIWRITE data...` | `LIB_SPI_SPIWRITE` | 送信のみ |
| `SPIREAD()` | `LIB_SPI_SPIREAD` | 送信後1ワード受信 |
| `SPIWRITEDATA buf, n` | `LIB_SPI_SPIWRITEDATA` | バルク送信 |
| `SPIREADDATA buf, n` | `LIB_SPI_SPIREADDATA` | バルク受信 |
| `SPISWAPDATA buf, n` | `LIB_SPI_SPISWAPDATA` | 全二重転送 |
### I2C (`lib_i2c`)
`I2C freq` 文で `i2c_init` を呼び出し、SDA/SCL ピンにプルアップを設定します。`I2CWRITE addr, data...` / `I2CREAD(addr, reg...)` は `i2c_write_blocking` / `i2c_read_blocking` を直接呼び出します。`I2CERROR()` でエラーコード(`PICO_OK=0`、`PICO_ERROR_TIMEOUT=-1`、`PICO_ERROR_GENERIC=-2` など)を取得できます。
`I2CWRITEDATA addr, buf, n` および `I2CREADDATA addr, buf, n` はバルク転送用で、任意個数のレジスタアドレスを前置してからバッファを転送します。
Sources: [io.c:438-568]()
### シリアル/UART (`lib_serial`)
UART は割り込み駆動のリングバッファ受信を採用しています。`SERIAL baud, parity` 文が IRQ ハンドラ `on_uart_rx` を登録し、受信バイトは `io_uart_buff[]`(動的確保)に格納されます。`SERIALIN()` でバッファから1バイト取得し、`SERIALIN(1)` でバッファ内の残バイト数を返します。`SERIALOUT` で1バイト送信します。
```c
// io.c — UART 受信割り込みハンドラ
void on_uart_rx() {
if ((uart_get_hw(g_io_uart_ch)->rsr & UART_UARTRSR_PE_BITS)) {
io_uart_buff[io_uart_buff_write_pos++] = 0x100 | uart_getc(g_io_uart_ch); // パリティエラーフラグ
} else {
io_uart_buff[io_uart_buff_write_pos++] = uart_getc(g_io_uart_ch);
}
if (io_uart_buff_size <= io_uart_buff_write_pos) io_uart_buff_write_pos = 0;
}
```
Sources: [io.c:570-650]()
---
## timer.c — 割り込み駆動タイマー
### タイマー種別と割り込みベクタ
システムは 2 本の `repeating_timer` を使います。`g_drawcount_timer` は常時 −16667 µs(約 60Hz)で動作し、`g_timer` はユーザーが `USETIMER` 文で任意間隔を指定します。
```c
// timer.c — timer_init() での初期化
add_repeating_timer_us(-16667, repeating_drawcount_callback, NULL, &g_drawcount_timer);
```
NTSCビデオルーティンが有効な場合、`trigger_drawcount_callback_once` がビデオフレームに同期して `g_drawcount_timer` を 1µs の one-shot 動作に切り替え、次フレームで再登録します。
Sources: [timer.c:95-111]()
`g_interrupt_vector[7]` 配列に最大 7 種の割り込みベクタを格納します:
| インデックス | 定数 | トリガー |
|---|---|---|
| 0 | `INTERRUPT_TIMER` | ユーザータイマー周期 |
| 1 | `INTERRUPT_DRAWCOUNT` | 60Hz フレームカウント |
| 2 | `INTERRUPT_KEYS` | キー状態変化 |
| 3 | `INTERRUPT_INKEY` | キー押下検出 |
| 4 | `INTERRUPT_MUSIC` | 音楽バッファ残少 |
| 5 | `INTERRUPT_WAVE` | WAV 再生終了 |
| 6 | `INTERRUPT_CORETIMER` | 一回限りタイマーアラーム |
Sources: [compiler.h:340-346]()
### `repeating_drawcount_callback` の処理フロー
```mermaid
sequenceDiagram
participant HW as ハードウェアタイマー(60Hz)
participant DC as repeating_drawcount_callback
participant MI as musicint()
participant IV as g_interrupt_vector[]
HW->>DC: 16667µs ごとに起動
DC->>DC: g_drawcount++ カウンタ更新
DC->>IV: INTERRUPT_DRAWCOUNT ベクタ呼び出し
DC->>DC: キー状態チェック
DC->>IV: INTERRUPT_KEYS (変化があれば)
DC->>MI: musicint() → PWM音声更新
DC->>IV: INTERRUPT_MUSIC フラグ確認・呼び出し
DC->>IV: INTERRUPT_WAVE フラグ確認・呼び出し
DC->>IV: INTERRUPT_INKEY キー確認・呼び出し
```
### `lib_timer` API
| オプション | BASICキーワード | 動作 |
|---|---|---|
| `TIMER_USETIMER` | `USETIMER n` | n µs 間隔でタイマー開始 |
| `TIMER_TIMER` | `TIMER n` | タイマーカウンタを n にセット |
| `TIMER_TIMERFUNC` | `TIMER()` | 現在のタイマーカウンタ値を返す |
| `TIMER_DRAWCOUNT` | `DRAWCOUNT n` | ドローカウント値をセット |
| `TIMER_DRAWCOUNTFUNC` | `DRAWCOUNT()` | ドローカウント値を返す |
| `TIMER_CORETIMER` | `CORETIMER n` | 下位 32 ビットが n になる時刻に一回アラーム |
| `TIMER_CORETIMERFUNC` | `CORETIMER()` | `time_us_32()` を返す |
Sources: [timer.c:113-170]()
---
## music.c — PWM 音声合成とウェーブ再生
### 音声モードの状態遷移
```mermaid
stateDiagram-v2
[*] --> NONE : stop_music() / 初期化
NONE --> MUSIC : init_music() + set_music()
NONE --> WAVE : set_wave()
MUSIC --> NONE : 再生完了 / stop_music()
WAVE --> NONE : EOF または g_wave_enable=0
MUSIC --> WAVE : set_wave() 呼び出し
```
Sources: [music.c:117-175]()
### 音楽文字列パーサ (`set_music`)
`MUSIC "文字列"` 文がBASICソースの音楽記法を解析します。構文は以下の指示子から成ります:
| 指示子 | 意味 | 例 |
|---|---|---|
| `L:n/m` | 音符の基本長(4分音符=`L:1/4=90`の相対値) | `L:1/8` |
| `Q:1/4=n` | テンポ(BPM相当) | `Q:1/4=120` |
| `K:xxx` | 調号(キー) | `K:C`, `K:G`, `K:F#`, `K:Bbm` |
| `A`〜`G` | 大文字:中オクターブ音符 | `C D E F G A B` |
| `a`〜`g` | 小文字:1オクターブ上 | `c d e` |
| `z` | 休符 | |
| `'` (シングルクォート) | 1オクターブ下げ | `C'` |
| `,` (カンマ) | 1オクターブ上げ | `C,` |
| `^` | シャープ | `C^` |
| `_` | フラット | `B_` |
| `=` | ナチュラル(調号キャンセル) | |
`g_keys[]` テーブルに 15 調 × 7 音のPWMラップ値が埋め込まれており、440Hz の A 音は wrap≒2048 に対応します(`clkdiv=138.75, clock=125MHz`)。
```c
// music.c — 周波数変換マクロ
#define toneFlat(x) ((((unsigned long)(x))*69433)>>16) // 半音下げ (×2^(-1/12))
#define toneSharp(x) ((((unsigned long)(x))*1933)>>11) // 半音上げ (×2^(+1/12))
```
音楽データは 32 エントリのリングバッファ `g_music[]` / `g_musiclen[]` に格納され、`musicint()` が 60Hz ごとに 1 エントリを消費してPWMのラップ値を更新します。バッファ残量が 1 エントリになると `INTERRUPT_MUSIC` フラグが立ちます。
Sources: [music.c:22-50](), [music.c:255-350]()
### WAVファイル再生 (`set_wave`)
WAV再生はモノラル・8ビット・15700〜16000Hz の PCM ファイルのみ対応します。`set_wave` は:
1. FAT ファイルシステムから WAV ヘッダ(44バイト)を読み取りフォーマット検証
2. 1024バイトのリングバッファ `g_wavtable[]` に先読み
3. PWM を 8ビット PCM 出力用に再設定(`pwm_set_wrap(255)`, `clkdiv=1.0`)
4. `add_repeating_timer_us(-63, repeating_wave_callback, ...)` で約 15873Hz の割り込みを開始
5. `core1` に `wave_core1_readfile` コールバックを登録し、~100Hz で後続データを充填
```c
// music.c — WAV PWM 初期化
pwm_set_clkdiv(AUDIO_SLICE, 1.0);
pwm_set_wrap(AUDIO_SLICE, 255); // 8ビット深度
pwm_set_chan_level(AUDIO_SLICE, AUDIO_CHAN, 128); // 50% デューティ
add_repeating_timer_us(-63, repeating_wave_callback, NULL, &g_wave_timer);
```
再生終了(EOF またはバッファ枯渇)で `INTERRUPT_WAVE` フラグが上がります。
Sources: [music.c:384-460]()
---
## rtc.c — リアルタイムクロック API
### 内部実装
RP2350 の「Always-On Timer」(`pico/aon_timer.h`)を使用します。RP2040 の場合も同じ API が提供されます。時刻は `struct timespec` で保持し、`mktime` / `localtime` を介して C 標準時刻型に変換します。
```c
// rtc.c — 初期時刻設定 (2020/01/01 00:00:00)
void init_machikania_rtc(void) {
struct tm ttm = {
.tm_mday = FF_NORTC_MDAY,
.tm_mon = FF_NORTC_MON - 1,
.tm_year = FF_NORTC_YEAR - 1900,
...
};
tts.tv_sec = mktime(&ttm);
aon_timer_start(&tts);
}
```
Sources: [rtc.c:36-55]()
### BASIC API
| 関数/文 | 説明 | 戻り値 |
|---|---|---|
| `GETTIME$()` | 現在時刻を ISO-8601 文字列で取得 | `"YYYY-MM-DDThh:mm:ss"` |
| `GETTIME$(t$, adj)` | `t
MachiKania Phyllosoma 開発者リファレンス Wiki · Grok-Wiki
を基点に `adj` 秒を加えた時刻文字列 | ISO-8601 文字列 |
| `SETTIME t
MachiKania Phyllosoma 開発者リファレンス Wiki · Grok-Wiki
| ISO-8601 文字列で時刻をセット | — |
| `STRFTIME$(fmt$)` | `strftime` フォーマット文字列で現在時刻を整形 | 整形済み文字列 |
| `STRFTIME$(fmt$, t$)` | `t
MachiKania Phyllosoma 開発者リファレンス Wiki · Grok-Wiki
の時刻を `fmt
MachiKania Phyllosoma 開発者リファレンス Wiki · Grok-Wiki
で整形 | 整形済み文字列 |
`get_fattime()` は `g_rtc4file` フラグが立っている場合のみ現在時刻を FAT タイムスタンプ形式(32ビットエンコード)で返し、フラグが立っていない場合はコンパイル時定数 `FF_NORTC_YEAR/MON/MDAY` 固定値を返します。
Sources: [rtc.c:68-94](), [rtc.c:138-210]()
### タイムゾーン処理
`set_time_from_utc(time_t t)` は `g_timezome`(`TIMEZONE` 設定値 × 4)に基づき、UTC に 15分単位でオフセットを加算・減算してから `aon_timer_set_time` を呼び出します。これにより Nepal Standard Time (+5:45) などの非整数タイムゾーンに対応します。
Sources: [rtc.c:122-136]()
---
## core1.c — Core1 非同期コールバック管理
### 設計
WAV再生中のファイル読み取りは Core0 をブロックしないよう Core1 に委譲されます。`core1.c` は 2 種類のコールバック登録機構を提供します:
| 関数 | 用途 |
|---|---|
| `request_core1_callback(func)` | FIFOキュー経由で即時コールバック登録(Core0→Core1) |
| `request_core1_callback_at(func, at)` | 指定 `time_us_32()` 値まで待ってから呼び出す(~100Hz ポーリング) |
`core1_entry` メインループは最大 16667µs(60Hz)のスリープを挟みながら `call_inner_requests` と `call_outer_requests` を繰り返します。`g_0w1r_core1_enabled` / `g_1w0r_core1_started` フラグで Core0 と Core1 の起動・停止を同期します。
Sources: [core1.c:41-80]()
---
## MACHIKAP.INI による I/O 初期化設定
起動時に `ini_file_io()` と `ini_file_rtc()` が `MACHIKAP.INI` を行単位でパースし、グローバル変数を更新します。ピン番号は RP2040/RP2350 の SPI/UART/I2C ピン割り当て規則に従って検証されます(無効な番号は無視)。
### SPI ピン設定
| キー | 有効値 (SPI0) | 有効値 (SPI1) |
|---|---|---|
| `SPIMISO=` | 0, 4, 16, 20 | 8, 12, 24, 28 |
| `SPIMOSI=` | 3, 7, 19, 23 | 11, 15, 27 |
| `SPICLK=` | 2, 6, 18, 22 | 10, 14, 26 |
`SPICLK` の値によって `g_io_spi_ch` が `spi0` または `spi1` に自動選択されます。
### UART ピン設定
| キー | UART0 | UART1 |
|---|---|---|
| `UARTTX=` | 0, 12, 16, 28 | 4, 8, 20, 24 |
| `UARTRX=` | 1, 13, 17, 29 | 5, 9, 21, 25 |
### I2C ピン設定
| キー | I2C0 | I2C1 |
|---|---|---|
| `I2CSDA=` | 0,4,8,12,16,20,24,28 | 2,6,10,14,18,22,26 |
| `I2CSCL=` | 1,5,9,13,17,21,25,29 | 3,7,11,15,19,23,27 |
### 追加 PWM・RTC 設定
```ini
# 追加 PWM ポート (GPIO 番号で指定, 0-29)
PWM4=21
PWM5=22
# ファイルシステム用に RTC を有効化
RTCFILE
# タイムゾーン (小数可: -12.0 ~ +14.0)
TIMEZONE=9 # JST
# TIMEZONE=5.75 # Nepal Standard Time
```
`PWM4`〜`PWM9` は `g_pwm_aux[]` 配列に格納され、BASIC の `PWM 4, freq, duty` 〜 `PWM 9, freq, duty` に対応します。
Sources: [io.c:31-84](), [rtc.c:18-35](), [PicoCalc/MACHIKAP.INI:67-114]()
---
## まとめ
Phyllosoma の I/O サブシステムは、単一の `lib_*` ディスパッチパターンのもとに GPIO・SPI・I2C・UART・PWM・ADC を均質に扱い、`MACHIKAP.INI` によるピン再割り当てで小型 RP2040 ボード(XIAO RP2040、RP2040-Zero 等)にも対応できる設計になっています。タイマーシステムは 60Hz の `repeating_drawcount_callback` を中心に 7 種の割り込みイベントを統括し、音楽・WAV・キー入力・ユーザー定義タイマーをすべてこの単一コールバックに集約することで Core0 の割り込み複雑度を最小化しています。WAV 再生のみ Core1 へのファイル読み取り委譲でレイテンシを吸収し、PCM データは 1024 バイトのリングバッファで Core0(PWM割り込み)と Core1(ファイルI/O)が非同期に協調します。RTC は RP2350 の Always-On Timer を `pico/aon_timer.h` 経由で利用し、ISO-8601 文字列・`strftime` フォーマット・FAT タイムスタンプの三インターフェースを提供します。
---
## 08. WiFiスタックとSDカードファイルシステム
> wifi/withwifi.c の CYW43 + lwIP + mbedTLS 統合、socket.c の TCP クライアント/サーバ/TLS API、wifi/picow_ntp_client.c の NTP 同期、file.c のファイル操作ステートメント群、interface/ff.c (FatFs) と interface/pico-sdmm.c (SPI MMC ドライバ)、withoutwifi.c のスタブ切り替えを解説します。
- Page Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/08-wifi-sd.md
- Generated: 2026-05-27T07:16:11.804Z
### Source Files
- `file.c`
- `wifi/withwifi.c`
- `wifi/socket.c`
- `wifi/wifi.h`
- `interface/ff.c`
- `interface/pico-sdmm.c`
- `wifi/withoutwifi.c`
<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:
- [wifi/withwifi.c](wifi/withwifi.c)
- [wifi/withoutwifi.c](wifi/withoutwifi.c)
- [wifi/socket.c](wifi/socket.c)
- [wifi/wifi.h](wifi/wifi.h)
- [wifi/picow_ntp_client.c](wifi/picow_ntp_client.c)
- [wifi/picow_tcp_client.c](wifi/picow_tcp_client.c)
- [wifi/picow_tcp_server.c](wifi/picow_tcp_server.c)
- [wifi/picow_tls_client.c](wifi/picow_tls_client.c)
- [wifi/wifierror.c](wifi/wifierror.c)
- [file.c](file.c)
- [interface/ff.c](interface/ff.c)
- [interface/pico-sdmm.c](interface/pico-sdmm.c)
</details>
# WiFiスタックとSDカードファイルシステム
本ページでは、Machikania Phyllosomaにおけるネットワーク接続とストレージの2大サブシステムを解説します。WiFi側はCYW43チップ・lwIP・mbedTLSの三層統合であり、BASICインタープリタからTCPクライアント/サーバ/TLS接続・NTP時刻同期を透過的に利用できるAPIを提供します。ストレージ側はFatFs (R0.14b) をハードウェアSPIで駆動するSDカードドライバを中心に、BASICステートメントへのファイル操作マッピングが完結しています。
Pico WとPico(非WiFiモデル)を単一のコードベースでサポートするため、`wifi/withwifi.c` と `wifi/withoutwifi.c` がビルド対象に応じて切り替えられるスタブ分離構造を採用しています。
---
## アーキテクチャ概観
```text
┌─────────────────────────────────────────────────────────────┐
│ KM-BASIC インタープリタ(BASICステートメント/関数) │
│ TCPCLIENT / TLSCLIENT / TCPSERVER / TCPSEND / NTP など │
│ FOPEN / FCLOSE / FGET / FPUT / FFIND / SETDIR など │
└──────────────┬──────────────────────────┬────────────────────┘
│ lib_wifi() │ lib_file() / lib_fopen()
┌─────────────▼──────────────┐ ┌────────▼────────────────────┐
│ wifi/withwifi.c │ │ file.c │
│ (設定・接続・ディスパッチ)│ │ (FatFs APIラッパー) │
└──┬──────┬──────┬───────────┘ └────────┬────────────────────┘
│ │ │ │ f_open / f_read / f_write …
│ │ │ ▼
│ DNS │ NTP │ ┌────────────────────────┐
│ │ │ │ interface/ff.c │
│ │ │ │ FatFs R0.14b (ChaN) │
│ │ │ └────────────┬───────────┘
│ │ │ │ disk_read/write/ioctl
│ │ │ ▼
│ │ │ ┌────────────────────────┐
│ │ │ │ interface/pico-sdmm.c │
│ │ │ │ SPI MMCドライバ │
│ │ │ └────────────────────────┘
│ │ │
▼ ▼ ▼
┌──────┐ ┌────┐ ┌─────────────────────────────────┐
│socket│ │NTP │ │ TCP/TLS クライアント・サーバ │
│.c │ │clnt│ │ picow_tcp_client.c │
└──┬───┘ └────┘ │ picow_tcp_server.c │
│ │ picow_tls_client.c │
▼ └──────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ CYW43 ドライバ(pico_cyw43_arch) │
│ lwIP TCP/IP スタック │
│ mbedTLS │
└─────────────────────────────────────────────────┘
```
---
## WiFiスタック
### withwifi.c — 設定・接続・BASICディスパッチ
`wifi/withwifi.c` はWiFiサブシステム全体のエントリポイントです。
#### INIファイルによる設定読み込み
起動時に `ini_file_wifi()` が呼ばれ、SDカード上の設定ファイルの各行をパースしてグローバル変数へ格納します。
| キー | 格納先 | 既定値 |
|------|--------|--------|
| `USEWIFI` | `g_usewifi=1` | 0(無効) |
| `WIFISSID=xxx` | `g_wifi_id[128]` | コンパイル時マクロ |
| `WIFIPASSWD=xxx` | `g_wifi_passwd[128]` | コンパイル時マクロ |
| `WIFICOUNTRY=XX` | `g_cyw43_country_char1/2` | `US` |
| `HOSTNAME=xxx` | `g_wifi_hostname[64]` | `PicoW` |
| `STATICIP=a.b.c.d` | `g_static_ip[4]` | なし(DHCP) |
| `NTPSERVER=xxx` | `g_ntp_server[64]` | `pool.ntp.org` |
| `INITIALNTP` | `g_initial_ntp=1` | 0 |
Sources: [wifi/withwifi.c:37-82]()
#### WiFi接続シーケンス
`connect_wifi()` は以下の手順で接続します。
```c
// wifi/withwifi.c:100-140 (簡略)
cyw43_arch_init_with_country(CYW43_COUNTRY(char1, char2, 0));
cyw43_arch_enable_sta_mode();
// WPA2-AES-PSK で最大5回リトライ (タイムアウト15秒/回)
cyw43_arch_wifi_connect_timeout_ms(ssid, passwd, CYW43_AUTH_WPA2_AES_PSK, 15000);
// 静的IPが設定されている場合はDHCPを停止して手動設定
if (g_static_ip[0]) dhcp_stop(&cyw43_state.netif[0]);
// INITIALNTP=1 なら NTP同期を最大5回試行
if (g_initial_ntp) lib_wifi(0, 0, LIB_WIFI_NTP);
```
Sources: [wifi/withwifi.c:97-156]()
#### `lib_wifi()` ディスパッチャ
BASICインタープリタから呼ばれるすべてのWiFi操作は `lib_wifi(r0, r1, r2)` に集約されます。`r2` がオペコード、`r0`/`r1` がオペランドです。
| `r2` (オペコード) | 操作 | 戻り値 |
|------------------|------|--------|
| `LIB_WIFI_IFCONFIG` | IPアドレス・サブネット・GW・DNS・MACアドレスを返す | `char*` |
| `LIB_WIFI_DNS` | DNS名前解決 (`dns_lookup()`) | `char*` (IP文字列) |
| `LIB_WIFI_NTP` | NTP時刻同期 | 0 (成功) |
| `LIB_WIFI_TCPCLIENT` | TCPクライアント接続開始 | エラーコード |
| `LIB_WIFI_TLSCLIENT` | TLSクライアント接続開始 | エラーコード |
| `LIB_WIFI_TCPSERVER` | TCPサーバ起動 | 0 |
| `LIB_WIFI_TCPSEND` | データ送信 | エラーコード |
| `LIB_WIFI_TCPRECEIVE` | バッファからデータ読み取り | 読み取りバイト数 |
| `LIB_WIFI_TCPSTATUS` | 接続状態照会 | 状態値 |
| `LIB_WIFI_TCPCLOSE` | 接続クローズ | エラーコード |
| `LIB_WIFI_TCPACCEPT` | 待機中の接続IDをFIFOから取得 | `void*` |
| `LIB_WIFI_ERR_INT` | 最後のエラーコード取得 | `int` |
| `LIB_WIFI_ERR_STR` | エラー文字列取得 | `char*` |
`g_wifi_enabled` が `0` の場合(接続前)、文字列返却系は空文字列、数値系はエラーコードを即座に返します。
Sources: [wifi/withwifi.c:200-285]()
#### DNS解決
`dns_lookup()` は lwIP の `dns_gethostbyname()` を呼び出し、`ERR_INPROGRESS` の場合はコールバックが来るまで10ms×最大150回(計1.5秒)ポーリングします。
Sources: [wifi/withwifi.c:90-115]()
---
### withoutwifi.c — 非WiFiビルド用スタブ
Raspberry Pi Pico(非WiFiモデル)向けビルドでは `wifi/withoutwifi.c` がリンクされます。すべてのWiFi関連関数がスタブとして定義されており、`connect_wifi()` は常に `1`(エラー)を返し、`lib_wifi()` は `r0` をそのまま返します。`board_led()` はCYW43ではなく汎用GPIO(`PICO_DEFAULT_LED_PIN`) で実装されています。
Sources: [wifi/withoutwifi.c:14-43]()
---
### socket.c — TCPバッファ管理層
`wifi/socket.c` はlwIPコールバックとBASIC APIの間に位置するバッファ管理層です。
#### 受信バッファ構造
受信データは動的に確保されたスロット型リンクリストで管理されます:
```text
g_socket_buffer (int*)
│
├─ buff[0] = 次バッファへのポインタ
├─ buff[1] = このスロットのデータ長(バイト)
├─ buff[2] = 現在の読み取り位置(バイト)
├─ buff[3] = 対応する tcp_pcb(識別子)
└─ buff[4..] = 実データ(可変長)
```
Sources: [wifi/socket.c:19-26]()
#### 主要関数
| 関数 | 役割 |
|------|------|
| `init_socket_system()` | 実行開始前の接続IDリセット |
| `init_tcp_socket()` | バッファ全解放・状態初期化 |
| `init_tls_socket()` | 上記 + TLSモードフラグ設定 |
| `tcp_receive_in_buff(data, bytes, pcb)` | lwIPコールバックからバッファへ追記 |
| `tcp_read_from_buffer(dest, bytes, conn_id)` | BASICから呼ばれるバッファ読み出し |
| `machikania_tcp_write(arg, len, conn_id)` | 最大 `WIFI_BUFF_SIZE`(2048B) 単位で送信 |
| `machikania_tcp_close(conn_id)` | クライアント/サーバ接続クローズ |
| `machikania_tcp_status(mode, conn_id)` | 接続状態・バッファ残量照会 |
`machikania_tcp_write()` はTLSモード時は `altcp_write()` / `altcp_output()`、プレーンTCPは `tcp_write()` / `tcp_output()` を使い分けます。送信後は最大100msポーリングして確認応答を待ちます。
Sources: [wifi/socket.c:175-220]()
#### サーバ接続管理FIFO
最大10個の接続IDを保持するFIFO (`g_pcb_fifo[10]`) があり、`shift_pcb_fifo()` / `add_pcb_to_fifo()` はARM割り込み無効化命令 (`cpsid i` / `cpsie i`) でアトミック操作を保証しています。
Sources: [wifi/socket.c:68-87]()
---
### TCP・TLSクライアント/サーバ
#### picow_tcp_client.c
`start_tcp_client(ipaddr, port)` がlwIPの `tcp_new_ip_type()` → `tcp_connect()` を呼び出し、接続完了コールバック `tcp_client_connected()` でヘッダバッファの自動送信と接続フラグの設定を行います。受信データは `tcp_client_recv()` コールバックから `tcp_receive_in_buff()` へ転送されます。
Sources: [wifi/picow_tcp_client.c:75-150]()
#### picow_tcp_server.c
`start_tcp_server(port, accept_mode)` がTCPリスナーを起動します。`tcp_accept_mode` の値によって、クライアント接続時 (`mode=0`) またはデータ受信時 (`mode=1`) にFIFOへ接続IDを追加するか決まります。
Sources: [wifi/picow_tcp_server.c:100-160]()
#### picow_tls_client.c
lwIPの `altcp_tls` レイヤを使用したTLSクライアント実装です。CA証明書検証は無効(`altcp_tls_create_config_client(NULL, 0)`)で、SNIは `mbedtls_ssl_set_hostname()` で設定します。mbedTLSの処理負荷を考慮し、TLS接続開始前にcore1を一時停止します(`stop_core1()` / `start_core1()`)。タイムアウトは15秒です。
Sources: [wifi/picow_tls_client.c:45-80, 145-165]()
---
### picow_ntp_client.c — NTP時刻同期
UDPを使ったSNTPクライアントです。
```mermaid
sequenceDiagram
participant BASIC
participant withwifi.c
participant ntp_client.c
participant lwIP/DNS
participant NTPサーバ
BASIC->>withwifi.c: LIB_WIFI_NTP
withwifi.c->>ntp_client.c: get_ntp_time("pool.ntp.org")
ntp_client.c->>lwIP/DNS: dns_gethostbyname()
lwIP/DNS-->>ntp_client.c: ntp_dns_found() callback
ntp_client.c->>NTPサーバ: UDP port 123 (NTP_MSG_LEN=48, req[0]=0x1b)
NTPサーバ-->>ntp_client.c: ntp_recv() callback
ntp_client.c->>ntp_client.c: seconds[40-43] - NTP_DELTA(2208988800)
ntp_client.c-->>withwifi.c: time_t*
withwifi.c->>withwifi.c: set_time_from_utc(now[0])
```
- 再送アラームは10秒後に `ntp_failed_handler()` を呼ぶ
- `dns_request_sent` フラグをポーリングしてブロッキング待機
- NTPエポック(1900-01-01)→UNIXエポック(1970-01-01)変換: `seconds_since_1900 - 2208988800`
Sources: [wifi/picow_ntp_client.c:90-135]()
---
### wifierror.c — エラー管理
エラー状態はグローバルな `g_err_wifi`(整数コード)と `g_err_str_wifi[32]`(文字列)の2種類で保持されます。
| 定数 | 値 | 文字列メッセージ |
|------|----|-----------------|
| `WIFI_ERROR_NO_ERROR` | 0 | `"No error"` |
| `WIFI_ERROR_CONNECTION_CLOSED` | 1 | `"Connection closed"` |
| `WIFI_ERROR_DNS_ERROR` | 2 | `"DNS error"` |
| `WIFI_ERROR_CONNECTION_ERROR` | 3 | `"Connection error"` |
| `WIFI_ERROR_WIFI_ERROR` | 4 | `"WiFi not connected"` |
なお `wifi.h` では `#define printf wifi_set_error(__LINE__); wifi_set_error_str` によりlwIP/mbedTLS内部のデバッグ出力をエラー管理に横取りしています。
Sources: [wifi/wifierror.c](), [wifi/wifi.h:1-10]()
---
## SDカードファイルシステム
### interface/pico-sdmm.c — SPI MMCドライバ
ChAN製のポータブルSPIドライバをRaspberry Pi Pico用にKenKenが改修したものです。ビットバンキングではなくPico SDK のハードウェアSPI (`spi_write_blocking` / `spi_write_read_blocking`) を使用します。
#### ピン設定
ボード構成ごとに `config/*.h` で定義されます。代表的な構成:
| ボード | CS | TX | RX | SCK | チャネル | ボーレート |
|--------|----|----|----|----|---------|-----------|
| pico_ntsc / pico_st7789 | 17 | 19 | 16 | 18 | spi0 | 10MHz |
| pico_restouch | 22 | 11 | 12 | 10 | spi1 | 16MHz |
| rp2350_lcd_1_47 | 15 | 11 | 12 | 10 | spi1 | 10MHz |
| xiao_ntsc | 6 | 3 | 4 | 2 | spi0 | 10MHz |
Sources: [config/pico_ntsc.h:113-118](), [config/pico_restouch.h:107-112]()
#### カード初期化シーケンス (`disk_initialize`)
```text
10ms 待機
↓
GPIO/SPI 初期化、80ダミークロック
↓
CMD0 (GO_IDLE_STATE) → Idleモード
↓
CMD8 (SEND_IF_COND, 0x1AA) → SDv2判定
├─ 成功: ACMD41(HCS=1) → CMD58 → CCS確認 → SDv2 or SDv2+Block
└─ 失敗: ACMD41(0) ≦1 → SDv1, CMD1 → MMCv3
↓
SDv1/MMC: CMD16(SET_BLOCKLEN, 512)
```
Sources: [interface/pico-sdmm.c:195-260]()
#### ディスクI/O関数
| 関数 | SPI実装 |
|------|---------|
| `disk_read(drv, buff, sector, count)` | CMD17(単体) / CMD18+CMD12(複数) |
| `disk_write(drv, buff, sector, count)` | CMD24(単体) / ACMD23+CMD25+0xFDトークン(複数) |
| `disk_ioctl(CTRL_SYNC)` | select()でpending write確認 |
| `disk_ioctl(GET_SECTOR_COUNT)` | CMD9(CSD読み取り)で計算 |
Sources: [interface/pico-sdmm.c:265-370]()
---
### interface/ff.c — FatFsモジュール
ChAN製FatFs R0.14b そのものです。FAT12/FAT16/FAT32/exFAT をサポートし、`diskio.h` の `disk_read` / `disk_write` / `disk_ioctl` / `disk_initialize` / `disk_status` を通じて `pico-sdmm.c` と接続されます。
Sources: [interface/ff.c:1-80]()
---
### file.c — BASICファイル操作API
`file.c` はFatFs APIをBASICステートメントと関数へマッピングします。
#### ファイルシステム初期化
```c
// file.c:35-38
FATFS g_FatFs;
void init_file_system(void){
if (FR_OK != f_mount(&g_FatFs, "", 0)) printstr("Initializing file system failed\n");
}
```
Sources: [file.c:35-38]()
#### BASICファイル操作一覧
`lib_file()` がすべての操作を `r2`(オペコード)で振り分けます。
| BASICステートメント/関数 | オペコード | FatFs API |
|------------------------|-----------|----------|
| `FOPEN(filename, mode, handle)` | `LIB_FOPEN` | `f_open()` (mode: "r"/"w"/"a"、"+"で読み書き兼用) |
| `FCLOSE [handle]` | `FILE_FCLOSE` | `f_close()` |
| `FILE handle` | `FILE_FILE` | ハンドル切り替え (1 or 2) |
| `FGET(bytes, addr)` | `FILE_FGET` | `f_read()` |
| `FPUT(bytes, addr)` | `FILE_FPUT` | `f_write()` |
| `FPUTC(char)` | `FILE_FPUTC` | `f_putc()` |
| `FGETC()` | `FILE_FGETC` | `f_read()` 1バイト |
| `FSEEK offset` | `FILE_FSEEK` | `f_lseek()` |
| `FSEEK()` | `FILE_FSEEKFUNC` | `f_tell()` |
| `FEOF()` | `FILE_FEOF` | `f_eof()` |
| `FLEN()` | `FILE_FLEN` | `f_size()` |
| `FINPUT([len])` | `FILE_FINPUT` | `f_gets()` 最大512バイト |
| `SETDIR path` | `FILE_SETDIR` | `f_chdir()` |
| `GETDIR$()` | `FILE_GETDIR` | `f_getcwd()` |
| `FFIND([pattern [, path]])` | `FILE_FFIND` | `f_findfirst()` / `f_findnext()` |
| `FINFO(n)` | `FILE_FINFO` | `fno.fsize` / `.fdate` / `.ftime` / `.fattrib` |
| `FINFO$(n)` | `FILE_FINFOSTR` | ISO-8601日時文字列生成など |
| `FREMOVE path` | `FILE_FREMOVE` | `f_unlink()` |
| `FRENAME old, new` | `FILE_FRENAME` | `f_rename()` |
| `MKDIR path` | `FILE_MKDIR` | `f_mkdir()` |
Sources: [file.c:120-290]()
#### ファイルハンドル管理
同時に2本のファイルを開けます。`g_pFileHandles[2]` と `g_FileHandles[2]` でポインタと実体を管理し、`g_active_handle`(1 または 2)がデフォルト対象を示します。`lib_fopen_main()` はオープン失敗時にまず `f_opendir(".")` でカード挿入確認を行い、未挿入なら `f_mount()` で再マウントを試みます。
Sources: [file.c:295-345]()
#### BASICファイルのコンパイル
`compile_file()` → `compile_file_sub()` はSDカード上の `.bas` ファイルをコンパイルします。クラスファイルは以下の優先順でサブディレクトリを探索します(RP2350/TypePU有無に応じた条件分岐あり):
```text
1. /lib/<classname>/TYPEPU/PICO2/<classname>.bas (PUERULUS && RP2350)
2. /lib/<classname>/PICO2/<classname>.bas (RP2350)
3. /lib/<classname>/TYPEPU/<classname>.bas (PUERULUS)
4. /lib/<classname>/<classname>.bas (共通)
```
Sources: [file.c:55-150]()
---
## まとめ
Phyllosomaのネットワーク・ストレージ統合は、**compile-time切り替え**(`withwifi.c` vs `withoutwifi.c`)による移植性と、**統一ディスパッチャ**(`lib_wifi()` / `lib_file()`)によるBASIC APIの単純さを両立しています。TCPとTLSのコールバックはすべて `socket.c` のリンクリストバッファに集約されるため、BASICコードからは接続プロトコルを意識せずに `TCPRECEIVE` 一本で受信できます。SDカードドライバは完全なFatFs準拠で、ボードごとのSPIピン割り当てはコンフィグヘッダで吸収されています。
Sources: [wifi/wifi.h:1-60](), [wifi/withwifi.c:160-285](), [file.c:35-40]()
---
## 09. エディタと入力ハンドリング — USBキーボード・PCシリアル・ファイル選択
> withkeyboard.c (TinyUSB ホスト HID キーボードモード) と withoutkeyboard.c (USB シリアル PC 接続モード) の分岐、interface/keyinput.c のキー入力バッファリング、interface/usbkeyboard.c の HID 仮想キーテーブル変換、interface/fileselect.c の SD カードファイルブラウザ、editor.c のインタラクティブ BASIC テキストエディタを解説します。
- Page Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/09-usb-pc.md
- Generated: 2026-05-27T07:17:00.290Z
### Source Files
- `withkeyboard.c`
- `withoutkeyboard.c`
- `editor.c`
- `interface/keyinput.c`
- `interface/usbkeyboard.c`
- `interface/fileselect.c`
<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:
- [withkeyboard.c](withkeyboard.c)
- [withoutkeyboard.c](withoutkeyboard.c)
- [editor.c](editor.c)
- [editor.h](editor.h)
- [interface/keyinput.c](interface/keyinput.c)
- [interface/usbkeyboard.c](interface/usbkeyboard.c)
- [interface/usbkeyboard.h](interface/usbkeyboard.h)
- [interface/fileselect.c](interface/fileselect.c)
- [CMakeLists.txt](CMakeLists.txt)
- [pcconnect.c](pcconnect.c)
</details>
# エディタと入力ハンドリング — USBキーボード・PCシリアル・ファイル選択
Phyllosoma は Raspberry Pi Pico 向け MachiKania BASIC 処理系であり、**2種類のファームウェアビルド**を提供します。USB HID キーボードを接続して使う `*_kb.uf2` ビルドと、PC から USB シリアル経由でプログラムを転送する `*.uf2` ビルドです。この分岐は `withkeyboard.c` と `withoutkeyboard.c` を排他的にコンパイルすることで実現されており、どちらのビルドでも共通の BASIC ランタイム API (`lib_readkey`・`lib_inkey`・`lib_input` など) が同じシグネチャで提供されます。キーボードビルドでは `interface/usbkeyboard.c` が TinyUSB ホスト HID ドライバを実装し、`interface/keyinput.c` がそのキーコードバッファを消費してカーソル付きの行入力機能を提供します。SD カードのファイル選択は、キーボードビルドでは `editor.c` 内のキーボード駆動ブラウザが、ゲームパッド(非キーボード)ビルドでは `interface/fileselect.c` の GPIO ボタン駆動ブラウザが担当します。
---
## コンパイル時分岐: `withkeyboard.c` と `withoutkeyboard.c`
CMakeLists.txt は 2 つの独立した実行ファイルを定義します。
| ターゲット | 追加ソース | USB 動作 |
|---|---|---|
| `${CODE_NAME}_kb` | `withkeyboard.c`, `editor.c`, `interface/usbkeyboard.c`, `interface/keyinput.c`, `hid_virtual_keytable.c` | TinyUSB ホスト(HID キーボード)。stdio USB は**無効** |
| `${CODE_NAME}` | `withoutkeyboard.c`, `pcconnect.c` | stdio USB CDC を**有効化**してシリアル通信 |
Sources: [CMakeLists.txt:108-145]()
`withkeyboard.c` は `const char g_active_usb_keyboard=1;` を定義し、`withoutkeyboard.c` は `=0` を定義します。`withoutkeyboard.c` では `vkey`・`lockkey`・`keytype` などのグローバル変数も同ファイルでダミー定義され、`usbkb_mounted()` は常に `false` を返します。
Sources: [withkeyboard.c:25](), [withoutkeyboard.c:25-32]()
### 初期化シーケンス
```text
電源投入
│
├─[キーボードビルド] post_inifile()
│ usbkb_init() ── TinyUSB ホスト初期化
│ start_core1()
│ request_core1_callback(usbk_polling_handler) ← 10ms 毎
│ usbkb_mounted() ループで待機 (g_wait_for_keyboard ms)
│ 見つかった → g_usb_peripheral = USB_PERIPHERAL_KEYBOARD
│ 見つからない → g_fileselect_no_keyboard=1
│
├─[シリアルビルド] post_inifile()
│ connect2pc() ← "MACHIKAP" ハンドシェイク
│
pre_fileselect()
│ [キーボードビルド] → texteditor() (editor.c) ← 戻らない
│ [シリアルビルド] → なにもしない → fileselect() (interface/fileselect.c)
```
Sources: [withkeyboard.c:30-78](), [withoutkeyboard.c:35-40](), [pcconnect.c:107-117]()
---
## `interface/usbkeyboard.c` — TinyUSB HID キーボードドライバ
### データ構造とキューイング
```text
Core1 (usbkb_polling) Core0 (アプリ)
────────────────────── ────────────────
tuh_task() usbkb_readkey()
usbkb_task() ──書込→ [keycodebuf: uint16_t[16]] ──読出→ vkey
usbkbled_task() keycodeExists() マクロ
semaphore keycodebuf_sem で保護
```
`keycodebuf` は 16 エントリのリングバッファです。`keycodebufp1`(書き込み先頭)と `keycodebufp2`(読み出し先頭)の 2 つの揮発ポインタで管理されます。セマフォ `keycodebuf_sem` によって Core0/Core1 間の競合を防ぎます。
Sources: [interface/usbkeyboard.c:26-37](), [interface/usbkeyboard.h:1]()
### HID レポート処理パイプライン
```mermaid
sequenceDiagram
participant USB as TinyUSB (Core1)
participant CB as tuh_hid_report_received_cb
participant RPT as process_kbd_report
participant TASK as usbkb_task
participant BUF as keycodebuf
participant APP as usbkb_readkey (Core0)
USB->>CB: HID割り込みレポート受信
CB->>RPT: hid_keyboard_report_t
RPT->>RPT: rollover エラー(keycode[0]==1)なら無視
RPT-->>TASK: usbkb_report に格納
Note over TASK: 10ms ポーリングで呼出
TASK->>TASK: shiftkeycheck(modifier)
TASK->>TASK: hidkey2virtualkey_jp/en[] で VK 変換
TASK->>BUF: pushkeycodebuf(vk | shift<<8)
TASK->>TASK: キーリピート処理
TASK->>TASK: usbkb_keystatus[] 更新
APP->>BUF: popkeycodebuf()
BUF-->>APP: vkey (下位8bit=VK, 上位8bit=修飾)
APP->>APP: vk2asc1/2_jp/en[] で ASCII 変換
```
Sources: [interface/usbkeyboard.c:71-175](), [interface/usbkeyboard.c:254-280]()
### HID コード → 仮想キーコード → ASCII 変換
`usbkb_task()` は最大 6 同時押しキーのそれぞれを変換テーブル `hidkey2virtualkey_jp[]` または `hidkey2virtualkey_en[]` で Windows 仮想キーコード (`VK_*`) に変換してバッファに積みます。`usbkb_readkey()` がバッファから取り出す際、シフト状態に応じて以下のテーブルで ASCII 変換します。
| モード | シフトなし | シフトあり |
|---|---|---|
| 日本語英数 (`keytype=0`) | `vk2asc1_jp[]` | `vk2asc2_jp[]` |
| 日本語カナ (`CHK_SCRLK`) | `vk2kana1[]` | `vk2kana2[]` |
| 英語 (`keytype=1`) | `vk2asc1_en[]` | `vk2asc2_en[]` |
Ctrl・Alt・Win が押されている場合は ASCII 0 を返し、グローバル `vkey` のみをセットします。キーリピートは初回 500 ms・以降 40 ms 間隔(`KEYREPEAT1`/`KEYREPEAT2`)です。
Sources: [interface/usbkeyboard.h:1-3](), [interface/usbkeyboard.c:195-255]()
---
## `interface/keyinput.c` — キー入力バッファリングとカーソル管理
`keyinput.c` は `usbkb_readkey()` の上位レイヤーで、エディタや BASIC `INPUT` 文から呼び出される高水準キー入力 API を提供します。
### 主要関数
| 関数 | 動作 |
|---|---|
| `inputchar()` | キーが押されるまでブロック。60Hz タイマで 1 フレーム毎に `usbkb_readkey()` をポーリング |
| `cursorinputchar()` | `inputchar()` と同じだが待機中にカーソルを点滅表示 |
| `printinputchar()` | 通常文字が入力されるまで待機し、入力文字を画面に出力 |
| `blinkcursorchar()` | `BLINKTIME` フレームで挿入カーソル(`CURSORCHAR`)と上書きカーソル(`CURSORCHAR2`)を交互表示 |
| `lineinput(s, n)` | フル行入力。挿入/上書きモード・←/→/Home/End/BS/Del に対応 |
`lineinput()` は内部バッファ `lineinputbuf[256]` を使い、初期文字列を事前ロードする機能も持ちます。`VK_RETURN`/`VK_SEPARATOR` で確定、`VK_ESCAPE`/`VK_CANCEL` で -1 を返して中断します。
Sources: [interface/keyinput.c:65-85](), [interface/keyinput.c:115-220]()
---
## `interface/fileselect.c` — SD カードファイルブラウザ(ゲームパッド版)
非キーボードビルドのファイル選択に使用される GPIO ボタン駆動のブラウザです。`shared_files` ライブラリに含まれるため、両ビルドにリンクされますが、キーボードビルドでは実行パスが通りません。
### GPIO ボタン管理
```c
// keystatus2: 今回新たに押されたボタン
// keystatus3: 今回離されたボタン
// keycountXX: 連続押下フレーム数(30超でリピート扱い)
```
`keycheck()` は `gpio_get_all()` で全ボタンを一括取得し、差分を計算します。
Sources: [interface/fileselect.c:38-64]()
### ファイル一覧と操作フロー
`fileselect()` はディレクトリを再帰的に走査し、最大 512 エントリ(`MAXFILE`)をリストアップします。ディレクトリは先頭に配置され、ファイルは `file_sort()` で整列されます。
| ボタン操作 | 動作 |
|---|---|
| UP/DOWN/LEFT/RIGHT | カーソル移動(30フレーム超でリピート) |
| FIRE(短押し) | ファイル選択 / ディレクトリ遷移 |
| START(短押し on ファイル) | `viewfile()` でテキスト内容プレビュー |
| START(長押し 20フレーム超) | タイムスタンプ表示切り替え / 画面回転 |
| FIRE(長押し 20フレーム超) | ソート順サイクル(名前昇順→降順→日付昇順→降順) |
`file_sort()` は選択ソートで実装され、`fnamecmp()` がソートモード (`filesortby` 0–3) に応じたキーを返します。
Sources: [interface/fileselect.c:175-340]()
---
## `editor.c` — インタラクティブ BASIC テキストエディタ
キーボードビルド専用のフルフィーチャーテキストエディタです。`texteditor()` から呼ばれると決して戻らず、BASIC ソースを編集して実行に引き渡します。
### テキストバッファのデータ構造
```mermaid
classDiagram
class _tbuf {
+_tbuf *prev
+_tbuf *next
+unsigned short n
+unsigned char Buf[200]
}
_tbuf --> _tbuf : linked list
class TextBuffer {
<<array>>
181 nodes (TBUFMAXLINE)
max 36000 chars total
}
TextBuffer o-- _tbuf
```
テキストは `TBUFSIZE=200` バイト単位の `_tbuf` ノードの双方向リンクリストとして保持されます。`TBufstart` がリスト先頭、`cursorbp`/`cursorix` がカーソル位置、`disptopbp`/`disptopix` が画面左上位置を指します。ノードが満杯になると自動的に `newTBuf()` で次ノードへ分割し、ガベージコレクション (`gabagecollect1/2`) がノード間の断片を詰め直します。
Sources: [editor.c:34-48](), [editor.h:20-25]()
### カーソル移動の設計
カーソル移動関数は常に `cursorbp`/`cursorix`(バッファ位置)と `cx`/`cy`(画面座標)と `disptopbp`/`disptopix`(スクロール位置)を同時に更新します。`cx2` は上下移動時の「意図した X 座標」を保持し、より短い行を通過しても元の列に戻る動作を実現します。
| 関数 | 主要動作 |
|---|---|
| `cursor_left/right` | 1 文字移動・行折り返し・スクロール |
| `cursor_up/down` | 1 行移動(`WIDTH_X` 分遡って改行を探索) |
| `cursor_home/end` | 行頭/行末移動 |
| `cursor_pageup/down` | 画面行数-1 ページ単位スクロール |
| `cursor_top` | バッファ先頭へジャンプ |
Sources: [editor.c:517-840]()
### アンドゥバッファ
`UNDOBUFSIZE=2048` バイトの環状バッファが `undobuf[]` として確保されます。操作ごとに `pushundomem()` でコマンドコードと位置情報を先頭から書き込み、アンドゥ時に `popundomem()` で逆順に読み出して逆操作を適用します。バッファが満杯になった場合は末尾の最古エントリを無効化します。
| コマンド定数 | 内容 |
|---|---|
| `UNDO_INSERT` (1) | 1 文字挿入(アンドゥ時: 削除) |
| `UNDO_OVERWRITE` (2) | 1 文字上書き |
| `UNDO_DELETE` (3) | 1 文字 Delete |
| `UNDO_BACKSPACE` (4) | 1 文字 BackSpace |
| `UNDO_CONTINS` (5) | 連続挿入(貼り付け) |
| `UNDO_CONTDEL` (6) | 範囲削除 |
Sources: [editor.c:213-299](), [editor.h:47-52]()
### シンタックスハイライト
`redraw()` は TVRAM を直接書き換えてシンタックスハイライトを適用します。画面外の行頭まで遡ってパース状態(`inRemark`/`inQuotation`)を復元してから、画面全体を 1 パスでスキャンします。
| 対象 | 色定数 |
|---|---|
| 通常テキスト | `COLOR_NORMALTEXT` (7) |
| BASIC 予約語 | `COLOR_RESERVEDWORD` (16) |
| `REM` コメント行 | `COLOR_REMARKTEXT` (17) |
| ダブルクォート内 | `COLOR_QUOATTEXT` (18) |
| 範囲選択テキスト | `COLOR_AREASELECTTEXT` (4) |
Sources: [editor.c:427-510](), [editor.h:28-36]()
### ファイル操作(SD カード)
`savetextfile()` はリンクリストを順にたどりながら `FILEBUFSIZE=256` バイト単位でバッファリングして FatFS 書き込みを行い、`\n` を `\r\n` に変換します。`loadtextfile()` は逆に `\r` を無視して `\n` のみ保持します。ファイルエラー時は `filesystemretry()` が再試行か ESC 中断を促します。拡張子 `.HEX` のファイルは `runHex()` に直接転送されます。
Sources: [editor.c:1462-1532]()
### キーボード版ファイルブラウザ(`editor.c` 内)
エディタのロード/セーブ操作に呼び出される `select_dir_file()` は、USB キーボードの `inputchar()` / `vkey` を使ってファイルを選択します。`interface/fileselect.c` のゲームパッド版と同じ `file_sort()` 関数(editor.c で定義)を共有しますが、操作系は完全にキーボード対応です。
| キー操作 | 動作 |
|---|---|
| ↑↓←→ / Num8246 | カーソル移動・スクロール |
| Enter / テンキー Enter | 選択確定・新規ファイル名入力 |
| ESC | キャンセル |
| F1 | タイムスタンプ表示切り替え |
| F3 / Shift+F3 | ソート順変更 |
| Ctrl+↑ | 親ディレクトリへ移動 |
Sources: [editor.c:1630-1945]()
### `texteditor()` メインループ
```mermaid
stateDiagram-v2
[*] --> Init: texteditor() 呼出
Init --> LoadTemp: WORKDIRFILE/TEMPFILENAME 存在?
LoadTemp --> MainLoop: 復元完了 or スキップ
Init --> MainLoop: 一時ファイルなし
MainLoop --> Redraw: redraw()
Redraw --> WaitKey: カーソル点滅 + GC
WaitKey --> NormalChar: ASCII 文字
WaitKey --> ControlChar: 仮想キー(VK_*)
NormalChar --> MainLoop: normal_code_process()
ControlChar --> MainLoop: control_code_process()
```
Sources: [editor.c:2757-2825]()
---
## まとめ
Phyllosoma の入力ハンドリングは、コンパイル時に選択される 2 つのモードを同一の BASIC ランタイム API で抽象化しています。`withkeyboard.c` ビルドは TinyUSB ホスト → `usbkb_task()` → `keycodebuf` リングバッファ → `usbkb_readkey()` → `keyinput.c` 高水準 API という多段パイプラインを Core1 ポーリングで動かし、`editor.c` の双方向リンクリストテキストバッファと 2048 バイトアンドゥ履歴を持つフルフィーチャーエディタへ入力を供給します。`withoutkeyboard.c` ビルドはすべてのキーボード関連構造をスタブ化し、`getchar_timeout_us()` ベースの USB CDC シリアル I/O で同じ API を実装します。SD カードファイルブラウザは用途に応じて GPIO ボタン版(`interface/fileselect.c`)とキーボード版(`editor.c` 内 `select_dir_file()`)の 2 実装が存在し、ビルドターゲットに応じて使い分けられます。
Sources: [CMakeLists.txt:108-160](), [withkeyboard.c:27-102](), [withoutkeyboard.c:25-90]()
---
## 10. 拡張ポイントと開発者リファレンス — auxcode・hexfile・デバッグ・help
> auxcode/auxcode.c の補助コードライブラリ拡張インタフェース (aux_statements / aux_int_functions 等)、hexfile.c の Intel HEX 直接実行機能 (runHex)、pcconnect.c の PC シリアルプロトコル、debug.c のデバッグユーティリティ、help.c の組み込みヘルプ辞書、開発者が新規ライブラリを追加する際の手順と注意点をまとめます。
- Page Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/pages/10-auxcode-hexfile-help.md
- Generated: 2026-05-27T07:18:03.149Z
### Source Files
- `auxcode/auxcode.c`
- `auxcode/auxcode.cmake`
- `hexfile.c`
- `pcconnect/pcconnect.c`
- `debug.c`
- `help.c`
<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:
- [auxcode/auxcode.c](auxcode/auxcode.c)
- [auxcode/auxcode.cmake](auxcode/auxcode.cmake)
- [hexfile.c](hexfile.c)
- [pcconnect.c](pcconnect.c)
- [debug.c](debug.c)
- [help.c](help.c)
- [debug.h](debug.h)
- [compiler.h](compiler.h)
- [CMakeLists.txt](CMakeLists.txt)
- [statements.c](statements.c)
- [integer.c](integer.c)
- [string.c](string.c)
- [float.c](float.c)
- [library.c](library.c)
- [withoutkeyboard.c](withoutkeyboard.c)
- [main.c](main.c)
</details>
# 拡張ポイントと開発者リファレンス — auxcode・hexfile・デバッグ・help
このページは、Phyllosoma (KM-BASIC for ARM) の**コンパイラ拡張レイヤー**と**開発者向けユーティリティ**を解説します。対象は、新しい BASIC ステートメント/関数を追加したい組み込みライブラリ開発者、Intel HEX ファイルを直接実行する仕組みを理解したい方、PC との USB シリアルプロトコルを把握したい方、デバッグモードやヘルプ辞書を活用したい方です。
組み込み機能はすべて固定のディスパッチテーブルに列挙されていますが、`aux_statements` / `aux_int_functions` / `aux_str_functions` / `aux_float_functions` / `lib_aux` の 5 関数だけが**意図的な拡張スタブ**として `auxcode/auxcode.c` に切り出されています。これらを差し替えるだけで、ビルドシステムを最小限しか変更せずに新規機能を追加できます。
---
## 1. auxcode — 補助コードライブラリ拡張インタフェース
### 1.1 スタブ関数の概要
`auxcode/auxcode.c` は以下の 5 関数のデフォルト実装を提供します。
```c
// auxcode/auxcode.c (全体)
int aux_statements(void) { return ERROR_STATEMENT_NOT_DETECTED; }
int aux_int_functions(void) { return ERROR_STATEMENT_NOT_DETECTED; }
int aux_str_functions(void) { return ERROR_STATEMENT_NOT_DETECTED; }
int aux_float_functions(void) { return ERROR_STATEMENT_NOT_DETECTED; }
int lib_aux(int r0, int r1, int r2) { return r0; }
```
`ERROR_STATEMENT_NOT_DETECTED`(値 `-17`)を返すと、コンパイラはその関数が「認識しなかった」と判断し、次の候補へ処理を移します。新規ステートメント・関数を実装する場合は、この返り値を使って「自分の担当ではない」ことを伝えます。
Sources: [auxcode/auxcode.c:8-21](), [compiler.h:27]()
### 1.2 コンパイラからの呼び出し位置
各スタブは、対応する型解析モジュールの末尾付近で呼ばれます。
| 関数 | 呼び出し元ファイル | タイミング |
|---|---|---|
| `aux_statements()` | `statements.c:1578` | 全組み込みステートメント判定の後 |
| `aux_int_functions()` | `integer.c:231` | 全組み込み整数関数判定の後 |
| `aux_str_functions()` | `string.c:70` | 全組み込み文字列関数判定の後 |
| `aux_float_functions()` | `float.c:74` | 全組み込み浮動小数点関数判定の後 |
```c
// statements.c — コンパイル時のステートメント検索末尾 (抜粋)
e = wifi_statements();
if (e != ERROR_STATEMENT_NOT_DETECTED) return e;
// Aux statements
e = aux_statements();
if (e != ERROR_STATEMENT_NOT_DETECTED) return e;
// Try call statement …
```
Sources: [statements.c:1574-1581](), [integer.c:229-233](), [string.c:68-72](), [float.c:72-76]()
### 1.3 ランタイムライブラリテーブルと `lib_aux`
コンパイル時に `call_lib_code(LIB_AUXCODE)` を生成すると、実行時に `kmbasic_library()` が `lib_aux(r0, r1, r2)` を呼び出します。`LIB_AUXCODE` の番号は **157**、ライブラリテーブルの最後のエントリです。
```c
// library.c — lib_list2[] テーブル (抜粋)
lib_wifi, // #define LIB_WIFI 156
lib_aux, // #define LIB_AUXCODE 157
```
Sources: [library.c:864-865](), [compiler.h:133]()
### 1.4 ビルドシステムへの統合 — `auxcode/auxcode.cmake`
デフォルトの CMake 定義は以下の通りです。
```cmake
# auxcode/auxcode.cmake
add_library(aux_files
auxcode/auxcode.c
)
target_link_libraries(aux_files
pico_stdlib
)
```
`CMakeLists.txt` はこのファイルを `include(auxcode/auxcode.cmake)` で読み込み、生成される両バイナリ(`_kb` / 無印)に `aux_files` をリンクします。
Sources: [auxcode/auxcode.cmake:1-8](), [CMakeLists.txt:100-115]()
### 1.5 新規ライブラリを追加する手順
```text
1. auxcode/ 以下に新しい .c / .h を作成する
2. auxcode/auxcode.cmake の add_library(aux_files ...) にソースを追記する
3. auxcode/auxcode.c の各スタブ関数内で instruction_is() などを使い、
自分のキーワードを判定・処理する
4. ランタイム動作が必要な場合は lib_aux() 内に実装し、
コンパイル側では call_lib_code(LIB_AUXCODE) を emit する
5. ERROR_STATEMENT_NOT_DETECTED を返す前にすべての自担当キーワードを
確認し終えること(落ちたら組み込み処理に制御が戻る)
```
---
## 2. hexfile.c — Intel HEX ファイル直接実行
### 2.1 機能概要
`runHex(char* fname)` は SD カード上の Intel HEX ファイルをパースし、`kmbasic_object[]` 配列へ書き込んだ後、ウォッチドッグリブートで指定アドレスへジャンプします。メインループでは `.HEX` 拡張子が検出されたときにこの関数を呼び出します。
```c
// main.c:175
if (!strncmp(&str[i-4], ".HEX", 4)) runHex(str);
```
Sources: [main.c:175](), [editor.c:1551]()
### 2.2 Intel HEX レコード処理
`hexfile.c` 先頭のコメントに示されているフォーマット仕様に従い、4 種類のレコードタイプを処理します。
| タイプ定数 | 値 | 意味 | 処理 |
|---|---|---|---|
| `HEX_DATA` | `0x00` | データ | `addr[laddr + i]` へコピー(`kmbasic_object` 範囲チェック付き) |
| `HEX_EOF` | `0x01` | 終端 | ループ終了 |
| `HEX_EXTENDED_ADDRESS` | `0x04` | 上位 16 ビットアドレス | `addr = (char*)(data<<16)` |
| `HEX_START_ADDRESS` | `0x05` | 開始アドレス | `start_address` に記録 |
```c
// hexfile.c:98-107
case HEX_DATA:
if (&addr[hexdata->laddr] < (char*)&kmbasic_object[0] ||
fbuff <= &addr[hexdata->laddr + hexdata->bytes]) return "HEX region doesn't fit";
for(i = 0; i < hexdata->bytes; i++)
addr[hexdata->laddr + i] = hexdata->data[i];
break;
```
データ書き込みは `kmbasic_object` の範囲内に限定されており、範囲外へのアクセスはエラー文字列を返して停止します。
### 2.3 リブートシーケンス
```c
// hexfile.c:119-124
if (KMBASIC_RP2350) {
watchdog_reboot(start_address, 0x20080000, 1000);
} else {
watchdog_reboot(start_address, 0x20040000, 1000);
}
sleep_ms(2000);
```
RP2350 と RP2040 でスクラッチパッドアドレスを切り替えています。呼び出し後は `sleep_ms(2000)` で待機し、ウォッチドッグタイムアウトにより新しいファームウェアが起動します。
Sources: [hexfile.c:78-143]()
---
## 3. pcconnect.c — PC シリアルプロトコル
### 3.1 呼び出しタイミング
`withoutkeyboard.c` (USB シリアルモードの起動設定) の `post_inifile()` から `connect2pc()` が呼ばれます。USB キーボードモード (`withkeyboard.c`) では呼ばれません。
```c
// withoutkeyboard.c:39
void post_inifile(void) {
connect2pc();
}
```
Sources: [withoutkeyboard.c:37-41]()
### 3.2 ハンドシェイクとコマンドループ
通信シーケンスは以下の通りです。
```mermaid
sequenceDiagram
participant D as MachiKania デバイス
participant P as PC アプリ
D->>P: "MACHIKAP" + BS×8
P->>D: "OK\x08\x08OK\x08\x08OK\x08\x08OK\x08\x08" (16 bytes)
D->>P: "SENDCMDS" + BS×8
loop コマンドループ
P->>D: 16 byte コマンド (CD: / SIZE: / CP: / DONEDONE)
alt CD:path
D->>P: "OK\x08\x08OK\x08\x08OK\x08\x08OK\x08\x08"
else SIZE:nnnn
D->>P: "OK\x08\x08OK\x08\x08OK\x08\x08OK\x08\x08"
else CP:filename
D->>P: "SENDFILE" + BS×8
P->>D: ファイルデータ (バイト列)
D->>P: "DONEDONE" + BS×8
else DONEDONE
D->>P: "ALL DONE" + BS×8
end
end
```
各コマンドは固定 16 バイトで受信します (`receive_command()`)。タイムアウトは **2 秒**で、超過すると `communication_error()` が呼ばれ無限ループに入ります。
### 3.3 ファイル転送の詳細
`CP:` コマンド受信後、デバイスはファイルを FA_WRITE | FA_CREATE_ALWAYS で開き、バイトを逐次受信します。内部バッファは `PC_CONNECT_BUFFER_SIZE = 256` バイトで、256 バイト毎にフラッシュします。
```c
// pcconnect.c:78-97
printf("SENDFILE\x08\x08\x08\x08\x08\x08\x08\x08");
for(i = 0; i < g_connect_file_size; i++) {
c = getchar_timeout_us(2000000);
g_connect_buffer[(PC_CONNECT_BUFFER_SIZE-1) & i] = c;
if ((PC_CONNECT_BUFFER_SIZE-1) == ((PC_CONNECT_BUFFER_SIZE-1) & i)) {
printf("OK\x08\x08OK\x08\x08OK\x08\x08OK\x08\x08");
f_write(&fh, g_connect_buffer, PC_CONNECT_BUFFER_SIZE, NULL);
}
…
}
```
Sources: [pcconnect.c:1-120]()
---
## 4. debug.c / debug.h — デバッグユーティリティ
### 4.1 通常モード(`MACHIKANIA_DEBUG_MODE` 未定義)
デバッグモードが無効な場合、`debug.c` は 2 つの関数のみ有効です。
| 関数 | 説明 |
|---|---|
| `memdump()` | `kmbasic_object[]` 全体 (256 KiB) を `MEMDUMP.BIN` へダンプ |
| `blink_led(int num)` | 指定回数 LED を点滅する無限ループ(クラッシュ時のデバッグ用) |
`dump()` / `dump_cmpdata()` / `dump_variables()` はすべて `debug_dummy()` (何もしない関数) に `#define` されます。
Sources: [debug.c:10-36](), [debug.h:75-86]()
### 4.2 デバッグモード(`MACHIKANIA_DEBUG_MODE` 定義時)
`config.cmake` の `set(MACHIKANIA_DEBUG_MODE 1)` のコメントを外すか、CMake 変数を設定すると有効になります。
**主な機能:**
1. **FatFS 置換マクロ** — `debug.h` が `f_open`、`f_close`、`f_gets` などを `debug_f_*` 関数へ `#define` で差し替えます。SD カードなしでコンパイル・実行できます。
2. **インメモリファイルシステム** — `debug_files[]` 配列がファイル名とその内容を交互に格納します。`MACHIKAP.BAS` には `rem_repeat64k` マクロで生成された膨大な `REM` 文が含まれており、大規模コンパイルのストレステストを SD カード不要で実施できます。
3. **`debug_fileselect()`** — 2500 ms 待機後(`g_disable_debugwait2500` フラグが未設定の場合)`"MACHIKAP.BAS"` を返します。
4. **ダンプ関数群:**
| 関数 | 出力内容 |
|---|---|
| `dump()` | `kmbasic_object` の先頭 256 ワード、クラスリスト、フィールド名 |
| `dump_class_list()` | クラス ID・アドレス・構造体フィールド |
| `dump_fieldnames()` | `CMPDATA_FIELDNAME` レコードの ID と名前 |
| `dump_cmpdata()` | 全コンパイルデータ (`CMPDATA_ALL`) の生 dump |
| `dump_variables()` | 変数番号・値・サイズ (A〜Z の 26+4 変数) |
5. **エラー追跡** — `MACHIKANIA_DEBUG_MODE` 時、`_throw_error(e)` は `throw_error(e, __LINE__, __FILE__)` に展開され、エラー発生箇所のファイル名と行番号を表示します。通常モードでは単に `(e)` に展開されます。
```c
// compiler.h:27-30
#ifdef MACHIKANIA_DEBUG_MODE
int throw_error(int e, int line, char* file);
#define _throw_error(e) throw_error(e, __LINE__, __FILE__)
#else
#define _throw_error(e) (e)
#endif
```
Sources: [debug.c:34-363](), [debug.h:18-86](), [compiler.h:27-30]()
---
## 5. help.c — 組み込みヘルプ辞書
### 5.1 ヘルプファイルの設定
```c
// help.c:13
char g_help_file[64] = "/docs/help-e.txt";
```
デフォルトは SD カード上の `/docs/help-e.txt`(英語版)です。`MACHIKAP.INI` に `HELPFILE=` を記載すると `ini_file_help()` が上書きします。
```ini
# MACHIKAP.INI (抜粋)
HELPFILE=/docs/help-e.txt
#HELPFILE=/docs/help-k.txt ← カタカナ日本語版
```
Sources: [help.c:13-22](), [scripts/MACHIKAP.INI:208-209]()
### 5.2 `get_help()` の動作
1. 検索ワードのハッシュを `cmpdata_nhash()` で計算する
2. ハッシュに基づいてグループキーワードを解決する(例: `TO` / `STEP` / `NEXT` はすべて `FOR` へ)
3. `num_lines` でそのエントリの継続行数、`num_funcs` で同一名前で複数記述がある場合の数を設定する
4. ヘルプファイルをシーケンシャルに読み込み、先頭 12 文字をキーワードと照合する
5. 一致したエントリとそれに続くタブインデント行を `g_file_buffer`(最大 1024 バイト)に格納して返す
**グループキーワードの例:**
```c
// help.c:41-58
case 0x00047392: //FOR
case 0x0000154f: //TO
case 0x01495110: //STEP
case 0x013c4654: //NEXT
word = "FOR";
num_lines = 2;
break;
case 0x0000114f: //DO
case 0x0134e390: //LOOP
case 0x543d520c: //UNTIL
word = "DO";
num_lines = 8;
break;
```
`num_funcs > 1` の場合(例: `GOSUB` は 4)、ファイル内で同じキーワードが複数回現れる分をすべて連結して返します。
**無効キーワード(`A`、`AND`、`OR`、`XOR`、`BASIC` など)は `return 0`** でヘルプなし扱いになります。
Sources: [help.c:27-130]()
### 5.3 エディタからの呼び出し
```c
// editor.c:2515
unsigned char *helptext = get_help(searchtext);
```
F3 キー押下時にカーソル位置のキーワードが `get_help()` に渡され、戻り値が画面に表示されます。
---
## 6. バイナリターゲットとモジュール配置
CMakeLists.txt は 2 つのバイナリを生成します。
```text
┌────────────────────────────────────┬─────────────────────────────────────┐
│ ${CODE_NAME}_kb.uf2 │ ${CODE_NAME}.uf2 │
│ (USB キーボードモード) │ (USB シリアル / PC 接続モード) │
├────────────────────────────────────┼─────────────────────────────────────┤
│ withkeyboard.c │ withoutkeyboard.c │
│ interface/${KEYBOARD}.c │ pcconnect.c │
│ interface/hid_virtual_keytable.c │ │
│ interface/keyinput.c │ │
│ tinyusb_host (USB HID ホスト) │ stdio_usb 有効 │
├────────────────────────────────────┴─────────────────────────────────────┤
│ 共通: shared_files / video_files / wifi_files / aux_files │
└──────────────────────────────────────────────────────────────────────────┘
```
`_kb` バイナリは `pico_enable_stdio_usb = 0`(USB シリアル無効)、無印バイナリは `pico_enable_stdio_usb = 1`(USB シリアル有効)です。`pcconnect.c` は無印バイナリにのみリンクされます。
Sources: [CMakeLists.txt:120-160]()
---
## 7. 新規ライブラリ追加 — 開発者向けチェックリスト
```text
[ ] auxcode/auxcode.c の適切なスタブ関数にキーワード判定を追加する
- BASIC ステートメント → aux_statements()
- 整数返し関数 → aux_int_functions()
- 文字列返し関数 → aux_str_functions()
- 浮動小数点返し関数 → aux_float_functions()
- ランタイム処理 → lib_aux()
[ ] 認識できなかったキーワードは必ず ERROR_STATEMENT_NOT_DETECTED を返す
[ ] ランタイム用コードを call_lib_code(LIB_AUXCODE) で emit する場合、
r0/r1/r2 に渡す引数の規約を組み込みライブラリ (library.c) に合わせる
[ ] auxcode/auxcode.cmake に新しいソースファイルを追記する
[ ] MACHIKANIA_DEBUG_MODE でのテストでは SD カードが不要。
debug_files[] にテスト用コードを追加して動作確認できる
[ ] ヘルプ辞書への対応: /docs/help-e.txt (または help-k.txt) に
新キーワードとそのタブインデント説明行を追記する
```
---
## まとめ
Phyllosoma の拡張ポイントは**コンパイラレイヤー**と**ランタイムレイヤー**の 2 段構えになっています。コンパイラ側の `aux_statements` / `aux_*_functions` 群はすべての組み込み処理の後に呼ばれるフォールバックフックであり、`auxcode/auxcode.cmake` を差し替えることで独立した CMake ライブラリとして管理できます。`hexfile.c` はファームウェア更新を BASIC エディタと統合する仕組みを提供し、`pcconnect.c` は 16 バイト固定長コマンドによるシンプルな PC ↔ デバイス間ファイル転送プロトコルを実装しています。`debug.c` の `MACHIKANIA_DEBUG_MODE` は FatFS を完全にインメモリ実装へ差し替えることで、SD カードなしの CI テストを可能にします。`help.c` はハッシュベースのキーワード照合とグループ展開により、外部テキストファイルから柔軟なヘルプ表示を実現しています。
Sources: [auxcode/auxcode.c:8-21](), [CMakeLists.txt:98-115](), [hexfile.c:135-143](), [pcconnect.c:107-120](), [debug.h:28-68](), [help.c:13-22]()
---