チップの上で — ファームウェアはC、画面は定義で
6 分チップを作る会社はチップだけを売らない。チップと一緒に 評価ボードを出す。「うちのチップはこういうことができます」と手に握らせる小さな基板だ。
そのボードに画面を付けた瞬間、仕事が大きくなる。グラフィックスライブラリを載せ、フォントを入れ、タッチドライバを繋ぎ、レイアウトを組み、その全部をフラッシュに焼く。ボタンの位置ひとつ変えるのにまた焼く。だからたいていの評価ボードは 画面無しで出て行き、画面が要る顧客は別の会社を探す。
この記事はそれを違うやり方でやる。ボードに GUI を載せない。 代わりにボードが二つを差し出す — 自分にできることの一覧と、それを見せる画面の定義。描くのは外だ。
机の上の WeAct STM32H723 ボードで実際にやった。
ボードが差し出すもの
USB シリアルで繋いで問い合わせると、こう答える。
[serial] connected via serial_bridge /dev/cu.usbmodem365D395E33331 115200
[serial] tools: led.set, sys.info
[serial] resources: ui://app, ui://app/info, bundle://manifest.json道具二つ、リソース三つ。 これがこのボードの表面の全部だ。
道具はこういう形をしている。
{"tools":[
{"name":"led.set","description":"Turn the on-board LED on or off",
"inputSchema":{"type":"object","properties":{"on":{"type":"boolean"}},"required":["on"]}},
{"name":"sys.info","description":"Report LED state and uptime",
"inputSchema":{"type":"object","properties":{}}}]}led.set と sys.info。このボードはほかにもできることが多い — フラッシュを消し、クロックを変え、ブートローダに落ちられる。その能力は一覧に無く、一覧に無ければ呼ぶ名が無い。
画面もボードが持っている
ui://app を読むと画面定義が出てくる。
[serial] ui://app — 656 B, type "page", title "WeAct H723 MCP Node"656 バイト。 画面ひとつでその程度だ。クライアントにはこのボード用の UI コードが一行も無い — 受け取って描く。

