前言

参考了许多大佬的文章,看着没有用python实现可视化pid的代码,运用所学知识尝试着自己做了一个,算是总结了一下自己所学的python知识


一、pid是什么?

关于pid,建议直接看下面两个大佬写的

PID参数解析+调参经验笔记
位置式PID与增量式PID区别浅析

PID 控制器以各种形式使用超过了 1 世纪,
广泛应用在机械设备、气动设备 和电子设备.
在工业应用中PID及其衍生算法是应用最广泛的算法之一,是当之无愧的万能算法

> P   (Proportion(比例))   单独的比例算法 会有稳态误差
> I  (Integral(积分))     积分控制算法 消除稳态误差(漏水,阻力)
> D   (Derivative(微分))    微分控制算法 减少控制过程中的震荡

在这里插入图片描述

pid基本公式

在这里插入图片描述
在这里插入图片描述

假设采样时间间隔为T,则在k时刻:
偏差为e(k);
积分为e(k)+e(k-1)+e(k-2)++e(0);
微分为(e(k)-e(k-1))/T;
从而公式离散化后如下:

在这里插入图片描述
在这里插入图片描述

    积分系数:Kp*T/Ti,可以用Ki表示;
    微分系数:Kp*Td/T,可以用Kd表示;
    则公式可以写成如下形式:

在这里插入图片描述

注意:1、e(k-1)比e(k)的误差大,所以(e(k)-e(k-1))这项是负的
	        2、在实际代码中可以在最后/50 以方便调参,如:
	        duk=(Kp*(e-e1)+Ki*e+Kd*(e-2*e1+e2))/50;  

二、位置式pid与增量式pid的区别。

数字 PID 控制算法通常分为位置式 PID 控制算法和增量式 PID 控制算法

位置式pid

在这里插入图片描述
当前实际位置与你想要的预期位置的偏差,
适用于执行机构不带积分部件的对象,如舵机,平衡小车的直立。
一般直接用PD控制,不用到I(微分控制),
实际写成

int Position_PID (int Encoder,int Target)
  { 	
   static float Bias,Pwm,Integral_bias,Last_Bias;
   Bias=Encoder-Target; //计算偏差
   Integral_bias+=Bias; //求出偏差的积分	     
   Pwm=Position_KP*Bias+Position_KI*Integral_bias+Position_KD*(Bias-Last_Bias);       
   Last_Bias=Bias;       //保存上一次偏差 
   return Pwm;           //输出
  }


从而实现位置闭环控制

增量式pid

在这里插入图片描述
Pwm+=Kp[e(k)-e(k-1)]+Kie(k)+Kd[e(k)-2e(k-1)+e(k-2)]
e(k):本次偏差
e(k-1):上一次的偏差
e(k-2):上上次的偏差
Pwm代表增量输出
在我们的速度控制闭环系统里面只使用PI控制,因此对PID控制器可简化为以下公式:
Pwm+=Kp[e(k)-e(k-1)]+Kie(k)
在这里插入图片描述
代码实现:

int Incremental_PI (int Encoder,int Target)
{ 	
  static float Bias,Pwm,Last_bias;
  Bias=Encoder-Target;                                  //计算偏差
  Pwm+=Velocity_KP*(Bias-Last_bias)+Velocity_KI*Bias;   //增量式PI控制器
  Last_bias=Bias;	                                    //保存上一次偏差 
  return Pwm;                                           //增量输出
}


与位置式pid相比没有误差累加,比较好,实现了速度闭环控制,可以运用M法测速(单位时间获取脉冲数)测得电机速度。

参考了以下
PID参数解析+调参经验笔记
位置式PID与增量式PID区别浅析

三、用到的matplotlib,scipy,numpy库

1.引入库

