6502tinymachines

オシロスコープの使い方、ソフトウェア開発者のために

印刷用: オシロスコープの使い方、ソフトウェア開発者のために (PDF、英語)。

2026-10-07

ログはピンのところで途切れる

オシロスコープは配線のためのデバッガだ。コードはチップが何を見たかを教えてくれる。そこに実際に何があったかを教えてくれるのはスコープだけだ。この二つが食い違うとき、コードを読む一時間は、ピンの反対側で費やす一時間になる。

この記事を通して使う例は本物だ。ガイガーカウンターの基板 (CAJOE RadiationD) は、管に線源を当てると音を立てて鳴る。そのパルスはフォトカプラを通って出て、ESP32-C6 の GPIO18 に届く。ESP32-C6 は各エッジに 6.25 ns の精度でタイムスタンプを打ち、乱数のビットとしてサーバーへ流す。カウンターは鳴っていた。ファームウェアの数はゼロだった。

私たちはソフトウェアの人間がまずやることをやった。もっと小さなプログラムを書いた。診断用のファームウェアが、ピンのレベルと立ち下がりエッジの数を毎秒一回表示した。それでピンが動くこと (ジャンパでグラウンドにつなぐと level=0 を読んだ) と、フォトカプラからは何も届いていないこと (falls=0、いつも) が証明された。それで故障は数センチの配線と二枚の小さな基板に絞られた。どの一センチかは言えなかった。プログラムに見えるのは連鎖の終わりだけで、故障はその上流のどこかにある。

スコープに手を伸ばすのは、その瞬間だ。

スコープは電圧のための print 文だ

スコープは電圧 (縦) を時間 (横) に対して、何度も繰り返し描く。前面パネルのどれもが、すでに持っている考えに対応する。

スコープの操作ソフトウェアの言葉で言うと
チャンネル (四チャンネルのスコープなら CH1 から CH4)ログに取る変数ひとつ。それぞれに自分のプローブがある
ボルト毎目盛り縦軸の目盛り。2 V/div で 8 目盛りなら 16 V を表示する
時間毎目盛り (タイムベース)横軸の目盛り。50 us/div で 12 目盛りなら 600 us の窓になる
オフセットと位置画面上でゼロがどこにあるか。窓の中でトリガがどこにあるか
トリガ条件付きのブレークポイント:「CH1 が 2.5 V を下に横切ったら記録を始める」
メモリ長バッファが保持するサンプルの数。長いメモリなら、あとから拡大できる
結合 (DC か AC)DC は本当のレベルを見せる。AC は平均を取り除くので、High に張り付いた線が隠れる

コードのデバッグから持ち越せる習慣が二つある。一つ目は、見る前に何を期待するかを知っておくこと。書き留める:「P3 の VIN ピンはふだん 5 V 近くで、カチッと鳴るたびに 100 us ほど 0 V 近くまで下がる」。予想のない波形はただの絵だ。二つ目は、一回の取り込みにつき変えるのは一つだけにすること。プローブを一本動かし、一回撮り、比べる。

グラウンドクリップは建物につながっている

プローブに触れる前に学ぶべきことが一つある。商用電源で動く卓上のスコープでは、どのプローブのグラウンドクリップも、スコープの電源コードを通して大地につながっている。グラウンドクリップはすべて互いにもつながっている。クリップは受け身の基準点ではない。壁へ通じる電線だ。

そこから三つのことが言える:

  • クリップはグラウンドにだけつなぐ。 グラウンドクリップを 5 V のピンに当てると、その電源を大地に短絡させる。私たちの NES のベンチでは、コントローラのコネクタの 1 番ピンが電源だ。そこにクリップを当てれば、コンソールの電源レールを短絡させる。
  • ベンチ上のクリップはすべて一つのグラウンドを共有する。 CH2 から CH4 がすでに NES につながった状態で CH1 のクリップをガイガー基板のグラウンドに当てると、ガイガー基板のグラウンドが NES のグラウンドに結ばれる。試験のためならそれでよい。だがそれは、プローブを当てているあいだ、二つの側が絶縁されていたかどうかを測れないということでもある。
  • 高電圧がどこにあるかを知っておく。 ガイガー基板は管のために 400 V ほどを作る。低電圧のヘッダ (P3) は触れても安全だが、管の側はそうではない。どの基板でも、プローブを近づける前に熱い側を見つけておく。