押せばボードが反応する
画面のボタンが led.set を呼ぶ。実際の往復だ。
[serial] led.set({"on":true}) -> "LED on" (1 ms)
[serial] sys.info({}) -> "LED=on uptime=205491208ms" (10 ms)
[serial] led.set({"on":false}) -> "LED off" (10 ms)
[serial] sys.info({}) -> "LED=off uptime=205491232ms" (11 ms)
[serial] uptime advanced 205491208 -> 205491232 ms机の上のボードの LED が点いて消えた。 そして最後の行を見てほしい — uptime が 205491208 から 205491232 へ、24 ミリ秒前進している。あらかじめ入れておいた答えではなく、その瞬間ボードが数えていた値だ。録画した応答なら二度問えば同じ数字が出る。
往復は 1〜11 ミリ秒。UART の上だからだ。
同じクライアントが別のチップにも繋がる
これがこの構造の値打ちだ。同じコードで Wi-Fi 越しの ESP32 に繋いだ。
[tcp] connected via tcp_bridge mcp-esp32.local 6270
[tcp] tools: led.set, sys.info
[tcp] ui://app — 158 B, type "application", title "ESP32 MCP Node"
[tcp] application — initialRoute "/" -> ui://page/main
[tcp] ui://page/main — 1894 B
[tcp] led.set({"on":true}) -> "LED on" (89 ms)
[tcp] uptime advanced 67004328 -> 67004391 ms
別会社のチップ、別の伝送、同じクライアント。 変わったのはブリッジプログラムひとつだ。
そして二台のボードで画面の構造が違う。STM32 はページひとつ(type: "page"、656 B)を、ESP32 はルートを持つアプリケーション(type: "application"、158 B + ページ 1894 B)を差し出す。ボードが自分の画面の形を自分で決めている。
遅延は違う。UART が 1〜11 ms、Wi-Fi TCP が 17〜89 ms。桁が違い、その差が UI 設計を分ける — 一桁の側は「押せばすぐ」を期待していいが、数十ミリ秒の側は待ち状態を画面に描かねばならない。
伝送はプログラムひとつだ
クライアントがシリアルを知らなくて済むようにした方法だ。伝送を 別プログラムに出した。
mcp_client ──stdio──▶ serial_bridge ──UART──▶ STM32H723
mcp_client ──stdio──▶ tcp_bridge ──TCP───▶ ESP32ブリッジは行単位の JSON をそのまま流す。シリアル側は cfmakeraw で端末加工を切り、TCP 側は TCP_NODELAY で短い要求がバッファに溜まらないようにする。
/* 同じ規則:JSON の行だけがプロトコルだ。ボードが同じストリームに
* メモを打っても、クライアントが詰まってはいけない。 */
if (up[0] == '{') { printf("%s\n", up); fflush(stdout); }
else { fprintf(stderr, "[board] %s\n", up); }伝送を選ぶ仕事が、どのプログラムを起動するかに縮んだ。 クライアントは一バイトも変わらない。
作る途中でクライアントが仕様に違反した
実機に繋いだ途端 listResources() が即死した。
type 'Null' is not a subtype of type 'String'ボードがリソースを description 無しで返したのに、mcp_client 1.1.1 がそのフィールドを非 null にキャストしていた。MCP 仕様上 description は任意で、ボードが正しかった。 2.1.0 で既に直っていたので、そのバージョンに上げて解決した。
シミュレータだけで回していたら出なかった欠陥だ。自作のシミュレータなら必ず description を埋めて送っていただろうから。
検証
$ bash verify.sh
[1/6] bridges (C)
[2/6] serial board? /dev/cu.usbmodem365D395E33331 -> WeAct H723 MCP Node
[3/6] network board? (mDNS _mcp._tcp) ESP32 MCP Node -> mcp-esp32.local:6270
[4/6] probe + real render
[5/6] checks
2 board(s) · 2 transports · LED round-tripped on both · uptime advanced on bothこのサンプルにシミュレータは無い。ボードが繋がっていなければ どこを探したかを言って失敗する。
[ -n "$SERIAL" ] || echo " none found on /dev/cu.usbmodem* or /dev/cu.usbserial*"ハードウェア無しで通るハードウェア検証は検証ではないからだ。
このボードで何を売るのか
評価ボードに画面を付ける費用が、グラフィックススタック+再ビルドから 道具二つ+定義 656 バイトに変わった。
チップ会社にとってこれがなぜ違うのか。画面を付けるために認証済みのファームウェアを焼き直さなくていい。顧客が画面を変えたいと言ったときにファームウェアを出し直さなくていい。画面は外で描かれ、ボードは自分にできることだけを言う。
灌水コントローラでも温室ゲートウェイでも、繋ぐ側が変わってもボードの言うことは同じだ。
自分で動かす
cd serial_bridge && cc -O2 -o serial_bridge serial_bridge.c && cd ..
cd tcp_bridge && cc -O2 -o tcp_bridge tcp_bridge.c && cd ..
bash verify.sh # find board → connect → tools/resources → open it in AppPlayer持ち帰るもの
- ブリッジ二種 —
serial_bridge.c・tcp_bridge.c。そのままコンパイルすればどのクライアントにも繋がる - ボード側の最小応答 —
initialize・tools/list・resources/readの三つで画面が出る - ハードウェア検証スクリプト — ボードが無ければ通さない
verify.sh
自分のボードに合わせるには — ファームウェアの道具名と ui://app の JSON だけを自分のものに変える。ブリッジもクライアントも触らない。伝送が違えばブリッジを選ぶだけだ。
練習課題
例にデバイスをもうひとつ追加してください。変更したファイルをすべて挙げてください。ホストがその中にあってはいけません。