mbed で測定した信号と周波数スペクトルを PyQt で表示

以前 に作成した mbed で測定した電圧値の時系列データを表示する GUI プログラムを改良中。

サンプリング周波数を可能な限り上げてみたかったので、シリアル通信の Baudrate を最大まで上げ、GUI 側では平行処理でデータを受信させることにしました。
結果的にサンプリング周波数を 2000 Hz まで上げることができました。

以前は PC 側からの信号を受信してからデータを送信するプロトコルを考えていたのですが、それだとやはり時間が掛かりすぎるので止めることにしました。
float 型のデータを送信しているのを止めれば、もう少しくらいはサンプリング周波数を上げられそうです。

mbed には以下のプログラムを書き込んでいます。

#include "mbed.h"
Ticker flipper;
Timer t;
Serial pc(USBTX, USBRX);
AnalogIn input(A0);
char temp;

void flip() {
  printf("%f %f\n", t.read(), input.read());
}

int main() {
  pc.baud(921600);
  while(true) {
    if(pc.readable()){
      temp = pc.getc();

      if(temp == '1'){
	t.start();      
	flipper.attach(&flip, 0.0005);
      }

      if(temp == '0'){
	flipper.detach();
	t.stop();
	t.reset();
      }
    }
  }
}

PC 側の GUI プログラムは以下のとおりです。スタートボタンを押すと測定を開始し、ストップボタンを押すと計測終了と同時に ~/example.csv にデータを書き出します。

グラフを 2000 Hz で更新するのは描画が追いつかないので、更新間隔は 1 秒としています。
1 秒もあれば FFT 処理も余裕でできるので、周波数特性も表示しています。
窓処理やフィルタ処理は後日追加する予定です(その前にコードをきれいにしなきゃ。。。)。

# coding: utf-8
import sys
import signal
import serial
import numpy as np
from scipy.fftpack import fft, fftfreq
from PyQt5.QtCore import QTimer, QThread
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QGridLayout,
			     QHBoxLayout, QVBoxLayout, QLCDNumber, QLabel)
import matplotlib as mpl
mpl.use("Qt5Agg")
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import csv


class Thread(QThread):
    def __init__(self, parent=None):
	QThread.__init__(self, parent)

    def stop(self):
	self.isStopped = True

    def initTime(self):
	for i in range(0, 100):
	    self.serData = ser.readline().strip().rsplit()
	self.t0 = float(self.serData[0])

    def updateData(self):
	global t
	global y
	global data

	self.serData = ser.readline().strip().rsplit()
	self.serT = self.getT()
	self.serY = self.getY()
	data.append([self.serT, self.serY])
	t = np.delete(np.append(t, self.serT), 0)
	y = np.delete(np.append(y, self.serY), 0)

    def getT(self):
	return float(self.serData[0])-self.t0

    def getY(self):
	return float(self.serData[1])*3.3

    def run(self):
	self.isStopped = False
	self.initTime()
	while not self.isStopped:
	    self.updateData()


class ApplicationWindow(QWidget):
    def __init__(self):
	super().__init__()

	self.initUI()

    def initUI(self):
	self.Thread = Thread()

	self.timer = QTimer(self)
	self.timer.setInterval(1000)
	self.timer.timeout.connect(self.update)

	self.initCanvas()
	self.initFigure()

	self.lcd1 = QLCDNumber(self)
	self.lcd1.setStyleSheet("QWidget { background-color: rgb(100, 100, 255) }")
	self.lcd1.setDigitCount(7)
	self.lcd2 = QLCDNumber(self)
	self.lcd2.setStyleSheet("QWidget { background-color: rgb(100, 100, 255) }")

	startButton = QPushButton("Start")
	startButton.clicked.connect(self.onStartButton)
	stopButton = QPushButton("Stop")
	stopButton.clicked.connect(self.onStopButton)

	l1 = QHBoxLayout()
	l1.addWidget(self.canvas)

	l2 = QVBoxLayout()
	l2.addWidget(startButton)
	l2.addWidget(stopButton)

	l3 = QGridLayout()
	l3.addWidget(QLabel("Time:"), 0, 0)
	l3.addWidget(self.lcd1, 0, 1)
	l3.addWidget(QLabel("s"), 0, 2)
	l3.addWidget(QLabel("Voltage:"), 1, 0)
	l3.addWidget(self.lcd2, 1, 1)
	l3.addWidget(QLabel("V"), 1, 2)

	l23 = QVBoxLayout()
	l23.addLayout(l2)
	l23.addLayout(l3)
	l23.addStretch(1)

	lMain = QHBoxLayout()
	lMain.addLayout(l1)
	lMain.addLayout(l23)

	self.setLayout(lMain)

	self.setWindowTitle("Realtime Monitor")

    def initCanvas(self):
	self.fig = mpl.figure.Figure(figsize=(6, 9), dpi=100)
	self.axes = self.fig.add_subplot(211)
	self.axes.hold(False)
	self.axes2 = self.fig.add_subplot(212)
	self.axes2.hold(False)
	self.canvas = FigureCanvas(self.fig)
	self.initFigure()

    def initFigure(self):
	self.li, = self.axes.plot(t, y)

	self.yf = fft(y)/(N/2)
	self.freq = fftfreq(N, dt)
	self.li2, = self.axes2.plot(self.freq[1:N/2], np.abs(self.yf)[1:N/2])

	self.axes.set_xlabel("Time[s]")
	self.axes.set_ylabel("Voltage[V]")
	self.axes.set_ylim(0, 3.3)
	self.axes2.set_xlabel("Frequency[Hz]")
	self.axes2.set_ylabel("Amplitude")
	self.axes2.set_ylim(0, 3.3)

    def update(self):
	self.li.set_xdata(t)
	self.li.set_ydata(y)
	self.axes.set_xlim(min(t), max(t))

	self.yf = fft(y)/(N/2)
	self.li2.set_ydata(np.abs(self.yf)[1:N/2])

	self.canvas.draw()

	self.lcd1.display("%6.4f" % t[1999])
	self.lcd2.display("%6.3f" % y[1999])

    def onStartButton(self):
	self.f = open('example.csv', 'w')
	self.w = csv.writer(self.f)
	ser.write("1".encode())
	self.Thread.start()
	self.timer.start()

    def onStopButton(self):
	self.Thread.stop()
	self.timer.stop()
	ser.write("0".encode())
	try:
	    for i in range(len(data)):
		self.w.writerow(data[i])
	    self.f.close()
	except:
	    pass

if __name__ == "__main__":
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    ser = serial.Serial(port="/dev/ttyACM0", baudrate=921600)

    N = 2000
    dt = 0.0005
    t = np.zeros(N)
    y = np.zeros(N)
    data = []

    app = QApplication(sys.argv)
    aw = ApplicationWindow()
    aw.show()
    app.exec_()

下図は 50Hz のサイン波を測定中の様子です。信号にノイズが生じているのですが、原因がよくわかっていません。プログラム側の問題ではないと思います。

15071201.jpg

コメント

Comments powered by Disqus
書籍更新情報
2016-10-21
Pythonによる科学技術計算 基礎編
PDF版の販売を開始しました。
販売ページはこちら

2016-09-09
Pythonによる科学技術計算 基礎編
1.2版への更新が可能になりました。
サポートページはこちら
電子書籍
Pythonによる科学技術計算 基礎編
Kindle ストア、Leanpubで販売中です
Pythonによる科学技術計算 基礎編
PDF版の販売はこちら
同人誌
技術書典(2016/6/25)
Emacs/org-modeのPDF作成術
電子版をBOOTHで販売中です
Emacs/org-modeのPDF作成術
Share