python实现PID温控算法

PID算法介绍

PID算法是一种常用的控制算法,用于调节和稳定控制系统的输出。

PID代表比例(Proportional)、积分(Integral)和微分(Derivative)

比例(Proportional):比例控制是根据当前误差的大小来产生输出的一部分。误差是指期望值与实际值之间的差异。比例控制通过将误差乘以一个比例常数来产生输出,该输出与误差成正比。比例控制的作用是使系统更快地响应误差,但可能会导致系统产生超调或震荡。

积分(Integral):积分控制是根据误差的累积来产生输出的一部分。积分控制通过将误差累积起来,并乘以一个积分常数来产生输出,该输出与误差的积分成正比。积分控制的作用是消除系统的稳态误差,即系统在长时间内无法达到期望值的情况。

微分(Derivative):微分控制是根据误差的变化率来产生输出的一部分。微分控制通过将误差的变化率乘以一个微分常数来产生输出,该输出与误差的微分成正比。微分控制的作用是抑制系统的过冲和震荡,使系统更加稳定。

PID算法通过将这三个部分的输出相加,得到最终的控制输出。每个部分的权重可以通过调整相应的常数来控制。PID算法的目标是使系统的输出尽可能接近期望值,并在系统受到扰动时能够快速恢复到期望状态。

PID算法广泛应用于工业控制、自动化系统、机器人控制、温度控制等领域。它是一种简单而有效的控制算法,可以根据具体的系统和需求进行调整和优化。

PID参数作用

P参数控制器的输出是与偏差(误差)成比例的,即控制器输出随着系统的偏差增加而增加。P参数的作用是限制系统的上升时间和稳定性,但过大的P值会导致震荡和不稳定的转移函数。

I参数控制器的输出是与偏差的积分成比例的,即控制器输出随着时间的累积而增加。

I参数的作用是消除系统的静态误差,即系统的偏差将在时间推移中逐渐消失,但过大的I值会导致超调和系统不稳定。

D参数控制器的输出是偏差的微分与时间成比例的,即控制器输出随着偏差的变化率的增加而增加。作用是降低系统的超调和减少震荡,但过大的D值可能导致噪声的放大或没有响应。

三个参数的综合作用是控制系统的响应速度(上升时间)、稳定性和精度。 调整PID控制器的参数可以帮助控制系统达到更高的响应速度和精度,同时保持系统的稳定性。通常,通过试验和调整这些参数,可以根据控制系统需求得到最佳的控制响应。

简单来说就是:

P <---> 比例控制<--->对当前状态的处理<--->提高响应速度,过大则无静差

I <---> 微分控制<--->对过去状态的处理<--->用于减小静差

D <---> 积分控制<--->对将来状态的预测<--->用于抑制震荡

位置式PID

位置式PID是当前系统的实际位置,与你想要达到的预期位置的偏差,进行PID控制

因为有误差积分 ∑e(i) 一直累加,也就是当前的输出u(k)与过去的所有状态都有关系,用到了误差的累加值;

输出的u(k)对应的是执行机构的实际位置,一旦控制输出出错(控制对象的当前的状态值出现问题 ),u(k)的大幅变化会引起系统的大幅变化

并且位置式PID在积分项达到饱和时,误差仍然会在积分作用下继续累积,一旦误差开始反向变化,系统需要一定时间从饱和区退出,所以在u(k)达到最大和最小时,要停止积分作用,并且要有积分限幅和输出限幅

所以在使用位置式PID时,一般我们直接使用PD控制,而位置式 PID 适用于执行机构不带积分部件的对象,如舵机和平衡小车的直立和温控系统的控制

增量式PID

增量式PID(Incremental PID)是PID控制算法的一种变体,与传统的位置式PID(Positional PID)相对应。增量式PID算法通过计算当前时刻的控制量与上一时刻的控制量之差,来得到增量控制量,从而实现对系统的控制。

在增量式PID中,控制器的输出是一个增量值,而不是一个绝对值。增量控制量表示了控制器输出的变化量,可以直接应用于系统中,而无需考虑系统的初始状态。

增量式PID相对于位置式PID的优点是:

不受系统初始状态的影响:增量式PID只关注控制量的变化,而不需要考虑系统的初始状态。这使得增量式PID在系统启动时更加稳定。减少积分饱和问题:位置式PID中的积分项可能会导致积分饱和问题,而增量式PID通过增量控制量的计算,可以减少积分饱和的发生。