#首先win+R 输入cmd 回车 输入(打开终端)
pip install matplotlib -i https://pypi.douban.com/simple scikit-learn
pip install scipy -i https://pypi.douban.com/simple scikit-learn
pip install numpy -i https://pypi.douban.com/simple scikit-learn
#加如豆瓣源使下载更快

SciPy(Scientific Python)和 Matplotlib(绘图库)
NumPy 是一个 Python 包。 它代表 “Numeric Python”。 它是一个由多维数组对象和用于处理数组的例程集合组成的库。

2.测试matplotlib

make_interp_spline 线性插值法 #使线平滑

import numpy as np
from matplotlib import pyplot as plt
from scipy.interpolate import make_interp_spline #spline 线性插值法  #使线平滑
x = np.array([6, 7, 8, 9, 10, 11, 12])#array 矩阵
y = np.array([1.53E+03, 5.92E+02, 2.04E+02, 7.24E+01, 2.72E+01, 1.10E+01, 4.70E+00])
x_smooth = np.linspace(x.min(), x.max(), 300)# 6-12 之间299个间隔 300个数据  np.linspace创建等差数列。
#print(x_smooth)   retstep=True可看数据间隔
fuc = make_interp_spline(x, y)##实现函数  线性插值法
y_smooth= fuc(x_smooth)#利用x_smooth和func函数生成y_smooth,x_smooth数量等于y_smooth数量
#  这两句 相当于
#y_smooth = make_interp_spline(x,y)(x_smooth)
print(x)
# print(y)
# print(x_smooth)
# print(y_smooth)
#print(fuc)
plt.plot(x_smooth, y_smooth)
plt.show()

上述代码实现的效果图
效果图

参考 线性插值

四、python中的小知识及源码

python中的类

class BenzCar():
    brand = '奔驰'  # 品牌属性
    country = '德国'  # 产地属性

    @staticmethod#类的静态方法#从而可以实现实例化使用 C().f(),当然也可以不实例化调用该方法 C.f()。
    def pressHorn():
        print('嘟嘟~~~~~~')

    def __init__(self,color,esn):
        self.color=color
        self.engineSN=esn
BenzCar.pressHorn()#直接调用
car3=BenzCar('green','42424')#实例化
#输出
嘟嘟~~~~~~

如果给这个文件取名Ben
另一个文件可以import Ben 来调用这个文件里写好的类
如Ben.BenzCar.pressHorn()

python中的time

import time
time.time()
print(time.time())
time.sleep(2)
print(time.time())
print("时间",time.ctime())
#输出
1642348585.668144  #1970纪元后经过的浮点秒数
1642348587.6698427
时间 Sun Jan 16 23:56:27 2022

python源代码PID,pidcontrol

代码注释有详解
PID.py

import time


class PID():
    def __init__(self, P, I, D): #父类初始化
        self.Kp = P
        self.Ki = I
        self.Kd = D
        self.sample_time = 0.00
        self.current_time = time.time()
        self.last_time = self.current_time
        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.last_last_error = 0.0
        #self.int_error = 0.0
        self.windup_guard = 20.0  ###积分限幅
        self.output = 0.0

    def update_position(self, feedback_value):#####位置式pid  (这个程序把T用进去了***)  kp=kp  ki=kp/Ti    kd=kp*Td   delta_time是采样周期T
        error = self.SetPoint - feedback_value
        self.current_time = time.time()
        delta_time = self.current_time - self.last_time #采样周期T
        delta_error = error - self.last_error   #e-e1
        if (delta_time >= self.sample_time): ##如果采样周期大于0
            self.PTerm = self.Kp * error  # 比例
            self.ITerm += error * delta_time  # 积分   # 位置式pid 求和
            if (self.ITerm < -self.windup_guard):  ##积分限幅   #位置式PID在积分项达到饱和时,误差仍然会在积分作用下继续累积,一旦误差开始反向变化,系统需要一定时间从饱和区退出,所以在u(k)达到最大和最小时,要停止积分作用,并且要有积分限幅和输出限幅
                self.ITerm = -self.windup_guard
            elif (self.ITerm > self.windup_guard):
                self.ITerm = self.windup_guard
            self.DTerm = 0.0
            if delta_time > 0:
                self.DTerm = delta_error / delta_time
            self.last_time = self.current_time  #更新上次时间
            self.last_error = error             #更新上次误差
            self.output = self.PTerm + (self.Ki * self.ITerm) + (self.Kd * self.DTerm)


    def update_increment(self,feedback_value):##增量式pid###可以只做PI控制
        error = self.SetPoint - feedback_value
        self.output += self.Kp * (error - self.last_error) + self.Ki * error + self.Kd * (error -2 * self.last_error + self.last_error)
        self.last_error = error
        self.last_last_error = self.last_error

        #感觉没用 只是一些说明
    # def setKp(self, proportional_gain):
    #     self.Kp = proportional_gain
    #
    # def setKi(self, integral_gain):
    #     self.Ki = integral_gain
    #
    # def setKd(self, derivative_gain):
    #     self.Kd = derivative_gain
    #
    # def setWindup(self, windup):
    #     self.windup_guard = windup
    #
    # def setSampleTime(self, sample_time):
    #     self.sample_time = sample_time

