とある科学の備忘録

とある科学の備忘録

CやPythonのプログラミング、Arduino等を使った電子工作をメインに書いています。また、木製CNCやドローンの自作製作記も更新中です。たまに機械学習とかもやってます。

【PID制御】PID制御をPythonで実装

フィードバックの一種で、PID制御と言うものがあります。

この記事では、PID制御をPythonで2通りのやり方で実装してみます。

 

方法1  matpllotlib のライブラリを使用

プログラム:

from control.matlab import *
from matplotlib import pyplot as plt

# PID制御器のパラメータ
Kp = 0.6  # 比例
Ki = 0.03 # 積分
Kd = 0.03 # 微分
num = [Kd, Kp, Ki]
den = [1, 0]
K = tf(num, den)

# 制御対象
Kt = 1
J = 0.01
C = 0.1
num = [Kt]
den = [J, C, 0]
G = tf(num, den)

# フィードバックループ
sys = feedback(K*G, 1)
t = np.linspace(0, 3, 1000)
y, T = step(sys, t)
plt.plot(T, y)
plt.grid()
plt.axhline(1, color="b", linestyle="--")
plt.xlim(0, 3)

(ほとんどこのサイトからのコピペです。
【python-control】PID制御のシミュレーション | アルゴリズム雑記

実行結果

f:id:pythonjacascript:20180827001327p:plain


方法2  ライブラリを使わずに自分で書く

import matplotlib.pyplot as plt
import numpy as np

import time

class PID:
    def __init__(self, P=0.2, I=0.0, D=0.0):
        self.Kp = P
        self.Ki = I
        self.Kd = D
        self.targetPos=0.
        self.clear()

    def clear(self):
        self.SetPoint = 0.0
        self.PTerm = 0.0
        self.ITerm = 0.0
        self.DTerm = 0.0
        self.last_error = 0.0
        self.delta_time = 0.1
        # Windup Guard
        self.int_error = 0.0
        self.windup_guard = 20.0
        self.output = 0.0

    def update(self, feedback_value):
        error = self.targetPos - feedback_value
        delta_error = error - self.last_error  
        self.PTerm = self.Kp * error  #PTermを計算
        self.ITerm += error * self.delta_time  #ITermを計算

        if (self.ITerm > self.windup_guard):  #ITermが大きくなりすぎたとき様
            self.ITerm = self.windup_guard
        if(self.ITerm < -self.windup_guard):
           self.ITerm = -self.windup_guard
           
        self.DTerm = delta_error / self.delta_time  #DTermを計算
        self.last_error = error
        self.output = self.PTerm + (self.Ki * self.ITerm) + (self.Kd * self.DTerm)
        
    def setTargetPosition(self, targetPos):
        self.targetPos = targetPos
        

if __name__ == "__main__":
    pid = PID(0.3, 0.3, 0.002)

    RepeatNum = 100
    feedback = 0
    target_position = np.ones(RepeatNum)
    
    feedback_list = []

    for i in range(1, RepeatNum):
        pid.update(feedback)
        feedback += pid.output
        pid.setTargetPosition(target_position[i])
        feedback_list.append(feedback)
        
    plt.title('PID control in python')
    plt.xlabel('time (s)')
    plt.ylabel('PID (PV)')
    plt.plot(feedback_list, label = 'target')
    plt.plot(target_position, label = 'feedback')
    plt.ylim(min(target_position) * -0.2, max(target_position) * 1.2)
    plt.legend(loc='lower right')
    plt.show()

(参考文献:https://github.com/ivmech/ivPID

実行結果

f:id:pythonjacascript:20180827001418p:plain

解説

それぞれの変数の説明です。

RepeatNum PID制御を実行する回数
target_position 目標値の配列
PID.SetPoint 現在時間の目標値
PID.Kp p制御の強さ
PID.Ki i制御の強さ
PID.Kd d制御の強さ
PID.PTerm p制御の制御量
PID.ITerm i制御の制御量
PID.DTerm d制御の制御量
PID.windup_guard ITermが大きく(小さく)なりすぎたことを検知する閾値
PID.output PID制御の出力
        if (self.ITerm > self.windup_guard):  #ITermが大きくなりすぎたとき様
            self.ITerm = self.windup_guard
        if(self.ITerm < -self.windup_guard):
           self.ITerm = -self.windup_guard

の部分は、ITermが大きくなりすぎることを防ぐためのものです。
この文のおかげで、ITermは、常に windup_guard <= ITerm <= windup_guardという条件を満たしています。