mz25key インターフェース 開発者ガイド

mz25key 開発者ガイド

本ガイドは、mz25keyファームウェアのソースコード、開発環境、ビルドシステムの詳細な解説です。ソフトウェアアーキテクチャ、全ソースモジュール、MZ-2500/MZ-2800キーボードプロトコルの詳細、キーマッピングシステムについて説明し、新しいホストコンピュータのサポート追加、キーマップの変更、継続的インテグレーションの設定方法を示します。
mz25keyインターフェースは、ESP32ベースのPS/2-MZ-2500/MZ-2800キーボードアダプタです。SharpKeyプロジェクトとコードベースを共有していますが、KconfigビルドオプションによりMZ-2500およびMZ-2800専用に構成されています。SharpKeyとは異なり、mz25keyはgitサブモジュールを使用しません — 必要なESP-IDFコンポーネントライブラリ(arduino-esp32およびesp_littlefs)はcomponents/ディレクトリに手動でクローンする必要があります。
ハードウェアの詳細、回路図、組立写真についてはmz25keyプロジェクトページを参照してください。マルチホスト対応の完全なコードベースに関するSharpKey開発者ガイドはSharpKeyファームウェアガイドを参照してください。

前提条件

mz25keyファームウェアをビルドおよびフラッシュするには、動作する開発環境が必要です。ネイティブESP-IDFインストールと、Docker(後述のDockerビルドセクション参照)の2つのアプローチがあります。

ネイティブツールチェーン
  • ESP-IDF v4.4 — Espressif IoT Development Framework。PS/2ライブラリが使用するArduino互換コンポーネントがこのバージョンを対象としているため、v4.4が必要です(v5.xではありません)。Espressif v4.4入門ガイドからインストールしてください。
  • Python 3.7+ — ESP-IDFビルドシステムに必要です。Linuxではupdate-alternativesを使用してpython3が最新バージョンを指すようにしてください。
  • Git — リポジトリおよびコンポーネントライブラリのクローンに必要です。
  • USB-TTL UARTアダプタ — フラッシュおよびモニタリング用です。自動リセット/プログラミングモード移行のため、DTRおよびRTSラインを備えた3.3Vアダプタを推奨します。

リポジトリとコンポーネントのクローン
重要: gitサブモジュールを使用するSharpKeyとは異なり、mz25keyリポジトリでは2つのコンポーネントライブラリをcomponents/に手動でクローンする必要があります。これが重要な違いです — この手順を省略すると、コンポーネント不足エラーでビルドが失敗します。
# Clone the mz25key repository
git clone https://git.eaw.app/eaw/mz25key.git
cd mz25key

# Clone required components manually (NOT submodules)
mkdir -p components

# Arduino-ESP32 compatibility layer (v2.0.3)
git clone https://github.com/espressif/arduino-esp32.git components/arduino-esp32
cd components/arduino-esp32 && git checkout 2.0.3 && cd ../..

# LittleFS filesystem support (v1.3.1)
git clone https://github.com/joltwallet/esp_littlefs.git components/esp_littlefs
cd components/esp_littlefs && git checkout v1.3.1 && git submodule update --init --recursive && cd ../..
クローン後、ディレクトリ構成は以下のようになります:
mz25key/
  components/
    arduino-esp32/          ← Arduino互換レイヤー (v2.0.3)
    esp_littlefs/           ← LittleFS ファイルシステム (v1.3.1)
  main/
    SharpKey.cpp            ← エントリポイント
    MZ2528.cpp              ← MZ-2500/2800 ホストインターフェース
    HID.cpp                 ← PS/2 HID処理
    ...
  webserver/
    version.txt             ← バージョンファイル(プロジェクトルートではない)
    html/                   ← Webインターフェースファイル
  Kconfig                   ← ビルド設定
  sdkconfig                 ← デフォルトビルド設定
  build_webfs.sh            ← Webファイルシステムビルドスクリプト
  CMakeLists.txt            ← CMakeプロジェクトファイル

Docker代替方法
ESP-IDFをネイティブにインストールしたくない場合は、Espressif公式Dockerイメージを使用できます。詳細な手順はDockerビルドセクションを参照してください。Dockerアプローチは、CI/CDパイプラインでも使用されています。

ソフトウェアアーキテクチャ

mz25keyファームウェアは、SharpKeyと同じソフトウェアアーキテクチャを共有しています。FreeRTOS上に構築されたデュアルコアESP32アプリケーションで、2つのCPUコアがそれぞれ異なる役割を担います:

アーキテクチャ概要
 ┌───────────────────────────────────────────────────────────────────────────┐
 │                        ESP-32S (Dual Core 240MHz)                        │
 │                                                                         │
 │  ┌─────────────────────────────┐  ┌──────────────────────────────────┐   │
 │  │       CORE 0 (PRO)         │  │        CORE 1 (APP)              │   │
 │  │   FreeRTOS スケジュール     │  │   スピンロック (FreeRTOS無し)     │   │
 │  │                            │  │                                  │   │
 │  │  ┌──────────────────────┐  │  │  ┌────────────────────────────┐  │   │
 │  │  │ PS/2 キーボード入力  │  │  │  │  MZ-2500/2800 インター     │  │   │
 │  │  │  (PS2KeyAdvanced)    │  │  │  │  フェース (MZ2528 クラス)  │  │   │
 │  │  └─────────┬────────────┘  │  │  │                            │  │   │
 │  │            │               │  │  │  - RTSN ストローブ監視     │  │   │
 │  │  ┌─────────▼────────────┐  │  │  │  - KDB[3:0]から行を読取   │  │   │
 │  │  │ Bluetooth キーボード │  │  │  │  - KDO[7:0]で列データ出力  │  │   │
 │  │  │  (BTHID クラス)      │  │  │  │  - KD4/MPXタイミング処理  │  │   │
 │  │  └─────────┬────────────┘  │  │  └────────────┬───────────────┘  │   │
 │  │            │               │  │               │                  │   │
 │  │  ┌─────────▼────────────┐  │  │               │                  │   │
 │  │  │ HID処理              │  │  │               │                  │   │
 │  │  │  PS/2 → 内部         │  │  │  ┌────────────▼───────────────┐  │   │
 │  │  │  スキャンコード       │  │  │  │  仮想スキャンマトリクス     │  │   │
 │  │  └─────────┬────────────┘  │  │  │  14行 × 8列               │  │   │
 │  │            │               │  │  │  (MZ-2800は15行)           │  │   │
 │  │  ┌─────────▼────────────┐  │  │  └────────────────────────────┘  │   │
 │  │  │ MZ2528::mapKey()     │──┼──┤                                  │   │
 │  │  │  内部 → マトリクス   │  │  │                                  │   │
 │  │  └──────────────────────┘  │  │                                  │   │
 │  │                            │  │                                  │   │
 │  │  ┌──────────────────────┐  │  │                                  │   │
 │  │  │ WiFi / Web サーバー  │  │  │                                  │   │
 │  │  │ OTA / NVS / LED      │  │  │                                  │   │
 │  │  └──────────────────────┘  │  │                                  │   │
 │  └─────────────────────────────┘  └──────────────────────────────────┘   │
 └───────────────────────────────────────────────────────────────────────────┘

         PS/2 キーボード                     MZ-2500 / MZ-2800
         ┌─────────┐                         ┌───────────────┐
         │ CLK/DAT │◄────── GPIO ──────────►│ RTSN/KDB/KDO  │
         └─────────┘                         │ KD4/MPX       │
                                             └───────────────┘