pidcontrol.py

import PID
import time
import matplotlib

#matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import numpy as np
#from scipy.interpolate import spline ### 会报错
from scipy.interpolate import make_interp_spline

# 这个程序的实质就是在前九秒保持零输出,在后面的操作中在传递函数为某某的系统中输出1

##模拟 L = 80  秒

def test_pid(P=0.2, I=0.0, D=0.0, L=100):
    """Self-test PID class

    .. note::
      ...
      for i in range(1, END):
        pid.update(feedback)
        output = pid.output
        if pid.SetPoint > 0:
          feedback += (output - (1/i))
        if i>9:
          pid.SetPoint = 1
        time.sleep(0.02)
      ---
    """
    pid = PID.PID(P, I, D) ##调用PID类

    pid.SetPoint = 0.0  #前9s设定0  后面设定自己想要的值
    #pid.setSampleTime(0.01)#####################

    END = L
    feedback = 0

    feedback_list = []  ##反馈列表
    time_list = []      ##时间  列表
    setpoint_list = []  ##设定列表

    for i in range(1, END):
        pid.update_increment(feedback)###选择pid类型 增量pid
        output = pid.output
        if pid.SetPoint >0:  #到了第9s
            feedback += output # (output - (1/i))控制系统的函数   #
        if i > 9:
            pid.SetPoint = 10  ##前9s 输出0###########################################################################380
        #time.sleep(0.01) ## 延时0.01s ##使点 少一些 #使仿真更直观

        feedback_list.append(feedback)
        setpoint_list.append(pid.SetPoint)#[0,0,0,0,0,0,0,0,0,1,1,1,...]
        time_list.append(i) ##[1,2,...,L]
        #print(feedback_list)
    time_sm = np.array(time_list)##x [1 2 3 ... L]
    feedback_sm =np.array(feedback_list)##y

    time_smooth = np.linspace(time_sm.min(), time_sm.max(), 300)###x_smooth 等差

    feedback_smooth = make_interp_spline(time_sm, feedback_sm)(time_smooth)  #####y_smooth 线性插值

    plt.figure("PID仿真")#窗口名
    plt.plot(time_smooth, feedback_smooth)####反馈的pid线
    plt.plot(time_list, setpoint_list)  ##设定的pid

    plt.xlim((0, L))### L 时间长度##横坐标的范围 (0,l)
    plt.ylim((min(feedback_list) - pid.SetPoint*0.5, max(feedback_list) + pid.SetPoint*0.5))  ##设置纵坐标范围

    plt.xlabel('time (s)')
    plt.ylabel('PID (PV)')
    plt.title('TEST PID')

    #plt.ylim((1 - 0.5, 1 + 0.5)) ##设置纵坐标范围(0.5,1.5)

    plt.grid(True)##有 坐标方格
    plt.show()


