Arduino UNOの6ch ADCをプロットしグラフ表示する(2)

##測定周期変更可能なADCプロット装置を作る

前回のArduinoのADCを6chグラフ表示したものに、測定周期を変更できる仕様を入れてみました。
複数のリチウムイオン電池の電圧簡易測定などに使えるかもしれません。

https://qiita.com/Nomisugi/items/c82a63d9b2b077a76be3

今回、周期測定するにあたり、できるだけ正確な周期を作り出すためArduinoとの通信やグラフ描写の
オーバーヘッドを考慮する必要があります。そのため区間測定できる下記ストップウォッチを使います。

https://qiita.com/Nomisugi/items/8cc7ea0c5731a5057985

###完成イメージ
というコンセプトで完成したのは下記のようなGUIとなります。

![ADGraph2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506495/5fc7a6a5-abc8-5cce-557e-fbe13af8a7cb.png)

Collection intervalに測定周期を設定できるようにしています。
設定範囲は0ms - 10000msまでとしています。

###周期測定に関しての結果
今回、周期的にADC値をArduinoから取得する仕組みを入れています。マイコンの世界では正確に時間を刻む事が要求されますが、WindowsなどのPCプログラムではそこまでの精度を出すことができません。
測定間隔をなるべく正確にするために、Arduino通信や描画処理などの時間を周期測定に考慮する処理を入れてみました。ただ、それでもPythonやWindows処理に関しての正確な時間処理までは考慮できていません。

![周期測定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506495/10e288eb-f998-ac0c-6961-baaf02626298.png)

そのため、1周期あたり20m程度の遅れが発生します。また、Arduinoとの通信やグラフ描画などの処理を考慮するとミニマム周期は約50ms程度となります。つまりサンプリングレート20Hz程度のUSB接続で
0-5Vまで10bit分解能で6ch同時測定可能な機材として使えることになります。

###ソースコード
```Python:Ardipy_ADGraph.py
import sys
import binascii
import re
import time
import tkinter as tk
import tkinter.ttk as ttk
sys.path.append('../')
from Ardipy_Driver import Ardipy
sys.path.append('../Tool')
from Ardipy_Frame  import Ardipy_Frame
from SelectableGraph import *
from StopWatch import *
import datetime

Ardipy_ADGraph = "1.4"

class AdgraphException(Exception):
    pass

class Control_Frame(Ardipy_Frame):
    def __init__(self, master):
        self.log = None
        self.ardipy = Ardipy(self.log)
        self.collection_interval = 100
        super().__init__(master, self.ardipy)

        self.sw1 = StopWatch("sw1")        
        self.sw1.start()
        #Control Frame
        self.run_flag = True
        control_frame = tk.LabelFrame(master, text= "Control",relief = 'groove')
        start_button = tk.Button(control_frame, text="START/STOP", command=self.start)
        start_button.pack(side = 'left')
        interval_label1 = tk.Label(control_frame, text=u'  Collection interval')
        interval_label1.pack(side='left')
        self.interval_txt = tk.Entry(control_frame, justify=tk.RIGHT, width=5)
        self.interval_txt.pack(side='left')
        self.interval_txt.insert(0, str(self.collection_interval))
        interval_label2 = tk.Label(control_frame, text=u'ms')
        interval_label2.pack(side='left')
        interval_button = tk.Button(control_frame, text="SET", command = self.frame_set)
        interval_button.pack(side = 'left')
        control_frame.pack(side = 'top', fill = 'x')

        graph_list = ["AD0", "AD1", "AD2", "AD3", "AD4", "AD5"]
        graph_frame = ttk.Frame(master)
        self.gf = SelectableGraph(graph_frame, graph_list)
        graph_frame.pack(side = 'right', fill = 'both')
        self.gf.setGraph_Y_Range(-1, 6)
        self.update()

    def frame_set(self):
        val = int(self.interval_txt.get())
        if( val > 10000 ): val = 10000
        if( val < 1 ): val = 1
        self.interval_txt.delete(0, 10)
        self.interval_txt.insert(0, str(val))
        self.collection_interval = val
        pass

    def start(self):
        self.run_flag = not self.run_flag
        if( self.run_flag ):
            self.gf.start()
        else:
            self.gf.stop()
            
    def update(self):
        print( datetime.datetime.now() )
        before_time  = self.sw1.stop()
        
        vals = []
        if (self.ardipy.isConnect() & self.run_flag):
            vals.append(self.ardipy.adRead(0) * (5/1024))
            vals.append(self.ardipy.adRead(1) * (5/1024))
            vals.append(self.ardipy.adRead(2) * (5/1024))
            vals.append(self.ardipy.adRead(3) * (5/1024))
            vals.append(self.ardipy.adRead(4) * (5/1024))
            vals.append(self.ardipy.adRead(5) * (5/1024))
            self.gf.setValues( vals )

        current_time  = self.sw1.stop()
        interval = current_time - before_time
        interval = self.collection_interval - int(interval*1000)
        self.master.after(interval, self.update)
        return
    

if __name__ == "__main__":
    win = tk.Tk()
    cf = Control_Frame(win)
    win.geometry("750x600")
    win.title("Ardipy ADC Graph viewer")
    win.mainloop()

```

###作ってみて分かった事
今回6ch同時測定することで、ADCの特性などが観測できました。
Arduinoに使われているATmegaにはADCポートは6chありますが、実際のマイコンには1個のADC装置が内臓しています。それを時分割で使う事で利用者から見たマイコンのADCは6chとなります。
ただし、この時分割が他のチャンネルのADC測定の影響がないかといわれると、測定前の結果に影響を受けます。
下記のは理想定な状態です。

```
ADC0 - 5V  
ADC1 - 接続なし(2.56V)
ADC2 - 接続なし(2.56V)
ADC3 - 0V
ADC4 - 接続なし(2.56V)
ADC5 - 接続なし(2.56V)
```

ポートとして接続なし(オープン)状態であればATmegaの測定基準電圧 2.56V付近になるはずです。
ただ、測定間隔が短いと前ポートの影響を受けます。

```
ADC0 - 5V  
ADC1 - 接続なし(4.9V)
ADC2 - 接続なし(4.9V)
ADC3 - 0V
ADC4 - 接続なし(0.1V)
ADC5 - 接続なし(0.1V)
```

と、影響を受けるはずでしたが、実際はADC4とADC5は5V付近で張り付きます。
実際の測定結果

```
ADC0 - 5V  
ADC1 - 接続なし(4.9V)
ADC2 - 接続なし(4.9V)
ADC3 - 0V
ADC4 - 接続なし(5V)
ADC5 - 接続なし(5V)
```

Arduino UNOの回路図を見ると、ADC4とADC5はI2Cと機能兼用であることがわかります。
https://www.arduino.cc/en/uploads/Main/Arduino_Uno_Rev3-schematic.pdf
マイコン自体のアナログポート回路を見ればこの原因は判断できると思いますが、今回は割愛します。


![ADGraph3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506495/8642c84d-4a30-6a84-fde0-5ba879b37cfd.png)

上図が実際測定結果です。測定周期を変えることで隣接のオープンポートへの影響がわかります。
ADC0 に5Vを入れて、ADC1, ADC2, ADC3の順番に大きな影響を受けている事が確認できます。
また、5秒程度時間が経過すると、それ以上周期を伸ばしても測定結果に変化ないことがわかりました。