PID就是一种控制策略

PID就是一种控制策略,或者说就是一种循环算法——比较数据,发现误差,修改误差。


算法公式:

离散化(为什么要离散化下面会说):

具体PID算法公式

output = self.Kp * (self.CurError + self.Ki * self.ErrorSum + self.Kd * dErr)

PID中的P比例环节(比例控制)
讲究的是 现在 的误差,主要体现在控制的力度
举例说明假如我们要孵化鸡蛋最佳温度是37度
室温27度
Kp取0.5
CurError = Target – curValue
当前误差是10度
Kp * CurError
增加的温度是5度
现在是温度是32度
CurError = Target – curValue
当前误差是5度
Kp * CurError
增加的温度是2.5度
现在是温度是34.5度
看系统仿真图

看着很不错,但是实际控制中,除了增温,还有散热会降温
假设在35度的时候每秒系统降低1度
此时系统当前温度是35度,系统误差为2度(Kp=0.5)
Kp * CurError
Kp * CurError 等于1度恰好升温和降温抵消,系统永远到达不了37度
例如下图的情况

这就是静态误差也叫稳态误差
此时需要引入积分环节

老师说过 I(积分integral)用加法,D (微分differential)用减法
香农采样定理
‌定理内容‌:采样定理说明采样频率与信号频谱之间的关系,是连续信号离散化的基本依据。
香农采样定理:香农采样定理又称采样定理,奈奎斯特采样定理,只要采样频率大于或等于有效信号最高频率的两倍,采样值就可以包含原始信号的所有信息,被采样的信号就可以不失真地还原成原始信号。
计算机还有的固有缺陷,字节数,固有频率,所以需要把连续的函数离散化。
PID中的I(积分integral)用加法。

积分控制就是对 过去 所有的误差求和。

在离散的情况就是做累加(求和sum)。

积分符号

考研数学张宇老师说过就是sum中是s拉长了后的符号。
我们用电脑做仿真,仿真就要离散化。所以下面的积分号 ∫ 变成累加符号Σ
算法公式:

离散化(为什么要离散化下面会说):

不可避免的在PID中就是要对每次的误差求和。
ErrorSum =CurError+ErrorSum

# 积分误差累加
self.ErrorSum += self.CurError

第一次误差是5度第二次是2.5度累计误差就是7.5度
Ki取0.2,0.2*7.5=1.5度

即便此时系统每秒降温1度这样我们也能逐渐到达37度

加入合适的Ki值后,被控量既快速又精准的到达目标值,但是这样的曲线不太行,因为超调了
举个例子:

50度孵鸡蛋变成煮鸡蛋
这是一次失败的调节
积分环节:消除稳态误差,但会增加超调量
此时就需要微分控制出场了
老师说过 I(积分integral)用加法,D (微分differential)用减法
微分 → 求导→斜率→瞬时变化率


我们也说过现实世界采样的限制和计算机的限制我们必须离散化
D (微分differential)用减法,微分控制就是

未来 做预测

微分控制就是当前时刻和前一时刻的误差量的差值
如果差值为正就认为误差在逐渐扩大,需要加大控制强度,使误差降下来
如果差值为负就认为误差在逐渐扩小,需要加小控制强度,让目标平稳的到达指定值
f(现在时刻)— f(前一时刻)
这就是做减法
Err = CurError – PreError

# 微分误差
dErr = self.CurError - self.PreError

我们给Kd一个合适的值平稳到达37度

稳是稳了,但是控制工程告诉我们要稳准快,这个峰值时间93秒太慢了

我们把Kp设置成0.2,Kp控制的力度
由控制孵鸡蛋的案例我们可以总结出PID三个环节各自的主要作用和效应:
比例环节:起主要控制作用,使反馈量向目标值靠拢,但可能导致振荡(现在)
积分环节:消除稳态误差,但会增加超调量(过去)
微分环节:产生阻尼效果,抑制振荡和超调,但会降低响应速度(未来)
在计算上升时间的时候我们会用到牛顿插值法

