はじめに
Xで、生成AIに3Dアニメーションを描かせて「どこまで表現できるか」を試している人をよく見かけます。あれの組み込み版をやってみました。
お題は組み込みで定番のプロトコル(UART / I2C / SPI)の波形アニメーションGIFで、Claudeの4モデルに同じプロンプトを渡して並べています。
結果を先に書くと、差が出たのはI2Cだけで、そこで1モデルが 「表示しているビット値は全部正しいのに、波形はプロトコル違反」 という間違い方をしました。とはいえプロトコルの基礎知識はどのモデルも持っており、あまり差がない結果となりました。
各モデル1回ずつ描かせただけなので、モデルの優劣というより「やってみたらこうなった」という記録として読んでください。
対象の3プロトコルについて
UART・I2C・SPIは、いずれも基板の上でマイコンとセンサなどのIC同士を数本の電線でつなぐための通信規格です。インターネットのプロトコルのような高い階層の話ではなく、「1本の電線の電圧が高いか低いか」を時間方向に並べて 1 と 0 を伝える、最も物理に近い層の取り決めになります。今回モデルに描かせた波形図は、まさにその電圧の時間変化です。
3つの違いはおおまかに次の通りです。
| UART | I2C | SPI | |
|---|---|---|---|
| 信号線 | 2本 | 2本 | 4本 |
| クロック線 | なし(非同期) | あり | あり |
| 速度の目安 | 9600 bps〜数 Mbps | 100k〜400k bps | 数M〜数十 Mbps |
- UART(TX / RX): 1対1。デバッグログ、GPS/無線モジュール
- I2C(SCL / SDA): アドレスで相手を指定。温度センサ、EEPROM、時計IC
- SPI(SCLK / MOSI / MISO / CS): CS線で相手を指定。SDカード、液晶、フラッシュメモリ
※ 本数はいずれもGNDを除いた信号線の数で、双方向のやりとりが成立する最小構成です。UARTは送信だけなら1本、SPIも送信だけならMISOを省けます。実際にはどの規格もGNDの共有が必須です。
線を減らすと配線は楽になりますが、速度や手続きの複雑さは線数だけで決まるわけではなく、アドレッシングやクロック同期の要否も効いてきます(UARTは2線でも手続きは単純、I2Cは2線で手続きが複雑、SPIは4線で高速かつ単純)。各ラウンドの冒頭に、その規格の考え方をもう少し噛み砕いた折りたたみを置いてあります。
実験の枠組み
| 表記 | 実行方法 |
|---|---|
| Opus 4.8 | デスクトップアプリで手動実行 |
| Opus 5 | Cowork のサブエージェント |
| Sonnet | Cowork のサブエージェント |
| Fable | Cowork のサブエージェント |
各モデルには、詳細を一切明記しない同一プロンプトを、モデル名を伏せた匿名の作業フォルダで渡しています。
ラウンド1:UART
UARTとは(クリックで展開)
3つの中で最も古くて単純な、1対1の通信です。送信線(TX)と受信線(RX)が1本ずつあるだけで、相手を選ぶ仕組みも、いつ読めばよいかを教えるクロック線もありません。
クロックがないのに読めるのは、送信側と受信側があらかじめ「1ビットを何秒で送るか」を約束しておくからです。これが「ボーレート」で、9600 bps なら1ビットは約 104 µs。受信側は最初の合図から時間を数えるだけで、以降のビットを切り出します。時計を合わせずにストップウォッチだけで待ち合わせるようなもので、速度設定を間違えると文字化けするのはこのためです。
誤差に耐えるため、受信側は各ビットの中央で電圧を読みます。端で読むと、わずかなずれや波形の鈍りで隣のビットを拾ってしまうからです。
線は暇なとき HIGH で待機しており、送信の開始は「LOWに落とす」ことで知らせます(STARTビット)。以後、決められた個数のデータビットが続き、最後に HIGH に戻して1文字分が終わります(STOPビット)。マイコンのデバッグログをPCで見るときの裏側は、たいていこれです。
参考: Microchip TB3208 - Basic Operation of UART / SparkFun - Serial Communication
UART 8N1・9600 baud で 'A' を1バイト送る波形のアニメーション GIF を作ってください。
START/STOP ビット、各ビット中央でのサンプリング点、サンプリングした値が集まって
0x41 と 'A' になる過程が見えるように。文字は最小限で。
出力条件: 800x400 px、長さ約10秒、無限ループ、ファイル名 uart.gif
(8N1 は「データ8ビット・パリティなし・ストップビット1」の意味。UARTで最も一般的な設定です)
難所は「データは下位ビットから送る」と書かないことです。'A' = 0x41 = 0b01000001 なので、線上に出るビット列は 1, 0, 0, 0, 0, 0, 1, 0 という一見あべこべな並びになります。人が読む順(上位から)のまま描いてしまうのが定番のミスです。
4モデルとも、下位ビットから送るところまで含めて素直に描いてきました。違いが出たのは見せ方だけで、Opus 5 は 104 µs/bit のタイミングを注記し、Sonnet は受信側の検証を独自に描き、Fable は文字数を最小限に抑えています。
この時点で、UARTでは差がつかなそうだと分かりました。
ラウンド2:I2C
I2Cとは(クリックで展開)
2本の線に何個ものICをぶら下げるための規格です。基板上の温度センサ、EEPROM、時計ICなど、速度は要らないが数が多いデバイスをまとめて繋ぐのに使われます。配線が2本で済むのが最大の利点で、その代わり速度は控えめです(標準モード100 kbps/ファストモード400 kbps。さらに上位のモードもありますが今回は扱いません)。
複数がぶら下がるので、UARTと違って「誰に話しかけているか」を示す必要があります。そこで各デバイスにはアドレスが割り当てられ、通信はまずアドレスを名乗ることから始まります。呼ばれたデバイスだけが応答し、他は黙って聞き流します。アドレスは7ビットで、線上に流れる最初の1バイトは、その7ビットを1つ左にずらし、空いた最下位ビットに「読み/書き」の区別(R/Wビット)を入れたものになります。なので 0x50 のデバイスへの書き込みは 0x50 << 1 = 0xA0 として現れます。
電気的な作りにも特徴があります。電源とデータ線の間には常に抵抗(プルアップ抵抗)がぶら下がっていて、各デバイスは線を LOWに引っ張ることしかできません。手を放せばこの抵抗経由で自動的に HIGH に戻ります(オープンドレイン)。誰も喋っていないときに両線が HIGH なのはこのためで、複数が同時に喋っても衝突で壊れない仕組みになっています。
START条件とSTOP条件には向きの違いがあります。STARTは SCL=HIGH のまま SDA を HIGH→LOW に落とすこと、STOPは逆に SCL=HIGH のまま SDA を LOW→HIGH に戻すことです。
そしてもう一つ、データを変えてよいのは SCL が LOW の区間だけ、という約束があります。SCL が HIGH のときの SDA の動きは、上の START / STOP という特別な合図として予約されているからです。
参考: NXP UM10204 - I2C-bus specification and user manual Rev.7.0(公式仕様書)
I2C(100 kHz・7bitアドレス)でマスタがスレーブ 0x50 に 'A' を1バイト書き込む波形の
アニメーション GIF を作ってください。SCL と SDA の2本の波形、START/STOP 条件、ACK、
受信ビットが集まってアドレスと 0x41・'A' になる過程が見えるように。文字は最小限で。
出力条件: 800x400 px、長さ約10秒、無限ループ、ライトテーマ(白背景)、ファイル名 i2c.gif
4モデルとも同じビット列を描いていて、アドレスもACKもSTART/STOPも揃っていました。ただ1つだけ、厳密に見るとSonnetだけSDAを動かすタイミングが間違っていて、SCLがHIGHのあいだに動いてしまっている箇所がありました。実際のバスなら、そこが偽の開始/終了条件と読まれて通信が壊れます。
ラウンド3:SPI(モード3・全二重)
SPIとは(クリックで展開)
速度が欲しいときに使う規格です。SDカード、液晶ディスプレイ、フラッシュメモリなど、まとまったデータを流し込む相手に向いています。I2Cが数百kbpsなのに対し、SPIは数十Mbpsまで出せます。
速い理由の一つは、クロック線をマスタが直接供給し、受信側は「クロックのエッジ(電圧が切り替わる瞬間)が来たら読む」だけでよいことです。もう一つは配線が単純で、線を引っ張り合う仕掛け(オープンドレイン)を使わず素直に電圧を出力できることです。その代償として線が4本必要になります。
| 信号 | 向き | 役割 |
|---|---|---|
| SCLK | マスタ→スレーブ | 同期クロック |
| MOSI | マスタ→スレーブ | マスタが送るデータ |
| MISO | スレーブ→マスタ | スレーブが返すデータ |
| CS | マスタ→スレーブ | 相手の選択(LOWで有効) |
I2Cのようなアドレスは持たず、CS線を1本ずつ引いて相手を名指しします。デバイスが増えるほどCS線も増えるのが弱点です。
送受のデータ線が別々にあるため、送信と受信が同時に起こります(全二重)。マスタ側とスレーブ側にそれぞれ1バイト分の箱(レジスタ)があり、クロックが1回来るたびに両方の箱から1ビットずつ同時に押し出して交換する、というイメージです。8回繰り返すと、互いの1バイトがそっくり入れ替わっています。
やっかいなのがモードです。「クロックが暇なときHIGHで待つのかLOWで待つのか(CPOL)」と「2つあるエッジのどちらで読むのか(CPHA)」の組み合わせで4通りあり、繋ぐ相手のデータシートに合わせないと1ビットもまともに読めません。
| モード | CPOL | CPHA | クロック待機 | データ変化 | 読み取り |
|---|---|---|---|---|---|
| 0 | 0 | 0 | LOW | 立下り | 立上り |
| 1 | 0 | 1 | LOW | 立上り | 立下り |
| 2 | 1 | 0 | HIGH | 立上り | 立下り |
| 3 | 1 | 1 | HIGH | 立下り | 立上り |
(厳密には、CPHA=0 のモード0/2では最初の1ビットだけ直前のエッジが存在しないため、CSをアサートした時点であらかじめ確定させます。2ビット目以降は表の通りです)
実務で最も多いのはモード0なので、「モード3」と言われたときに待機HIGH・立上り読みへ正しく切り替えられるかが、このラウンドの見どころでした。
参考: TI Application Brief
「SPIバスの理解」(日本語)
SPI(モード3・全二重)でマスタがスレーブに 'A' を送り、同時にスレーブから 0x5A が返る
波形のアニメーション GIF を作ってください。SCLK・MOSI・MISO・CS の4本の波形、
各ビットのサンプリングタイミング、送受のビットが集まって 0x41・'A' と 0x5A に
なる過程が見えるように。文字は最小限で。
出力条件: 800x400 px、長さ約10秒、無限ループ、ライトテーマ(白背景)、ファイル名 spi.gif
「モード3」とだけ書き、その意味は説明していません。CPOL/CPHAはベテランでも取り違える定番ポイントです。
これも4モデルとも、モード3の待機HIGHと立上りサンプルに正しく切り替えて描いていました。しかしSonnetでは上余白がなくはみ出してしまっています。
おまけの工夫がそれぞれ違って、Opus 4.8 は「Sampling bit 4/8 on SCLK rising edge」という実況キャプションを添え、Opus 5 と Fable は CS区間外のデータ線をハイインピーダンス(誰も電圧をかけていない“浮いた”状態)として破線で描いています。
まとめ
- 定番プロトコルの教科書知識の描画だけだと、最近のモデルではもう差が見えない。UARTもSPIも危なげなかった
- I2Cのタイミング関係だけ差が出たが、概要理解には問題ないレベル
- 表示している値が正しくても図としては成立していない、という間違い方が実在する。これは留意しておきたいです
おまけ:同じI2Cを3Dで描かせてみた
基礎知識では差がつかなかったので、趣向を変えて「I2Cの線はプルアップで吊られていて、ICは引き下げることしかできない」という性質を3Dアニメーションで表現させてみました。細かい指定はせず、three.js・12秒・カメラ固定という枠だけ揃えています。
結果は見事にバラバラで、ロープで引くもの、線の色と高さごと変えるもの、爪で掴むもの、球の色だけで示すものと、4モデルが4通りの絵を出してきました。面白いのは、2Dの波形では誰も描かなかった「ACKのときだけスレーブ側が線を引く」を、この形式だと全員が描いてきたことです。
ただし組み込み知識というよりはthree.jsが上手く使えるかに比重が移っているのは注意です。
注意点
軽い遊びなので、以下は割り引いて読んでください。
- 各モデル1回ずつの生成(n=1)です。もう一度やれば結果が変わる可能性は普通にあります
- 採点や本記事の下書きはFableに実行させています
- Opus 4.8 のみ旧バージョンのため手動実行でしか選択できず、他と経路が異なります。また Opus 5 は3ラウンドとも effort=high、他3モデルはセッション既定値なので、条件は揃っていません
- 測っているのは「仕様知識+それを破綻なく図示するコーディング」の複合であって、モデルの総合的な性能ではありません



