45年、照明制御(DMX512/Art-Net)の現場でやってきた組込み屋です。この週末、「DMX値→LED駆動PWMのデューティ」をAIが1コマンドで無人計測する実験室を、AI(Claude)との分業で1日で組み、その日のうちに自作調光基板のガンマカーブ4種を全実測しました。本記事はその構築記録です。完成品の自慢ではなく、ハマった所を全部書きます。
先に結果
自作DMX調光基板(STM32F103、PWM 2197.27Hz、DIPスイッチで調光カーブ4種切替)の実測:
| DMX | Linear | Mild | Standard | Strong | 理論Linear(DMX/255) |
|---|---|---|---|---|---|
| 8 | 3.32% | 2.58% | 0.37% | 0.16% | 3.14% |
| 64 | 25.22% | 20.42% | 3.51% | 1.80% | 25.10% |
| 128 | 50.42% | 43.77% | 12.50% | 12.65% | 50.20% |
| 192 | 75.33% | 70.49% | 36.25% | 42.26% | 75.29% |
| 248 | 97.09% | 96.35% | 89.66% | 91.17% | 97.25% |
(正確な全32点×4本はCSV参照。表は抜粋)
検証のポイントは3つ:
- Linearカーブは理論線 DMX/255 に全点±0.4ptで一致 — 計測パイプラインの校正証明
- ファームの変換式(Linear=d×16 / Mild=(48d+d²/16)/4 / Strong=d³/4096 / Standard=ガンマLUT)と実測が全カーブ±0.5pt以内で一致
- PWM周波数は全計測点で理論値2197.27Hzに対し誤差0.1%未満
そして駆動波形そのもの:
この波形も比較グラフも、AIが測ってAIが描いたものです。人間(私)がやったのはプローブを1本当てることと、DIPスイッチの切替だけ。
構成
ソフトは全部自作C#(.NET)。そしてDMXインターフェースのハードも自作です——ENTTEC Proクローンは ESP32-S3(StampS3)に自作ファームを書いた物(FW=2.3)で、DmxUsbProIF(C# GUI)から22fpsで継続送出します。
このGUIの各セルをAIがHTTP(/tx_set?ch=&val=)で書き換えることで、スイープが無人で回ります。つまりこの実験室、市販品は PicoScope とUSBカメラだけで、DMX送出のハード・ファーム・GUI・計測制御・被測定基板まで全部自作です。今回新規に作ったのは PicoScopeBridge と DmxSweepRunner の --scope 連携で、残りは既存ツールへのAPI追加でした。
設計理念:「AIが操作できるアプリ」3原則
ツール群(39本)に共通で入れているルールです。
- AIが再起動できる: 強制停止→ビルド→起動を1スクリプトに
-
状態をファイルに出す:
tmp\ai_<name>\status.json+latest.txt。AIはプロセスを覗かずファイルを読む - 操作をHTTP/ファイルで受ける: GUIしか無い機能はAIには存在しないのと同じ
この3点があるアプリは、AIが夜中に一人で検証まで回せます。今回の計測も、この土台の上に1日で載りました。
ハマりどころ実録
1. PicoScope 2204A/2205A は新APIで開けない
PicoSDKの ps2000aOpenUnit 系(a-API)では PICO_NOT_FOUND になります。この2機種は旧世代 ps2000.dll(snake_caseの ps2000_open_unit)専用。しかもGUI(PicoScope 7)がデバイスを掴んでいる時も同じ NOT_FOUND が返るので、APIを間違えているのか占有されているのか、エラーコードでは区別できません。切り分け手順を文書化して初めて安定しました。
2. timebase=0 が interval_ns=0 を返す
サンプル間隔の自動探索で最速timebaseを選ぶと、ps2000_get_timebase が interval=0 を返すことがあり、周波数計算が数十億Hzという明らかな異常値に。interval<=0 のtimebaseをスキップするガードで解決。
3. ENTTEC Proクローンはテキストで駆動できない
シリアル橋(テキスト1行=1コマンド)でDMXを送るつもりが、ENTTEC Proクローンはバイナリプロトコル専用。素直にHTTP API(継続送出型)経由に切り替えました。
4. プローブしたピンがどのDMXチャンネルか分からない
配線図を追う代わりに、AIがch1〜6を1本ずつ点灯させて総当たり。周波数が理論値と一致し振幅が有意なチャンネル(ch1)を数分で特定。残りのchはノイズフロア(21.6mV)と周波数のバラつきで機械的に棄却しました。
5. DMX=0で周波数解析が壊れる(壊れていない)
消灯=PWM信号そのものが消えるので、周期解析は当然無効になります。これを「既知の正常」として扱う判定を入れないと、無人運転でアラートの嵐になります。
6. DIPの切替が反映されない、と思い込んでいた(最大のハマり)
カーブ切替のDIPを通電中に切り替えて2本のスイープを取り、「LinearもStandardも理論と合わない」と首をひねりました。当初は「ファームはDIPを起動時にしか読まない(電源入れ直しが必要)」と結論づけましたが、これは誤りでした(詳細は末尾の追記参照)。真因はS8/S9の組み合わせ表の思い込み(両方ON=Standardではなく Strong でした)。決め手は、AIにファームの main.c を読ませてDIP読み取りコードと変換式を確認させたこと。おかげで「基板の液晶に表示される Cv:番号 を見てから測る」という確認手順ができ、取り直しなし・ラベルの貼り直しだけで4カーブが確定しました。疑わしいのはいつも配線と設定——45年前から変わりません。
AIとの分業
このシステムは4つのAIセッションが並走して作りました。段取り役(指示書を書く)と実装役(Claude Code)を分け、進捗は会話でなく指示書の「結果」欄で交換。人間の役割は、方針の一言・プローブ当て・DIP切替・電源入れ直しだけです。
まとめ
- 測定器・カメラ・DMX送出がAIの「手」になると、実測は夜中に無人で進む
- 鍵はアプリ側の作法(再起動・状態ファイル・HTTP操作)で、AIの賢さではない
- Linearカーブが理論線±0.4ptで一致=道具の校正証明。そこから先の3カーブは「設計意図の実測確認」
- 45年分の「何を測るべきか・何を疑うべきか」と、AIの「休まず測る」は、いい分業になる
次回はこの実験室で 1/4096 深調光の実測をやります。
追記(2026-07-13): Mildカーブを再定義、そしてDIPスイッチもAIに触らせた
上の表の「Mild」列(DMX128で43.77%)、実は自分で見返してLinearに寄りすぎだと感じていました。
旧式は (48d + d²/16) / 4(75%線形+25%二乗)——Linearの"おまけ"程度のカーブです。
そこで今回、MildをLinearとStrongの単純平均に再定義しました: dd = (d×16 + d³/4096) / 2。
実測との照合:
| DMX | Mild旧(75%L+25%Q) | Mild新(Linear/Strong平均) | 理論値 | 差 |
|---|---|---|---|---|
| 8 | 2.58% | 1.78% | 1.57% | +0.21pt |
| 64 | 20.42% | 13.62% | 13.33% | +0.29pt |
| 128 | 43.77% | 31.33% | 31.37% | -0.04pt |
| 192 | 70.49% | 58.88% | 58.82% | +0.06pt |
| 248 | 96.35% | 94.12% | 94.26% | -0.14pt |
DMX=128でのMildは定義上「Linear(50.20%)とStrong(12.55%)のちょうど中間」= 31.375%になるはずで、実測31.33%とは±0.04pt。検算が気持ちいいくらい合いました。
ついでにDIPスイッチもAIの「手」にした
4カーブを毎回測り直すたびに人間がDIPスイッチをカチカチ切り替えるのは、この実験室の思想(AIが1コマンドで無人計測)に反します。そこで、**ST-LinkのSWD経由でRAMを直接書き換える「デバッグ用の裏口」**をファームに1バイト追加しました。
volatile uint8_t dbg_curve_ovr = 0; // 0=無効(物理DIPのまま) / 1-4=curve_sel強制
...
curve_sel = dbg_curve_ovr ? (dbg_curve_ovr - 1) : /* 物理DIP読み取り */;
ST-LINK_CLI.exe -c SWD HOTPLUG -w8 <addr> <val> で、基板を止めずに(リセット不要・コア停止不要)この1バイトを書き換えられます。HOTPLUGはデバッガがターゲットを停止せずに接続するモードで、Cortex-Mのデバッグポート(AHB-AP)はコアを動かしたままメモリの読み書きができるので、PWM出力は書き換え中も途切れませんでした。これで4カーブぶんのDIP切替が完全無人化できます。
訂正: 「DIPは起動時にしか読まれない」は誤りでした
本文「ハマりどころ実録#6」でファームはDIPを起動時にしか読まないと書きましたが、これは誤りでした。今回main.cを読み直したところ、メインループ内でsw_input()が20tick周期で常時再実行されており、DIPは通電中も継続的に再読込されています。ST-Linkでのcurve_sel上書きテストでも、電源を入れ直さずに即座に値が切り替わることを確認しました。当時「電源入れ直しが必要」と結論づけたのは早合点で、実際の原因はS8/S9の組み合わせ表の思い込みだけだった可能性が高いです。
そして今回もハマった: 原因不明の「DMXがどの値を送っても15%固定」
新Mildの実測を回したら、DMX 0〜255の32点すべてでduty=15.1%固定という不可解な結果に。カーブ云々ではなく、そもそもDMXが効いていない挙動でした。AIに疑わせたのは以下の順番です:
- まず
curve_selのRAM上の値を直接読んで、上書きが効いていることを確認(問題は上書き機構ではない) - 次に「基板が実際に受信しているDMX値」を持つ内部変数
R1D[]をRAM上で直読み → DMXを0→8→…→248と何度送り直してもR1D[1]が常に同じ値のままだと判明 - PWM周波数だけは理論値と0.1%未満の精度で合っている → プローブは正しいピンに刺さったまま。つまり受信側の問題
犯人はDMXケーブルの物理接続でした。ソフト(DmxUsbProIF)は送信成功を報告し続けるのに、配線の先が切れていたので基板には届いていなかった、という「疑わしいのはいつも配線」を今回も地で行くオチです。ただし今回は目視やテスタではなく、ファーム内部変数をST-Linkで直読みして「受信側が凍っている」ことを特定できたのが去年までとの違いです。ケーブルを繋ぎ直した瞬間、R1D[1]が送信値と一致し始めました。
おまけ: cut_d(下駄)の効きも実測した
dbg_curve_ovr と同じ手口で、もう1つのDIP設定 cut_d(S1-S4、ローカット下駄値0-15)も1バイトの上書き変数(dbg_cutd_ovr)にして、Linearカーブ固定のままcut_d=0/5/10/15の4本をST-Link切替だけで無人スイープしました。
DMX=8での実測(理論値も併記):
| cut_d | 実測duty | 理論値 |
|---|---|---|
| 0 | 3.25% | 3.14% |
| 5 | 3.36% | 3.24% |
| 10 | 3.45% | 3.36% |
| 15 | 3.55% | 3.48% |
全DMX域を重ねると4本はほぼ重なって見えます(グラフ右下の拡大インセット参照)。これは実装を読めば当然で、cut_dはmain.c上「dd += cut_d - 1」という加算オフセット(最大+14、12bit中4080分の14)でしかなく、下位1〜2桁を底上げする程度の"ほんの下駄"止まりです。名前どおり「靴の下駄」であって「フロア(全体持ち上げ)」ではなかった、というのが実測して初めて言い切れたポイントです。
まとめ(追記分)
- カーブの定義は「なんとなく良さそう」で決めず、既存カーブの単純な数式(平均・合成)から作ると検算で裏が取れる
- DIPスイッチのような「人間しか触れない入力」も、デバッガのメモリ書き込みという裏口を1バイト用意するだけでAIの操作範囲に入る
- 実測が理論と合わない時、疑うべき順番は今回も同じでした: ①ソフトのロジック(上書き機構)→②ハードが受け取っている値そのもの(
R1D直読み)→③物理配線。内部変数を直読みできると、②の切り分けが勘ではなく事実になる - 「効果が小さい」ことも実測すれば言い切れる。cut_dは加算オフセットの小さな下駄であり、フロア持ち上げではないことがグラフで裏付けられた
この記事は筆者とAI(Claude)の協働で執筆しました。実測データ・エラー実録・ファーム解析はすべて当日のコミットログに基づきます。