def newton_interpolating_polynomial(x_points, y_points, x):
    """
    使用牛顿插值法计算插值多项式在给定点x的值
    :param x_points: x坐标点的列表
    :param y_points: y坐标点的列表
    :param x: 需要计算插值值的x坐标
    :return: 插值y值
    """
    diff_table = divided_diff(x_points, y_points)
    # print(diff_table)
    n = len(x_points)
    result = diff_table[0][0]
    product_term = 1.0

    for i in range(1, n):
        product_term *= (x - x_points[i - 1])
        result += product_term * diff_table[i][0]

    return result

上升时间通常定义为系统输出信号从起始值(如零时刻或某一初始值)上升到最终稳态值(或某一规定值)的某一特定比例所需的时间。在控制系统中,这个特定比例通常被设定为稳态值的10%到90%。
例如在计算上升时间的时候我们需要计算0.9倍的稳态值
需要知道y=34.02处的时间

但是由于数据是离散的并没有该点对应的y值,这时候我们需要用牛顿插值法估算出大概的值。

原始代码

# -*-coding :     UTF-8
# @FileName :    pid算法演示.py
# @time     :    2024/10/13 10:57
# @Author   :     Cooper
import numpy as np  # 是一个由多维数组对象和用于处理数组的例程集合组成的库
import matplotlib.pyplot as plt  # Matplotlib 是Python中类似 MATLAB 的绘图工具,熟悉 MATLAB 也可以很快的上手 Matplotlib。


def divided_diff(x, y):
    """
    计算差商表
    :param x: x坐标点的列表
    :param y: y坐标点的列表
    :return: 差商表,二维列表
    """
    n = len(x)
    diff_table = [y[:]]  # 差商表的第一行为y值

    for j in range(1, n):
        row = [0] * (n - j)
        for i in range(n - j):
            row[i] = (diff_table[j - 1][i + 1] - diff_table[j - 1][i]) / (x[i + j] - x[i])
        diff_table.append(row)

    return diff_table


def newton_interpolating_polynomial(x_points, y_points, x):
    """
    使用牛顿插值法计算插值多项式在给定点x的值
    :param x_points: x坐标点的列表
    :param y_points: y坐标点的列表
    :param x: 需要计算插值值的x坐标
    :return: 插值y值
    """
    diff_table = divided_diff(x_points, y_points)
    # print(diff_table)
    n = len(x_points)
    result = diff_table[0][0]
    product_term = 1.0

    for i in range(1, n):
        product_term *= (x - x_points[i - 1])
        result += product_term * diff_table[i][0]

    return result


class PID:
    def __init__(self, P=0, I=0, D=0, initValue=0):
        self.Kp = P
        self.Ki = I
        self.Kd = D
        '''
        self.curValue 表示现在的值
        self.ErrorSum 表示前面所有误差之和
        self.PreError 表示前一次的误差
        self.CurError 表示现在的误差
        '''
        self.curValue = initValue
        self.ErrorSum = 0.0
        self.PreError = 0.0
        self.CurError = 0.0

    def PID_output(self, Target):
        self.PreError = self.CurError
        self.CurError = Target - self.curValue
        # 微分误差
        dErr = self.CurError - self.PreError
        # 积分误差
        self.ErrorSum += self.CurError
        # PID算法公式
        output = self.Kp * (self.CurError + self.Ki * self.ErrorSum + self.Kd * dErr)
        # output = self.Kp * self.CurError + self.Ki * self.ErrorSum + self.Kd * dErr
        self.curValue += output
        return round(self.curValue, 5)

    # 最大超调量 即是响应曲线的最大峰值和和稳态值的差,通常用百分比表示
    def Mp(self, target, pid_list: np.ndarray):
        return (round((max(pid_list) - target) / target, 2)) * 100

    # 峰值时间,响应曲线 0 时刻到达峰值的时间
    def Tp(self, pid_list: np.ndarray):
        pid_list = list(pid_list)
        return pid_list.index(max(pid_list))

    def Tr(self, time_list: np.ndarray, pid_list: np.ndarray, Y1, Y2, target) -> float:
        # 示例用法
        for index1, j in enumerate(pid_list):
            if (j > Y1):
                break
        index1 += 1
        print(index1)
        x_points = time_list[:index1]
        y_points = pid_list[:index1]
        y = Y1

        # y_interpolated = newton_interpolating_polynomial(x_points, y_points, x)
        x_interpolated = round(newton_interpolating_polynomial(y_points, x_points, y), 2)
        print(f"在y={y}处的插值x值为: {x_interpolated}")
        for index1, j in enumerate(pid_list):
            if (j > Y2):
                break
        index1 += 2
        print(index1)
        x_points = time_list[:index1]
        y_points = pid_list[:index1]
        y = Y2

        # y_interpolated = newton_interpolating_polynomial(x_points, y_points, x)
        x_interpolated2 = round(newton_interpolating_polynomial(y_points, x_points, y), 2)
        print(f"在y={y}处的插值x值为: {x_interpolated2}")
        return x_interpolated2 - x_interpolated


