Комментарии 47
Эх, как-же редко на хабре встречается что-то полезное, интересное и относящееся к IT.
Побольше-бы таких статей и поменьше мусора про ИИ.
Ну так этот симулятор с ИИ и был сделан.
Это и есть тот самый мусор. Только по другому завернутый.
На практике важно знать что PID поломает шумная или медленная или нелинейная обратная связь и еще куча нюансов.
Но об этом помалкивают такие статьи, потому что проблема пока неподвластна ИИ и требует исследований.
ИИ здесь только проверил математику, сгенерил картинки робота, стрелок (да и то я правил их). Ему сейчас вообще тяжело на pygame-ce sdl2 что-то делать
А, то есть это вообще для игр, а не для инженеров.
Тут обратная связь даже не предусматривалась.
Робототехника только чтобы побольше аудиторию охватить?
Жаль, что когда вы писали предыдущий комментарий, вы не только не прочитали раздел "Ограничения", но и даже не открыли репозиторий, а уже обвинили.
Симуляция предусматривает обратную связь, и о наличии управления свидетельствует его описание в правом верхнем углу и в репозитории. Симулятор создавался не только ради статьи.
Написание симулятора - это не классическая инженерная задача вроде построения графиков, и для неё используются соответствующие фреймворки. Pygame-ce предназначен далеко не только для написания игр, хотя название действительно могло вас запутать.
Да , не читал. Я не читаю мусор.
Но согласен, что использованный вами ИИ очень уж примитивен.
Вот даю вам интерактивный движок для изучения PID с автоматичеким расчетом оптимальных параметров.
Из него вы узнает, что для ваших идеальных игровых объектов всегда можно найти идеальные коэфициенты PID без этих танцев с бубном.
И смысла играться нет, всегда можно рассчитать не прибегая к понятиям.
Проблема совсем в другом месте и там понятия не помогают.