if __name__ == "__main__":
    #test_pid(0.85, 0.001, 0.000, L=80)
    test_pid(0.68,0.010,0.05, L=100)


效果图
在这里插入图片描述

参考代码

五、pyqy5创建ui界面

pyqt5安装与设计界面

pip install pyqt5

pyqt5官方文档
在G:\Python39\Lib\site-packages\qt5_applications\Qt\bin中(类似相对位置)找到designer.exe
创建ui
在这里插入图片描述

将制作的ui界面转化为py文件

在G:\Python39\Scripts (类似相对位置)中找到pyuic5
将保存的.ui文件放到该目录下

#cmd #到该文件夹下
G:
cd G:\Python39\Scripts
pyuic5 - o 目标文件名.py 源文件名.ui

就可以转为py文件
我命名为pid_ui.py
记得先把文件拓展名先勾上
在这里插入图片描述
之后就可以引用这个类了

from pid_ui import Ui_MainWindow

常用几个控件使用

在这里插入图片描述
介绍几个常用的控件 —对应上ui图
QLabel(标签) —Kp
QTextEdit(文本) —0.68
QPushButton(按钮) —仿真
QComboBox(下拉框)—增量式pid(推荐PI控制)
QGroupBox (分组框) —仿真区域
QGridLayout(布局管理器) —代码中
标签仅仅改文本

#文本框使用
#1、赋值
input_kp = self.textEdit_kp.toPlainText()#将原有的值'0.68'赋给input_kp 不过此时赋予的是字符串
P = float(input_kp)
#2、设值
self.textEdit.setText(str(feedback_smooth))#将得到的字符串赋给(y轴数据)

#以下介绍按钮和下拉框事件
mode = 0
class MainWindow(QMainWindow,Ui_MainWindow):

    def __init__(self):
        super().__init__()  # 调用父类构造函数,创建窗体
        #self = Ui_MainWindow()  # 创建UI对象
        self.setupUi(self)  # 构造UI界面

        # 仿真button 点击事件 #我把按钮命名为btnfangzhen
        self.btnfangzhen.clicked.connect(self.fangzhen)
        # 下拉菜单信号事件
        self.comboBox.currentIndexChanged.connect(self.select)
	def fangzhen(self):
	    ...
	def select(self):
		global mode
        if self.comboBox.currentText() == "增量式pid (推荐PI控制)":
            mode = 1
            print(self.comboBox.currentIndex())
        elif self.comboBox.currentText() == "位置式pid (推荐PD控制)":
            mode = 2

如何在ui界面中插入matplotlib(分组框,布局管理器的使用)

请添加图片描述
这个groupbox千万不能和别的控件一起做水平垂直设置,不然图像一直出不来

QGroupBox为构建分组框提供了支持。分组框通常带有一个边框和一个标题栏,作为容器部件来使用,在其中可以布置各种窗口部件。布局时可用作一组控件的容器,但是需要注意的是,内部必须使用布局控件(如QBoxLayout)进行布局。

QGridLayout(网格布局)是将窗口分割成行和列的网格来进行排列,通常可以使用函数addWidget()将被管理的控件(Widget)添加到窗口中,或者使用addLayout()函数将布局(layout)添加到窗口中,也可以通过addWIdget()函数对所添加的控件设置行数与列数的跨越,最后实现网格占据多个窗格