def test_PID(P=0, I=0, D=0, initValue=0, number=1, target=0):
    '''
    target 表示目标值
    number 表示循环次数
    '''
    # 实例化类对象
    pid = PID(P, I, D, initValue)
    pid_list = []
    time_list = []
    # 初始化当前的值
    pid_list.append(pid.curValue)
    time_list.append(0)

    for i in range(1, number):
        output = pid.PID_output(target)
        pid_list.append(output)
        time_list.append(i)
    print('pid_list=', pid_list)
    print(len(pid_list))
    print('time_list=', time_list)
    print(len(time_list))
    # 创建数组
    # time_list = np.array(time_list)
    # pid_list = np.array(pid_list)
    # print(time_list)
    # print(pid_list)
    # plt.figure()
    # 画图的风格
    plt.style.use('seaborn-v0_8')
    # plt.style.use('grayscale')
    # 正常显示中文字体
    plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
    # 设置画布的figsize大小,dpi分辨率,num左上角的名称
    plt.figure(num='PID控制', dpi=220, figsize=(7, 4))
    # plt.figure(dpi=300,figsize=(10, 6))
    # 改变文字大小参数-fontsize
    plt.xticks(fontsize=10)
    # 传入数组数据
    plt.plot(time_list, pid_list, color="darkorange")
    # 画一条红色的线 y=target
    plt.axhline(target, c='red')
    # x坐标轴的范围
    plt.xlim((0, number + 1))
    plt.xticks([i for i in range(0, number + 1, 10)])
    # y坐标轴的范围
    plt.ylim((min(pid_list) - 1, max(pid_list) + 1))
    plt.yticks([i for i in range(0, int(max(pid_list)), int(max(pid_list) / 10) - 1)])
    # 设置x轴和y轴的名称
    plt.xlabel('时间(s)', loc='right')
    plt.ylabel('值', loc='top')
    # 算最大超调量
    Mp = pid.Mp(target=target, pid_list=pid_list)
    print('最大超调量', Mp, '%')
    # 算峰值时间
    Tp = pid.Tp(pid_list=pid_list)
    print('峰值时间', Tp, 's')
    # 算上升时间
    Y1 = round(target * 0.1, 2)
    print("Y1=", Y1)
    Y2 = round(target * 0.9, 2)
    print("Y2=", Y2)
    Tr = pid.Tr(time_list=time_list, pid_list=pid_list, Y1=Y1, Y2=Y2, target=target)
    print('上升时间Tr为:', Tr, "s")
    # 设置大标题的名称
    title = f'P={P}  I={I}  D={D}  最大超调Mp: {Mp} %  峰值时间Tp: {Tp} s  上升时间Tr: {Tr} s'

    plt.title(title)
    # grid格网
    plt.grid(True, color='gray')  # 开始画表,颜色为蓝色
    # 'k'表示黑色,'c'表示青色,’g'表示绿色,'m'表示洋红,'r'表示红色,'y'表示黄色,'gray'表示灰色,'lightgray'表示浅灰色
    # plt.grid(True)
    # 开始画图
    plt.show()


if __name__ == '__main__':
    # test_PID(P=0.5, I=0.0, D=0.0, initValue=27, number=20, target=37.8)
    # test_PID(P=0.5, I=0.1, D=0.0, initValue=27, number=20, target=37.8)
    # test_PID(P=0.1, I=0.1, D=0.0, initValue=27, number=50, target=37.8)
    test_PID(P=0.1, I=0.001, D=0.5, initValue=27, number=200, target=37.8)  # 不够快
    test_PID(P=0.2, I=0.001, D=0.5, initValue=27, number=200, target=37.8)  # 可以了
    test_PID(P=0.2, I=0.002, D=0.5, initValue=27, number=200, target=37.8)
    test_PID(P=0.1, I=0.1, D=0.1, initValue=0, number=200, target=37.8)
    test_PID(P=0.2, I=0.1, D=0.1, initValue=0, number=200, target=37.8)  # 孵鸡蛋的最佳温度37.8°
    test_PID(P=0.1, I=0.05, D=0.5, initValue=0, number=200, target=37.8)
    test_PID(P=0.25, I=0.001, D=0.5, initValue=0, number=200, target=37.8)  # 最佳调试参数

