• トップ
  • ブログ一覧
  • 【第4回】Pythonによるオシロスコープ波形データ解析の秘訣【デジタルデータ編】
  • 【第4回】Pythonによるオシロスコープ波形データ解析の秘訣【デジタルデータ編】

    メディアチームメディアチーム
    2021.09.22

    IT技術

    Pythonによるオシロスコープ波形データ解析の秘訣(デジタルデータ編)

    Python でオシロスコープ波形データを解析しよう!

    前回に引き続き、オシロスコープから得た csv ファイルを Python で解析していく方法をご紹介します。

    今回は「デジタル編」として、クロック波形やシリアル通信の解析を行っていきましょう!

    実際の波形データを Python でオシロスコープから取得する方法は、前回の記事をご参照ください。

    前回の記事はこちら!

    Pythonによるオシロスコープ波形データ解析の秘訣(アナログデータ編)2021.09.16【第3回】Pythonによるオシロスコープ波形データ解析の秘訣【アナログデータ編】Python でオシロスコープ波形データを解析しよう!前回は、オシロスコープで波形データを取得する意義について解説しま...

    Python でデジタルデータを解析するメリット

    デジタルデータとは?

    まずは、今回の解析対象である「デジタルデータ」について説明します。

    デジタルデータは、「High」と「Low」の2値で決まる論理データのことです。

    これを可視化したのが、「クロック波形」と「データ波形」となります。

    普通、2値の論理は、クロック波形の「立ち上がり」もしくは「立ち下がり」のエッジタイミングで決まります。

    例えば、シリアル通信のデータ送受信では、クロックエッジにおけるデータ波形の電圧値で論理値を決定しているのです。

    デジタルデータにおけるクロック

    デジタルデータを決定する「クロック波形」は、電源電圧と同じ電圧値と、0V の電圧値を一定周期で繰り返す信号です。

    クロック波形の品質は、周期の安定性が最重要ポイントで、常に一定値とはなり得ません。

    そして、一定期間の周期のズレを「ジッタ」といいます。

    デジタル波形データ解析のメリット

    この「クロックジッタ」は、オシロスコープの画面上で波形を重ねたり、内蔵機能で測定できます。

    前回のアナログデータ同様、ここでも Python を使うことで、波形データの転送からジッタ測定まで自動化できます。

    同時に、シリアル通信など、クロックに同期したデータの解析も行えるのです。

    データ取得から解析までを全てこなせるのは、Python ならではのメリットと言えるでしょう。

    波形データの準備

    今回解析するのは、「クロックのジッタ」と「クロックに対する論理データ値」です。

    まずは、Python のコードで波形データを生成していきましょう!

    1import pandas as pd
    2import random
    3import math 
    4
    5X_SAMPLE = 3000
    6NOISE_SEED_OFF = 123
    7#CLOCK
    8CLOCK_HLFINT = 200
    9CLOCK_INT = CLOCK_HLFINT*2
    10JITTER = 20
    11NOISE_AMP_1=0.1
    12AMP_1= 5.0
    13RC_CLOCK=10
    14#DATA
    15DATA_INT = 400
    16AMP_2 =5
    17NOISE_AMP_2=0.1
    18DATA =[0,1,0,0,1,0,1,0,0]
    19RC_DATA = 14
    20
    21xx = [ i for i in range(3000)]
    22
    23clock_wave=[]
    24noise_1=[]
    25for i in range(len(xx)):
    26    random.seed(NOISE_SEED_OFF+i)
    27    noise_1.append(NOISE_AMP_1*random.randint(-100,100)/100)
    28    if (i % CLOCK_INT) < CLOCK_HLFINT:
    29        clock_wave.append(noise_1[i])
    30        if(i % CLOCK_INT) ==0:
    31            jitter = random.randint(0,JITTER)
    32    elif (i % CLOCK_INT) >= CLOCK_HLFINT:
    33        time = (i % CLOCK_INT)-CLOCK_HLFINT
    34        if time <jitter :
    35            clock_wave.append(noise_1[i])
    36        else:
    37            rise_dt = AMP_1*(1 - math.exp(-1/RC_CLOCK*xx[time-jitter]))
    38            clock_wave.append(rise_dt + noise_1[i])
    39
    40data_wave=[]
    41noise_2=[]
    42prev_dt =0
    43no=0
    44for i in range(len(xx)):
    45    random.seed(NOISE_SEED_OFF+i)
    46    noise_2.append(NOISE_AMP_2*random.randint(-100,100)/100)
    47    
    48    if (i % DATA_INT) == 0:
    49        prev_dt=DATA[no]
    50        no+=1
    51        
    52    if prev_dt == 0:
    53        if DATA[no] > 0:
    54            time = i % DATA_INT
    55            rise_dt = AMP_2*(1 - math.exp(-1/RC_DATA*xx[time]))
    56            data_wave.append(rise_dt + noise_2[i])
    57        else:
    58            data_wave.append(noise_2[i])
    59    else:
    60        if DATA[no] > 0:
    61            data_wave.append(AMP_2+noise_2[i])
    62        else:
    63            time = i % DATA_INT
    64            rise_dt = AMP_2*math.exp(-1/RC_DATA*xx[time])
    65            data_wave.append(rise_dt + noise_2[i])
    66
    67wave_data= pd.DataFrame({"wave_1":clock_wave,"wave_2":data_wave})
    68wave_data.to_csv("digital_wave_test.csv")

    上記コードでは、実際の波形データにある「ランダムノイズ成分」や「過渡応答」、「ジッタ成分」についても再現されています。

    今回生成する波形データのサンプル数は3,000個で、仕様は以下の通りです。

    1. 振幅 5V のクロック波形(チャンネル1)
    2. 振幅 5V で HIGH と LOW を繰り返すデータ波形(チャンネル2)

    クロック波形

    クロック波形をグラフ化すると、このようになります。

    クロック波形

    データ波形

    一方、データ波形のグラフはこんな感じです。

    データ波形

    時系列逐次処理でデータを解析!

    今回も、波形データに特化した「時系列逐次処理」で処理を行っていきます。

    詳しい方法は、前回の記事を参照してください。

    前回同様、以降の処理は繰り返し文の中に記述していきます。

    前回の記事はこちら

    Pythonによるオシロスコープ波形データ解析の秘訣(アナログデータ編)2021.09.16【第3回】Pythonによるオシロスコープ波形データ解析の秘訣【アナログデータ編】Python でオシロスコープ波形データを解析しよう!前回は、オシロスコープで波形データを取得する意義について解説しま...

    ①クロックデータの解析

    さて、まずはクロックデータの解析です。

    クロック波形のジッタを取得しましょう!

    該当するコードは下記になります。

    1    #CH1:クロック波形のRise/Fallタイミングを測定
    2    ##CLKのRiseを検出
    3    if index_dt % DET_SMP_INT == 0:
    4        #リングバッファへの保存
    5        for i in range(len(clk_buf)-1):
    6            clk_buf[len(clk_buf)-1-i] = clk_buf[len(clk_buf)-1-i-1]
    7        clk_buf[0]=ch1_dt
    8        #5回連続で値が閾値を超えた場合に閾値超過と判定する
    9        for i in range(len(clk_buf)):
    10            #Rise検出フラグがTrueでないとRise検出は行わない
    11            if clk_buf[i] > CLK_LOGIC_TH and clk_logic_det_f[0]==True:
    12                if i >4 :
    13                    clk_rise_point.append([index_dt,ch1_dt])
    14                    clk_logic_det_f =[False,True]
    15                    #Riseを検出したらデータ値の取得を開始する
    16                    data_det_f = True 
    17                    break
    18            #Fall検出フラグがTrueでないとFall検出は行わない
    19            elif clk_buf[i] < CLK_LOGIC_TH and clk_logic_det_f[1]==True:
    20                if i >4 :
    21                    clk_fall_point.append([index_dt,ch1_dt])
    22                    clk_logic_det_f =[True,False]
    23                    break
    24            else:
    25                break

    クロック立ち上がりタイミングの取得

    クロック波形を解析するにあたって必要なことは、「立ち上がりと立ち下がりを順々に検出していくこと」です。

    リングバッファを使って、5回連続で閾値を超えたタイミングを立ち上がりとします。

    今回の閾値は中間の「2.5V」としましたが、実際の閾値は、立ち上がりと立ち下がりで異なるケースもあるようです。

    フラグで検出タイミングを管理

    検出のタイミングで前回と異なる点は

    1if clk_buf[i] > CLK_LOGIC_TH and clk_logic_det_f[0]==True:

    の箇所でclk_logic_det_f[0]==True: という条件が増えている点です。

    今回は、「立ち上がり→立ち下がり→立ち上がり」のように、交互に検出を行うためです。

    そのため、以下のように、要素を2つ持つフラグを作りましょう。

    1clk_logic_det_f =[True,False]

    1番目を立ち上がり、2番目を立ち下がりの検出フラグとして、コード上で管理していきます。

    立ち上がり検出後のコードで

    1clk_logic_det_f =[False,True]

    としているのは、立ち上がり検出をやめて、立ち下がり検出を次から行うように切り替えているからです。

    検出した立ち上がりの位置と値は

    1clk_rise_point.append([index_dt,ch1_dt])

    によってリストへ付加されていきます。

    立ち下がりタイミングの取得

    立ち上がり検出後は、5回連続で閾値以下となったタイミングで、立ち下がりを検出します。

    コード上では

    1elif clk_buf[i] < CLK_LOGIC_TH and clk_logic_det_f[1]==True:

    となっており、検出するとその位置を記録し、フラグを立ち上がり検出に切り替えるようになっています。

    記録した立ち上がりタイミングからのジッタ測定

    こうして波形データを最後まで読み出したら、検出した立ち上がり位置が、リストclk_rise_point に記録されます。

    このリストを使って

    1#ジッタの測定
    2clk_rise_int=[]
    3##Riseの間隔を記録
    4for i in range(len(clk_rise_point)-1):
    5    clk_rise_int.append(clk_rise_point[i+1][0]-clk_rise_point[i][0])

    という計算を行えば、立ち上がり検出の間隔、つまり「周期」を求めることができます。

    この周期の変動量が「ジッタ」です。

    普通、最大変動量をジッタ量として評価するので、このクロックのジッタは

    1print("CLK Jitter:"+str(max(clk_rise_int)-min(clk_rise_int)))

    を計算してCLK Jitter:18.0 ということになります。

    ②データ波形の解析

    続いて、データ波形を解析していきましょう!

    コードの全体像は以下になります。

    1    #CH2:クロックRise時のデータ波形の論理値を取得
    2    if index_dt % DET_SMP_INT == 0 and data_det_f==True:
    3        #リングバッファへの保存
    4        for i in range(len(data_buf)-1):
    5            data_buf[len(data_buf)-1-i] = data_buf[len(data_buf)-1-i-1]
    6        #CH2の電圧値に応じてHIGH/LOWに分ける
    7        if ch2_dt > DATA_LOGIC_TH:
    8            data_buf[0]=HIGH
    9        else:
    10            data_buf[0]=LOW
    11        #バッファの全ての値が同じ論理ならデータを確定する
    12        if all([data_buf[i] == HIGH for i in range(len(data_buf))]):
    13            detect_data.append(HIGH)
    14            data_det_f=False
    15            data_buf =[ X for i in range(10)]
    16        elif all(data_buf[i] == LOW for i in range(len(data_buf))):
    17            detect_data.append(LOW)
    18            data_det_f=False
    19            data_buf =[ X for i in range(10)]

    クロックとデータの連携

    一般的には、データ波形は単体ではなく、クロック波形の解析とセットで行われます。

    そのため、最初にデータとクロックの連動が必要です。

    実際の回路動作でも、クロックの立ち上がりタイミングに同期して、データ波形の論理を確認するようになっています。

    立ち上がり検出時に

    1data_det_f = True

    というフラグ管理をして、解析するタイミングを決定します。

    クロック立ち上がりタイミングでのデータ確定

    クロックの立ち上がりを検出したら、同タイミングでデータ波形の論理を確認します。

    データ波形を読み取り、クロックタイミングでのデータ値を確定しなければなりません。

    ただし、一度の検出で確定してしまうと、ノイズなど誤検出が発生するリスクがあります。

    all 関数でデータを確定させる

    こうしたリスクを防ぐには、連続して複数のデータをサンプリングするのが有効です。

    1if all([data_buf[i] == HIGH for i in range(len(data_buf))]):

    上記コードで、バッファに貯めたデータの論理が全て一致した時に、データを確定するようにしています。

    Python の all 関数を使えば、リスト内の値が全て同一かを1行で検出可能です。

    データが確定したら、バッファを「不定値 X」で初期化しておきましょう!

    取得データの表示

    クロックと同様に、データ読み込みが終了した時点で、リストdetect_data に確定したデータ配列が格納されています。

    print で確認してみると

    1[1, 0, 0, 1, 0, 1, 0]

    となっており、HIGH と LOW の論理データが順番に取得できていることがわかりますね。

    サンプルコード:クロック波形とデータ波形を同時に解析し、ジッタとデジタルデータを取得

    最後に、ここまでで紹介した解析処理をそのまま実行できるコードを載せておきます。

    ファイル名やスキップする行を変えれば、どんな波形データでも対応できますよ!

    1wave_path = "./digital_wave_test.csv"
    2wave_file = open(wave_path,'r')
    3skip_row_count = 1
    4
    5#論理値用の値
    6HIGH = 1
    7LOW = 0
    8X = 2
    9
    10#設定値関連
    11MAX_REC = 5 #最大・最小を記録する際に何個まで記録するか
    12CLK_LOGIC_TH = 5*0.5 #チャンネル2で検出する電圧の値(既定電圧の20%13DATA_LOGIC_TH = 5*0.5 #チャンネル2で検出する電圧の値(既定電圧の80%14DET_SMP_INT = 2 #電圧閾値を検出する際にデータ比較をするサンプル間隔
    15
    16#ヘッダ行等のスキップ
    17while skip_row_count !=0:
    18    row_data = wave_file.readline()
    19    skip_row_count-=1
    20
    21#変数の定義
    22clk_rise_point=[]
    23clk_fall_point=[]
    24detect_data=[]
    25clk_buf =[ 0 for i in range(10)]
    26data_buf =[ X for i in range(10)]
    27
    28#データの読み込み開始
    29##最初の1行目はループ前に読み込む
    30row_data = wave_file.readline()
    31split_data = row_data.split(",")
    32
    33clk_logic_det_f =[True,False]
    34data_det_f = False
    35dt_cnt=0
    36while row_data:
    37    index_dt = float(split_data[0])
    38    ch1_dt = float(split_data[1])
    39    ch2_dt = float(split_data[2])
    40    ###########この間に解析処理を入れる##########
    41    
    42    #CH1:クロック波形のRise/Fallタイミングを測定
    43    ##CLKのRiseを検出
    44    if index_dt % DET_SMP_INT == 0:
    45        #リングバッファへの保存
    46        for i in range(len(clk_buf)-1):
    47            clk_buf[len(clk_buf)-1-i] = clk_buf[len(clk_buf)-1-i-1]
    48        clk_buf[0]=ch1_dt
    49        #5回連続で値が閾値を超えた場合に閾値超過と判定する
    50        for i in range(len(clk_buf)):
    51            #Rise検出フラグがTrueでないとRise検出は行わない
    52            if clk_buf[i] > CLK_LOGIC_TH and clk_logic_det_f[0]==True:
    53                if i >4 :
    54                    clk_rise_point.append([index_dt,ch1_dt])
    55                    clk_logic_det_f =[False,True]
    56                    #Riseを検出したらデータ値の取得を開始する
    57                    data_det_f = True 
    58                    break
    59            #Fall検出フラグがTrueでないとFall検出は行わない
    60            elif clk_buf[i] < CLK_LOGIC_TH and clk_logic_det_f[1]==True:
    61                if i >4 :
    62                    clk_fall_point.append([index_dt,ch1_dt])
    63                    clk_logic_det_f =[True,False]
    64                    break
    65            else:
    66                break
    67            
    68    #CH2:クロックRise時のデータ波形の論理値を取得
    69    if index_dt % DET_SMP_INT == 0 and data_det_f==True:
    70        #リングバッファへの保存
    71        for i in range(len(data_buf)-1):
    72            data_buf[len(data_buf)-1-i] = data_buf[len(data_buf)-1-i-1]
    73        #CH2の電圧値に応じてHIGH/LOWに分ける
    74        if ch2_dt > DATA_LOGIC_TH:
    75            data_buf[0]=HIGH
    76        else:
    77            data_buf[0]=LOW
    78        #バッファの全ての値が同じ論理ならデータを確定する
    79        if all([data_buf[i] == HIGH for i in range(len(data_buf))]):
    80            detect_data.append(HIGH)
    81            data_det_f=False
    82            data_buf =[ X for i in range(10)]
    83        elif all(data_buf[i] == LOW for i in range(len(data_buf))):
    84            detect_data.append(LOW)
    85            data_det_f=False
    86            data_buf =[ X for i in range(10)]
    87            
    88    #######################################
    89    
    90    ##次の行を読み込む。ここで最終行なら空白が返ってくるためループ終了
    91    dt_cnt+=1
    92    row_data = wave_file.readline()
    93    split_data = row_data.split(",")
    94wave_file.close()
    95
    96#ジッタの測定
    97clk_rise_int=[]
    98##Riseの間隔を記録
    99for i in range(len(clk_rise_point)-1):
    100    clk_rise_int.append(clk_rise_point[i+1][0]-clk_rise_point[i][0])
    101print("CLK Jitter:"+str(max(clk_rise_int)-min(clk_rise_int)))
    102    
    103##確定したデータを確認
    104print(str(detect_data))

    第5回へつづく!

    今回は、デジタル回路や通信波形の基本である「クロック波形と対応するデータ波形の解析手法」を紹介しました。

    正直、この程度ならオシロスコープの機能だけでも十分まかなえます。

    しかし、Python なら、さらに高度な解析が可能ですし、何より「自動解析」という強みがあります。

    ぜひ、本記事を応用して役立ててくださいね!

    そして次回は、「Python で Modbus 通信を行う方法」について解説していきます。

    第5回はこちら!

    Pythonで外部計測機器とModbus通信をするには2021.09.23【第5回】Pythonで外部計測機器とModbus通信をしてみた!PythonでModbus 通信をしてみよう!今回は、計測機器を制御する通信プロトコルである「Modbus 通信」を ...

    第1回はこちら!

    Pythonを使ってオシロスコープを遠隔操作したり、画面キャプチャをしてみよう2021.02.08Pythonを使ってオシロスコープを遠隔操作したり、画面キャプチャをしてみようpython でオシロスコープを操作しよう!オシロスコープなどの計測器は「NI-VISA」という通信規格に対応していま...

    こちらの記事もオススメ!

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
    featureImg2020.07.30Python 特集実装編※最新記事順Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた!P...

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    メディアチーム
    メディアチーム
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background