#创建一个matplotlib图形绘制类
class MyFigure(FigureCanvas):
    def __init__(self,width=5, height=4, dpi=100):
        #第一步:创建一个创建Figure
        self.fig = Figure(figsize=(width, height), dpi=dpi)
        #第二步:在父类中激活Figure窗口
        super(MyFigure,self).__init__(self.fig) #此句必不可少,否则不能显示图形  #python2 中super(B, self).show()  # 需要传入自己的类名以及对象self
        #第三步:创建一个子图,用于绘制图形用,111表示子图编号,如matlab的subplot(1,1,1)#第一行第一列第一个
        self.axes = self.fig.add_subplot(111)#创建axes对象实例,这个也可以在具体函数中添加  给个坐标轴

    #第四步:就是画图,【可以在此类中画,也可以在其它类中画】
    def plotcos(self): ##cos图
        self.axes0 = self.fig.add_subplot(111)
        t = np.arange(0.0, 3.0, 0.01)
        s = np.sin(2 * np.pi * t)
        self.axes.plot(t, s)
        plt.show()
    class MainWindow(QMainWindow,Ui_MainWindow):

    	def __init__(self):
        	super().__init__()  # 调用父类构造函数,创建窗体
        	#self = Ui_MainWindow()  # 创建UI对象
        	self.setupUi(self)  # 构造UI界面


    	def Plot(self):##这里是绘图关键
        	# 第五步:定义MyFigure类的一个实例
        	self.F = MyFigure(width=3, height=2, dpi=100)  #意思就是给个画布

        	# 第六步:在GUI的groupBox中创建一个布局,用于添加MyFigure类的实例(即图形)后其他部件。
        	self.F_layout= QGridLayout(self.groupBox)  # 继承容器groupBox    #F_layout 这个是自己随便取的名字 且之前千万不要布好局了
        	self.F_layout.addWidget(self.F)

    	def Plot_sin(self):###测试绘图
        	self.Plot()
        	x = np.linspace(0, 2 * np.pi, 240, endpoint=True)
        	y = np.sin(x)
        	self.F.axes.plot(x, y)  # 绘图  axes 绘制坐标系图形对象  即self.fig.add_subplot(111)

