
【第3回】Pythonによるオシロスコープ波形データ解析の秘訣【アナログデータ編】
2021.12.20
Python でオシロスコープ波形データを解析しよう!
前回は、オシロスコープで波形データを取得する意義について解説しました。
今回は、オシロスコープで得た「csv 形式の波形データを、Python で解析する方法」を紹介します。
Python で波形データを解析するメリット
波形データ解析に Python を用いると、以下のようなメリットがあります。
- スクリプト型言語なので、トライアンドエラーでデバッグが迅速
- 数学に強いライブラリが充実しているため、複雑な演算も可能
- グラフ描画ライブラリで、解析結果をすぐに可視化できる
- PyVISA との連携で、データ取得から解析まで一括でできる
実際にどうやって解析するのか?
今回ご紹介するのは、PC メモリを圧迫しない「時系列逐次処理」を使った解析方法です。
実際のアナログ波形データを想定した正弦波と RC 回路の過渡応答波形を用いて、最大・最小と立ち上がり時間の測定を行います。
最後に、csv 読み込みからデータ取得まで行えるサンプルコードを記載していますので、活用してみてください。
前回の記事はこちら
環境準備
実行環境は、「Python(3.6)」「Spyder(3.2.6)」となっています。
また、コード実行には、以下のモジュールが波形データ生成に必要です。
- Pandas モジュール
- math モジュール
- random モジュール
解析する波形データ
使用する波形データは、Python で簡単に生成できますので、下記のコードを実行してみてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | import pandas as pd import random import math X_SAMPLE = 3000 NOISE_SEED_OFF = 123 #sin_wave SIN_INT = 1000 NOISE_AMP_1=0.1 AMP_1= 5.0 #rise_wave RISE_TIME=300 AMP_2 =5 NOISE_AMP_2=0.1 RC=300 xx = [ i for i in range(3000)] analog_wave_1=[] noise_1=[] for i in range(len(xx)): random.seed(NOISE_SEED_OFF+i) noise_1.append(NOISE_AMP_1*random.randint(-100,100)/100) analog_wave_1.append(AMP_1* math.sin(2/SIN_INT*xx[i]*math.pi)+noise_1[i]) analog_wave_2=[] noise_2=[] for i in range(len(xx)): random.seed(NOISE_SEED_OFF+i) noise_2.append(NOISE_AMP_2*random.randint(-100,100)/100) if i<RISE_TIME: analog_wave_2.append(noise_2[i]) else: rise_dt = AMP_2*(1 - math.exp(-1/RC*xx[i-RISE_TIME])) analog_wave_2.append(rise_dt+noise_2[i]) wave_data= pd.DataFrame({"wave_1":analog_wave_1,"wave_2":analog_wave_2}) wave_data.to_csv("analog_wave_test.csv") |
実際のアナログデータを想定しているので、上記にはランダムノイズも含まれます。
今回生成した波形データのサンプル数は3,000個で、仕様は以下のとおりです。
- 振幅 5V の正弦波(チャンネル1)
- RC 回路の過渡応答を想定した 5V の立ち上がり波形(チャンネル2)
チャンネル1
理解を深めるため、グラフで見ていきましょう。
振幅 5V の正弦波(チャンネル1)は、このようになっています。

チャンネル2
一方、RC 回路の過渡応答を想定した5Vの立ち上がり波形(チャンネル2)は、こんな感じです。