それからプローブの倍率を確かめる。たいていのプローブには 1x/10x の切り替えがあり、スコープにはチャンネルごとに対応する設定がある。両者が食い違うと、読む値がすべて十倍ずれる。このベンチでは、スコープが 10x に設定されたまま 1x のプローブが使われていて、5 V のレールが画面で 50 V と読めた。両方を設定し、ほかの何かを信じる前に、分かっている電圧 (基板の 3.3 V や 5 V のピン) で確かめる。

1x は低い電圧で小さくきれいな波形をくれる。10x は回路への負荷が小さく、速いエッジにはこちらが正しい。

トリガは条件付きのブレークポイントだ

ガイガーのパルスは幅が 100 us ほどで、管に線源を当てると、だいたい一秒に十回、でたらめな間隔で来る。トリガがなければ、スコープは 600 us の窓を一秒に何千回も描き直し、パルスは見えないほど速く流れ去る。トリガは、どの瞬間を画面の真ん中に置くかをスコープに伝える。

エッジトリガには設定が三つある:

  • ソース: どのチャンネルを見張るか (CH1)。
  • レベル: 横切る電圧 (5 V のふだんと 0 V の中間の 2.5 V)。
  • スロープ: どちら向きに横切るか (パルスは線を Low に引くので、立ち下がり)。

掃引モードは、何も合わないあいだに何が起きるかを決める:

掃引振る舞い使うとき
Auto事象で、それが来なければタイマーでトリガするそもそも信号を見つけるとき。平らな線も描かれる
Normal本物のトリガでだけ描き、最後のものを画面に残すまれな事象、でたらめな事象
Singleトリガを一回待ち、取り込み、止まる残しておく、あるいはスクリプトから読む取り込みを一回撮るとき

Auto の落とし穴: まったく動かない線も描かれるので、健全な平らな信号に見える。事象を待っているなら Normal か Single を使う。そうすれば暗い画面は事象が一度も起きなかったことを意味し、それはそれで結果だ。

窓は事象に合わせる。100 us のパルスを見るには、50 us/div でパルスが二目盛りにわたる。一秒に十回のパルスを数えるには、100 ms/div まで引いて 1.2 秒の窓で十個ほどを捉える。

信号の連鎖を、コミットの範囲のように二分探索する

信号の連鎖は一続きの段で、なくなったエッジはそのうちのちょうど一つで失われた。それを見つけるのはプローブでやる git bisect だ。真ん中の一点を見れば、答えが連鎖の半分を捨ててくれる。

ガイガーの信号の連鎖と、見るべき三つの場所

ファームウェアは C 点で、ゼロと言った。次にいちばん安く見られるのは A 点、つまりガイガー基板自身の出力だ。絶縁の壁のところで連鎖を二つに分けるからだ:

  • A が一秒に十回ほど下がる: カウンターは正常。プローブを B へ移す。
  • A がまったく動かない: 故障はガイガー基板の側にある。ここでいちばんありそうなのは、配線が P3 の違うピンに付いていることだ。P3 のピンの順は写真から読み取っただけだった。
  • A は下がるのに B は下がらない: フォトカプラか、その配線が故障だ。
  • B は下がるのに C はゼロのまま: 故障はフォトカプラとチップのあいだか、コードの中にある。

プローブを当てる点にはそれぞれ自分のグラウンドがある。A はガイガー基板のグラウンドに対して測り、B は ESP のグラウンドに対して測る。フォトカプラは、その二つを分けておくためにあるのだから。

私たちはこの一歩を推論で飛び越えようとしたことがある。推測 (二本の配線を入れ替える、二つのピンを短絡させる、グラウンドに触れる) はそれぞれベンチまでの往復を一回ずつ費やし、エッジがどこで失われたかは言えなかった。A に一本プローブを当てれば、一回の取り込みでそれに答えが出る。

スコープには API がある

この十五年ほどに作られた卓上のスコープのほとんどは、SCPI (Standard Commands for Programmable Instruments) を話す。TCP ソケット越しの平文のコマンドで、Rigol ならたいていポート 5555 だ。? で終わるコマンドは一行を返す。だからスコープは、ソケットを開けるものなら何からでもスクリプトで操れる。

import socket

s = socket.create_connection((SCOPE, 5555), timeout=5)