上面的代码还没有引入环境扰动,举个例子pid控制保温箱孵鸡蛋的例子来说就是,保温箱放在环境中会自然冷却,并且与环境温度温差越大,失温越快。
牛顿冷却定律(Newton’s Law of Cooling)是物理学中的一个基本定律,它描述了一个物体冷却的速度与其和周围环境的温差之间的正比关系。具体来说,牛顿冷却定律指出,在没有热交换的情况下,一个物体的冷却速率与该物体和周围环境的温差成正比。

这个定律可以用数学公式来表示:dT/dt = -k * (T – T∞)
加入牛顿冷却定律后

def newton_cool(T_initial, T_env, k=0.05, dt=1):
    """ 牛顿冷却定律参数

    :param T_initial:初始温度(摄氏度)
    :param T_env:环境温度(摄氏度)
    :param k:冷却速率常数(秒^-1)一般设置0.05
    :param dt:时间步长(秒)
     """
    temperature = - k * dt * (T_initial - T_env)
    return temperature

pid程序设计思路

为了可以在没有配置Python解释器的电脑里使用该程序,使用pyqt5做简单的界面设计,然后打包成exe。该程序已经和该文章进行资源绑定。

优点,方便pid教学任务,这个系统我已经搭建好了。
使用MATLAB也可以完成此功能但是需要安装MATLAB(10几个G)。
打包好的软件60M左右使用方便,可以使用QQ微信等社交软件传输,随时随地使用该软件轻量、简单、方便,完美做到了教学任务。

点执行后是这样的,上升的曲线是pid自己调节加热孵化箱(孵化箱类内有扰动自然降温),下降的曲线是撤去pid控制后自然冷却的曲线(牛顿冷却定律)

最后献上源代码

# @Python   :    3.13
# @FileName :    pid界面.py
# @time     :    2024/10/25 12:00
# @Author   :     Cooper

import sys  # 导入系统模块
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QMessageBox, QWidget
import numpy as np  # 是一个由多维数组对象和用于处理数组的例程集合组成的库
import matplotlib.pyplot as plt  # Matplotlib 是Python中类似 MATLAB 的绘图工具,熟悉 MATLAB 也可以很快的上手 Matplotlib。


def newton_cool(T_initial, T_env, k=0.05, dt=1):
    """ 牛顿冷却定律参数

    :param T_initial:初始温度(摄氏度)
    :param T_env:环境温度(摄氏度)
    :param k:冷却速率常数(秒^-1)一般设置0.05
    :param dt:时间步长(秒)
     """
    temperature = - k * dt * (T_initial - T_env)
    return temperature


def divided_diff(x, y):
    """计算差商表

    :param x: x坐标点的列表
    :param y: y坐标点的列表
    :return: 差商表,二维列表
    """
    n = len(x)
    diff_table = [y[:]]  # 差商表的第一行为y值

    for j in range(1, n):
        row = [0] * (n - j)
        for i in range(n - j):
            row[i] = (diff_table[j - 1][i + 1] - diff_table[j - 1][i]) / (x[i + j] - x[i])
        diff_table.append(row)

    return diff_table


def newton_interpolating_polynomial(x_points, y_points, x):
    """
    使用牛顿插值法计算插值多项式在给定点x的值
    :param x_points: x坐标点的列表
    :param y_points: y坐标点的列表
    :param x: 需要计算插值值的x坐标
    :return: 插值y值
    """
    diff_table = divided_diff(x_points, y_points)
    # print(diff_table)
    n = len(x_points)
    result = diff_table[0][0]
    product_term = 1.0

    for i in range(1, n):
        product_term *= (x - x_points[i - 1])
        result += product_term * diff_table[i][0]

    return result