コアピニングとFreeRTOSタスク
ESP32のデュアルコアは以下のように使用されます:
  • Core 0 (PROコア) — 通常のタスクスケジューリングでFreeRTOS上で動作します。PS/2キーボード入力(割り込み駆動)、Bluetooth HID、WiFi、Webサーバー、NVSストレージ、LED制御、およびキーマッピングロジックを処理します。複数のFreeRTOSタスクがこのコアで動作します。
  • Core 1 (APPコア) — スピンロックを使用してFreeRTOSから切り離されています。MZ-2500/MZ-2800本体からのRTSNストローブ信号を監視する永続的なタイトループを実行します。RTSNがアクティブになると、Core 1はKDB[3:0]から行番号を読み取り、仮想スキャンマトリクスから対応する列データを検索し、74LS257マルチプレクサを介してKDO[7:0]で出力します。このコアはナノ秒単位で応答する必要があるため、FreeRTOSスケジューリングによる割り込みを受けることができません。
  • </ul>
    データフロー
    PS/2キー押下がMZ-2500に到達するまでの完全なデータフロー:
    1. PS/2キーボードがクロック/データGPIOライン経由でメイクまたはブレークスキャンコードを送信します。
    2. PS2KeyAdvancedライブラリ(Core 0上で割り込み駆動)が生のPS/2プロトコルを標準化されたキーコードにデコードします。
    3. HID::hidProcess()がPS2KeyAdvancedコードを内部SharpKey HIDコードフォーマットに変換します。
    4. MZ2528::mapKey()がMZ-2500/MZ-2800マッピングテーブルで内部HIDコードを検索し、対応するスキャンマトリクスの行と列位置を決定します。
    5. 仮想スキャンマトリクス — メモリ内の14要素(MZ-2800は15要素)配列が更新されます:行バイト内の該当ビットがセット(キー押下)またはクリア(キー解放)されます。
    6. Core 1タイトループ — RTSNを継続的に監視します。本体が行を要求すると、Core 1はKDB[3:0]から行インデックスを読み取り、仮想マトリクスから対応するバイトをKDO[7:0]経由で74LS257マルチプレクサに出力します。マルチプレクサは、本体からのMPX信号により制御され、KD[3:0]での送信用に上位または下位ニブルを選択します。
    -------------------------------------------------------------------------------------------------------- ## ソースモジュール
    mz25keyファームウェアは、SharpKeyと同じ17のソースファイルを共有しています。すべてのソースファイルはmain/に配置されています。ビルド時のKconfig設定MZ25KEY_MZ2500またはMZ25KEY_MZ2800により、コンパイルするホストインターフェースが選択されます。
    | ソースファイル | 説明 | |-------------|-------------| | `SharpKey.cpp` | エントリポイント、FreeRTOSタスク作成、Core 1スピンロック設定 | | `MZ2528.cpp / .h` | MZ-2500/MZ-2800ホストインターフェース — スキャンマトリクス、GPIO、RTSNハンドラ | | `HID.cpp / .h` | PS/2 HIDキーコード処理および変換 | | `PS2KeyAdvanced.cpp / .h` | PS/2キーボードプロトコルドライバ(割り込み駆動) | | `PS2Mouse.cpp / .h` | PS/2マウスプロトコルドライバ | | `BT.cpp / .h` | Classic Bluetooth SPPインターフェース | | `BTHID.cpp / .h` | Bluetooth HIDキーボード/マウスインターフェース | | `MZB.cpp / .h` | MZ-80Bホストインターフェース(代替ターゲット) | | `X1.cpp / .h` | Sharp X1シリーズホストインターフェース(代替ターゲット) | | `X68K.cpp / .h` | Sharp X68000ホストインターフェース(代替ターゲット) | | `NVS.cpp / .h` | 設定永続化のための不揮発性ストレージ | | `LED.cpp / .h` | ステータスLED制御 | | `SWITCH.cpp / .h` | 物理スイッチ/ボタン入力処理 | | `WiFi.cpp / .h` | WiFiステーション/APモード、DHCP、mDNS | | `WebServer.cpp / .h` | 設定およびOTA用HTTPウェブサーバー | | `KeyMap.cpp / .h` | LittleFSからのバイナリキーマップファイル読込 | | `Kconfig` | ビルド時設定定義 |
    SharpKey.cpp — エントリポイント
    SharpKey.cppにはESP-IDFのエントリポイントであるapp_main()関数が含まれています。以下の初期化シーケンスを実行します:
    1. 永続設定用のNVSフラッシュストレージを初期化します。
    2. LEDおよびスイッチのGPIOピンを初期化します。
    3. ホストインターフェースオブジェクトを作成します — mz25keyの場合、これはKconfigによりコンパイル時に選択されるMZ2528インスタンスです。
    4. 設定されたGPIOピンでPS/2キーボードドライバ(PS2KeyAdvanced)を初期化します。
    5. 設定で有効になっている場合、オプションでBluetooth HIDを初期化します。
    6. WiFiスイッチが有効な場合、オプションでWiFiとWebサーバーを初期化します。
    7. キーボード処理、Bluetooth、WiFiなどのFreeRTOSタスクを作成し、すべてCore 0にピン留めします。
    8. portMUXスピンロックを使用してCore 1をFreeRTOSから切り離し、ホストインターフェースのタイトループ — MZ2528::run() — を起動します。このループはリターンしません。
    MZ2528.cpp — MZ-2500/MZ-2800 ホストインターフェース
    これはmz25keyにとって最も重要なモジュールです。MZ2528クラスはMZ-2500およびMZ-2800のキーボードプロトコルを実装し、仮想スキャンマトリクスを管理します。
    14x8 スキャンマトリクス
    MZ-2500キーボードは14行×8列のスキャンマトリクスです(MZ-2800はネイティブ80286モード用の追加キー用に15行目を追加)。MZ2528クラスはこのマトリクスをバイト配列として保持します:
    // Virtual scan matrix — one byte per row, 8 bits per column
    // 0xFF = no keys pressed in this row (active LOW logic)
    uint8_t scanMatrix[15];  // 14 rows for MZ-2500, 15 for MZ-2800
    
    // Row 0:  F1-F5, NUM, GRAPH, KANA
    // Row 1:  F6-F10, HOME/CLR, semicolons, colon
    // Row 2:  1-8
    // Row 3:  9, 0, -, ^, YEN, TAB, Q, W
    // Row 4:  E, R, T, Y, U, I, O, P
    // Row 5:  @, [, RETURN, A, S, D, F, G
    // Row 6:  H, J, K, L, ;, :, ], SPACE
    // Row 7:  Z, X, C, V, B, N, M, ,
    // Row 8:  ., /, _, ↓, ←, →, ↑, DEL/BS
    // Row 9:  ROLLUP, ROLLDOWN, INS, CLR, cursor keys (alt)
    // Row 10: Tenkey 0-7
    // Row 11: Tenkey 8-9, *, +, -, /, ., =
    // Row 12: CTRL, SHIFT, LOCK, KANA_LOCK, GRAPH_LOCK
    // Row 13: Japanese transform keys
    // Row 14: MZ-2800 specific keys (80286 mode only)
    RTSNストローブプロトコルハンドラ
    run()メソッドはCore 1上のタイトループで、RTSN GPIOピンをポーリングします。クリティカルパスは以下の通りです:
    void MZ2528::run() {
        // Spinlock — Core 1 is fully detached from FreeRTOS
        while(1) {
            // Wait for RTSN rising edge (main unit sending row)
            while(!(REG_READ(GPIO_IN_REG) & RTSN_BIT)) { }
    
            // Read row number from KDB[3:0] — valid ~160ns after RTSN rises
            uint8_t row = (REG_READ(GPIO_IN_REG) >> KDB_SHIFT) & 0x0F;
    
            // Sample KD4 to determine mode
            uint8_t kd4 = (REG_READ(GPIO_IN_REG) & KD4_BIT) ? 1 : 0;
    
            if(kd4) {
                // Normal mode: output column data for requested row
                REG_WRITE(GPIO_OUT_REG, scanMatrix[row] << KDO_SHIFT);
            } else {
                // STROBEALL mode: output logical AND of all rows
                REG_WRITE(GPIO_OUT_REG, strobeAllData << KDO_SHIFT);
            }
    
            // Wait for RTSN falling edge
            while(REG_READ(GPIO_IN_REG) & RTSN_BIT) { }
        }
    }
    パフォーマンスに関する注意: ArduinoのdigitalRead()/digitalWrite()関数の代わりに、直接レジスタアクセス(REG_READ/REG_WRITE)を使用しています。ArduinoのGPIO関数は大きなオーバーヘッド(関数呼び出し、パラメータ検証、ピンマッピング検索)を追加し、約300nsのタイミングウィンドウを超過する可能性があります。直接レジスタアクセスは240MHzで1クロックサイクル(約4ns)で完了します。
    Core 1 スピンロック
    Core 1はportMUX_TYPEスピンロックを使用してFreeRTOSから切り離されています。これにより、FreeRTOSがCore 1でタスクをスケジューリングしたり割り込みを処理したりすることが防止され、RTSN監視ループがジッターゼロで動作することが保証されます。スピンロックはMZ2528::run()を呼び出す前にapp_main()で取得され、解放されることはありません:
    // In app_main(), after all Core 0 tasks are created:
    portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
    portENTER_CRITICAL(&mux);      // Disables interrupts, prevents scheduling
    hostInterface->run();           // Never returns
    portEXIT_CRITICAL(&mux);       // Never reached
    ALT+F1/F2/F3 ホットキー
    MZ2528モジュールは、実行時に異なるキーボードレイアウトを切り替えるためのホットキーの組み合わせをサポートしています:
    • ALT+F1 — MZ-2500キーボードレイアウト(MZ25KEY_MZ2500ビルドのデフォルト)
    • ALT+F2 — MZ-2000キーボードレイアウト(旧ソフトウェア用互換レイアウト)
    • ALT+F3 — MZ-80Bキーボードレイアウト(MZ-80Bモード互換用)
    これらのホットキーは、LittleFSファイルシステムに保存された対応するバイナリキーマップファイルからキーマッピングテーブルをリロードし、ファームウェアを再書込みすることなくレイアウトを切り替えることができます。
    その他のホストインターフェース
    共有コードベースには、Kconfigで選択可能な他のSharpコンピュータ用ホストインターフェースモジュールが含まれています:
    • MZB.cpp — Sharp MZ-80B(アクティブストローブマトリクス、直接互換プロトコル)
    • X1.cpp — Sharp X1シリーズ(シリアルプロトコル、アクティブLOWデータ)
    • X68K.cpp — Sharp X68000(シリアルプロトコル、マウスサポート付きアクティブLOWデータ)
    mz25keyビルドでは、MZ2528モジュールのみがコンパイルされます。Kconfigのターゲットを変更することで、異なるSharpコンピュータ用にハードウェアを再利用したい場合は、他のモジュールを使用できます。
    入力デバイスモジュール
    • HID.cpp — 中央HID処理ハブ。PS/2またはBluetoothからキーイベントを受信し、内部スキャンコードに正規化し、アクティブなホストインターフェースのmapKey()メソッドにディスパッチします。モディファイアキーの追跡(Shift、Ctrl、Alt、GUI)とロック状態(Caps Lock、Num Lock、Scroll Lock)を処理します。
    • PS2KeyAdvanced.cpp — 割り込み駆動のPS/2キーボードプロトコルハンドラ。双方向クロック/データプロトコルをデコードし、メイクコード(キー押下)、ブレークコード(キー解放)、拡張コード(E0/E1プレフィックス付きキー)を処理します。GPIO割り込みでCore 0上で動作します。
    • PS2Mouse.cpp — PS/2マウスプロトコルハンドラ。3バイト(標準)および4バイト(IntelliMouse)マウスパケットをデコードします。マウス入力をサポートするホストインターフェース(X68000)で使用されます。MZ2528インターフェースでは積極的には使用されません。
    • BT.cpp — Classic Bluetooth SPP(Serial Port Profile)インターフェース。Bluetoothシリアルデバイスとのペアリングを可能にします。
    • BTHID.cpp — Bluetooth HIDインターフェース。Bluetooth HIDキーボードおよびマウスとのペアリングを可能にし、PS/2コネクタに代わるワイヤレスの選択肢を提供します。キーイベントはPS/2イベントと同じHID処理パイプラインに送られます。
    サポートモジュール
    • NVS.cpp — 永続設定用のESP-IDF不揮発性ストレージ(NVS)APIをラップします。WiFi認証情報、アクティブなキーマップ選択、Bluetoothペアリングデータ、その他のユーザー設定を電源サイクル間で保存します。
    • LED.cpp — mz25key PCB上のステータスLEDを制御します。電源、キーアクティビティ、WiFiステータス、Bluetoothペアリング状態の視覚的フィードバックを提供します。異なる状態用の点滅パターンをサポートします。
    • SWITCH.cpp — mz25key PCB上の物理WiFi有効スイッチを読み取ります。スイッチがON位置にある場合、起動時にWiFiとWebサーバーが初期化されます。
    • WiFi.cpp — WiFi接続を管理します。ステーションモード(既存APへの接続)とAPモード(設定用ホットスポットの作成)の両方をサポートします。DHCPクライアント、mDNSアドバタイズメント、接続リトライロジックを含みます。
    • WebServer.cpp — 設定Webインターフェースおよびアップデートエンドポイントを提供するHTTPサーバー。LittleFSファイルシステムからHTML/CSS/JSファイルを配信します。キーマップ編集、設定、ファームウェアアップロード用のREST APIエンドポイントを公開します。
    • KeyMap.cpp — LittleFSファイルシステムからバイナリキーマップファイルを読込み管理します。各キーマップファイルは、特定のキーボードレイアウト用のPS/2-ホストマッピングテーブルのバイナリ表現です。
    -------------------------------------------------------------------------------------------------------- ## MZ-2500/MZ-2800 キーボードプロトコル
    このセクションでは、Sharp MZ-2500およびMZ-2800コンピュータで使用されるキーボードプロトコルの詳細な説明を提供します。このプロトコルの理解は、MZ2528ホストインターフェースモジュールを変更する際に不可欠です。
    スキャンマトリクスの概念
    MZ-2500キーボードは14行×8列のスキャンマトリクスを使用しています。MZ-2800はこれを15行に拡張しています(行14には80286ネイティブモード固有のキーが含まれます)。本体のゲートアレイは、キーボードに行アドレスを送信し、各行の列データを読み返すことで、マトリクスを継続的にスキャンします。押されたキーは、その行の対応する列ビットをLOWに引き下げます。
    キーボードマトリクスは、本体とキーボード間の4ビット双方向バス上でシリアライズされます。本体が行番号(4ビット、0-13/14)を送信し、キーボードが8ビットの列データを2つの4ビットニブルとして返します。1つの完全な行スキャンには約1.2usかかります — 本体が約600nsで行を送信し、約300nsずつの2つのニブルを読み返します。
    GPIO信号
    ハードウェアインターフェースは7つの信号と5VおよびGNDで構成されます:
    | 信号 | 方向 | 説明 | |--------|-----------|-------------| | RTSN (Row Strobe) | 本体 -> キーボード | HIGH: 行アドレス送信中。LOW: キーボードがデータ送信中。 | | KD4 (Type Strobe) | 本体 -> キーボード | HIGH: 実際の行データを返す。LOW: STROBEALL(全行のAND)を返す。 | | MPX (Nibble MUX) | 本体 -> キーボード | HIGH: 上位ニブル選択。LOW: 下位ニブル選択。 | | KDB[3:0] (双方向バス) | 双方向 | RTSN HIGH: 4ビット行番号を搬送(本体 -> キーボード)。RTSN LOW: 4ビット列データを搬送(キーボード -> 本体)。 | | KDO[7:0] (列出力) | キーボード -> MUX | 74LS257マルチプレクサへの全8ビット列データ出力(mz25key固有 — オリジナルキーボードはKD[3:0]を直接使用)。 |
    mz25key GPIO マッピングに関する注意: mz25key実装では、ESP32はKDO[7:0]経由で8ビットの列データすべてを同時に74LS257クワッド2:1マルチプレクサに出力します。本体からのMPX信号がマルチプレクサを直接制御して上位または下位ニブルを選択します。これにより、ESP32がMPXタイミングに応答する必要がなくなります — RTSNが下がる前に全バイトを出力するだけで済みます。
    タイミングダイアグラム
      MZ-2500 Key Data Retrieval Protocol (one row scan cycle):
    
                     ┌────────── 660ns ──────────┐┌────────── 680ns ──────────┐
                     │                            ││                           │
            ┌────────┘                            └┘                           └────────┐
      RTSN  │   HIGH (row address phase)          │    LOW (data return phase)          │
            │                                     │                                     │
            │  ┌─ 160ns ─┐                        │                                     │
            │  │         │                        │                                     │
      KDB   │  │  ROW VALID                      │ ┌── nibble hi ──┐┌── nibble lo ──┐  │
      [3:0] │  │  (row 0-13)                     │ │   KDO[7:4]    ││   KDO[3:0]    │  │
            │  │                                  │ │               ││               │  │
            │                                     │                                     │
      KD4   ──── HIGH (row data mode) ──────────── HIGH ────────────────────────────────
            │                                     │                                     │
      MPX   ──────────────────────────────────────┘ ┌── 320ns ──┐┌── 320ns ──┐          │
            │                                       │   HIGH    ││   LOW     │          │
            │                                       │ upper nib ││ lower nib │          │
            └───────────────────────────────────────────────────────────────────────────┘
    
      STROBEALL Mode (idle, waiting for key press):
    
            ┌── 660ns ──┐┌── 660ns ──┐┌── 660ns ──┐
      RTSN  │   HIGH    ││   LOW     ││   HIGH    │ ... repeating
            │           ││           ││           │
      KD4   ── LOW ──────────────────────────────── (AND of all columns)
            │                                     │
      KDB   │  row = 4 (ignored)                 │  AND data returned via nibbles
            │                                     │
    
    キーデータ取得: STROBEALLでキー押下が検出されると、本体はKD4をHIGHに切り替え、特定の行のスキャンを開始します。まずデバウンス用に行11(特殊キー:CTRL、SHIFT、LOCK、KANA、GRAPH)を約30usプローブし、次に行12(日本語変換キー)を約50usスキャンし、続いて行0から行13(MZ-2800では行14)まで順次スキャンします。押されたキーを含む行が見つかると、その行はデバウンス検証のために600us以上スキャンされ、その後順次スキャンが続行されます。
    STROBEALLモード: キーが押されていない場合、KD4はLOWに保持されます。本体は連続して行4を送信します(これは無視されます — 値はハードワイヤードされているようです)。このモードでは、キーボードは各列の全行の論理ANDを返します — いずれかの列のキーが押されていると、対応するANDビットがLOWになり、キーデータ取得モードへの切り替えがトリガーされます。
    MZ-2500 と MZ-2800 の違い
    MZ-2500とMZ-2800は同じ物理信号を共有していますが、重要なプロトコルの違いがあります:
    • タイミング: MZ-2800はセットアップ時間が長くなっています — KD4はRTSNに対して少なくとも200ns遅延し、KD[3:0]行データはRTSNに対して少なくとも650ns遅延します(MZ-2500の160nsと比較)。ESP32は行番号を読み取る前により長く待つ必要があります。
    • 行14: MZ-2800はネイティブ80286モードで、80286オペレーティングシステム固有のキーを含む追加の15行目(行14)をスキャンします。MZ-2500互換モードでは、MZ-2800はMZ-2500と同じく行0-13のみをスキャンします。
    • デバウンスタイミング: MZ-2800はMZ-2500(約600us)と比較して短いデバウンス期間(初回約100us、リピート約300us)を使用します。
    • コネクタ: MZ-2500は8ピンmini-DINコネクタを使用します。MZ-2800は廃番のAMP 9ピンD-Subコネクタを使用します(3M 10120-6000L/10220-5212PL互換の代替品が使用できます)。
    • STROBEALL行: MZ-2500はSTROBEALL中に行4を送信し、MZ-2800は行0を送信します。いずれの場合も、STROBEALLモードではキーボードは行値を無視します。
    これらの違いは、MZ25KEY_MZ2500 / MZ25KEY_MZ2800 Kconfigオプションによってファームウェア内で処理されます。このオプションはタイミング定数を調整し、MZ-2800ビルドでは15行目を有効にします。
    -------------------------------------------------------------------------------------------------------- ## キーマッピング
    キーマッピングシステムは、PS/2キーボードスキャンコードをMZ-2500/MZ-2800スキャンマトリクス位置に変換します。これは複数の変換ステップを含む多段階プロセスです。
    変換パイプライン
      PS/2 Scan Code        Internal HID Code        MZ Scan Matrix Code
      ┌──────────────┐      ┌──────────────┐         ┌──────────────┐
      │ Make: 0x1C   │ ──►  │ Code: 0x41   │ ──►     │ Row: 5       │
      │ Break: 0xF0  │      │ (letter 'A') │         │ Col: 3       │
      │        0x1C  │      │              │         │ Bit: 0x08    │
      └──────────────┘      └──────────────┘         └──────────────┘
    
      Stage 1: PS2KeyAdvanced    Stage 2: HID         Stage 3: MZ2528::mapKey()
      (interrupt-driven)         (normalisation)      (matrix lookup)
    
    MZ2528 マッピングテーブル
    MZ2528::mapKey()メソッドは165エントリのマッピングテーブルを使用します。各エントリは内部HIDコードをスキャンマトリクスの行(MZ-2500は0-13、MZ-2800は0-14)と列ビット位置(0-7)のペアにマッピングします。テーブルにはメイク(キー押下)とブレーク(キー解放)の両方の処理が含まれます — メイク時は対応する行バイトの列ビットがクリアされ(アクティブLOW)、ブレーク時はビットがセットされます。
    マッピングテーブルの代表的な一部:
    | PS/2 キー | 内部コード | MZ 行 | MZ 列 | MZ キー | |----------|---------------|--------|--------|--------| | A | 0x41 | 5 | 3 | A | | B | 0x42 | 7 | 4 | B | | C | 0x43 | 7 | 2 | C | | 1 | 0x31 | 2 | 0 | 1 | | F1 | 0x70 | 0 | 0 | F1 | | F5 | 0x74 | 0 | 4 | F5 | | RETURN | 0x0D | 5 | 2 | RETURN | | SPACE | 0x20 | 6 | 7 | SPACE | | LEFT SHIFT | 0xE1 | 12 | 1 | SHIFT | | LEFT CTRL | 0xE0 | 12 | 0 | CTRL | | KANA | (mapped) | 12 | 3 | KANA | | GRAPH | (mapped) | 12 | 4 | GRAPH | | UP ARROW | 0x26 | 8 | 6 | cursor up | | DOWN ARROW | 0x28 | 8 | 3 | cursor down | | DEL/BS | 0x08 | 8 | 7 | DEL/BS | | TAB | 0x09 | 3 | 5 | TAB | | ESC | 0x1B | — | — | (MZ-2500には存在しない) |
    レイアウト切替 (ALT+F1/F2/F3)
    ALT+F1、ALT+F2、ALT+F3ホットキーは、LittleFSファイルシステム上のプリロード済みキーマップファイルを切り替えます:
    • ALT+F1/keymap_mz2500.bin — MZ-2500標準レイアウト
    • ALT+F2/keymap_mz2000.bin — MZ-2000互換レイアウト
    • ALT+F3/keymap_mz80b.bin — MZ-80B互換レイアウト
    アクティブなキーマップ選択はNVSに保存され、起動時に復元されるため、選択したレイアウトは電源サイクル間で永続します。
    バイナリキーマップファイル
    キーマップファイルはLittleFSファイルシステムパーティション上にバイナリblobとして保存されます。各ファイルにはヘッダの後にパックされたバイナリ形式の165マッピングエントリが含まれます。KeyMapモジュールはこれらのファイルを読み込み、MZ2528::mapKey()が使用するマッピングテーブルを構築します。
    Web UIキーマップエディタ
    WiFiが有効な場合、Webインターフェースはhttp://<device-ip>/keymapでアクセス可能なビジュアルキーマップエディタを提供します。エディタはPS/2キーボードレイアウトとMZ-2500/MZ-2800キーボードレイアウトを並べて表示し、PS/2キーをクリックしてからマッピング先のMZキーをクリックすることができます。変更はREST API経由でLittleFS上のアクティブなキーマップファイルに保存されます。
    -------------------------------------------------------------------------------------------------------- ## 新しいホストコンピュータの追加
    SharpKey/mz25keyコードベースは、共通インターフェースクラスを通じて複数のホストコンピュータをサポートするように設計されています。新しいホストコンピュータのサポートを追加するには、以下の手順に従ってください:
    1. キーボードプロトコルを調査する — ターゲットコンピュータのキーボードインターフェースのタイミングダイアグラム、信号説明、スキャンマトリクスレイアウトを取得またはリバースエンジニアリングします。この手順にはロジックアナライザが不可欠です。
    2. 新しいソースモジュールを作成するmain/NEWHOST.cppmain/NEWHOST.hを追加します。クラスは共通ホストインターフェースの基底クラスを継承し、最低限以下を実装する必要があります:
      • init() — ホストインターフェース信号用のGPIO設定。
      • mapKey(uint16_t code, bool makeBreak) — 内部HIDコードをホストのスキャンマトリクスまたはシリアルフォーマットに変換。
      • run() — ホストプロトコルのタイミングを処理するCore 1タイトループ。
    3. Kconfigエントリを追加するKconfigで、ホストインターフェースメニューの下に新しい選択肢オプション(例:NEWHOST_TARGET)と、新しいインターフェース信号用のGPIOピン設定オプションを追加します。
    4. SharpKey.cppを更新するapp_main()に、対応するKconfigオプションが選択されたときに新しいクラスをインスタンス化する#ifdefブロックを追加します。
    5. キーマップファイルを作成する — 新しいホスト用のマッピングテーブルを定義し、バイナリキーマップファイルを生成します。Webファイルシステムビルドに追加します。
    6. CMakeLists.txtを更新する — 新しいソースファイルをCOMPONENT_SRCSリストに追加します。
    7. テスト — ビルド、フラッシュし、ロジックアナライザを使用して実際のハードウェアで正しいタイミングを確認します。
    既存のモジュール(MZ2528、MZB、X1、X68K)がリファレンス実装として使用できます。MZ2528モジュールは双方向バスプロトコルの厳しいタイミング要件のため最も複雑であり、X1モジュールはよりシンプル(単方向シリアル)で、出発点として適しているかもしれません。
    -------------------------------------------------------------------------------------------------------- ## キーマップの更新
    mz25keyファームウェアでキーマッピングを変更する方法は3つあります:
    方法1: Web UI(推奨)
    最も簡単な方法はWebベースのキーマップエディタを使用することです:
    1. mz25key PCBのWiFiスイッチを有効にして、デバイスの電源を入れます。
    2. mz25key WiFiホットスポットに接続するか、ネットワーク上のIPアドレスを確認します。
    3. Webブラウザでhttp://<device-ip>/keymapにアクセスします。
    4. リマップしたいPS/2キーをクリックし、次にターゲットのMZ-2500キーをクリックします。
    5. 「Save」をクリックして、更新されたマッピングをLittleFSファイルシステムに書き込みます。
    6. </ol>
      方法2: ソースコード
      恒久的な変更の場合は、MZ2528.cppのマッピングテーブルを直接編集します。keyMapTable[]配列を見つけてエントリを変更します。各エントリは以下の構成です:
      // Format: { internalCode, mzRow, mzCol }
      // Example: map internal code 0x41 ('A') to MZ-2500 row 5, column 3
      { 0x41, 5, 3 },
      編集後、ファームウェアを再ビルドして再フラッシュします。
      方法3: バイナリキーマップファイル
      バイナリキーマップファイルを作成または変更し、LittleFSファイルシステムにアップロードできます:
      1. REST API経由で現在のキーマップをエクスポートします:GET /api/keymap
      2. マッピングテーブルのJSON表現を編集します。
      3. 変更したキーマップをアップロードします:POST /api/keymap(JSONボディ付き)。
      4. WebサーバーがJSONをバイナリ形式に変換し、LittleFSに書き込みます。
      -------------------------------------------------------------------------------------------------------- ## Webインターフェース
      mz25keyファームウェアには、ブラウザベースの設定インターフェースを提供する組込みWebサーバーが含まれています。WebサーバーはFreeRTOSタスクとしてCore 0上で動作し、PCB上のWiFiスイッチがON位置にある場合のみアクティブになります。
      Webページ
      Webインターフェースは以下のページを提供します:
      • / — ファームウェアバージョン、稼働時間、WiFiステータス、接続されたキーボード情報を表示するダッシュボード。
      • /keymap — PS/2-MZキーマッピングを変更するためのビジュアルキーマップエディタ。
      • /config — WiFi設定(SSID、パスワード、静的IP)、Bluetooth設定、LEDオプション。
      • /update — ファイルアップロードフォーム付きのOTAファームウェアおよびファイルシステム更新ページ。
      • /info — システム情報:ESP32チップリビジョン、フラッシュサイズ、空きヒープ、パーティションテーブル。
      REST API
      Webサーバーはプログラムによるアクセス用のREST APIエンドポイントを公開しています:
      | エンドポイント | メソッド | 説明 | |----------|--------|-------------| | `/api/keymap` | GET | 現在のキーマップをJSONとして取得 | | `/api/keymap` | POST | 新しいキーマップをアップロード(JSONボディ) | | `/api/config` | GET | 現在の設定を取得 | | `/api/config` | POST | 設定を更新 | | `/api/update/firmware` | POST | OTA更新用ファームウェアバイナリをアップロード | | `/api/update/filesystem` | POST | OTA更新用ファイルシステムイメージをアップロード | | `/api/info` | GET | システム情報 | | `/api/reboot` | POST | デバイスを再起動 |
      build_webfs.sh
      Webインターフェースファイル(HTML、CSS、JavaScript、バイナリキーマップのデフォルト値)は、build_webfs.shスクリプトによりLittleFSファイルシステムイメージにパッケージされます。このスクリプトは:
      1. webserver/html/ディレクトリからすべてのファイルを読み込みます。
      2. HTML、CSS、JavaScriptファイルを圧縮してフラッシュ使用量を削減します。
      3. バイナリキーマップファイルをファイルシステムにコピーします。
      4. mklittlefsツールを使用してLittleFSイメージ(build/filesys.bin)を生成します。
      Webインターフェースファイルを変更した場合は、ファームウェアのビルド前にこのスクリプトを実行する必要があります:
      chmod +x build_webfs.sh
      ./build_webfs.sh
      -------------------------------------------------------------------------------------------------------- ## OTAアップデート
      mz25keyファームウェアは、Webインターフェース経由のOTA(Over-The-Air)アップデートをサポートしています。これにより、UARTプログラミングヘッダへの物理的アクセスなしにファームウェアおよびファイルシステムの更新が可能です。
      デュアルバンクパーティション方式
      ESP32フラッシュは、2つのアプリケーションバンク(OTA_0とOTA_1)、ブートローダー、パーティションテーブル、OTAデータパーティション、LittleFSデータパーティションに分割されています:
      Flash Layout:
      ┌──────────────────────────────┐  0x000000
      │ Bootloader (0x8000 bytes)    │
      ├──────────────────────────────┤  0x008000
      │ Partition Table              │
      ├──────────────────────────────┤  0x009000
      │ OTA Data                     │
      ├──────────────────────────────┤  0x010000
      │ OTA_0 (Application Bank 0)   │
      ├──────────────────────────────┤
      │ OTA_1 (Application Bank 1)   │
      ├──────────────────────────────┤
      │ LittleFS (Filesystem)        │
      ├──────────────────────────────┤
      │ NVS (Non-Volatile Storage)   │
      └──────────────────────────────┘
      OTAアップデート中、新しいファームウェアは非アクティブバンクに書き込まれます。書込みが完了しSHA-256チェックサムで検証されると、OTAデータパーティションが新しいバンクを指すように更新され、デバイスは更新されたファームウェアで再起動します。新しいファームウェアが起動に失敗した場合、ブートローダーは自動的に前のバンクにロールバックします。
      アップデートプロセス
      1. Webブラウザでhttp://<device-ip>/updateにアクセスします。
      2. 「Firmware」または「Filesystem」のアップデートタイプを選択します。
      3. .binまたは.bin.gzファイルを選択します(gzip圧縮ファイルがサポートされており、転送速度向上のため推奨されます)。
      4. 「Upload」をクリックします — Webサーバーがファイルを受信し、非アクティブフラッシュバンクに書込み、チェックサムを検証し、デバイスを再起動します。
      5. デバイスが新しいファームウェアで再起動します。Webインターフェースのダッシュボードに更新されたバージョン番号が表示されます。
      -------------------------------------------------------------------------------------------------------- ## Bluetooth統合
      mz25keyファームウェアにはオプションのBluetooth HIDサポートが含まれており、PS/2コネクタの代わりに(または併用して)ワイヤレスBluetoothキーボードを入力ソースとして使用できます。Bluetoothはビルド時にKconfigで有効にします。
      ペアリングプロセス
      1. idf.py menuconfig経由でファームウェアビルドのBluetoothを有効にします。
      2. mz25keyインターフェースの電源を入れます — LEDがペアリングパターンで点滅します。
      3. Bluetoothキーボードをペアリングモードにします。
      4. mz25keyインターフェースが自動的にキーボードを検出してペアリングします(HIDデバイスにはPINは不要です)。
      5. ペアリングが完了すると、LEDが安定したパターンを表示します。ペアリング情報はNVSに保存され、以降の電源投入時にキーボードが自動的に再接続されます。
      制限事項
      • Bluetoothは、PS/2有線接続と比較して約5-15msのレイテンシが追加されます。タイピングでは感知できませんが、アクションの速いゲームでは気になる場合があります。
      • BluetoothとWiFiはESP32の無線ハードウェアを共有しています。両方を同時に実行すると、スループットが低下しレイテンシが増加する可能性があります。
      • Bluetoothがアクティブな場合、消費電力が大幅に増加します(PS/2のみの約30mAに対して約80mA)。ホストコンピュータのキーボードポートが十分な電流を供給できることを確認してください。
      • 一度にペアリングできるBluetoothキーボードは1台のみです。
      -------------------------------------------------------------------------------------------------------- ## eFuse設定
      ESP32にはワンタイムプログラマブルのeFuseビットバンクが含まれており、ハードウェア固有の設定を保存するために使用できます。mz25keyファームウェアはカスタムeFuseフィールドを機能ゲーティングに使用しています — ハードウェアレベルで機能の有効化/無効化を行うことで、同じファームウェアバイナリを異なるハードウェアリビジョン間で使用できます。
      カスタムeFuseフィールド
      mz25key Jenkinsパイプラインには、eFuseフィールド定義を処理する「Generate eFuse Table」ステージが含まれています。カスタムeFuseフィールドには以下が含まれます:
      • ハードウェアリビジョン — PCBリビジョン(v1.0、v1.1、v1.2)を識別し、ボードリビジョン間で異なるGPIOマッピングの調整や機能の有効化/無効化をファームウェアが行えるようにします。
      • 機能フラグ — オプションのハードウェア機能(OLEDディスプレイの有無、外部マルチプレクサの実装、Bluetoothアンテナタイプなど)を有効にする個別ビットです。
      • ターゲットマシン — 目的のホストコンピュータ(MZ-2500またはMZ-2800)を識別し、Kconfigコンパイル時設定に加えてハードウェアレベルのオーバーライドを提供します。
      警告: eFuseビットはワンタイムプログラマブルです — 一度1にセットすると、クリアすることはできません。誤ったeFuse値のプログラミングは不可逆であり、デバイスが動作不能になる可能性があります。プログラミング前に必ずeFuse値を確認してください。
      -------------------------------------------------------------------------------------------------------- ## ビルド環境
      ネイティブビルド (ESP-IDF v4.4)
      ネイティブビルドには、開発マシンにESP-IDF v4.4がインストールされている必要があります。前提条件セクションに従ってリポジトリとコンポーネントをクローンした後:
      # Source the ESP-IDF environment (adjust path to your installation)
      . ~/esp/esp-idf/export.sh
      
      # Navigate to the mz25key directory
      cd mz25key
      
      # Build the web filesystem (required on first build or after web file changes)
      chmod +x build_webfs.sh && ./build_webfs.sh
      
      # Build the firmware
      idf.py build
      
      # Build output:
      #   build/main.bin                           - Firmware binary (for OTA update)
      #   build/filesys.bin                        - Web filesystem image (for OTA update)
      #   build/bootloader/bootloader.bin          - Bootloader
      #   build/partition_table/partition-table.bin - Partition table
      #   build/ota_data_initial.bin               - OTA data partition
      フラッシュとモニタリング:
      # Connect USB-to-TTL UART adapter to programming header
      # Find the device: ls /dev/ttyUSB*
      
      # Build, flash, and monitor in one command
      idf.py -p /dev/ttyUSB0 build flash monitor
      
      # Press Ctrl+] to exit the monitor
      Kconfig / menuconfig
      ファームウェアはESP-IDF Kconfigシステムを使用してビルド時に設定されます。設定メニューにアクセスするにはidf.py menuconfigを実行します。mz25key固有の主要オプションはMZ25Key Configurationメニューの下にあります:
      • MZ-2500/2800 インターフェース
        • ターゲットマシン: MZ25KEY_MZ2500またはMZ25KEY_MZ2800 — 使用するプロトコルバリアントと行数を選択します。
        • ストローブ入力GPIO (RTSN): 本体からのRTSN信号に接続されたGPIOピン。
        • スキャンデータ出力GPIO (KDO[7:0]): 74LS257マルチプレクサへの列データ出力用の連続する8つのGPIOピン。
        • 行入力GPIO (KDB[3:0]): 本体からの行アドレスを読み取るための4つのGPIOピン。
        • KD4入力GPIO: KD4(Type Strobe)信号用のGPIOピン。
      • PS/2 キーボード
        • Clock GPIO: PS/2クロック信号用のGPIOピン。
        • Data GPIO: PS/2データ信号用のGPIOピン。
        • キーボードタイプ: PS/2キーボードモデル選択(例:Wyse KB-3926)。
      • WiFi — SSID、パスワード、静的IP、APモード設定。
      • Bluetooth — Bluetooth HIDサポートの有効化/無効化。
      • デバッグ — OLEDディスプレイ出力、UARTログレベル、コンポーネント無効化フラグ。
      デフォルトのsdkconfigがリポジトリに含まれています。ターゲットマシン、GPIOピン割当て、その他のオプションを変更したい場合のみmenuconfigを実行する必要があります。
      Webファイルシステムビルド
      webserver/html/内のファイルを変更した場合やキーマップファイルを追加/変更した場合は、ファームウェアの前にWebファイルシステムをビルドする必要があります:
      chmod +x build_webfs.sh
      ./build_webfs.sh
      
      # Output: build/filesys.bin
      # This is flashed to the LittleFS partition during 'idf.py flash'
      # or uploaded via OTA as the "Filesystem" update type.
      -------------------------------------------------------------------------------------------------------- ## Dockerによるビルド
      完全なESP-IDFツールチェーンをネイティブにインストールする代わりに、Espressif公式Dockerイメージを使用できます。Dockerのインストールのみが必要で、ツールチェーン、コンパイラ、ビルドシステム全体がコンテナ内で動作します。これはCI/CDおよびESP-IDFをシステム全体にインストールしたくない開発者に推奨される方法です。
      重要: mz25keyファームウェアはArduino互換モジュールサポートのためにESP-IDF v4.4が特に必要です(v5.xではありません)。
      # Install Docker if not already present
      sudo apt update && sudo apt install -y docker.io
      sudo systemctl enable docker && sudo systemctl start docker
      
      # Clone the repository
      git clone https://git.eaw.app/eaw/mz25key.git
      cd mz25key
      
      # The mz25key repo requires ESP-IDF component libraries to be installed manually.
      # Clone the required components:
      mkdir -p components
      git clone -b 2.0.3 https://github.com/espressif/arduino-esp32.git components/arduino-esp32
      git clone -b v1.3.1 https://github.com/joltwallet/esp_littlefs.git components/esp_littlefs
      
      # Build the web filesystem
      chmod +x build_webfs.sh && ./build_webfs.sh
      
      # Build the firmware using the ESP-IDF v4.4 Docker container
      docker run --rm -v $PWD:/project -w /project espressif/idf:v4.4 idf.py build
      
      # Build output:
      #   build/main.bin                         - Firmware binary (for OTA update)
      #   build/filesys.bin                      - Web filesystem image (for OTA update)
      #   build/bootloader/bootloader.bin        - Bootloader
      #   build/partition_table/partition-table.bin - Partition table
      #   build/ota_data_initial.bin             - OTA data partition
      Docker経由でmz25keyインターフェースにファームウェアをフラッシュするには(USB-TTL UARTアダプタの接続が必要):
      # Flash and monitor (requires USB device access)
      docker run --rm --privileged \
          --volume /dev:/dev --volume /sys:/sys:ro --volume /dev/bus/usb:/dev/bus/usb \
          -v $PWD:/project -w /project \
          espressif/idf:v4.4 \
          idf.py -p /dev/ttyUSB0 build flash monitor
      便利なように、Dockerコマンドを簡略化するシェルエイリアスを作成できます:
      # Add to ~/.bashrc or ~/.zshrc
      alias idf44='docker run --rm --privileged --volume /dev:/dev --volume /sys:/sys:ro \
          --volume /dev/bus/usb:/dev/bus/usb -v $PWD:/project -w /project -it \
          espressif/idf:v4.4 idf.py "$@"'
      
      # Then use:
      idf44 build
      idf44 menuconfig
      idf44 -p /dev/ttyUSB0 flash monitor
      -------------------------------------------------------------------------------------------------------- ## 継続的インテグレーション (Jenkins)
      CI/CDとは? 継続的インテグレーション(CI)は、コード変更をプッシュするたびに専用サーバーが自動的にプロジェクトをビルドするプラクティスです。ファームウェアを手動でコンパイルしてリリースファイルを手作業でアップロードする代わりに、CIサーバーがリポジトリをクローンし、ファームウェアをビルドし、リリース成果物をパッケージし、ダウンロードページに公開します — すべて自動的に。ビルドが壊れた場合は、すぐにメール通知を受け取ります。
      mz25keyプロジェクトは、VPS上で実行されるJenkinsを使用して、masterブランチへのプッシュごとにESP32ファームウェアを自動ビルドします。ビルドにはEspressif IDF v4.4 Dockerイメージを使用するため、サーバーにネイティブESP-IDFインストールは不要です。
      動作の仕組み
      1. コードをプッシュします — Giteaリポジトリのmasterブランチに。
      2. GiteaがWebhookを送信します — JenkinsサーバーへのHTTP通知。
      3. Jenkinsがリポジトリをクローンします。
      4. Jenkinsがコンポーネントを取得します — arduino-esp32 (v2.0.3)とesp_littlefs (v1.3.1)をcomponents/にクローンします。これは、クローン中に自動的に取得されるgitサブモジュールを使用するSharpKeyとの重要な違いです。
      5. Jenkinsがバージョンを決定します — webserver/version.txtを読み取ります(注意:SharpKeyで使用されるプロジェクトルートのversion.txtではなく、webserver/version.txtです)。
      6. JenkinsがWebファイルシステムをビルドします — build_webfs.shを実行します。
      7. JenkinsがeFuseテーブルを生成します — ターゲットハードウェア用のカスタムeFuseフィールド定義を処理します。
      8. Jenkinsがファームウェアをビルドします — espressif/idf:v4.4 Dockerコンテナを起動し、idf.py buildを実行します。
      9. Jenkinsがパッケージします — 3つのリリース成果物:
        • mz25key-FW-v*.bin.gz — Webインターフェース経由のOTA更新用ファームウェアバイナリ
        • mz25key-FilePack-v*.bin.gz — OTA更新用Webファイルシステムイメージ
        • mz25key-FlashPack-v*.tar.gz — esptool経由の新規プログラミング用完全フラッシュイメージ
      10. JenkinsがGiteaリリースを作成します — ダウンロード可能なアセットとして成果物を添付します。
      11. Jenkinsがメールを送信します — 成功または失敗を報告します。
      サーバーセットアップ
      Jenkinsサーバーのセットアップは、FusionXおよびSharpKeyプロジェクトと共有されています。Docker、docker-compose.yml、初期設定、プラグインインストールを含む完全なJenkinsインストール手順については、FusionX開発者ガイド — 継続的インテグレーションセクションを参照してください。
      mz25keyビルドに必要な追加要件はEspressif IDF v4.4 Dockerイメージのみです:
      # Pull the ESP-IDF v4.4 Docker image on the build server
      docker pull espressif/idf:v4.4
      パイプラインステージ
      Jenkinsパイプライン(mz25key-build.groovy)は以下のステージを実行します:
      1. Checkout: Giteaからリポジトリをクローンします。
      2. Fetch Components: arduino-esp32 (v2.0.3)とesp_littlefs (v1.3.1)をcomponents/にクローンします。このステージはmz25key固有のもので、SharpKeyは代わりにgitサブモジュールを使用します。
      3. Determine Version: webserver/version.txtを読み取るか、最新のGiteaリリースタグから自動インクリメントします。
      4. Build Web Filesystem: build_webfs.shを実行してHTML、CSS、JavaScriptをパッケージします。
      5. Generate eFuse Table: ターゲットハードウェア用のカスタムeFuseフィールド定義を処理します。このステージもmz25key固有のものです。
      6. Build Firmware: espressif/idf:v4.4 Dockerコンテナを起動し、idf.py buildを実行します。
      7. Package Release: バージョン付きのファームウェア、ファイルシステム、フラッシュパックアーカイブを作成します。
      8. Create Gitea Release: Giteaにタグ付きリリースを作成し、すべての成果物をアップロードします。
      Gitea Webhook
      Giteaリポジトリで、Settings -> Webhooks -> Add Webhook -> Giteaに移動し、以下を設定します:
      • Target URL: http://your-server:8080/generic-webhook-trigger/invoke?token=mz25key-build-trigger
      • Content Type: application/json
      • Trigger On: Push Events
      保存後、masterにコミットをプッシュしてJenkinsを確認します — 新しいビルドが自動的に表示されるはずです。
      SharpKey CIパイプラインとの違い
      mz25key CIパイプラインはSharpKeyと以下の点で異なります:
      • Fetch Componentsステージ: mz25keyはarduino-esp32とesp_littlefsを明示的にcomponents/にクローンします。SharpKeyは代わりにgit submodule update --init --recursiveを使用します。
      • Generate eFuse Tableステージ: mz25keyにはeFuse設定テーブルを生成する追加のパイプラインステージが含まれています。このステージはSharpKeyパイプラインには存在しません。
      • バージョンファイルの場所: mz25keyはwebserver/version.txtからバージョンを読み取りますが、SharpKeyはプロジェクトルートのversion.txtから読み取ります。
      • 成果物の命名: リリース成果物はmz25key-プレフィックスを使用します(例:mz25key-FW-v1.0.bin.gz)。
      • Webhookトークン: mz25key-build-triggerを使用します(sharpkey-build-triggerではありません)。
      -------------------------------------------------------------------------------------------------------- ## デバッグのヒント
      mz25keyインターフェースのデバッグには、シリアルモニタリング、ロジック解析、体系的なテストの組み合わせが必要です。以下に一般的なデバッグシナリオのヒントを示します。
      UARTシリアルモニター
      ESP32は115200ボーでUART経由で診断メッセージを出力します。USB-TTL UARTアダプタをプログラミングヘッダに接続し、IDFモニタを使用します:
      idf.py -p /dev/ttyUSB0 monitor
      
      # Or with Docker:
      docker run --rm --privileged --volume /dev:/dev --volume /sys:/sys:ro \
          -v $PWD:/project -w /project -it espressif/idf:v4.4 \
          idf.py -p /dev/ttyUSB0 monitor
      モニタは起動メッセージ、PS/2スキャンコード、キーマッピング結果、WiFi接続状態、エラーメッセージを表示します。ログレベルはidf.py menuconfigComponent config -> Log output -> Default log verbosityで調整できます。
      OLEDディスプレイ
      I2C OLEDディスプレイ(128x64、SSD1306)がプログラミングヘッダに接続されている場合、仮想スキャンマトリクスをリアルタイムで表示できます。これはPS/2キーが正しい行/列位置にマッピングされていることを確認するために非常に有用です。menuconfigのMZ25Key Configuration -> Debug -> OLED Displayで有効にします。
      ロジックアナライザ
      ロジックアナライザは、MZ-2500/MZ-2800インターフェースのプロトコルタイミング問題をデバッグするために不可欠です。RTSN、KD4、MPX、KDB[3:0]信号にプローブを接続します。確認すべき重要な点:
      • RTSN応答時間: ESP32はRTSNが下がる前に行番号を読み取り、列データを出力する必要があります(約660nsのウィンドウ)。応答が遅すぎると、本体は無効なデータを読み取ります。
      • MPXタイミング: 74LS257マルチプレクサが正しいニブルを選択していることを確認します。上位ニブルはMPXがHIGHになってから20ns以内に出力され、下位ニブルはMPXがLOWになったときに出力されるべきです。
      • KD4サンプリング: MZ-2800では、KD4はRTSNに対して最大200ns遅延します。ファームウェアがKD4をサンプリングする前に十分に待機していることを確認してください。
      • 行データの有効性: MZ-2800では、KDB[3:0]はRTSNに対して最大650ns遅延します。ファームウェアが行番号を読み取る前に十分に待機していることを確認してください。
      • STROBEALL応答: KD4がLOWの場合、全行のANDが正しく返されていることを確認します。不正なSTROBEALL応答はキー検出を妨げます。
      MZ-2500/MZ-2800固有の問題
      • 本体からの応答がない: KM-24ケーブルが8ピンmini-DIN(MZ-2500)またはD-Sub(MZ-2800)コネクタに正しく配線されていることを確認してください。ピン番号付けに注意してください — mini-DINの番号付けはプラグ面からではなく、はんだ面からです。
      • キーは検出されるが文字が違う: PS/2-MZマッピングテーブルに不正なエントリがあります。UARTモニタを使用してどの内部HIDコードが生成されているかを確認し、そのコードのマッピングテーブルをチェックしてください。または、Webキーマップエディタを使用してマッピングを修正してください。
      • キーが断続的に検出されない: タイミングの問題の可能性があります。ESP32が列データを十分速く出力できていない可能性があります。Core 1ループにレイテンシを追加する可能性のあるコード(ログ、条件分岐)がないか確認してください。MZ-2800では、より長いセットアップ時間が考慮されていることを確認してください。
      • モディファイアキー(SHIFT/CTRL)が動作しない: 行12にモディファイアキーが含まれています。仮想マトリクスでモディファイアキーの行ビットが正しく設定されていることを確認してください。また、STROBEALL AND値がモディファイアキーの状態を正しく反映していることも確認してください。
      • MZ-2800モードキーが動作しない: ビルドターゲットがMZ25KEY_MZ2800であり、行14がスキャンされていることを確認してください。MZ-2800のMZ-2500互換モードでは、行14はスキャンされません — これは正しい動作です。
      • MZ-2500では動作するがMZ-2800では動作しない: MZ-2800プロトコルは異なるタイミングを持っています — KD4とKDB[3:0]はRTSNに対して大幅に遅延します。タイミング定数を調整するMZ25KEY_MZ2800ターゲットでビルドしていることを確認してください。
      • ESP32コアパニック/リブートループ: 通常、FreeRTOSタスクのスタックオーバーフローまたはNULLポインタ参照が原因です。UARTモニタにパニックメッセージとレジスタダンプが表示されます。必要に応じてmenuconfigでスタックサイズを増やしてください。
      コンポーネント無効化フラグ
      menuconfigのデバッグメニューには、分離テスト用に個々のコンポーネントを無効化するフラグがあります:
      • MZ-2500インターフェース無効化: Core 1ループは動作しますがデータを出力しません — PS/2入力を単独でテストするのに便利です。
      • PS/2入力無効化: HID処理は動作しますがキーイベントを生成しません — 仮想マトリクスに固定テストパターンを使用してCore 1ループとプロトコルをテストするのに便利です。
      • WiFi無効化: 消費電力を削減し、Core 0からWiFiタスクスケジューリングのオーバーヘッドを除去します。
      • Bluetooth無効化: Bluetoothタスクスケジューリングのオーバーヘッドを除去します。
      -------------------------------------------------------------------------------------------------------- ## 参考サイト --- ## 無線規制に関する注意事項
      本デバイスは 2.4 GHz ISM バンドで送信する事前認証済み ESP32-S 無線モジュール(AI Thinker)を搭載しており、世界各国の無線周波数規制(米国の FCC Part 15 Subpart C、欧州連合の無線機器指令 2014/53/EU を含む)において意図的放射器に該当します。
      ESP32-S モジュールは無改造で使用されており、独自の規制認証(FCC、CE など)を取得しています。ただし、そのモジュールレベルの認証は、モジュールを組み込んだ完成品に自動的に適用されるものではありません。事前認証モジュールの免除規定は、個人の趣味愛好家個人使用、実験、または教育目的で少数のデバイスを製作する場合に、個別の機器認可を取得せずに行うことを許可するものです。
      重要な制限事項
      • 組み立てられたデバイスは、完成品が独自にテストされ、該当する管轄区域で機器認可(例:FCC ID、認定機関による CE マーキング評価)を取得しない限り、第三者への販売、販売の申し出、贈与、またはその他の方法での配布を行ってはなりません
      • 個人使用のために少数を製作することは、趣味愛好家および実験使用の規定(例:FCC § 15.23)に基づき、デバイスが有害な干渉を引き起こさない限り、一般的に許可されています。
      • 規制要件は国によって異なります。米国外の製作者は、適用される規則について自国の無線周波数当局に確認してください。
      製作者の責任
      本設計に基づいて製作されたデバイスが、管轄区域内の適用されるすべての無線周波数規制に準拠することは、製作者の単独の責任です。著者は本設計を個人使用、教育、および趣味愛好家向けに提供しており、本設計から製作されたデバイスが商業的配布の規制要件を満たすことについて、いかなる表明も行いません。