
RP2040 ファームウェア。Toshiba MK4001MTD 0.85インチ SDIO マイクロドライブを USB マスストレージデバイスとしてブリッジし、PIO アクセラレーションによる読み書きと不良セクタリカバリを備えた完全な SDIO-ATA プロトコルスタックをゼロから実装します。
東芝MK4001MTD 0.85インチSDIOマイクロドライブをUSBマスストレージデバイスとしてブリッジするRP2040 Picoファームウェア。

MK4001MTDは、フラッシュストレージがまだかなり高価だった時代に、ノキアN91ミュージックフォンや一部のMP3プレーヤー、USBドライブなどのデバイスで元々使用されていた4GBのマイクロドライブです。
このドライブがMMCプロトコルを使用していると主張する紹介を見たことがあるかもしれませんが、それは実際には間違いです。私はしばらく調査してきました:8ビットMMCplusカードリーダーを構築しようとし、さまざまなSD/MMCリーダーをテストしましたが成功しませんでした。最後の手段として、ノキアN91を購入してロジックトレースを取得し、実際に使用しているプロトコルを確認しました。
8ビットMMCPlusリーダーボードで使用しようとしたときの写真です。MMCではないことが判明しました:(

そこでN91を入手してトレースを収集しました:

標準のATA/CFマイクロドライブとは異なり、ATAコマンドをCMD52/CMD53でトンネリングするSDIOインターフェースを使用しています。既存のドライバーはこのプロトコルをサポートしていないため、このファームウェアはフルスタックをゼロから実装しています。
CE-ATAと呼ばれるSDIO-to-ATA規格が存在するため、これは驚きでした。しかし、リリースタイムラインをよく見ると、CE-ATAはこのドライブよりも後に登場しています。その結果、このドライブは完全にSDIOコマンドに依存しており、CE-ATAは利用できません。CE-ATAには2つの新しいコマンドCMD60/CMD61があり、CMD12/39を利用しますが、トレースからはそれらのいずれも使用していないことがわかります。
言及すべき2つ目のハードウェアのポイントは、8ビットMMCPlusカードであると主張する誤情報が広まっていることですが、それは虚偽であるだけでなく、ピン配置もMMC標準に従っていません。ノキアN91のサービスマニュアルにはピン配置に関するドキュメントがあります。ピン番号はMMCPlus標準に従っていますが、ピンのマッピングは異なります。これは自分で配線する場合の重要な詳細です。同じMMCコネクタを使用しますが、ピンマッピングが異なります。詳細はハードウェアセクションで。
最後に、これはClaude/OpenClawとの共同開発であることに注意してください。私が手動でロジックトレースを収集し、OpenClawが開発を反復するためのクローズドループテストステーションをセットアップしました(トレースの分析と機能の実装)。ドキュメントは主にClaudeによって書かれます。私もインラインでメモを追加します。ドキュメントは私自身が読み、再確認しており、信頼性が高くわかりやすいものになっているはずです。
N91トレースの分析に関する洞察は、/docs/N91_TRACE_ANALYSIS.mdにあります。生のロジックトレースとともにN91サービスマニュアルもそこに置いてあります。
詳細はブログ記事を参照: https://www.willwhang.dev/Reading-MK4001MTD/
動作の様子: https://youtu.be/GC4xil3_Bbc
PIOアクセラレーションによる読み取り/書き込みとアイドル時電力管理を備えた完全機能のUSBマスストレージ。
USB Host ←→ USB MSC (TinyUSB) ←→ ATA Layer ←→ SDIO Layer (PIO) ←→ MK4001MTD
ファームウェアは4つのレイヤーで構成されています:
USB MSC (msc_device.c) — TinyUSBマスストレージクラス。SCSI READ(10)/WRITE(10)をATAセクター操作に変換します。32KBのEPバッファ、USB転送あたり最大64セクターをバッチ処理。ドライブI/Oは双方向でUSBとオーバーラップしており、キャッシュディスクを備えた実際のATA-USBブリッジのように動作します:シーケンシャルリードプリフェッチャーは前のチャンクがホストにストリーミングされている間に次のチャンクを取得し、書き込みはUSBが次のピースを受信している間にステージングされてフラッシュされます。デバイスは書き込みキャッシュ(キャッシングモードページ、WCE=1 — ホストは「Write cache: enabled」を報告し、fsync/アンマウント/サスペンド時にSYNCHRONIZE CACHEを発行します。ファームウェアはこれを尊重します)をアドバタイズします。バックグラウンドフラッシュの失敗は、次のWRITEまたはSYNCHRONIZE CACHEでMEDIUM ERRORとして現れます。既知の不良セクタへの書き込みは、厳密な同期パスを取ります。
ATA-over-SDIO (ata_sdio.c) — CMD52を介してSDIOファンクション1アドレス空間にマッピングされたATAレジスタに書き込み、CMD53を介してセクタデータを転送することにより、ATAコマンド(IDENTIFY、READ SECTORS、WRITE SECTORS)を実装します。CMD、データ、ATAレベルの3層リトライロジック。
PIO SDIO (sdio_pio.c、sdio.pio) — RP2040のPIOペリフェラルを使用したハードウェアアクセラレーションSDIO(4ビットバス、10 MHz、入力シンクロナイザーをバイパスした1ビットあたり4 PIOサイクル)。3つのPIOプログラムが動的プログラムスワッピングを介して単一のステートマシンを共有します:
ピン/電源 (sdio_hw.c) — GPIO初期化とHDD電源制御。すべてのSDIO通信はPIOを使用します。
人間のメモ:興味深いことに、ClaudeはPIOでSDIOを実装することに非常に消極的で、PIOとビットバンギングの間を行き来する多くの開発サイクルが無駄になりました。
MK4001MTDは、1つのI/O機能を持つSDIOカードとして認識されます。標準のSDIOカード初期化(CMD5/CMD3/CMD7)でバスを設定し、その後SDIOコマンドを介してATAレジスタにアクセスします:
レジスタアクセス (CMD52): 各ATAレジスタはファンクション1のアドレスにマッピングされています:
データ転送 (CMD53): セクタデータは、DATAレジスタ(アドレス0x00)をターゲットにしてブロックモードでCMD53を発行することで転送されます。マルチセクタ読み取りの場合、block_count=Nの単一CMD53で、1回のSDIOマルチブロックトランザクションでN×512バイトを転送します。
割り込みシグナリング: ドライブは、SDIO割り込み(CCCRレジスタ0x05のINT_PENDINGビット1)をアサートすることでセクタの準備完了を通知します。ATA STATUSレジスタを読み取ると割り込みがクリアされます。
16セクターの読み取りの場合:
1. PIO CMD52経由でATAレジスタに書き込み:
SECCOUNT=16, LBA_LO/MID/HI, DEV/HEAD=0xE0, CMD=0x20
2. CMD52経由でSTATUSをポーリングし、DRQ (ビット3) がセットされるまで待機
3. PIOをDAT読み取りプログラムにスワップ
4. CMD53を送信:block_mode=1, fn=1, addr=0x0000, block_count=16
5. PIO DAT読み取り:16ブロックのそれぞれについて:
a. スタートビット(全DATラインLow)を待機
b. PIO RX FIFOからバッファに1024ニブル(512バイト)をDMA転送
c. SMがCRC+エンドニブルのクロック出力を完了するのを待機(SM PCをポーリング)
d. ニブルをインプレースでバイトに再パック
6. PIOをCMDプログラムにスワップバック
16セクターの書き込みの場合:
1. PIO CMD52経由でATAレジスタに書き込み:
SECCOUNT=16, LBA, DEV/HEAD=0xE0, CMD=0x30
2. CMD52経由でSTATUSをポーリングし、DRQ (ビット3) がセットされるまで待機
(STATUS 0xD8 = BSY+DRQはDRQ準備完了として扱う。N91トレースより)
3. PIOをDAT書き込みプログラムにスワップ
4. CMD53を送信:block_mode=1, fn=1, addr=0x0000, block_count=16
5. PIO DAT書き込み:16ブロックのそれぞれについて:
a. DATラインごとにCRC16-CCITTを事前計算(4つの独立CRC)
b. ニブルストリームを構築:start(0x0) + data(1024ニブル) + CRC(16) + end(0xF)
c. ニブルストリームをPIO TX FIFOにDMA転送
d. PIOがすべてのニブルをクロック出力した後:
- DATを入力に切り替え
- カードからのCRCステータス用に16サイクルクロック
- DAT0がカードのビジー解除までポーリング
- IRQ 0を発生させてブロック完了を通知
6. PIOをCMDプログラムにスワップバック
RP2040のPIOは、ブロックあたり32命令スロットを持っています。3つのプログラムは合計55命令であるため、共存できません。代わりに、PIO0上の単一のSM0を使用し、PIO命令メモリに直接書き込むことでプログラムをスワップします:
static void load_program_raw(const pio_program_t *program) {
for (uint i = 0; i < program->length; i++)
pio->instr_mem[FIXED_OFFSET + i] = program->instructions[i];
}
これにより、SDKのpio_add_program/pio_remove_programアロケータがバイパスされます。プログラムスワップには約1 µsかかります。各スワップの後、ピン割り当て、シフト方向、クロック分周器を設定するプログラム固有の再初期化が続きます。
ノキアN91のロジックトレース分析により、積極的な電力管理が明らかになりました:
ファームウェアはこの動作を設定可能なアイドルタイムアウトで再現します:
#define IDLE_STANDBY_MS 5000 // main.c内
HDDのパワーゲーティングをトリガーする2つのパス:
両方のパスでATA STANDBY IMMEDIATE (0xE0)を送信して書き込みキャッシュをフラッシュし、ヘッドをパークした後、GP9を介して電源を遮断します。
ウェイクシーケンス (ゲート後の最初のREAD/WRITEでトリガー):
マルチセクタ転送が不良セクタにヒットした場合:
STATUS/ERRORビットをキャプチャするのに十分な時間待機MEDIUM ERROR(読み取り:03/11/00、書き込み:03/0C/00)で失敗させるポイント6は理論上の話ではありません。このドライブにはLBA 1952に長期間読み取り不能なセクタがありました(READ: ST=0x51 ERR ERR=0x40 UNC)。ブリッジが実際に書き込みを到達できるようになると、ドライブはセクタを書き換え、それ以降はクリーンに読み戻せています:
[ATA] FAST-RD: ST=0x51 ERR ERR=0x40 UNC LBA=1952
[MSC] BAD SECTOR read LBA=1952
[MSC] Bad sector LBA=1952 repaired by write
arm-none-eabi-gcc)SDKバージョンは固定されています:PICO_SDK_PATHが設定されている場合(環境変数またはCMake変数)、それが使用され、そのバージョンが固定バージョンと照合されます — 不一致があると、指示とともに設定が失敗します(-DMK4001_ALLOW_SDK_MISMATCH=ONで上書き可能)。PICO_SDK_PATHがまったく設定されていない場合、固定されたSDKリリースが設定時にGitHubから自動的に取得されるため、git clone && cmake && makeだけで完全に再現可能です。
ファームウェアには、パッチが適用されたTinyUSB MSCクラスドライバが必要です(読み取り/書き込みエラー時にアプリケーションセンスデータを保持 + WCE=1のキャッシングモードページ)。そのファイルはこのリポジトリにベンダリングされており、lib/tinyusb_patched/msc_device.cにあります — ビルドは自動的にSDKのコピーではなくこのファイルをコンパイルするため、SDKの修正は一切不要です。アップストリームのTinyUSB(pico-sdk 2.2.0にバンドルされている0.18.0)との差分はlib/tinyusb_patched/にあります。SDKの固定は、このベンダリングされたファイルがSDKのTinyUSBを追跡する必要があるために正確に存在します。
cd /home/pi/mk4001_bridge/build
cmake ..
make -j4
sudo openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg \
-c "adapter speed 1000" -c "init" -c "reset halt" -c "sleep 200" \
-c "program /home/pi/mk4001_bridge/build/mk4001_bridge.elf verify" \
-c "reset run" -c "exit"
注: この特定のPicoユニットではGP0とGP1は使用できません。すべてのSDIOピン割り当ては+2シフトされています。
人間のメモ:Claudeはここで間違っていました。なぜなら、GP0とGP1がそのビルド構成でUARTターミナルに使用されていることに気づいていなかったからです。それを何度も忘れたため、SDIO GPIOをそのUARTから外すことにしました。
HDD_PWRは必須ではありません。ドライブの電源を入れ直さなくても使用できます。多くのものがハードコードされている開発時に、HDDをリセットするための便宜上のものです。とはいえ、電力節約が必要な場合はその信号を使用できますが、ウォームリセットは問題なく処理できます。
UART経由でデバッグメッセージが表示されます。これらはUSB-CDC経由ではありません。なぜなら、Claudeが開発初期に切断したり不安定になったりしない別のUART-USBロギングリンクを設定する方が簡単だったからです。
UARTログは、ドライブがアクティブな間、30秒ごとにドライブ温度も報告します([TEMP] drive temperature: 29 C)。このセンサーは、東芝ベンダーコマンド0xC2のリバースエンジニアリングによって発見されました — N91はすべてのドライブセッションの開始時にこのコマンドを読み取り、HDD動作温度制限を適用しています。詳細はdocs/N91_TRACE_ANALYSIS.md §4を参照してください。
ログの例:
========================================
MK4001MTD USB Bridge v0.11
SDIO-ATA → USB Mass Storage (PIO)
========================================
[MAIN] Pre-delay 5000ms...
[PIO] Init OK: clkdiv=3.12 (~10.0 MHz), CMD@0
[MAIN] Power cycling HDD...
[SDIO] HDD power OFF
[SDIO] HDD power ON
[MAIN] SDIO init (PIO)...
[SDIO] CMD5 ready (OCR=0x901F8000)
[SDIO] RCA=0x0001
[SDIO] fn1 ready (attempt 0)
[MAIN] ATA IDENTIFY...
[ATA] IDENTIFY complete
Model: [TOSHIBA MK4001MTD]
Serial: [ 763B004HA]
Firmware: [VH173A]
Sectors: 7862400 (3839 MB)
SMART: not supported (supported=0, enabled=0)
IDENTIFY: W0=0040 W47=0000 W49=0000 W59=0000
ATA W80=0000 Cmd W82=0000 W83=0000 W84=0000
En W85=0000 W86=0000 W87=0000 W89=0008 W128=0001
[DIAG] === Drive Diagnostics ===
[DIAG] Standard SMART: not supported (IDENTIFY W82 bit0 = 0)
[DIAG] Toshiba vendor CMD 0xC2:
FEAT=0x01 unknown_01 → SC=00 LBA=02/00/00 ST=50
FEAT=0x02 unknown_02 → SC=00 LBA=02/00/00 ST=50
FEAT=0x03 unknown_03 → SC=00 LBA=02/00/00 ST=50
FEAT=0x04 unknown_04 → SC=00 LBA=02/00/00 ST=50
FEAT=0x10 diag_10 (LBA_LO varies) → SC=00 LBA=00/00/00 ST=50
FEAT=0x11 diag_11 → SC=00 LBA=00/00/00 ST=50
FEAT=0x12 diag_12 (LBA_LO varies) → SC=00 LBA=01/00/00 ST=50
FEAT=0x20 query_20 (N91: SC=0xFF always) → SC=FE LBA=00/FF/00 ST=50
FEAT=0x21 query_21 (N91: SC varies per boot) → SC=1B LBA=00/FF/00 ST=50
[MAIN] MBR: valid 0x55AA
[MAIN] Warming up...
[MAIN] PIO OK, STATUS=0x50
[MAIN] Drive: 7862400 sectors (3839 MB)
[MAIN] Ready.
[PWR] Idle 5000ms → STANDBY + power gate
[PWR] STANDBY IMMEDIATE → power gate
[SDIO] HDD power OFF
最後に、実際のドライブへの配線です。
N91回路図からの切り抜きです。ピン番号もマッピングできます。