class PID:
    def __init__(self, P=0, I=0, D=0, initValue=0, k=0.05):
        self.Kp = P
        self.Ki = I
        self.Kd = D
        '''
        self.curValue 表示现在的值
        self.ErrorSum 表示前面所有误差之和
        self.PreError 表示前一次的误差
        self.CurError 表示现在的误差
        self.initValue表示环境温度
        self.k        表示冷却速率常数
        '''
        self.initValue = initValue
        self.curValue = initValue
        self.k = k
        self.ErrorSum = 0.0
        self.PreError = 0.0
        self.CurError = 0.0

    def PID_output(self, Target):
        # 求冷却的温度-0.25
        ct = newton_cool(T_initial=self.curValue, T_env=self.initValue, k=self.k)
        self.PreError = self.CurError
        self.CurError = Target - self.curValue
        # 微分误差
        dErr = self.CurError - self.PreError
        # 积分误差
        self.ErrorSum += self.CurError
        # PID算法公式
        output = self.Kp * (self.CurError + self.Ki * self.ErrorSum + self.Kd * dErr)
        # output = self.Kp * self.CurError + self.Ki * self.ErrorSum + self.Kd * dErr
        self.curValue += output
        # 计算当前的温度
        self.curValue += ct
        return round(self.curValue, 5)

    # 最大超调量 即是响应曲线的最大峰值和和稳态值的差,通常用百分比表示
    def Mp(self, target, pid_list: np.ndarray):
        return (round((max(pid_list) - target) / target, 2)) * 100

    # 峰值时间,响应曲线 0 时刻到达峰值的时间
    def Tp(self, pid_list: np.ndarray):
        pid_list = list(pid_list)
        return pid_list.index(max(pid_list))

    def Tr(self, time_list: np.ndarray, pid_list: np.ndarray, Y1, Y2, target) -> float:
        # 示例用法
        for index1, j in enumerate(pid_list):
            if (j > Y1):
                break
        index1 += 1
        print(index1)
        x_points = time_list[:index1]
        y_points = pid_list[:index1]
        y = Y1

        # y_interpolated = newton_interpolating_polynomial(x_points, y_points, x)
        x_interpolated = round(newton_interpolating_polynomial(y_points, x_points, y), 2)
        print(f"在y={y}处的插值x值为: {x_interpolated}")
        for index1, j in enumerate(pid_list):
            if (j > Y2):
                break
        index1 += 2
        print(index1)
        x_points = time_list[:index1]
        y_points = pid_list[:index1]
        y = Y2

        # y_interpolated = newton_interpolating_polynomial(x_points, y_points, x)
        x_interpolated2 = round(newton_interpolating_polynomial(y_points, x_points, y), 2)
        print(f"在y={y}处的插值x值为: {x_interpolated2}")
        return x_interpolated2 - x_interpolated