pid.ui.py 和gui_pid.py源码及详细注释

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'pid.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(665, 492)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(260, 20, 63, 20))
        self.label.setTextFormat(QtCore.Qt.AutoText)
        self.label.setScaledContents(False)
        self.label.setObjectName("label")
        self.layoutWidget = QtWidgets.QWidget(self.centralwidget)
        self.layoutWidget.setGeometry(QtCore.QRect(10, 50, 601, 101))
        self.layoutWidget.setObjectName("layoutWidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.layoutWidget)
        self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.comboBox = QtWidgets.QComboBox(self.layoutWidget)
        self.comboBox.setObjectName("comboBox")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.horizontalLayout.addWidget(self.comboBox)
        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem1)
        self.btnfangzhen = QtWidgets.QPushButton(self.layoutWidget)
        self.btnfangzhen.setObjectName("btnfangzhen")
        self.horizontalLayout.addWidget(self.btnfangzhen)
        spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem2)
        self.horizontalLayout.setStretch(0, 1)
        self.horizontalLayout.setStretch(1, 3)
        self.horizontalLayout.setStretch(2, 1)
        self.horizontalLayout.setStretch(3, 1)
        self.horizontalLayout.setStretch(4, 1)
        self.verticalLayout_2.addLayout(self.horizontalLayout)
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        spacerItem3 = QtWidgets.QSpacerItem(13, 14, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem3)
        self.label_2 = QtWidgets.QLabel(self.layoutWidget)
        self.label_2.setObjectName("label_2")
        self.horizontalLayout_2.addWidget(self.label_2)
        spacerItem4 = QtWidgets.QSpacerItem(19, 17, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem4)
        self.label_3 = QtWidgets.QLabel(self.layoutWidget)
        self.label_3.setObjectName("label_3")
        self.horizontalLayout_2.addWidget(self.label_3)
        spacerItem5 = QtWidgets.QSpacerItem(20, 17, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem5)
        self.label_4 = QtWidgets.QLabel(self.layoutWidget)
        self.label_4.setObjectName("label_4")
        self.horizontalLayout_2.addWidget(self.label_4)
        spacerItem6 = QtWidgets.QSpacerItem(19, 17, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem6)
        self.label_5 = QtWidgets.QLabel(self.layoutWidget)
        self.label_5.setLayoutDirection(QtCore.Qt.RightToLeft)
        self.label_5.setObjectName("label_5")
        self.horizontalLayout_2.addWidget(self.label_5)
        spacerItem7 = QtWidgets.QSpacerItem(13, 14, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem7)
        self.horizontalLayout_2.setStretch(0, 2)
        self.horizontalLayout_2.setStretch(1, 1)
        self.horizontalLayout_2.setStretch(2, 4)
        self.horizontalLayout_2.setStretch(3, 1)
        self.horizontalLayout_2.setStretch(4, 4)
        self.horizontalLayout_2.setStretch(5, 1)
        self.horizontalLayout_2.setStretch(6, 4)
        self.horizontalLayout_2.setStretch(7, 1)
        self.horizontalLayout_2.setStretch(8, 2)
        self.verticalLayout.addLayout(self.horizontalLayout_2)
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        self.textEdit_kp = QtWidgets.QTextEdit(self.layoutWidget)
        self.textEdit_kp.setMouseTracking(False)
        self.textEdit_kp.setAcceptDrops(True)
        self.textEdit_kp.setObjectName("textEdit_kp")
        self.horizontalLayout_3.addWidget(self.textEdit_kp)
        spacerItem8 = QtWidgets.QSpacerItem(13, 21, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_3.addItem(spacerItem8)
        self.textEdit_ki = QtWidgets.QTextEdit(self.layoutWidget)
        self.textEdit_ki.setObjectName("textEdit_ki")
        self.horizontalLayout_3.addWidget(self.textEdit_ki)
        spacerItem9 = QtWidgets.QSpacerItem(13, 21, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_3.addItem(spacerItem9)
        self.textEdit_kd = QtWidgets.QTextEdit(self.layoutWidget)
        self.textEdit_kd.setObjectName("textEdit_kd")
        self.horizontalLayout_3.addWidget(self.textEdit_kd)
        spacerItem10 = QtWidgets.QSpacerItem(13, 21, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_3.addItem(spacerItem10)
        self.textEdit_set = QtWidgets.QTextEdit(self.layoutWidget)
        self.textEdit_set.setObjectName("textEdit_set")
        self.horizontalLayout_3.addWidget(self.textEdit_set)
        self.verticalLayout.addLayout(self.horizontalLayout_3)
        self.verticalLayout_2.addLayout(self.verticalLayout)
        self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
        self.groupBox.setEnabled(True)
        self.groupBox.setGeometry(QtCore.QRect(10, 160, 441, 281))
        self.groupBox.setMouseTracking(False)
        self.groupBox.setFlat(False)
        self.groupBox.setCheckable(False)
        self.groupBox.setObjectName("groupBox")
        self.label_6 = QtWidgets.QLabel(self.centralwidget)
        self.label_6.setGeometry(QtCore.QRect(470, 164, 121, 21))
        self.label_6.setObjectName("label_6")
        self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit.setGeometry(QtCore.QRect(460, 190, 151, 251))
        self.textEdit.setObjectName("textEdit")
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:11pt;\">pid仿真</span></p></body></html>"))
        self.comboBox.setItemText(0, _translate("MainWindow", "增量式pid (推荐PI控制)"))
        self.comboBox.setItemText(1, _translate("MainWindow", "位置式pid (推荐PD控制)"))
        self.btnfangzhen.setText(_translate("MainWindow", "仿真"))
        self.label_2.setText(_translate("MainWindow", "Kp"))
        self.label_3.setText(_translate("MainWindow", "Ki"))
        self.label_4.setText(_translate("MainWindow", "Kd"))
        self.label_5.setText(_translate("MainWindow", "目标"))
        self.textEdit_kp.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">0.68</p></body></html>"))
        self.textEdit_ki.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">0.5</p></body></html>"))
        self.textEdit_kd.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">0</p></body></html>"))
        self.textEdit_set.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">100</p></body></html>"))
        self.groupBox.setTitle(_translate("MainWindow", "仿真区域"))
        self.label_6.setText(_translate("MainWindow", "Y轴数据"))