求める特性値
これで求める特性値は、以下の2つです。
- 振幅 5V の正弦波における最大・最小値
- RC 回路における立ち上がり時間
時系列逐次処理でデータ解析!
それでは、いよいよ実際にデータを解析してみましょう!
ただし、csv ファイルをそのまま読み込むとメモリを大幅に圧迫してしまいます。
そこで、使うのが「時系列逐次処理」です。
時系列逐次処理とは?
時系列逐次処理は、一行ごとに csv データを読み捨てるため、メモリをほとんど使用せずに解析ができます。
オシロスコープの波形が、横軸を時間とする時系列データだからこそ、採用できる方法です。
あるトリガタイミングから過去に遡ってデータ解析を行うことは難しいですが、大抵の特性値なら簡単に取得することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | wave_path = "./analog_wave_test.csv" wave_file = open(wave_path,'r') skip_row_count = 1 #ヘッダ行等のスキップ while skip_row_count !=0: row_data = wave_file.readline() skip_row_count-=1 #データの読み込み開始 ##最初の1行目はループ前に読み込む row_data = wave_file.readline() split_data = row_data.split(",") dt_cnt=0 while row_data: index_dt = float(split_data[0]) ch1_dt = float(split_data[1]) ch2_dt = float(split_data[2]) ###########この間に解析処理を入れる########## ####################################### ##次の行を読み込む。ここで最終行なら空白が返ってくるためループ終了 dt_cnt+=1 row_data = wave_file.readline() split_data = row_data.split(",") wave_file.close() |
読み込んだ csv データの1列目がインデックスで、2列目がチャンネル1、3列目がチャンネル2のデータです。
csv ファイル中のデータ開始行が先頭からでないケースに備えて、開始行までスキップする機能も含まれています。
今回のサンプル波形データは1行目がヘッダになっているので、1行分スキップしていますね。
以降の処理は、この繰り返し文の中に記述していく形になります。
①正弦波データの最大・最小
まず、正弦波データの最大・最小値から確認していきましょう。
全データを一括で読み込んだ場合なら「Max」「Min」のメソッドを使えば完了ですが、逐次処理なので少々ややこしい手順となります。
以下のコードで、最大値と最小値を大きいものから順に5個測定できます。
※別途、各種リストの初期化処理が必要なので、最後のサンプルコードを参照ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #最大値とそのインデックスを大きい順に5つ格納する for i in range(len(ch1_max)): if ch1_dt > ch1_max[i][1]: ch1_max.insert(i,[index_dt,ch1_dt]) if len(ch1_max) >MAX_REC: del ch1_max[-1] break #最小値とそのインデックスを大きい順に5つ格納する for i in range(len(ch1_min)): if ch1_dt < ch1_min[i][1]: ch1_min.insert(i,[index_dt,ch1_dt]) if len(ch1_min) >MAX_REC: del ch1_min[-1] break |
最大値
行を読み込んでいく過程で最大値を更新した場合に、検出した値とインデックス番号を、 ch1_max というリストへ保存していきます。
リスト用の「insert」メソッドを活用すれば、更新データを自由に挿入できるため、自動的に順位を更新可能です。
溢れた6個目のデータは、「del」メソッドで削除されていきます。
最小値
最小値も、基本的な動作は最大値と同じです。
不等号が逆な点と、使用するリスト名が異なるくらいしか違いはありません。
②RC 回路の立ち上がり時間
次に、RC 回路立ち上がりデータにおける、電圧20%から80%までの時間を測定してみましょう。
コードは以下になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #CH2:RC回路立ち上がり #バッファを使って閾値の超過タイミングを検出する ##サンプル間隔を開けてノイズの影響を軽減する if index_dt % DET_SMP_INT == 0 and rise_st_f: #リングバッファへの保存 for i in range(len(ch2_rise_st_buf)-1): ch2_rise_st_buf[len(ch2_rise_st_buf)-1-i] = ch2_rise_st_buf[len(ch2_rise_st_buf)-1-i-1] ch2_rise_st_buf[0]=ch2_dt #5回連続で値が閾値を超えた場合に閾値超過と判定する for i in range(len(ch2_rise_st_buf)): if ch2_rise_st_buf[i] > CH2_20: if i >4 : ch2_rise_st.append([index_dt,ch2_dt]) ch1_at_ch2_rise.append([index_dt,ch1_dt]) rise_st_f=False break else: break |
タイミング検出のポイント
ノイズ重畳のせいで、指定された電圧値の到達タイミング検出が難しくなっています。
そのため、要素数10個のリングバッファを使い、5回連続で指定電圧を超えたタイミングを記録します。
この時、以下のポイントを意識してください。
- 検出処理は1サンプルごとではなく、任意に行う(ノイズに強い検出ができる)
- rise_st_f フラグを使い、検出が終了したら以後は行わない(フラグ管理しないと延々と検出・更新され続ける)
電圧80%の検出タイミングも、変数や閾値が違うだけで基本は同じです。
③両データの連携:立ち上がりの指定タイミングでの正弦波の値
最後に、あるチャンネルの指定タイミングにおけるもう片方のチャンネルの測定値を取得する方法をご紹介します。
チャンネル2で電圧20%を検出したときに、チャンネル1の電圧値を取得してみましょう。
②のコード内にある ch1_at_ch2_rise.append([index_dt,ch1_dt]) がその処理にあたります。
処理中は全チャンネル時間的な同期がとれているので、片方のチャンネル処理中に割り込む形で処理を追加すればいいのです。
サンプルコード:波形データの最大・最小と立ち上がり時間の解析
本記事で紹介したテクニックをまとめて、1つのコードにしてみました。
コード自体は今回使用するサンプルの波形データに合わせたものですが、ファイル名やスキップする行を変えれば、どんな波形データでも対応できますよ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | wave_path = "./analog_wave_test.csv" wave_file = open(wave_path,'r') skip_row_count = 1 #設定値関連 MAX_REC = 5 #最大・最小を記録する際に何個まで記録するか CH2_20 = 5*0.2 #チャンネル2で検出する電圧の値(既定電圧の20%) CH2_80 = 5*0.8 #チャンネル2で検出する電圧の値(既定電圧の80%) DET_SMP_INT = 2 #電圧閾値を検出する際にデータ比較をするサンプル間隔 #ヘッダ行等のスキップ while skip_row_count !=0: row_data = wave_file.readline() skip_row_count-=1 #変数の定義 ch1_max=[] ch2_max=[] ch1_min=[] ch2_min=[] ch2_rise_st=[] ch2_rise_ed=[] ch2_rise_st_buf =[ 0 for i in range(10)] ch2_rise_ed_buf =[ 0 for i in range(10)] ch1_at_ch2_rise=[] #立ち上がり検出を1回だけ実施するためのフラグ rise_st_f = True rise_ed_f = True #データの読み込み開始 ##最初の1行目はループ前に読み込む row_data = wave_file.readline() split_data = row_data.split(",") #各測定値の初期値を設定 ch1_max.insert(0,[float(split_data[0]),float(split_data[1])]) ch2_max.insert(0,[float(split_data[0]),float(split_data[2])]) ch1_min.insert(0,[float(split_data[0]),float(split_data[1])]) ch2_min.insert(0,[float(split_data[0]),float(split_data[2])]) #2行目以降の読み出し処理を開始 dt_cnt=0 while row_data: #読み出した値がデータ終了後の空白なら終了 index_dt = float(split_data[0]) ch1_dt = float(split_data[1]) ch2_dt = float(split_data[2]) ###########この間に解析処理を入れる########## #CH1:正弦波の処理 #最大値とそのインデックスを大きい順に5つ格納する for i in range(len(ch1_max)): if ch1_dt > ch1_max[i][1]: ch1_max.insert(i,[index_dt,ch1_dt]) if len(ch1_max) >MAX_REC: del ch1_max[-1] break #最小値とそのインデックスを大きい順に5つ格納する for i in range(len(ch1_min)): if ch1_dt < ch1_min[i][1]: ch1_min.insert(i,[index_dt,ch1_dt]) if len(ch1_min) >MAX_REC: del ch1_min[-1] break #CH2:RC回路立ち上がり #バッファを使って閾値の超過タイミングを検出する ##サンプル間隔を開けてノイズの影響を軽減する if index_dt % DET_SMP_INT == 0 and rise_st_f: #リングバッファへの保存 for i in range(len(ch2_rise_st_buf)-1): ch2_rise_st_buf[len(ch2_rise_st_buf)-1-i] = ch2_rise_st_buf[len(ch2_rise_st_buf)-1-i-1] ch2_rise_st_buf[0]=ch2_dt #5回連続で値が閾値を超えた場合に閾値超過と判定する for i in range(len(ch2_rise_st_buf)): if ch2_rise_st_buf[i] > CH2_20: if i >4 : ch2_rise_st.append([index_dt,ch2_dt]) ch1_at_ch2_rise.append([index_dt,ch1_dt]) rise_st_f=False break else: break #バッファを使って閾値の超過タイミングを検出する ##サンプル間隔を開けてノイズの影響を軽減する if index_dt % DET_SMP_INT == 0 and rise_ed_f: #リングバッファへの保存 for i in range(len(ch2_rise_ed_buf)-1): ch2_rise_ed_buf[len(ch2_rise_ed_buf)-1-i] = ch2_rise_ed_buf[len(ch2_rise_ed_buf)-1-i-1] ch2_rise_ed_buf[0]=ch2_dt #5回連続で値が閾値を超えた場合に閾値超過と判定する for i in range(len(ch2_rise_ed_buf)): if ch2_rise_ed_buf[i] > CH2_80: if i >4 : ch2_rise_ed.append([index_dt,ch2_dt]) rise_ed_f=False break else: break ####################################### ##次の行を読み込む。ここで最終行なら空白が返ってくるためループ終了 dt_cnt+=1 row_data = wave_file.readline() split_data = row_data.split(",") wave_file.close() #測定結果を表示 print("正弦波の最大値:"+str(ch1_max[0][1])) print("正弦波の最小値:"+str(ch1_min[0][1])) print("RC回路の立ち上がり時間:"+str(ch2_rise_ed[0][0]-ch2_rise_st[0][0])) print("RC回路の20%時の正弦波の電圧値:"+str(ch1_at_ch2_rise[0][1])) |
さいごに
今回は、電圧の最大・最小や立ち上がり時間といった「アナログ特性の取得方法」を紹介しました。
1つの波形データだけなら、オシロスコープのカーソル機能や測定機能だけでも事足りるでしょう。
しかし、Python を使えば、大量のデータ処理ができる上、解析結果の集約も非常に簡単です。
回路動作を自動化し、オシロスコープと連携してしまえば、無人測定も可能ですよ!
そして、次回は「シリアル通信の解析」を行っていきたいと思います。
お楽しみに!
第4回はこちら!
第1回はこちら!
こちらの記事もオススメ!
書いた人はこんな人

- 「好きを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もり大歓迎!
また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。
以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit
ITエンタメ10月 13, 2023Netflixの成功はレコメンドエンジン?
ライトコードの日常8月 30, 2023退職者の最終出社日に密着してみた!
ITエンタメ8月 3, 2023世界初の量産型ポータブルコンピュータを開発したのに倒産!?アダム・オズボーン
ITエンタメ7月 14, 2023【クリス・ワンストラス】GitHubが出来るまでとソフトウェアの未来