def test_PID(P=0, I=0, D=0, initValue=0, number=1, target=0, k=0.05):
    '''
    target 表示目标值
    number 表示循环次数
    k      表示冷却速率常数
    '''
    # 实例化类对象
    pid = PID(P, I, D, initValue, k=k)
    pid_list = []
    time_list = []
    # 初始化当前的值
    pid_list.append(pid.curValue)
    time_list.append(0)

    for i in range(1, number):
        output = pid.PID_output(target)
        pid_list.append(output)
        time_list.append(i)
    print('pid_list=', pid_list)
    print(len(pid_list))
    print('time_list=', time_list)
    print(len(time_list))
    # 创建数组
    # time_list = np.array(time_list)
    # pid_list = np.array(pid_list)
    # print(time_list)
    # print(pid_list)
    # plt.figure()
    # 画图的风格
    plt.style.use('seaborn-v0_8')
    # plt.style.use('grayscale')
    # 正常显示中文字体
    plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
    # 设置画布的figsize大小,dpi分辨率,num左上角的名称
    plt.figure(num='PID控制', dpi=220, figsize=(7, 4))
    # plt.figure(dpi=300,figsize=(10, 6))
    # 改变文字大小参数-fontsize
    plt.xticks(fontsize=10)
    for i in range(number, number + 101):
        ct = newton_cool(pid_list[i - 1], initValue, k=k)
        pid_list.append(pid_list[i - 1] + ct)
        time_list.append(i)
    # 传入数组数据
    plt.plot(time_list, pid_list, color="darkorange")
    # 画一条红色的线 y=target
    plt.axhline(target, c='red')
    # x坐标轴的范围
    plt.xlim((0, number + 101))
    plt.xticks([i for i in range(0, number + 101, 15)])
    # y坐标轴的范围
    plt.ylim((min(pid_list) - 1, max(pid_list) + 1))
    if max(pid_list) > target:
        plt.yticks([i for i in range(int(initValue - 5), int(max(pid_list)), int(max(pid_list) / 8) - 1)])
    else:
        plt.yticks([i for i in range(int(initValue - 5), int(target) + 5, int(target / 8) - 1)])
    # 设置x轴和y轴的名称
    plt.xlabel('时间(s)', loc='right')
    plt.ylabel('温度°C', loc='top')
    # 算最大超调量
    Mp = pid.Mp(target=target, pid_list=pid_list)
    print('最大超调量', Mp, '%')
    # 算峰值时间
    Tp = pid.Tp(pid_list=pid_list)
    print('峰值时间', Tp, 's')
    # 算上升时间
    Y1 = round(target * 0.1, 2)
    print("Y1=", Y1)
    Y2 = round(target * 0.9, 2)
    print("Y2=", Y2)
    Tr = pid.Tr(time_list=time_list, pid_list=pid_list, Y1=Y1, Y2=Y2, target=target)
    print('上升时间Tr为:', Tr, "s")
    # 设置大标题的名称
    title = f'P={P}  I={I}  D={D}  最大超调Mp: {Mp} %  峰值时间Tp: {Tp} s  上升时间Tr: {Tr} s'

    plt.title(title)
    # grid格网
    plt.grid(True, color='gray')  # 开始画表,颜色为蓝色
    # 'k'表示黑色,'c'表示青色,’g'表示绿色,'m'表示洋红,'r'表示红色,'y'表示黄色,'gray'表示灰色,'lightgray'表示浅灰色
    # plt.grid(True)
    # 开始画图
    plt.show()