然而,增量式PID也存在一些限制和注意事项:

对控制器的输出限制要求较高:增量式PID的输出是控制量的增量,因此需要确保控制器的输出范围足够大,以避免输出限制问题。对采样周期要求较高:增量式PID对采样周期的要求较高,需要保证采样周期足够小,以减小误差的累积。

PID离散化公式

PID算法公式

PID算法公式如下:

PWM (k) =PWM(k-1)+Kp*(T(k)-T(k-1))+Ki*(T(k)-Ttarget)+Kd*(T(k)-2*T(k-1)+T(k-2))

参数整定口诀

参数整定寻最佳,从大到小顺次查。

先是比例后积分,最后再把微分加。

曲线振荡很频繁,比例度盘要放大。

曲线漂浮绕大弯,比例度盘往小扳。

曲线偏离回复慢,积分时间往下降。

曲线波动周期长,积分时间再加长。

理想曲线两个波,调节过程高质量。

调试效果图

最终效果图

调试代码

from cProfile import label
import time
from turtle import width
import numpy as np
import matplotlib.pyplot as plt
from subprocess import PIPE, Popen, DEVNULL

from numpy import append


def run(cmd, retype="r"):
    '''run System Command and Return Command Stdout Object'''
    try:
        with Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, encoding="utf-8") as f:
            Ret_Type = {"r": f.stdout.read, "rl": f.stdout.readline, "rls" : f.stdout.readlines, "rc": f.wait}
            if retype == 're':
                return f.stdout.read() + f.stderr.read()
            return Ret_Type[retype]()
    except Exception as e:
        print("\033[31mExecute Err:%s\033[0m"%e)


class DeltaPid(object):
    '''
        PID calculate
        pwm = pre_pwm + kp*(err-pre_ee) + ki*err + kd*(err-2*pre_err+pre_pre_ee)
    '''
    def __init__(self, target_temp, max_pwm, min_pwm, p, i, d):
        self.max_pwm = max_pwm
        self.min_pwm = min_pwm
        self.k_p = p
        self.k_i = i
        self.k_d = d
        self.target_temp = target_temp
        self._pre_temp = target_temp
        self._pre_pre_temp = target_temp - 1

    def calculate(self, cur_temp, pwm_in):
        # pwm = pre_pwm + kp*(err-pre_ee) + ki*err + kd*(err-2*pre_err+pre_pre_ee)
        pwm_out = 0
        p_change = self.k_p * (cur_temp - self._pre_temp)
        i_change = self.k_i * (cur_temp - self.target_temp)
        d_change = self.k_d * (cur_temp - 2 * self._pre_temp + self._pre_pre_temp)
        print(f"p:{p_change} i:{i_change} d:{d_change}")

        delta_output = p_change + i_change + d_change
        print(f"p+i+d output={delta_output}")

        pwm_out = delta_output + pwm_in
        print(f"calculate pwm={pwm_out}")
        self._pre_pre_temp = self._pre_temp
        self._pre_temp = cur_temp

        pwm_out = self.max_pwm if pwm_out > self.max_pwm else (self.min_pwm if pwm_out < self.min_pwm else pwm_out )
        print(f"actual output pwm={pwm_out}")

        return pwm_out


class Pwm(object):
    '''
        function1: set and get fan and heater pwm
        function2: get socket temp
    '''
    def __init__(self, path):
        self.path = f"{path}"
        self.fan_en = []
        self.heater_en = []
        self.fan_pwm = []
        self.heater_pwm = []
        self.temp = []
        for i in range(1, 5):
            self.fan_en.append(f"{self.path}fan{i}_en")
            self.heater_en.append(f"{self.path}heater{i}_en")
            self.fan_pwm.append(f"{self.path}fan{i}_pwm")
            self.heater_pwm.append(f"{self.path}heater{i}_pwm")
            self.temp.append(f"{self.path}temp{i}")

    def en_fan(self, index):
        cmd = f"echo 100 > {self.fan_en[index - 1]}"
        run(cmd)

    def en_heater(self, index):
        cmd = f"echo 100 > {self.heater_en[index - 1]}"
        run(cmd)

    def get_temp(self, index):
        cmd = f"cat {self.temp[index - 1]}"
        return run(cmd).replace('\n', '')

    def get_fan_pwm(self, index):
        cmd = f"cat {self.fan_pwm[index - 1]}"
        return run(cmd).replace('\n', '')

    def set_fan_pwm(self, pwm, index):
        cmd = f"echo {pwm} > {self.fan_pwm[index - 1]}"
        run(cmd)

    def get_heater_pwm(self, index):
        cmd = f"cat {self.heater_pwm[index - 1]}"
        return run(cmd).replace('\n', '')

    def set_heater_pwm(self, pwm, index):
        cmd = f"echo {pwm} > {self.heater_pwm[index - 1]}"
        run(cmd)


