kiln-controller-max31856/zieglernicols.py

114 wiersze
3.8 KiB
Python
Czysty Wina Historia

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import csv
import argparse
# Using the method described in "Ziegler–Nichols Tuning Method" by Vishakha Vijay Patel
# (https://www.ias.ac.in/article/fulltext/reso/025/10/1385-1397)
def line(a, b, x):
return a * x + b
def invline(a, b, y):
return (y - b) / a
def plot(xdata, ydata,
tangent_min, tangent_max, tangent_slope, tangent_offset,
lower_crossing_x, upper_crossing_x):
from matplotlib import pyplot
minx = min(xdata)
maxx = max(xdata)
miny = min(ydata)
maxy = max(ydata)
pyplot.scatter(xdata, ydata)
pyplot.plot([minx, maxx], [miny, miny], '--', color='purple')
pyplot.plot([minx, maxx], [maxy, maxy], '--', color='purple')
pyplot.plot(tangent_min[0], tangent_min[1], 'v', color='red')
pyplot.plot(tangent_max[0], tangent_max[1], 'v', color='red')
pyplot.plot([minx, maxx], [line(tangent_slope, tangent_offset, minx), line(tangent_slope, tangent_offset, maxx)], '--', color='red')
pyplot.plot([lower_crossing_x, lower_crossing_x], [miny, maxy], '--', color='black')
pyplot.plot([upper_crossing_x, upper_crossing_x], [miny, maxy], '--', color='black')
pyplot.show()
def calculate(filename, tangentdivisor, showplot):
# parse the csv file
xdata = []
ydata = []
filemintime = None
with open(filename) as f:
for row in csv.DictReader(f):
try:
time = float(row['pid_time'])
temp = float(row['pid_ispoint'])
if filemintime is None:
filemintime = time
xdata.append(time - filemintime)
ydata.append(temp)
except ValueError:
continue # just ignore bad values!
# gather points for tangent line
miny = min(ydata)
maxy = max(ydata)
midy = (maxy + miny) / 2
yoffset = int((maxy - miny) / tangentdivisor)
tangent_min = tangent_max = None
for i in range(0, len(xdata)):
rowx = xdata[i]
rowy = ydata[i]
if rowy >= (midy - yoffset) and tangent_min is None:
tangent_min = (rowx, rowy)
elif rowy >= (midy + yoffset) and tangent_max is None:
tangent_max = (rowx, rowy)
# calculate tangent line to the main temperature curve
tangent_slope = (tangent_max[1] - tangent_min[1]) / (tangent_max[0] - tangent_min[0])
tangent_offset = tangent_min[1] - line(tangent_slope, 0, tangent_min[0])
# determine the point at which the tangent line crosses the min/max temperaturess
lower_crossing_x = invline(tangent_slope, tangent_offset, miny)
upper_crossing_x = invline(tangent_slope, tangent_offset, maxy)
# compute parameters
L = lower_crossing_x - min(xdata)
T = upper_crossing_x - lower_crossing_x
# Magic Ziegler-Nicols constants ahead!
Kp = 1.2 * (T / L)
Ti = 2 * L
Td = 0.5 * L
Ki = Kp / Ti
Kd = Kp * Td
# outut to the user
print(f"Kp: {Kp} 1/Ki: {1/ Ki}, Kd: {Kd}")
if showplot:
plot(xdata, ydata,
tangent_min, tangent_max, tangent_slope, tangent_offset,
lower_crossing_x, upper_crossing_x)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Perform Ziegler-Nichols PID tuning')
parser.add_argument('csvfile', type=str, help="The CSV file to read from. Must contain two columns called pid_time (time in seconds) and pid_ispoint (observed temperature)")
parser.add_argument('--showplot', action='store_true', help="If set, also plot results (requires pyplot to be pip installed)")
parser.add_argument('--tangentdivisor', type=float, default=4, help="Adjust the tangent calculation to fit better. Must be >= 2.")
args = parser.parse_args()
if args.tangentdivisor < 2:
raise ValueError("tangentdivisor must be >= 2")
calculate(args.csvfile, args.tangentdivisor, args.showplot)