class Ui_MainWindow(QtWidgets.QWidget):
    def __init__(self, parent=None):  # parent = None代表此QWidget属于最上层的窗口,也就是MainWindows.
        QtWidgets.QWidget.__init__(self)  # 因为继承关系,要对父类初始化

    def setupUi(self, Translation):
        Translation.setObjectName("Translation")
        Translation.resize(500, 400)
        font = QtGui.QFont()
        font.setFamily("黑体")
        font.setPointSize(12)
        font.setBold(False)
        font.setItalic(False)
        font.setWeight(50)
        Translation.setFont(font)
        self.centralwidget = QtWidgets.QWidget(Translation)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(0, 280, 200, 30))
        font = QtGui.QFont()
        font.setFamily("楷体")
        font.setPointSize(20)
        font.setBold(False)
        font.setItalic(False)
        font.setWeight(50)
        # self.label.setFont(font)
        self.label.setObjectName("label")
        # 第二个文本标签
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(55, 50, 260, 20))
        self.label_4 = QtWidgets.QLabel(self.centralwidget)
        self.label_4.setGeometry(QtCore.QRect(55, 90, 260, 20))
        self.label_5 = QtWidgets.QLabel(self.centralwidget)
        self.label_5.setGeometry(QtCore.QRect(55, 130, 260, 20))
        self.label_7 = QtWidgets.QLabel(self.centralwidget)
        self.label_7.setGeometry(QtCore.QRect(15, 170, 260, 20))
        self.label_8 = QtWidgets.QLabel(self.centralwidget)
        self.label_8.setGeometry(QtCore.QRect(0, 210, 260, 20))
        self.label_9 = QtWidgets.QLabel(self.centralwidget)
        self.label_9.setGeometry(QtCore.QRect(15, 250, 260, 20))

        self.label_6 = QtWidgets.QLabel(self.centralwidget)
        # 横的是x(10),竖的是y(80)    QtCore.QRect(10, 80,
        self.label_6.setGeometry(QtCore.QRect(100, 0, 350, 40))
        self.label_6.setFont(font)
        font = QtGui.QFont()
        font.setFamily("黑体")
        font.setPointSize(12)
        font.setBold(False)
        font.setItalic(False)
        font.setWeight(50)
        self.label_2.setFont(font)
        self.label_2.setObjectName("label_2")
        self.label_4.setFont(font)
        self.label_4.setObjectName("label_4")
        self.label_5.setFont(font)
        self.label_5.setObjectName("label_5")
        # self.label_6.setFont(font)
        self.label_6.setObjectName("label_6")
        self.label_7.setFont(font)
        self.label_7.setObjectName("label_7")
        self.label_8.setFont(font)
        self.label_8.setObjectName("label_8")
        self.label_9.setFont(font)
        self.label_9.setObjectName("label_9")

        self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
        # 第一个文本输入控件
        self.lineEdit.setGeometry(QtCore.QRect(75, 42, 400, 30))
        self.lineEdit.setObjectName("lineEdit")
        # 设置默认输入
        self.lineEdit.setText("0.2")
        # 设置提示输入
        # self.lineEdit.setPlaceholderText("P(比例)")
        # 设置回车键执行函数
        # self.lineEdit.editingFinished.connect(self.slot)
        # 第二个文本输入控件
        self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_2.setGeometry(QtCore.QRect(75, 82, 400, 30))
        self.lineEdit_2.setObjectName("lineEdit_2")
        # 设置默认输入
        self.lineEdit_2.setText("0.05")
        # self.lineEdit_2.setPlaceholderText("I(积分)")
        # 第三个文本输入控件
        self.lineEdit_3 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_3.setGeometry(QtCore.QRect(75, 122, 400, 30))
        self.lineEdit_3.setObjectName("lineEdit_3")
        self.lineEdit_3.setText("1.0")
        # 第4个文本输入控件
        self.lineEdit_4 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_4.setGeometry(QtCore.QRect(75, 162, 400, 30))
        self.lineEdit_4.setObjectName("lineEdit_4")
        # 设置默认输入
        self.lineEdit_4.setText("27")
        # 第5个文本输入控件
        self.lineEdit_5 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_5.setGeometry(QtCore.QRect(75, 202, 400, 30))
        self.lineEdit_5.setObjectName("lineEdit_5")
        # 设置默认输入     
        self.lineEdit_5.setText("200")
        # 第6个文本输入控件
        self.lineEdit_6 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_6.setGeometry(QtCore.QRect(75, 242, 400, 30))
        self.lineEdit_6.setObjectName("lineEdit_6")
        # 设置默认输入
        self.lineEdit_6.setText("37.8")
        # 第7个文本输入控件
        self.lineEdit_7 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_7.setGeometry(QtCore.QRect(75, 282, 400, 30))
        self.lineEdit_7.setObjectName("lineEdit_7")
        # 设置默认输入
        self.lineEdit_7.setText("0.05")

        font = QtGui.QFont()
        font.setFamily("黑体")
        font.setPointSize(12)
        font.setBold(False)
        font.setItalic(False)
        font.setWeight(50)

        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(100, 330, 100, 50))
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setGeometry(QtCore.QRect(300, 330, 100, 50))
        font = QtGui.QFont()
        font.setFamily("黑体")
        font.setPointSize(15)
        font.setBold(True)
        font.setItalic(True)
        font.setUnderline(False)
        font.setWeight(75)
        font.setStrikeOut(False)
        self.pushButton.setFont(font)
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2.setFont(font)
        self.pushButton_2.setObjectName("pushButton_2")
        font = QtGui.QFont()
        font.setFamily("ItalicT")
        font.setPointSize(15)
        font.setBold(True)
        font.setItalic(True)
        font.setWeight(50)
        Translation.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(Translation)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 555, 25))
        self.menubar.setObjectName("menubar")
        Translation.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(Translation)
        self.statusbar.setObjectName("statusbar")
        Translation.setStatusBar(self.statusbar)
        self.retranslateUi(Translation)

        # 点击功能,触发函数
        self.pushButton.clicked.connect(self.slot)
        self.pushButton_2.clicked.connect(self.slot2)
        QtCore.QMetaObject.connectSlotsByName(Translation)

    def retranslateUi(self, Translation):
        _translate = QtCore.QCoreApplication.translate
        # 左上角图标的名字
        Translation.setWindowTitle(_translate("Translation", "PID"))
        self.label.setText(_translate("Translation", "冷却速率:"))
        self.label_2.setText(_translate("Translation", "P:"))
        self.label_4.setText(_translate("Translation", "I:"))
        self.label_5.setText(_translate("Translation", "D:"))
        self.label_7.setText(_translate("Translation", "初始值:"))
        self.label_8.setText(_translate("Translation", "循环次数:"))
        self.label_9.setText(_translate("Translation", "目标值:"))
        self.label_6.setText(_translate("Translation", "PID演示(恒温箱孵鸡蛋)"))

        self.pushButton.setText(_translate("MainWindow", "执行"))
        self.pushButton.setStyleSheet("color:red")
        self.label_6.setStyleSheet("color:red")
        self.pushButton_2.setText(_translate("MainWindow", "恢复默认"))
        self.pushButton_2.setStyleSheet("color:gray")

        # 当我们关闭一个窗口时,在PyQt中就会^触发一个QCloseEvent的事件,正常情况下会直接关闭这个窗口,
        # 但是我们不希望这样的事情发生,所以我们需要重新定义QCloseEvent,函数名称为closeEvent不可变

    def fanyi(self):
        try:
            print('*' * 10)
            # 返回的是字符串str类型
            # print(type(self.lineEdit.text()))
            print('P:', self.lineEdit.text())
            P = float(self.lineEdit.text())
            print('I:', self.lineEdit_2.text())
            I = float(self.lineEdit_2.text())
            print('D:', self.lineEdit_3.text())
            D = float(self.lineEdit_3.text())
            print('初始值:', self.lineEdit_4.text())
            initValue = float(self.lineEdit_4.text())
            print('循环次数:', self.lineEdit_5.text())
            number = int(self.lineEdit_5.text())
            print('目标值:', self.lineEdit_6.text())
            target = float(self.lineEdit_6.text())
            print('冷却速率:', self.lineEdit_7.text())
            k = float(self.lineEdit_7.text())
            if int(self.lineEdit_5.text()) > 20:
                print('循环次数输入正确')
                # P=0.2 I=-0.003 D=0.8
                test_PID(P=P, I=I, D=D, initValue=initValue, number=number, target=target, k=k)
            else:
                print('循环次数输入错误,请重新输入')
                widget = QWidget()
                QMessageBox.warning(widget, '警告', '循环次数输入错误', QMessageBox.Close, )

        except Exception as e:
            print(f'出错了,错误原因是:{e}')

    def slot(self):
        print('执行')
        self.fanyi()

    def slot2(self):
        print('恢复默认')
        self.lineEdit.clear()
        self.lineEdit_2.clear()
        self.lineEdit_3.clear()
        self.lineEdit_4.clear()
        self.lineEdit_5.clear()
        self.lineEdit_6.clear()
        self.lineEdit_7.clear()
        # 设置默认输入
        self.lineEdit.setText("0.2")
        self.lineEdit_2.setText("0.05")
        self.lineEdit_3.setText("1.0")
        self.lineEdit_4.setText("27")
        self.lineEdit_5.setText("200")
        self.lineEdit_6.setText("37.8")
        self.lineEdit_7.setText("0.05")


