PythonとPyQtGraphで作るPIDシミュレータ

Share on:

PID制御の動きをグラフで表示してみたかったのでPyQtGraphで作ってみました。指令値が赤で制御値が青として表示されるようにして、指令値や係数はスライダーで指定できるようにしました。指令値のスライダーを急激に変化させたときのオーバーシュートや、係数を変えた時の挙動を確認できたりします。

制御量の計算

PIDの係数はそれぞれkp、ki、kdで、QSliderから取得した値としています。制御量はQSliderの値とグラフ描画用のnumpy配列であるself.plot_dataの値から計算して、計算結果をmove_valueという変数に入れています。

1# PID calculation
2kp, ki, kd = self.slider_kp.value() * 0.01, self.slider_ki.value() * 0.01, self.slider_kd.value() * 0.01
3e0, e1, e2 = self.slider.value(), self.plot_data['Y2'][-1], self.plot_data['Y2'][-2]
4move_value = kp * (e0 - e1) + ki * e0 + kd * ( (e0 - e1) - (e1 - e2) )
5self.plot_data['Y2'] = np.append( self.plot_data['Y2'][1:], round(move_value, 3) )

グラフの更新

一定時間間隔でグラフを更新したいのでQTimerを使ってグラフを更新することにしました。実際にグラフの更新をするメソッドはupdate_data()というメソッドで、QtCore.QTimer().timeoutが発行されたときにupdate_data()が実行されるようにconnectしています。update_data()ではtimerをstop()してからplotwidgetをclear()して、plotwidgetにaddItem()した後にtimerをstart()します。これで一定時間間隔でupdate_data()が呼び出されて、plotwidgetのclearとaddItemが繰り返されるようになり一定間隔毎のグラフの更新ができるようになりました。

 1def __init__(self, parent=None):
 2    # timer
 3    self.timer = QtCore.QTimer()
 4    self.timer.timeout.connect(self.update_data)
 5    self.timer.start(30)
 6
 7def update_data(self):
 8    # stop timer
 9    self.timer.stop()
10
11    # clear
12    self.plotwidget.clear()
13
14    # set data
15    self.plotwidget.addItem( ... )
16        
17    # start timer
18    self.timer.start(30)

ソースコード

  1# -*- coding: utf-8 -*-
  2import sys
  3from PyQt5 import QtCore, QtGui, QtWidgets
  4from pyqtgraph import PlotWidget
  5import pyqtgraph
  6import numpy as np
  7
  8class MainWindow(QtWidgets.QMainWindow):
  9    def __init__(self, parent=None):
 10        super(MainWindow, self).__init__(parent)
 11
 12        self.plot_data = { 'X':np.arange(0, 256), 'Y':np.full(256, 0), 'Y2':np.full(256, 0) }
 13
 14        self.resize(600, 600)
 15        self.setStyleSheet("QMainWindow {background: 'white';}")
 16
 17        # leyout
 18        self.centralwidget = QtWidgets.QWidget(self)
 19        self.setCentralWidget(self.centralwidget)
 20        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
 21        self.horizontalLayout = QtWidgets.QHBoxLayout()
 22        self.horizontalLayout2 = QtWidgets.QHBoxLayout()
 23        self.verticalLayout.addLayout(self.horizontalLayout)
 24        self.verticalLayout.addLayout(self.horizontalLayout2)
 25        
 26        # pot widget
 27        self.plotwidget = PlotWidget(self)
 28        self.plotwidget.setBackground("#FFFFFFFF")
 29        plotitem = self.plotwidget.plotItem
 30        plotitem.setLabels(bottom='time', left='position')
 31        plotitem.getAxis('bottom').setPen( pyqtgraph.mkPen(color='#000000') )
 32        plotitem.getAxis('left').setPen( pyqtgraph.mkPen(color='#000000') )
 33        plotitem.setRange(yRange = (0, 50), padding = 0)
 34
 35        # slider
 36        self.slider = QtWidgets.QSlider(self)
 37        self.slider.setMaximum(50)
 38        
 39        self.slider_kp = QtWidgets.QSlider(QtCore.Qt.Horizontal)
 40        self.slider_kp.setMaximum(100)
 41        self.slider_kp.setValue(30)
 42        self.slider_ki = QtWidgets.QSlider(QtCore.Qt.Horizontal)
 43        self.slider_ki.setMaximum(100)
 44        self.slider_ki.setValue(100)
 45        self.slider_kd = QtWidgets.QSlider(QtCore.Qt.Horizontal)
 46        self.slider_kd.setMaximum(100)
 47        self.slider_kd.setValue(10)
 48        
 49        # leyout
 50        self.horizontalLayout.addWidget(self.slider)
 51        self.horizontalLayout.addWidget(self.plotwidget)
 52        self.horizontalLayout2.addWidget(QtWidgets.QLabel('Kp'))
 53        self.horizontalLayout2.addWidget(self.slider_kp)
 54        self.horizontalLayout2.addWidget(QtWidgets.QLabel('Ki'))
 55        self.horizontalLayout2.addWidget(self.slider_ki)
 56        self.horizontalLayout2.addWidget(QtWidgets.QLabel('Kd'))
 57        self.horizontalLayout2.addWidget(self.slider_kd)
 58
 59        # timer
 60        self.timer = QtCore.QTimer()
 61        self.timer.timeout.connect(self.update_data)
 62        self.timer.start(30)
 63
 64    def update_data(self):
 65        # stop timer
 66        self.timer.stop()
 67
 68        # clear
 69        self.plotwidget.clear()
 70
 71        # increase data
 72        self.plot_data['X'] = np.append( self.plot_data['X'][1:], self.plot_data['X'][-1] + 1 )
 73        self.plot_data['Y'] = np.append( self.plot_data['Y'][1:], self.slider.value() )
 74
 75        # PID calculation
 76        kp, ki, kd = self.slider_kp.value() * 0.01, self.slider_ki.value() * 0.01, self.slider_kd.value() * 0.01
 77        e0, e1, e2 = self.slider.value(), self.plot_data['Y2'][-1], self.plot_data['Y2'][-2]
 78        move_value = kp * (e0 - e1) + ki * e0 + kd * ( (e0 - e1) - (e1 - e2) )
 79        self.plot_data['Y2'] = np.append( self.plot_data['Y2'][1:], round(move_value, 3) )
 80
 81        # set data
 82        self.plotwidget.addItem(
 83            pyqtgraph.PlotDataItem(
 84                x=self.plot_data['X'], 
 85                y=self.plot_data['Y'], 
 86                pen=pyqtgraph.mkPen(color='#FF0000', width=1), 
 87                antialias=True
 88            )
 89        )
 90        
 91        # set data 2
 92        self.plotwidget.addItem(
 93            pyqtgraph.PlotDataItem(
 94                x=self.plot_data['X'], 
 95                y=self.plot_data['Y2'], 
 96                pen=pyqtgraph.mkPen(color='#0000FF', width=1), 
 97                antialias=True
 98            )
 99        )
100        
101        # start timer
102        self.timer.start(30)
103
104def main():
105    app = QtWidgets.QApplication(sys.argv)
106    mainwindow = MainWindow(None)
107    mainwindow.show()
108    app.exec()
109
110if __name__ == '__main__':
111    main()

関連記事