gui_pid.py源码

import sys
import PID

from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtWidgets import QMainWindow, QGridLayout, QApplication
from PyQt5 import QtCore,QtGui,QtWidgets
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QTextCursor
from PyQt5.QtCore import Qt

import matplotlib
matplotlib.use("Qt5Agg")  # 声明使用QT5
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt

import numpy as np
from scipy.interpolate import make_interp_spline



from pid_ui import Ui_MainWindow
#import sip


mode = 0 ### 全局变量 定义开始

#创建一个matplotlib图形绘制类
class MyFigure(FigureCanvas):
    def __init__(self,width=5, height=4, dpi=100):
        #第一步:创建一个创建Figure
        self.fig = Figure(figsize=(width, height), dpi=dpi)
        #第二步:在父类中激活Figure窗口
        super(MyFigure,self).__init__(self.fig) #此句必不可少,否则不能显示图形  #python2 中super(B, self).show()  # 需要传入自己的类名以及对象self
        #第三步:创建一个子图,用于绘制图形用,111表示子图编号,如matlab的subplot(1,1,1)
        self.axes = self.fig.add_subplot(111)#创建axes对象实例,这个也可以在具体函数中添加  给个坐标轴

    #第四步:就是画图,【可以在此类中画,也可以在其它类中画】
    def plotcos(self): ##cos图
        self.axes0 = self.fig.add_subplot(111)
        t = np.arange(0.0, 3.0, 0.01)
        s = np.sin(2 * np.pi * t)
        self.axes.plot(t, s)
        plt.show()