補足:このドライブは3V駆動ですが、3.3Vでも問題ないと思います。主にレベルシフトの作業を省くためです。
このドライブ専用に設計されたHWは、/hardwareにあります!

# デバイスが出現したか確認
lsblk -dno NAME,MODEL | grep MK4001
# ファイルシステムテスト — マウント、ファイルコピー、確認
sudo mount /dev/sdX1 /mnt/mk4001
cp /tmp/testfile /mnt/mk4001/
sync
md5sum /tmp/testfile /mnt/mk4001/testfile # 一致するはず
sudo umount /mnt/mk4001
# 速度ベンチマーク(rawデバイス。先にマウントしないこと — ファイルシステムを破損します)
# ファイルシステムの先の安全なオフセットか、パーティション化されていないドライブを使用
sudo dd if=/dev/sdX of=/dev/null bs=64k count=128 iflag=direct # 読み取り
sudo dd if=/dev/zero of=/dev/sdX bs=64k count=64 oflag=direct seek=1024 # 書き込み(FSの先のオフセット)
人間のメモ:面白い事実:最初に速度テストを始めたとき、実際にドライブに直接ddを実行してファイルシステムを破損しました... 開発中はそれほど問題にはなりませんが、OpenClawを扱うときは常にセットアップに注意してください。
気にしません。
| 指標 | 値 |
|---|
| 読み取り速度 | ~985 kB/s (USBフルスピード制限) |
| 書き込み速度 | ~920 kB/s (USBフルスピード制限、アドバタイズされた書き込みキャッシュ) |
| 生SDIO側速度 | ~2.35 MB/s 読み取り / ~2.15 MB/s 書き込み (ドライブ制限) |
| 容量 | 3.75 GB (7,862,400セクタ) |
| ファイルシステム | FAT32確認済み (マウント/アンマウント/fsck正常) |
| データ整合性 | 書き込み+読み戻し確認済み; 全4DATラインでブロックごとのCRC16 |
| アイドルスタンバイ | 5秒アイドルまたはUSBサスペンド → STANDBY IMMEDIATE + パワーゲート |
| アドレス | レジスタ | 用途 |
|---|
| 0x00 | DATA | セクタデータ用CMD53ターゲット |
| 0x01 | ERR/FEAT | エラー (読み取り) / フィーチャ (書き込み) |
| 0x02 | SECCOUNT | セクタ数 |
| 0x03 | LBA_LO | LBAビット0-7 |
| 0x04 | LBA_MID | LBAビット8-15 |
| 0x05 | LBA_HI | LBAビット16-23 |
| 0x06 | DEV/HEAD | デバイス/ヘッド + LBAビット24-27 |
| 0x07 | CMD/STATUS | コマンド (書き込み) / ステータス (読み取り) |
| Pico GPIO | 機能 | 備考 |
|---|
| GP2 | SDIO_CLK | ホストクロック出力 |
| GP3 | SDIO_CMD | 双方向コマンドライン |
| GP4 | SDIO_DAT0 | データビット0 |
| GP5 | SDIO_DAT1 | データビット1 |
| GP6 | SDIO_DAT2 | データビット2 |
| GP7 | SDIO_DAT3 | データビット3 |
| GP9 | HDD_EN | ドライブ電源有効 (HIGH=オン) |
| GP12 | UART TX | デバッグ出力 @ 115200 |
| GP13 | UART RX | デバッグ入力 |
| GP16 | LED: HDD電源 | アクティブロー |
| GP17 | LED: HDD正常 | アクティブロー |
| GP18 | LED: 読み取り | アクティブロー |
| GP19 | LED: 書き込み | アクティブロー |
| ファイル | 行数 | 目的 |
|---|
main.c | 210 | 初期化、アイドルスタンバイ、USBサスペンド/レジューム |
msc_device.c | 400 | USB MSCコールバック、パワーゲート起動、不良セクタキャッシュ |
ata_sdio.c | 390 | ATAコマンド、エラーリカバリ、ベンダーダイアグノスティクス |
sdio_pio.c | 635 | PIO SDIO:CMD52、CMD53読み取り/書き込み、プログラムスワップ、CRC16 |
sdio_hw.c | 45 | ピン初期化 + HDD電源制御 |
sdio.pio | 200 | PIOアセンブリ + C SDK初期化ヘルパー |
led.h | 37 | LEDヘルパー(GP16-GP19、アクティブロー) |
usb_descriptors.c | 77 | USBデバイス/コンフィグ/ストリングディスクリプタ |
tusb_config.h | 20 | TinyUSB設定(MSC、32KB EPバッファ) |
| バージョン | 読み取り | 書き込み | 主な変更 |
|---|
| v0.1–v0.3 | 105 kB/s | 93 kB/s | ビットバングSDIO、CRC16、リトライロジック |
| v0.5 | 374 kB/s | — | シングルSM PIO、直接命令メモリスワップ |
| v0.6 | 583 kB/s | 93 kB/s | マルチブロックCMD53読み取り、CRCクロックドレイン修正 |
| v0.8 | 588 kB/s | 274 kB/s | PIO書き込み、OSRフラッシュ修正 |
| v0.9 | 475 kB/s | 371 kB/s | 64セクタチャンク、CRC16読み取り検証 |
| v0.10 | 453 kB/s | 329 kB/s | LED再マッピング、HDD ENピン、UART on GP12/GP13 |
| v0.11 | ~450 kB/s | ~340 kB/s | HDDパワーゲート、PIO起動、不良セクタセンス、USBサスペンド |
| v0.12 | ~985 kB/s | ~920 kB/s | ドライブ/USBオーバーラップ(読み取りプリフェッチ + アドバタイズド書き込みキャッシュ+ライトビハインド)、パイプラインPIOブロック、bswap DMA、4サイクルPIOループ、SBCスタイルの不良セクタセマンティクス(書き込み修復)、ベンダリングTinyUSB MSCドライバ |