Скрытый текст
import sys
import numpy as np
import matplotlib
try:
matplotlib.use('TkAgg')
except ImportError:
try:
matplotlib.use('Qt5Agg')
except ImportError:
pass
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
import matplotlib.patches as mpatches
import matplotlib.gridspec as gridspec
print(f"Matplotlib backend: {matplotlib.get_backend()}")
class PIDController:
def __init__(self, kp=1.0, ki=0.0, kd=0.0):
self.kp = kp
self.ki = ki
self.kd = kd
self.reset()
def reset(self):
self.integral = 0.0
self.prev_error = 0.0
self.p_hist = []
self.i_hist = []
self.d_hist = []
self.error_hist = []
def update(self, error, dt):
p = self.kp * error
self.integral += error * dt
i = self.ki * self.integral
d = self.kd * (error - self.prev_error) / dt if dt > 0 else 0.0
self.prev_error = error
self.p_hist.append(p)
self.i_hist.append(i)
self.d_hist.append(d)
self.error_hist.append(error)
return p + i + d
def simulate(kp, ki, kd, setpoint, mass, damping, stiffness,
duration=12.0, dt=0.005, disturbance_t=None, disturbance_amp=0.0):
pid = PIDController(kp, ki, kd)
n = int(duration / dt)
t = np.linspace(0, duration, n)
x, v = 0.0, 0.0
x_hist = np.zeros(n)
u_hist = np.zeros(n)
for i in range(n):
error = setpoint - x
u = pid.update(error, dt)
dist = 0.0
if disturbance_t is not None and t[i] >= disturbance_t:
dist = disturbance_amp
a = (u + dist - damping * v - stiffness * x) / mass
v += a * dt
x += v * dt
x_hist[i] = x
u_hist[i] = u
return (t, x_hist, u_hist,
np.array(pid.p_hist), np.array(pid.i_hist),
np.array(pid.d_hist), np.array(pid.error_hist))
def draw_block_diagram(ax):
ax.clear()
ax.set_xlim(0, 20)
ax.set_ylim(0, 5)
ax.axis('off')
box_kw = dict(boxstyle="round,pad=0.5", linewidth=2)
cy = 3.0
ax.annotate("", xy=(2.2, cy), xytext=(0.4, cy),
arrowprops=dict(arrowstyle='->', lw=2.5, color='green'))
ax.text(0.2, cy, 'SP', fontsize=14, color='green', fontweight='bold',
ha='center', va='center')
circle = plt.Circle((3.0, cy), 0.4, fill=False, lw=2.5, color='black')
ax.add_patch(circle)
ax.text(3.0, cy, 'Σ', fontsize=18, ha='center', va='center', fontweight='bold')
ax.text(2.7, cy + 0.3, '+', fontsize=12, ha='center', va='center', color='green')
ax.text(2.7, cy - 0.35, '−', fontsize=14, ha='center', va='center', color='red')
ax.annotate("", xy=(5.2, cy), xytext=(3.4, cy),
arrowprops=dict(arrowstyle='->', lw=2.5))
ax.text(4.3, cy + 0.35, 'e(t)', fontsize=12, ha='center', va='center',
color='#555', style='italic')
pid_box = mpatches.FancyBboxPatch((5.2, cy - 0.8), 4.0, 1.6,
**box_kw, facecolor='#DDEEFF',
edgecolor='#3366AA')
ax.add_patch(pid_box)
ax.text(7.2, cy + 0.3, 'PID-регулятор', fontsize=13, ha='center',
va='center', fontweight='bold', color='#3366AA')
ax.text(7.2, cy - 0.3, r'$K_p e + K_i\!\int\!e\,dt + K_d \frac{de}{dt}$',
fontsize=12, ha='center', va='center', color='#333')
ax.annotate("", xy=(10.8, cy), xytext=(9.2, cy),
arrowprops=dict(arrowstyle='->', lw=2.5))
ax.text(10.0, cy + 0.35, 'u(t)', fontsize=12, ha='center', va='center',
color='#555', style='italic')
plant_box = mpatches.FancyBboxPatch((10.8, cy - 0.8), 4.5, 1.6,
**box_kw, facecolor='#FFF3DD',
edgecolor='#CC8800')
ax.add_patch(plant_box)
ax.text(13.05, cy + 0.3, 'Объект управления', fontsize=13, ha='center',
va='center', fontweight='bold', color='#CC8800')
ax.text(13.05, cy - 0.3, r'$m\ddot{x}+b\dot{x}+kx=u$', fontsize=12,
ha='center', va='center', color='#333')
ax.annotate("", xy=(17.5, cy), xytext=(15.3, cy),
arrowprops=dict(arrowstyle='->', lw=2.5, color='blue'))
ax.text(18.3, cy, 'x(t)', fontsize=14, color='blue', fontweight='bold',
ha='center', va='center')
ax.annotate("", xy=(13.05, cy - 0.8), xytext=(13.05, 0.6),
arrowprops=dict(arrowstyle='->', lw=2, color='purple',
linestyle='dashed'))
ax.text(13.05, 0.3, 'Возмущение d(t)', fontsize=11, ha='center',
va='center', color='purple', style='italic')
ax.plot([16.8, 16.8], [cy, 1.2], lw=2, color='red')
ax.plot([16.8, 3.0], [1.2, 1.2], lw=2, color='red')
ax.annotate("", xy=(3.0, cy - 0.4), xytext=(3.0, 1.2),
arrowprops=dict(arrowstyle='->', lw=2, color='red'))
ax.text(10.0, 0.85, 'Обратная связь (feedback)', fontsize=11,
ha='center', va='center', color='red', style='italic')
def calc_metrics(t, x, setpoint):
overshoot = max((np.max(x) - setpoint) / setpoint * 100, 0) if setpoint != 0 else 0
idx_90 = np.where(x >= 0.9 * setpoint)[0]
rise_time = t[idx_90[0]] if len(idx_90) > 0 else float('inf')
band = 0.02 * abs(setpoint)
settle_time = t[-1]
for i in range(len(t) - 1, -1, -1):
if abs(x[i] - setpoint) > band:
settle_time = t[min(i + 1, len(t) - 1)]
break
tail = x[int(0.9 * len(x)):]
steady_error = abs(setpoint - np.mean(tail))
return overshoot, rise_time, settle_time, steady_error
# ═══════════════════════════════════════════════════════════════
# Автоподбор PID — минимизация ITAE (интеграл |t·e(t)|)
#
# Цель: максимально быстрый и точный выход на уставку,
# минимальная статическая ошибка, без оглядки на
# возмущение.
#
# Метод: грубый перебор по сетке → уточнение Нелдера-Мида
# по критерию ITAE + штраф за перерегулирование.
# ═══════════════════════════════════════════════════════════════
def auto_tune_pid(mass, damping, stiffness):
"""
Числовая оптимизация Kp, Ki, Kd для быстрого выхода
на уставку с минимальной ошибкой.
"""
dt = 0.005
duration = 12.0
setpoint = 1.0
n = int(duration / dt)
t = np.linspace(0, duration, n)
def cost(params):
kp, ki, kd = params
if kp < 0 or ki < 0 or kd < 0:
return 1e12
pid = PIDController(kp, ki, kd)
x, v = 0.0, 0.0
total = 0.0
for i in range(n):
error = setpoint - x
u = pid.update(error, dt)
a = (u - damping * v - stiffness * x) / mass
v += a * dt
x += v * dt
# ITAE: ∫ t·|e|·dt — штрафует за долгую ошибку
total += t[i] * abs(error) * dt
# Штраф за перерегулирование (мягкий — не запрещаем,
# но стараемся минимизировать)
peak = max(pid.error_hist) # макс. выход ≈ setpoint - min(error)
# error_hist хранит (setpoint - x), значит x_max = setpoint - min(error_hist)
x_max = setpoint - min(pid.error_hist)
overshoot_frac = max(x_max - setpoint, 0) / setpoint
total += overshoot_frac * 5.0 # умеренный штраф
# Штраф за статическую ошибку
x_final = setpoint - pid.error_hist[-1]
total += abs(setpoint - x_final) * 50.0
return total
# ── Фаза 1: грубый перебор по сетке ──
best_cost = 1e15
best_params = (2.0, 1.0, 0.5)
# Диапазоны зависят от параметров объекта
omega0 = np.sqrt(max(stiffness, 0.1) / mass)
kp_range = np.linspace(0.5, max(20, mass * 30), 8)
ki_range = np.linspace(0.1, max(10, mass * 15), 6)
kd_range = np.linspace(0.0, max(5, mass * 8), 6)
for kp in kp_range:
for ki in ki_range:
for kd in kd_range:
c = cost((kp, ki, kd))
if c < best_cost:
best_cost = c
best_params = (kp, ki, kd)
# ── Фаза 2: Нелдер-Мид (симплекс) уточнение ──
try:
from scipy.optimize import minimize
result = minimize(cost, best_params, method='Nelder-Mead',
options={'maxiter': 500, 'xatol': 0.01,
'fatol': 1e-6, 'adaptive': True})
if result.fun < best_cost:
best_params = tuple(result.x)
except ImportError:
# Если scipy нет — ручной симплекс-спуск
params = list(best_params)
step = [0.5, 0.3, 0.2]
for iteration in range(200):
improved = False
for dim in range(3):
for direction in [1, -1]:
trial = list(params)
trial[dim] += direction * step[dim]
trial[dim] = max(trial[dim], 0.0)
c = cost(trial)
if c < best_cost:
best_cost = c
params = trial
improved = True
if not improved:
step = [s * 0.7 for s in step]
if max(step) < 0.005:
break
best_params = tuple(params)
kp, ki, kd = best_params
kp = max(round(kp, 2), 0.01)
ki = max(round(ki, 2), 0.01)
kd = max(round(kd, 2), 0.00)
return kp, ki, kd
def main():
mass_init = 1.0
damp_init = 0.3
stiff_init = 0.5
print("Оптимизация PID под параметры объекта...")
kp_init, ki_init, kd_init = auto_tune_pid(mass_init, damp_init, stiff_init)
print(f"Результат: Kp={kp_init}, Ki={ki_init}, Kd={kd_init}")
init = dict(kp=kp_init, ki=ki_init, kd=kd_init,
sp=1.0, mass=mass_init, damp=damp_init, stiff=stiff_init,
dist_t=6.0, dist_a=-2.0)
fig = plt.figure(figsize=(19, 14))
fig.patch.set_facecolor('#F8F8F8')
fig.suptitle("Интерактивная демонстрация PID-регулятора",
fontsize=16, fontweight='bold', y=0.99)
gs = gridspec.GridSpec(3, 3, height_ratios=[1.0, 1.0, 1.0],
hspace=0.55, wspace=0.30,
left=0.06, right=0.97,
top=0.95, bottom=0.40)
ax_schema = fig.add_subplot(gs[0, :])
ax_out = fig.add_subplot(gs[1, 0:2])
ax_comp = fig.add_subplot(gs[1, 2])
ax_err = fig.add_subplot(gs[2, 0:2])
ax_info = fig.add_subplot(gs[2, 2])
draw_block_diagram(ax_schema)
slider_h = 0.013
gap = 0.032
col1_left, col1_width = 0.10, 0.28
col2_left, col2_width = 0.55, 0.28
slider_top = 0.31
slider_defs_left = [
("Kp", 0.0, 20.0, init['kp'], '#CC3333'),
("Ki", 0.0, 10.0, init['ki'], '#22AA44'),
("Kd", 0.0, 5.0, init['kd'], '#2277CC'),
("Уставка SP", 0.1, 3.0, init['sp'], '#888888'),
]
slider_defs_right = [
("Масса m", 0.1, 5.0, init['mass'], '#CC8800'),
("Демпф. b", 0.0, 5.0, init['damp'], '#AA6600'),
("Жёстк. k", 0.0, 5.0, init['stiff'], '#886600'),
("t возм., с", 0.0, 11.0, init['dist_t'], '#8844AA'),
("Ампл. возм.", -5.0, 5.0, init['dist_a'], '#8844AA'),
]
sliders = {}
fig.text(col1_left + col1_width / 2, slider_top + 0.022,
"▸ Параметры PID", ha='center', fontsize=10,
fontweight='bold', color='#333')
fig.text(col2_left + col2_width / 2, slider_top + 0.022,
"▸ Объект и возмущение", ha='center', fontsize=10,
fontweight='bold', color='#333')
for idx, (label, vmin, vmax, vinit, color) in enumerate(slider_defs_left):
y = slider_top - idx * gap
ax_s = fig.add_axes([col1_left, y, col1_width, slider_h],
facecolor='#EEEEEE')
sliders[label] = Slider(ax_s, label, vmin, vmax, valinit=vinit,
color=color, valstep=0.01)
for idx, (label, vmin, vmax, vinit, color) in enumerate(slider_defs_right):
y = slider_top - idx * gap
ax_s = fig.add_axes([col2_left, y, col2_width, slider_h],
facecolor='#EEEEEE')
sliders[label] = Slider(ax_s, label, vmin, vmax, valinit=vinit,
color=color, valstep=0.01)
# ── Кнопки ──
btn_w, btn_h = 0.06, 0.025
ax_auto = fig.add_axes([col2_left + col2_width + 0.04,
slider_top - 0.5 * gap,
btn_w, btn_h])
btn_auto = Button(ax_auto, 'Авто', color='#DDEEFF', hovercolor='#BBDDFF')
ax_reset = fig.add_axes([col2_left + col2_width + 0.04,
slider_top - 2.5 * gap,
btn_w, btn_h])
btn_reset = Button(ax_reset, 'Сброс', color='#DDDDDD', hovercolor='#FFCCCC')
# Индикатор автоподбора
auto_text = fig.text(col2_left + col2_width + 0.04,
slider_top - 4 * gap, '',
fontsize=8, color='#2266AA', family='monospace')
def update(val=None):
kp = sliders["Kp"].val
ki = sliders["Ki"].val
kd = sliders["Kd"].val
sp = sliders["Уставка SP"].val
mass = sliders["Масса m"].val
damp = sliders["Демпф. b"].val
stiff = sliders["Жёстк. k"].val
dt_ = sliders["t возм., с"].val
da = sliders["Ампл. возм."].val
t, x, u, p, i_c, d_c, err = simulate(
kp, ki, kd, sp, mass, damp, stiff,
duration=12.0, dt=0.005,
disturbance_t=dt_ if da != 0 else None,
disturbance_amp=da)
ax_out.clear()
ax_out.plot(t, x, 'b-', lw=1.8, label='Выход x(t)')
ax_out.axhline(sp, color='green', ls='--', lw=1.2,
label=f'Уставка = {sp:.2f}')
ax_out.fill_between(t, sp * 0.98, sp * 1.02, alpha=0.12,
color='green', label='±2% зона')
if da != 0:
ax_out.axvline(dt_, color='purple', ls=':', lw=1, alpha=0.7,
label=f'Возмущение t={dt_:.1f}с')
ax_out.set_title("Выход системы", fontsize=10, fontweight='bold')
ax_out.set_xlabel("Время, с")
ax_out.set_ylabel("x(t)")
ax_out.legend(fontsize=7, loc='upper right')
ax_out.grid(True, alpha=0.25)
ax_out.set_xlim(0, 12)
ax_comp.clear()
ax_comp.plot(t, p, 'r-', lw=1.2, label='P', alpha=0.85)
ax_comp.plot(t, i_c, 'g-', lw=1.2, label='I', alpha=0.85)
ax_comp.plot(t, d_c, 'b-', lw=1.2, label='D', alpha=0.85)
ax_comp.plot(t, u, 'k--', lw=1.0, label='u=P+I+D', alpha=0.6)
ax_comp.set_title("Составляющие P, I, D", fontsize=10, fontweight='bold')
ax_comp.set_xlabel("Время, с")
ax_comp.legend(fontsize=7)
ax_comp.grid(True, alpha=0.25)
ax_comp.set_xlim(0, 12)
ax_err.clear()
ax_err.plot(t, err, 'm-', lw=1.2, label='Ошибка e(t)')
ax_err.plot(t, u, 'k-', lw=1.0, alpha=0.6, label='Управление u(t)')
ax_err.axhline(0, color='gray', ls='-', lw=0.5)
ax_err.set_title("Ошибка и управляющее воздействие",
fontsize=10, fontweight='bold')
ax_err.set_xlabel("Время, с")
ax_err.legend(fontsize=7, loc='upper right')
ax_err.grid(True, alpha=0.25)
ax_err.set_xlim(0, 12)
# ── Метрики ──
ax_info.clear()
ax_info.axis('off')
overshoot, rise_t, settle_t, ss_err = calc_metrics(t, x, sp)
col_l = 0.05
y0 = 0.97
dy = 0.08
def put(row, txt, color='#333', size=9.5, weight='normal'):
ax_info.text(col_l, y0 - row * dy, txt,
transform=ax_info.transAxes,
fontsize=size, color=color, fontweight=weight,
family='monospace', va='top')
put(0, "── Метрики качества ──", '#333', 10, 'bold')
put(1, f"Перерегулир.: {overshoot:6.1f} %", '#CC3333')
put(2, f"Время нараст.: {rise_t:6.3f} с", '#2266AA')
put(3, f"Время устан.: {settle_t:6.3f} с", '#2266AA')
put(4, f"Стат. ошибка: {ss_err:6.4f}", '#22AA44')
put(5.3, "── Параметры PID ──", '#333', 10, 'bold')
put(6.3, f"Kp = {kp:.2f}", '#CC3333')
put(7.3, f"Ki = {ki:.2f}", '#22AA44')
put(8.3, f"Kd = {kd:.2f}", '#2277CC')
put(9.6, "── Объект ──", '#333', 10, 'bold')
put(10.6, f"Масса m = {mass:.2f}", '#CC8800')
put(11.6, f"Демпф. b = {damp:.2f}", '#AA6600')
put(12.6, f"Жёстк. k = {stiff:.2f}", '#886600')
fig.canvas.draw_idle()
def auto_tune(event):
"""Кнопка «Авто» — оптимизировать PID под текущий объект."""
mass = sliders["Масса m"].val
damp = sliders["Демпф. b"].val
stiff = sliders["Жёстк. k"].val
auto_text.set_text("Оптимизация...")
fig.canvas.draw_idle()
fig.canvas.flush_events()
kp, ki, kd = auto_tune_pid(mass, damp, stiff)
print(f"Авто: Kp={kp}, Ki={ki}, Kd={kd} "
f"(m={mass}, b={damp}, k={stiff})")
auto_text.set_text(f"Готово!\nKp={kp}\nKi={ki}\nKd={kd}")
sliders["Kp"].set_val(kp)
sliders["Ki"].set_val(ki)
sliders["Kd"].set_val(kd)
for s in sliders.values():
s.on_changed(update)
def reset(event):
auto_text.set_text('')
for s in sliders.values():
s.reset()
btn_reset.on_clicked(reset)
btn_auto.on_clicked(auto_tune)
update()
print("Окно открывается...")
plt.show()
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"\nОшибка: {e}")
print(f"Backend: {matplotlib.get_backend()}")
import traceback
traceback.print_exc()
print("\nПопробуйте: pip install PyQt5 или sudo apt install python3-tk")Милота. Что-то вспомнилось, как один наш сосед делал ночной обогрев теплицы при помощи ламп накаливания и простого термореле. Ночью можно было наблюдать циклическое включение света, происходящее из за тепловой инерционности системы.
Хорошая статья, легкая подача материала (по работе я как раз связан с автоматикой). Отдельное спасибо за симулятор, очень наглядный!
Замечу, что в чистом виде ПИД регулятор в реальном использовании никогда не применяется, а его "обвязка" и нюансы настройки - как раз самое интересное, о чем обычно в простом виде не пишут
В следующей статье как раз можно написать про фильтрацию сигнала датчика и сигнала уставки, про борьбу с насыщением И-составляющец (anti-windup), настройку зон нечувствительности и прочее
А если добертесь до адаптивного регулировния и ускорения работы ПИД, будет вообще хорошо
Я технолог. И настроил не одну сотню пид регуляторов. То есть, у тебя вновь построенная установка переработки нефти или газа, и как минимум 5_10 пид регуляторов, которые должны четко отрабатывать. По легендам, вроде как это должны делать асушники... Но хочешь сделать хорошо, делай сам... Поэтому, открываешь тренд параметра среды, регулируемой клапаном, и интуитивно подбирая коэффициенты, делаешь что тебе нужно. В моей 30 летней практике, я помню случаи, когда киповец или асушник, садился за пульт со словами "Счас все сделаю...". Но не помню ни одного случая, чтоб и правда сел и сделал. Обычно энтузиазма хватало минут на 15. А для того чтоб настроить что-то важное, типа регулятор подачи нефти в куб колонны, или подача газа на регенерацию в печь, нужны дни, иногда недели .
На такие претензии мне я обычно отвечаю: "Уважаемый товарищ технолог, у вас всего одна установка (две, три, ...). Я же должен обслуживать 42 установки, одновременно".
Но по мере возможности и времени - ПИД регулятор настрою (увы без рассчётов, на глаз, ведь времени на полное изучение теоретического технологического процесса как всегда нет, а к практическому применению техпроцесса меня естественно не допустят).
Можно взять какой-нибудь Овен ПЛК, импортировать в CoDeSys pid_reg2.lib, набросать нужный код с нужными блоками автоподстройки, прогнать его на оборудовании и затем извлечь рассчитанные им коэффициенты.
Уж при 42 установках хотя бы один ПЛК для этой цели можно иметь?
Извините, под установкой имел в виду не прибор, а установку из разнообразного количества оборудования и зоопарка приборов.
Плюс использование каскадов и B-каскадов.
Owen как PID вроде неплох, но как обособленный прибор - нам не подходит.
DCS это вам не SCADA
Owen как PID вроде неплох, но как обособленный прибор - нам не подходит.
Я этого и не предлагал. Перечитайте внимательно, что же я писал: "извлечь рассчитанные им коэффициенты". С автоподстройкой PID коэффициентов он отлично справляется, а больше от него для настройки другого PID регулятора и не требуется.
Во многих ПЛК есть АвтоТюн, т.е. система сама может погонять выход, посмотреть на обратную связь и показать коэффициенты для ПИДа. Но как правило его не используют. АвтоТюн не технолог и может перегреть до недопустимых границ/перелить нефть. Технолог начинает с минимального p, и постоянно контролирует тех.процесс. если видит что скорость процесса большая - оперативно меняет уставку
Вы понимаете разницу между использованием автоподстройки в целях получения коэффициентов для этого ПЛК или другого PID регулятора и постоянной автоподстройкой в ПЛК?
Во многих ПЛК есть АвтоТюн, т.е. система сама может погонять выход, посмотреть на обратную связь и показать коэффициенты для ПИДа. Но как правило его не используют.
Во многих ПЛК автоподстройка ограничена разгонной кривой или гармоническими колебаниями. Точные методы, вроде метода Дудникова Е. Г., в ПЛК не особо популярны, так как требуют существенных вычислительных мощностей.
Спасибо! Примерно такой план и был, а это скорее для проверки насколько оно вообще нужно
А про ПИД с циферками будет? Скажем, П2ИД2?
На всякий случай. Это означает, что управляющий сигнал пропорционален не только величине ошибки, но и её квадрату, а дифференциальный — скорости и ускорению изменения ошибки.
Пока я планировал разобрать основные упущения школьной робототехники
Ну, и то хлеб, спасибо.
А как задать требования к переходному процессу и определить коэффициенты — будет? Или это уже не школа?
P.S. В части анимации — когда сила, действующая на робота небольшая, стрелка, изображающая её величину, воспринимается как факел реактивного двигателя и возникает (лично у меня) когнитивный диссонанс. Если что-то с этим можно сделать, то, пожалуйста.
Интересная статья, освежить универские давно забытые знания. На мой взгляд, лучше бы смотрелись графики вместо анимаций. На координатор оси оно нагляднее
Спасибо! Я специально избегал графиков, так как, к сожалению, у многих возникают трудности с их чтением. А те, кто умеют, могут прочитать большое количество уже написанных статей с их использованием или, что лучше, страницу в wiki. Если потребуется добавить их в симулятор, достаточно добавить их отрисовку в функцию регулятора.
Было б очень познавательно как определить эти коэффициенты.
В непромышленной робототехнике чаще всего подбирают вручную. Сначала P (I, D коэффициенты в это время равны нулю), потом вводят либо I, либо D в зависимости от системы, потом последний. Если есть желание, можно поэкспериментировать в симуляторе
Для простых задач есть метод Никольса-Циглера.
Топорный метод, в идеале надо идентифицировать параметры системы, а потом теоретически рассчитать.
А для идентификации применить как раз методы настройки, все эти Циглеры, Коэны, Никольсы и Куны, через реакцию на тестовое воздействие.
Я поэтому и написал "для простых задач" =) Если человек знает про идентификацию систем, он не будет читать статьи "PID без математики" и спрашивать "как настроить PID" =)
PID-регулятор — это программа
ТОлько ли программа?
Теперь робот вообще не может достигнуть своей цели: в таких условиях чтобы остановиться там, где мы хотим, ему всегда надо прилагать усилия, иначе он будет скатываться, а PD-регулятор это делает только при отдалении от цели.
Что-то робот не в ту сторону скатывается... Или включается вакуумная тяга?
Сначала робот далеко от цели, поэтому P-составляющей достаточно, чтобы приблизиться. По мере приближения она уменьшается, и робот начинает останавливаться. В итоге система приходит в равновесие, где наклон даёт силу направленную вправо, а робот компенсирует это своей силой направленной влево.
О, вспомнил институт и ТАУ. На операционных усилителях удобнее всего это наблюдать и изучать - в электронике есть всевозможные элементы, но базово хватает конденсаторов и индуктивности (конечно без резисторов и самих усилителей никак). Можно прямо в каком-то мультисим погонять.
В институте у нас была аналоговая ЭВМ советского ещë производства. Элементы схемы наглядно можно было соединять проводками, на выходе вольтметр со стрелкой показывал колебания, на входе можно было указать точное напряжение на старте. И если для решения уравнения того же ПИД регулятора надо было немало повозиться, то тут решалось наглядно за пару минут (пока соберëшь схему), разве что на выходе только примерные цифры. Но как минимум, устойчивость системы можно было понять, и наглядно видно какие элементы как влияют. Жаль, эти машины просто списали, разобрали, точнее варварски раскурочили.
Робототехника без математики это путь в никуда. Говорю без тени снобизма. Сам пытался пойти этим путём, а потом всё таки разобрался с передаточными функциями и преобразованием Лапласа. Без понимания математического аппарата на каком-то этапе САУ перестаёт стабилизироваться, и сидишь и не знаешь, что делать.
спасибо! очень понятно и доступно, изобретал аналогичные вещи для анимации на флеше, но как работает реальный двигатель только сейчас понял!
Тоже делал визуализацию PID-регулятора, попроще, конечно: https://chev.me/pid-demo/.
Это лишь видимая часть айсберга. А для практического применения всё равно лучше математика, чем метод малонаучного тыка.
Будь этот метод автонастройки по разгонной кривой или по гармоническими колебаниям - без математики уже никак. А ведь часто требуется не разовая автонастройка, а автоподстройка на ПЛК или МК в реальном времени. Это даже не касаясь специальных случаев, когда применяются куда более сложные комбинации оффлайн и онлайн методов.
Плюсик поставил) Действительно очень наглядно и понятийно просто написано. У меня давно в эту тему есть вопрос: как думаете возможно ли написать скрипт который по известным даннным реальных испытаний, например 5000 точек, и известным параметрам ПИДа, который процессом управлял, понять как оптимизировать настройки ПИДа?
В Р регуляторе истинное значение никогда не достигается. Как то странно нарисована визуализации. Такое ощущение, что при управляющем воздействии равном нулю на объект нет воздействия, а по факту при управляющем воздействии равном нулю клапан закроется/робот будет "справа в нуле".
Пардон, все ок, не сразу понял визуализацию наклона.

PID (ПИД) без математики: как просто понять P, I и D