class MainWindow(QMainWindow,Ui_MainWindow):

    def __init__(self):
        super().__init__()  # 调用父类构造函数,创建窗体
        #self = Ui_MainWindow()  # 创建UI对象
        self.setupUi(self)  # 构造UI界面

        # 仿真button 点击事件
        self.btnfangzhen.clicked.connect(self.fangzhen)
        # 下拉菜单信号事件
        self.comboBox.currentIndexChanged.connect(self.select)



    def Plot(self):##这里是绘图关键
        # 第五步:定义MyFigure类的一个实例
        self.F = MyFigure(width=3, height=2, dpi=100)  #意思就是给个画布

        # 第六步:在GUI的groupBox中创建一个布局,用于添加MyFigure类的实例(即图形)后其他部件。
        self.F_layout= QGridLayout(self.groupBox)  # 继承容器groupBox    #F_layout 这个是自己随便取的名字 且之前千万不要布好局了
        self.F_layout.addWidget(self.F)

    def Plot_sin(self):###测试绘图
        self.Plot()
        x = np.linspace(0, 2 * np.pi, 240, endpoint=True)
        y = np.sin(x)
        self.F.axes.plot(x, y)  # 绘图  axes 绘制坐标系图形对象  即self.fig.add_subplot(111)

        #self.F.axes.plot.xlim((0, 1))  ### L 时间长度##横坐标的范围 (0,l)
        # plt.ylim((min(feedback_list) - pid.SetPoint * 0.5, max(feedback_list) + pid.SetPoint * 0.5))  ##设置纵坐标范围
        #
        #self.F.axes.plot.xlabel('time (s)')
        # plt.ylabel('PID (PV)')
        # plt.title('TEST PID')

    def select(self):
        global mode
        if self.comboBox.currentText() == "增量式pid (推荐PI控制)":
            mode = 1
            print(self.comboBox.currentIndex())
        elif self.comboBox.currentText() == "位置式pid (推荐PD控制)":
            mode = 2


    def fangzhen(self):
        self.Plot()

        self.F_layout.deleteLater()  # 删除布局


        global mode
        #plt.close()
        input_kp = self.textEdit_kp.toPlainText()
        input_ki = self.textEdit_ki.toPlainText()
        input_kd = self.textEdit_kd.toPlainText()
        input_set = self.textEdit_set.toPlainText()
        ##result = float(input_set) *6.7

        P = float(input_kp)
        I = float(input_ki)
        D = float(input_kd)


        pid = PID.PID(P, I, D)  ##调用PID类

        pid.SetPoint = 0.0  # 前9s设定0  后面设定自己想要的值
        # pid.setSampleTime(0.01)#####################

        L = 100
        END = L
        feedback = 0

        feedback_list = []  ##反馈列表
        time_list = []  ##时间  列表
        setpoint_list = []  ##设定列表

        for i in range(1, END):
            if (mode ==1 ):
                pid.update_increment(feedback)  ###选择pid类型 增量pid
            elif (mode == 2):
                pid.update_position(feedback)
            else :
                pid.update_increment(feedback)
            output = pid.output
            if pid.SetPoint > 0:  # 到了第9s
                feedback += output  # (output - (1/i))控制系统的函数   #
            if i > 9:
                pid.SetPoint = float(input_set) ##前9s 输出0###########################################################################380
            # time.sleep(0.01) ## 延时0.01s ##使点 少一些 #使仿真更直观

            feedback_list.append(feedback)
            setpoint_list.append(pid.SetPoint)  # [0,0,0,0,0,0,0,0,0,1,1,1,...]
            time_list.append(i)  ##[1,2,...,L]
            # print(feedback_list)
        time_sm = np.array(time_list)  ##x [1 2 3 ... L]
        feedback_sm = np.array(feedback_list)  ##y

        time_smooth = np.linspace(time_sm.min(), time_sm.max(), 300)  ###x_smooth 等差

        feedback_smooth = make_interp_spline(time_sm, feedback_sm)(time_smooth)  #####y_smooth 线性插值


        self.F.axes.plot(time_smooth, feedback_smooth)
        self.F.axes.plot(time_list, setpoint_list)

        #plt.figure("PID仿真")  # 窗口名
        #plt.plot(time_smooth, feedback_smooth)  ####反馈的pid线
        #plt.plot(time_list, setpoint_list)  ##设定的pid

        # plt.xlim((0, L))  ### L 时间长度##横坐标的范围 (0,l)
        # plt.ylim((min(feedback_list) - pid.SetPoint * 0.5, max(feedback_list) + pid.SetPoint * 0.5))  ##设置纵坐标范围
        #
        # plt.xlabel('time (s)')
        # plt.ylabel('PID (PV)')
        # plt.title('TEST PID')
        #
        # # plt.ylim((1 - 0.5, 1 + 0.5)) ##设置纵坐标范围(0.5,1.5)
        #
        # plt.grid(True)  ##有 坐标方格
        # plt.show()
        self.textEdit.setText(str(feedback_smooth))
        #print(feedback_smooth)



if __name__ == "__main__":
    app = QApplication(sys.argv)  # 创建GUI应用程序
    ui = MainWindow()  # 创建窗体
    ui.show()
    sys.exit(app.exec_())

效果图
在这里插入图片描述

六、制作小程序

pyinstaller

由于自己电脑死活找不到pyinstaller.exe装哪了,在朋友电脑上帮忙制作的,详细
参考白月黑羽
成果:

在这里插入图片描述
点开就能运行了。

总结

这是自己第一次用python做的一个小项目,借鉴了不少大佬的程序,发现自己还有好多地方不足,需要去学习,这次记录下来,希望给自己一个好的开端。

Logo

鸿蒙生态一站式服务平台。

更多推荐