class QMainWindow(QtWidgets.QMainWindow):
    """对QMainWindow类重写,实现一些功能"""

    def closeEvent(self, event):
        """
        重写closeEvent方法,实现dialog窗体关闭时执行一些代码
        :param event: close()触发的事件
        :return: None
        """
        reply = QtWidgets.QMessageBox.question(self,
                                               '本程序',
                                               "是否要退出程序?",
                                               QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
                                               QtWidgets.QMessageBox.No)
        if reply == QtWidgets.QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()


def show_MainWindow():
    app = QtWidgets.QApplication(sys.argv)  # 实例化QApplication类,作为GUI主程序入口
    MainWindow = QMainWindow()  # 注意修改为了自己重写的QMainWindow类
    ui = Ui_MainWindow()  # 实例化ui类
    ui.setupUi(MainWindow)  # 设置窗口UI
    MainWindow.show()  # 显示窗口
    sys.exit(app.exec_())  # 当窗口创建完成时,需要结束主循环过程


if __name__ == "__main__":
    show_MainWindow()

该项目进行了pid调节恒温箱孵鸡蛋的演示,基本了解数字仿真的基本流程。缺点是纯电脑仿真,未进行实物模拟。

作者:择~城

物联沃分享整理
物联沃-IOTWORD物联网 » python和pid控制

发表回复