def filter(index, limit):
    usb_path = "/sys/dev/char/USB0/USB/"
    pwm = Pwm(usb_path)
    temp = []
    for i in range(0, 7):
        temp_old = int(pwm.get_temp(index))
        temp_new = int(pwm.get_temp(index))
        print(temp_old, temp_new)
        if abs(temp_old - temp_new) < limit: 
            temp.append(temp_old)
            temp.append(temp_new)
    print(temp)
    if not temp:
        return int(pwm.get_temp(index))
    return int(sum(temp)/len(temp))


def test(count=5000, target_temp = 105):
    usb_path = "/sys/dev/char/USB0/USB/"
    pwm = Pwm(usb_path)
    counts = np.arange(count)
    outputs = []
    pwms = []
    # enable fan and heater
    pwm.en_fan(1)
    pwm.en_heater(1)

    # initial fan and heater pwm
    pwm.set_fan_pwm(0, 1)
    pwm.set_heater_pwm(100, 1)

    pid = DeltaPid(target_temp, 45, 5, 10, 0.7, 0.3)
    print(f"Now temp is {pwm.get_temp(1)}")
    print("start test ...")
    print(f"set heater pwm to 100, target temp is {target_temp} ...")

    # set temp to (target) and keep heater in 80 pwm
    print(f"time: {time.ctime()}")
    while True:
        temp1 = filter(1, 20)
        print(f"Now temp is {temp1}")
        time.sleep(1)
        if temp1 / 10 >= (target_temp):
            print("keep heater pwm to 80 ...")
            pwm.set_fan_pwm(35, 1)
            pwm.set_heater_pwm(80, 1)
            break
            # draw
    print(f"time: {time.ctime()}")

    for i in counts:
        print(f"No.{i} pid adjust")
        temp1 = filter(1, 20)
        now_fan_pwm = int(pwm.get_fan_pwm(1))
        pwms.append(now_fan_pwm)
        print(f"temp={temp1}C , fan pwm={now_fan_pwm}")
        now_pwm = pid.calculate(int(temp1) / 10, now_fan_pwm)
        pwm.set_fan_pwm(int(now_pwm), 1)
        time.sleep(1)
        outputs.append(int(temp1) / 10)

    print('Done')

    # draw
    plt.figure()
    plt.axhline(target_temp, c='red', label = "target_temp")
    plt.axhline(target_temp-3, c='yellow', label = "target_temp_min")
    plt.axhline(target_temp+3, c='red', label = "target_temp_max")
    plt.plot(counts, np.array(outputs), 'b.')
    plt.ylim(0, 130)
    plt.plot(counts, outputs, label = "temp")
    plt.plot(counts, pwms, label = "pwm")
    plt.title("PID")
    plt.xlabel('count')
    plt.ylabel('temperature')
    plt.legend()
    plt.tick_params(axis='both', width=1, length=5)
    plt.xticks(fontsize=13)
    plt.yticks(fontsize=13)
    plt.show()


if __name__ == "__main__":
    test()
相关推荐
Swift社区2 小时前
LeetCode - #139 单词拆分
算法·leetcode·职场和发展
Kent_J_Truman2 小时前
greater<>() 、less<>()及运算符 < 重载在排序和堆中的使用
算法
IT 青年3 小时前
数据结构 (1)基本概念和术语
数据结构·算法
Dong雨3 小时前
力扣hot100-->栈/单调栈
算法·leetcode·职场和发展
SoraLuna3 小时前
「Mac玩转仓颉内测版24」基础篇4 - 浮点类型详解
开发语言·算法·macos·cangjie
liujjjiyun3 小时前
小R的随机播放顺序
数据结构·c++·算法
¥ 多多¥4 小时前
c++中mystring运算符重载
开发语言·c++·算法
trueEve4 小时前
SQL,力扣题目1369,获取最近第二次的活动
算法·leetcode·职场和发展
天若有情6734 小时前
c++框架设计展示---提高开发效率!
java·c++·算法
ahadee5 小时前
蓝桥杯每日真题 - 第19天
c语言·vscode·算法·蓝桥杯