# 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 初期化設定を解説します。

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

## Source Files

- `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$` を基点に `adj` 秒を加えた時刻文字列 | ISO-8601 文字列 |
| `SETTIME t$` | ISO-8601 文字列で時刻をセット | — |
| `STRFTIME$(fmt$)` | `strftime` フォーマット文字列で現在時刻を整形 | 整形済み文字列 |
| `STRFTIME$(fmt$, t$)` | `t$` の時刻を `fmt$` で整形 | 整形済み文字列 |

`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 タイムスタンプの三インターフェースを提供します。