def q(cmd):
    s.sendall((cmd + "\n").encode())
    if not cmd.split()[0].endswith("?"):
        return None
    buf = b""
    while not buf.endswith(b"\n"):
        buf += s.recv(4096)
    return buf.decode().strip()

print(q("*IDN?"))                      # maker, model, serial, firmware
q(":CHAN1:PROB 1")                     # match the probe's 1x switch
q(":CHAN1:SCAL 2")                     # 2 V/div
q(":TIM:SCAL 50e-6")                   # 50 us/div
q(":TRIG:EDG:SOUR CHAN1")
q(":TRIG:EDG:SLOP NEG")                # falling
q(":TRIG:EDG:LEV 2.5")
q(":SING")                             # arm one capture
print(q(":TRIG:STAT?"))                # WAIT until it fires, then STOP
print(q(":MEAS:ITEM? VMIN,CHAN1"))     # the dip's floor, in volts

このベンチの DS1054Z に最初に送った問い合わせは、プローブを一本も動かす前に、四つのチャンネルすべての状態を読み返した。CH1 は 0 V ほどで平らなのに、CH2 と CH4 は NES のコントローラの読み取りに合わせて 60 Hz で切り替わっていた。だから CH1 が借りられる空きのチャンネルだった。

スクリプトで手に入るもの:

  • 数としての測定値。 チャンネルごとの VMAX、VMIN、FREQ を、目盛りを読まずに。
  • PNG としてのスクリーンショット。 :DISPlay:DATA? ON,OFF,PNG は画面に映っているものを返すので、取り込みをそのままバグ報告に入れられる。
  • 毎回同じ取り込み。 スコープを構え、回路をトリガし、結果を確かめる試験は、人がいなくても走る。

このベンチで踏んだ落とし穴:

落とし穴どう見えたかどうするか
前面パネルがロックされるスクリプトが接続したあと、つまみが効かなくなるスコープの Local (Force) キーを押す
一度に一つのクライアント二つ目のスクリプトが止まるか、別のものの返事を読むセッションごとに持ち主は一つ。ソケットを閉じる
バッファに残った古い返事スクリーンショットの PNG のヘッダの前に、前の状態問い合わせの OP が付いていた各コマンドの前にソケットを空にする
プローブの倍率5 V が 50 V と読めたスクリプトの中で :CHANn:PROB を設定する
波形の読み出しこのファームウェアでは :WAVeform:DATA? が空か平らで返ってきたまず測定値とスクリーンショットを信じる
外部トリガの入力が無いDS1054Z はソースとしての EXT を拒んだトリガ信号にチャンネルを一つ使う
変えたまま置いていく次の人が、シングルショットで変な目盛りのスコープを見つける終わったら RUN、AUTO、もとのトリガに戻す

計器も間違えることがある

スコープの波形は証拠であって、真実ではない。このベンチでの間違った結論はどれも、回路からではなく、設定を間違えた計器から来た:

  • ピンに付いていなかったジャンパ。 「18」と書かれたヘッダのピンに当てたグラウンド線は、ファームウェアの読みを一度も変えなかった。死んだピンに見えた。チップの内側から駆動した試験パルスはきちんと取り込めたので、ピンは動いていて、線はどこか別の場所にあった。プローブの当て所の沈黙を信じる前に、その当て所を証明する。
  • 何も試していなかった試験。 私たちはクリックを偽るために、フォトカプラの二本の入力ピンを短絡させることを提案した。LED の二本の脚を短絡させれば LED は消えるので、この試験はエッジを決して作れなかった。試すものが壊れていたらその試験がどんな結果を出すかを問い、それが動いていたときに見えるものと違うことを確かめる。
  • Auto モードの平らな線。 何もつながっていないチャンネルでも、整った平らな線が描かれる。Normal モードなら、トリガが働くまで何も描かれない。

取り込みのたびに、その前に:

  • 何が見えるはずかを、数で知っている
  • グラウンドクリップはグラウンドに付いていて、それがどのグラウンドを何に結ぶかを知っている
  • プローブの切り替えとチャンネルの倍率が一致し、分かっている電圧で確かめてある
  • トリガのソース、レベル、スロープが事象に合っている。まれな事象には Normal か Single
  • タイムベースは事象 (見るため) か頻度 (数えるため) に合わせてある
  • 前回の取り込みから変えたのは一つだけ
  • スコープは見つけたときの状態に戻した