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

    広告メディア事業部広告メディア事業部
    2021.09.22

    IT技術

    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...

    広告メディア事業部

    広告メディア事業部

    おすすめ記事

    GitHubActionsのランナーに触れてみた

    こやまん(エンジニア)

    こやまん(エンジニア)

    2024.03.28

    IT技術

    Azure Data FactoryでSlackへ通知をしてみる

    たかやん(エンジニア)

    たかやん(エンジニア)

    2024.03.28

    IT技術

    GCP Secret Managerを使ってみた

    たなゆー(エンジニア)

    たなゆー(エンジニア)

    2024.03.21

    IT技術

    Bitriseのパイプラインと環境変数

    加納(エンジニア)

    加納(エンジニア)

    2024.03.11

    IT技術