Обновить

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

Уровень сложностиПростой
Время на прочтение5 мин
Охват и читатели24K
Всего голосов 183: ↑183 и ↓0+214
Комментарии47

Комментарии 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")

Пример хороший, но диалог выглядит примерно как:

"— Дети, вот учебник алгебре для второго класса. Смотрите, у Вовочки 2 яблока, у Петеньки - 3 яблока...

— Всё это ерунда, в высшей математике это через интеграл решается вот так..."

:)))

Милота. Что-то вспомнилось, как один наш сосед делал ночной обогрев теплицы при помощи ламп накаливания и простого термореле. Ночью можно было наблюдать циклическое включение света, происходящее из за тепловой инерционности системы.

Хорошая статья, легкая подача материала (по работе я как раз связан с автоматикой). Отдельное спасибо за симулятор, очень наглядный!

Замечу, что в чистом виде ПИД регулятор в реальном использовании никогда не применяется, а его "обвязка" и нюансы настройки - как раз самое интересное, о чем обычно в простом виде не пишут

В следующей статье как раз можно написать про фильтрацию сигнала датчика и сигнала уставки, про борьбу с насыщением И-составляющец (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-регулятор дети в кружках по робототехнике используют с 7 класса, если не раньше

спасибо! очень понятно и доступно, изобретал аналогичные вещи для анимации на флеше, но как работает реальный двигатель только сейчас понял!

Тоже делал визуализацию PID-регулятора, попроще, конечно: https://chev.me/pid-demo/.

Это лишь видимая часть айсберга. А для практического применения всё равно лучше математика, чем метод малонаучного тыка.

Будь этот метод автонастройки по разгонной кривой или по гармоническими колебаниям - без математики уже никак. А ведь часто требуется не разовая автонастройка, а автоподстройка на ПЛК или МК в реальном времени. Это даже не касаясь специальных случаев, когда применяются куда более сложные комбинации оффлайн и онлайн методов.

Полностью согласен, но начинать с чего-то надо. PID-регулятор дети в кружках по робототехнике используют с 7 класса, если не раньше

Плюсик поставил) Действительно очень наглядно и понятийно просто написано. У меня давно в эту тему есть вопрос: как думаете возможно ли написать скрипт который по известным даннным реальных испытаний, например 5000 точек, и известным параметрам ПИДа, который процессом управлял, понять как оптимизировать настройки ПИДа?

Спасибо! Думаю, вполне. И скорее всего такие методы уже есть. У меня даже есть идея заняться чем-то подобным летом, когда отпустят соревнования и универ

Для инерционных объектов часто хватает снятия кривой разгона и метода Коэна-Куна.

В Р регуляторе истинное значение никогда не достигается. Как то странно нарисована визуализации. Такое ощущение, что при управляющем воздействии равном нулю на объект нет воздействия, а по факту при управляющем воздействии равном нулю клапан закроется/робот будет "справа в нуле".

Пардон, все ок, не сразу понял визуализацию наклона.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации