Learning Objectives
- 用時基、垂直刻度與觸發準位,讓示波器畫面穩定地顯示想看的波形
- 用自動量測指令讀峰對峰值、頻率、週期與兩通道的相位差
- 看懂 :WAV:DATA? 回傳的 IEEE 488.2 二進位區塊,用 query_binary_values 讀回
- 用 :WAV:PRE? 的前導資料把位元組換算成電壓與時間,並說明量化誤差
示波器:看電壓怎麼隨時間變化
電錶給你一個數字,示波器給你一張圖:橫軸是時間,縱軸是電壓。上一課用示波器量了函數產生器的頻率與 Vrms, 這一課把示波器本身講清楚,並且把整段波形讀回電腦,用 Python 自己分析。
本課的工作台:函數產生器的輸出接到一個 RC 低通濾波器(1 kΩ、100 nF)的輸入,同時接到示波器的 CH1; 濾波器的輸出接到 CH2。所以 CH1 是「送進去的訊號」,CH2 是「電路輸出的訊號」。
這一課要回答:
- 怎麼設定示波器,讓畫面剛好顯示兩、三個週期?
- 示波器內建的自動量測可以直接給哪些數字?
- 一整段波形怎麼傳回電腦?傳回來的位元組怎麼變成電壓?
本課的示波器是模擬的教學儀器(Uedu Sim),指令採常見的寫法(冒號開頭);各廠牌的指令、前導資料的欄位順序與資料格式差異很大,以手冊為準。
時基、垂直刻度、觸發
示波器的螢幕通常是橫向 10 格、縱向 8 格。三個最重要的設定:
| 設定 | 指令(常見寫法) | 意思 | 怎麼選 |
|---|---|---|---|
| 時基 | :TIM:SCAL 0.0005 |
每一格幾秒 | 想看 n 個週期:每格=n × 週期 ÷ 10 |
| 垂直刻度 | :CHAN1:SCAL 0.5 |
每一格幾伏特 | 讓波形占畫面高度的大半,但不超出 |
| 觸發準位 | :TRIG:LEV 0 |
訊號穿過這個電壓時開始畫 | 設在波形的範圍內,畫面才會穩定 |
1 kHz 的訊號週期是 1 ms,想看 5 個週期:5 × 1 ms ÷ 10=0.5 ms/格。
觸發讓每一次掃描都從波形的同一個位置開始畫,畫面才不會左右飄動。:RUN 持續擷取、:STOP 停住目前的畫面、:SING 只擷取一次。
:AUT(上一課用過)會自動選時基與刻度,但它要花時間,而且不一定是你要的設定;寫程式時通常自己設定。
示波器的自動量測直接給數字::MEAS:VPP? CHAN1(峰對峰值)、:MEAS:FREQ?(頻率)、:MEAS:PER?(週期)、:MEAS:VRMS?,
以及兩個通道的相位差 :MEAS:PHAS? CHAN1,CHAN2。
逐步看:設定示波器並自動量測
看電路畫面的示波器:第 7 行改時基之後,畫面從 10 個週期變成 5 個週期。CH2(濾波器的輸出)比 CH1 小,而且往右偏移,這就是相位差。
把波形讀回電腦:二進位區塊
自動量測只有幾個數字;要自己分析(例如找最大值的時間、算 FFT、存檔畫圖),就要把整段波形讀回來。四個指令:
scope.write(":WAV:SOUR CHAN1") # 讀哪一個通道
scope.write(":WAV:FORM BYTE") # 格式:每一點 1 個位元組(0~255)
scope.write(":WAV:POIN 40") # 讀幾點
data = scope.query_binary_values(":WAV:DATA?", datatype="B")
為什麼用 BYTE 而不是文字(ASC)?一點用文字表示要十幾個字元(-9.9200E-01,),用位元組只要 1 個,
傳 1000 點,文字約 12 KB、位元組約 1 KB。示波器一次常要傳幾萬到幾百萬點,差距很大。
二進位資料前面有一個 IEEE 488.2 的定長區塊標頭,告訴電腦後面有幾個位元組:
#9000000040<40 個位元組的資料>
│└───┬───┘
│ └── 9 位數字:資料長度=40
└── # 後面一位數字:長度欄位有 9 位
不能用 query() 讀它:query() 會把位元組當成文字解碼,128 以上的值變成亂碼。
query_binary_values(…, datatype="B") 會解析標頭、把每個位元組轉成 0~255 的整數("B" 是 Python struct 模組的「無號 8 位元整數」)。
逐步看:拆開二進位區塊
第 11~16 行用 read_raw() 拿到原始位元組,自己照標頭的格式拆開;第 19 行用 query_binary_values 一行做完同樣的事。比較兩種方法拿到的前 6 點。
位元組變成電壓:前導資料(preamble)
位元組只是 0~255 的整數,要變成電壓與時間,需要示波器告訴你換算方式。:WAV:PRE? 回傳一串用逗號隔開的前導資料。
本課的示波器回傳 10 個欄位:
| 索引 | 欄位 | 意思 |
|---|---|---|
| 0、1 | format、type | 資料格式、擷取模式 |
| 2 | points | 點數 |
| 4 | xincrement | 相鄰兩點的時間間隔(秒) |
| 5 | xorigin | 第一點的時間(秒;觸發點是 0,所以常是負的) |
| 7 | yincrement | 每 1 個整數單位是幾伏特 |
| 8 | yorigin | 垂直偏移 |
| 9 | yreference | 對應 0 V 的整數值(這台是 128) |
換算公式:
volt = (b - yref - yorig) * yinc
time = xorig + k * xinc # 第 k 點
8 位元只能分 256 階,所以電壓的最小變化就是 yinc。垂直刻度每格 0.5 V 時,yinc 是 0.016 V;1 V 的峰值換算成 62.5 個單位,
只能存成 62,讀回來就是 0.992 V,這是量化誤差。垂直刻度設得越剛好(波形占滿畫面),量化誤差相對越小。
另外,取樣點太少也會錯過真正的最高點:每個週期至少要有十幾點,畫出來的形狀才接近原本的波形。
逐步看:換算成電壓與時間
第 12 行讀前導資料,第 16 行一行換算全部 40 點。看印出的前 6 點:時間每點加 0.05 ms,電壓從 0 V 升到最高點。最後和示波器自己量的 Vpp 比較。
小測驗:設定與量測
訊號是 2 kHz,你想在 10 格的畫面上看到 4 個週期,時基每格應該設多少?
示波器畫面上的波形一直左右飄動,最可能要調哪一個設定?
為什麼讀 :WAV:DATA? 要用 query_binary_values,而不是 query?
小測驗:區塊與換算
下面的程式會印出什麼?
raw = b"#9000000004" + bytes([128, 190, 128, 66]) + b"\n"
nd = int(raw[1:2])
n = int(raw[2:2 + nd])
body = raw[2 + nd:2 + nd + n]
print(nd, n, list(body))
前導資料的 yincrement 是 0.032、yorigin 是 0、yreference 是 128。下面的程式會印出什麼?
yinc, yorig, yref = 0.032, 0, 128
data = [128, 159, 97]
volts = [round((b - yref - yorig) * yinc, 3) for b in data]
print(volts)
8 位元的波形資料,垂直刻度每格 1 V(8 格),能分辨的最小電壓變化大約是多少?
重點整理
- 畫面橫向 10 格:時基=想看的週期數 × 週期 ÷ 10;垂直刻度讓波形占滿畫面大半;觸發準位設在波形範圍內,畫面才穩定。
- 自動量測:
:MEAS:VPP?、FREQ?、PER?、VRMS?、PHAS? CHAN1,CHAN2,直接回傳數字。 - 讀波形:
:WAV:SOUR、:WAV:FORM BYTE、:WAV:POIN,再用query_binary_values(":WAV:DATA?", datatype="B")。 #9000000040…是 IEEE 488.2 定長區塊:#、長度欄位的位數、長度、資料。:WAV:PRE?給換算方式:電壓=(b − yref − yorig) × yinc,時間=xorig + k × xinc;8 位元有量化誤差,取樣點太少會錯過峰值。
到這裡,四種常見的儀器(電源供應器、數位電錶、函數產生器、示波器)都會用程式控制了。下一課把它們組合起來,自動掃描電壓,量出 LED 的 I-V 曲線。