Посмотр рубрик

ПИД регулятор

ПИД регулятор - один из самых распространенных автоматических регуляторов. Он настолько универсален, что применяется практически везде, где нужно автоматическое управление. Например температурой: печи, холодильники, инкубаторы, паяльники, сопло и стол 3D принтера, ИК паяльные станции и прочее. Поддержание частоты оборотов мотора, например для станков. Всевозможные балансирующие штуки, гироскутеры, сигвеи, левитирующие магнитные платформы, и конечно же квадрокоптеры и самолёты с автопилотом. Всё это - ПИД регулятор.

Почему именно ПИД? Существуют и другие регуляторы, превосходящие ПИД по адаптивности к управляемой системе и стабильности, например линейно-квадратичный. Но синтезировать такой регулятор очень сложно, а настройка ПИД регулятора дело хоть и неприятное, но фактически очень простое и под силу любому, а сам регулятор занимает буквально 5 строк кода с очень простыми вычислениями.

Система управления #

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

Объект управления – это устройство, которым мы управляем, например печка или мотор. Для этого у нас есть управляющее устройство, например диммируемый тен или драйвер мотора. Управляющее устройство получает от регулятора управляющий сигнал, то есть конкретное число. Это может быть заполнение шим сигнала, от 0 до 255, а может быть угол поворота сервомашинки от 0 до 180 - регулятору без разницы чем управлять. В объекте управления стоит датчик, с которого регулятор получает величину, которой он по сути и управляет. Это - обратная связь, которая и даёт возможность системе точно поддержать заданное значение. В случае с печкой это температура, а с мотором – частота оборотов. Ну и наконец регулятор получает уставку (установку, настройку, заданное значение), то есть число, к которому он должен привести текущее значение с датчика. Уставка может быть фиксированной или задаваться каким угодно образом: крутилкой, ползунком, энкодером, кнопками - регулятору это неважно, для него это просто число.

Задача регулятора состоит в том, чтобы сравнивать текущее значение (обратная связь) с заданным (уставка) и выдавать управляющий сигнал на управляющее устройство. Ошибка регулирования - буквально разность уставки и обратной связи, на которую ориентируется регулятор. В программе это будет выглядеть условно так: регулятор получил значение с датчика, нашёл разницу с уставкой, произвёл вычисления и выдал управляющий сигнал. Если это ШИМ – мы его подаём через функцию генерации ШИМ, если угол для привода - отправляем на привод.

В программной (дискретной) реализации регулятор должен делать расчёты и применять управляющий сигнал через равные промежутки времени, то есть с равным периодом или частотой. Эта частота называется частотой дискретизации системы, а период обозначается как dt, прямо как период интегрирования. По сути это время в секундах, которое прошло с прошлого расчёта.

Под капотом у ПИД регулятора #

ПИД регулятор состоит из трёх составляющих: пропорциональной P (proportional), интегрирующей I (integral) и дифференциирующей D (derivative) и формируется просто как сумма трёх значений, умноженных каждая на свой коэффициент. Эта сумма после вычислений становится управляющим сигналом, который подаётся на управляющее устройство. Обозначим его как output:

output = P * Kp + I * Ki + D * Kd;

Где Kp, Ki и Kd это коэффициенты, которые нужно настроить для работы ПИДа. Значения тут могут быть самые разные, от 0.001 то десятков и тысяч, это зависит от конкретной системы и её диапазона значений входа и выхода.

Тут есть ещё один момент: любой коэффициент может быть равен нулю, в таком случае обнуляется вся его компонента. То есть регулятор можно превратить в П, ПИ, ПД и прочие сочетания. Разные системы требуют разного подхода, именно поэтому ПИД регулятор такой универсальный.

В дальнейшем будем пользоваться следующими именами переменных:

  • output - выход с регулятора (управляющий сигнал)
  • setpoint - уставка (заданное значение)
  • input - вход (значение с датчика)
  • error - ошибка регулирования
  • dt - период дискретизации
error = setpoint - input;

P составляющая #

Пропорциональная составляющая представляет собой текущее значение ошибки:

P = error;

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

Если система пришла к заданной величине, то ошибка станет равной нулю - и управляющий сигнал тоже! Другими словами, П-регулятор никогда не сможет привести к заданному значению - всегда будет некая ошибка.

  • Является основной в ПИД регуляторе
  • Увеличивает быстродействие системы
  • Сильно уменьшает статическую ошибку, но не до нуля
  • При большом значении может вызвать колебания и неустойчивость

P составляющая исправляет ошибку в текущий момент времени

I составляющая #

Интегральная составляющая суммирует в саму себя ошибку, умноженную на период дискретизации системы (время, прошедшее с предыдущего расчёта) - фактически берёт интеграл от ошибки по времени:

I = I + error * dt;

Здесь I - интегральная сумма накопившихся ошибок. Её нужно хранить между вызовами расчёта.

В самом регуляторе это ещё умножается на коэффициент Ki, которым и настраивается вклад данной составляющей в процесс. В интегральной составляющей буквально копится ошибка, что позволяет регулятору с течением времени полностью её устранить, то есть привести систему ровно к заданному значению с минимальной ошибкой.

  • Полностью устраняет статическую ошибку
  • Большое значение приводит к колебаниям, перерегулированию, медленному выходу из насыщения

I составляющая исправляет прошлые, накопившиеся ошибки

D составляющая #

Дифференциальная составляющая представляет собой разность текущей и предыдущей ошибок, поделенную на dt – это производная от ошибки по времени:

D = (error - prevError) / dt;
prevError = error;

Здесь prevError - предыдущая ошибка, которая нужна для расчёта производной. Её тоже нужно хранить между вызовами расчёта.

Фактически D составляющая реагирует на изменение сигнала с датчика и чем сильнее происходит это изменение, тем большее значение прибавляется к общей сумме. Иными словами, D позволяет компенсировать резкие изменения в системе и при правильной настройке предотвратить сильное перерегулирование и уменьшить раскачку, т.е. тормозит систему и уменьшает управляющий сигнал. Коэффициент Kd позволяет настроить вес данной компенсации.

D составляющая может усиливать шумы сигнала и приводить к нестабильности системы, поэтому нужно фильтровать сигнал перед регулятором, если он шумит, либо вовсе не использовать D - множество систем отлично работает на ПИ-регуляторе, который нормально себя ведёт даже на сигнале с шумом.

  • Добавляет предсказание - реагирует на скорость изменения ошибки
  • Снижает колебания, улучшает устойчивость, гасит резкие скачки
  • Чувствительна к шуму в измерениях и усиливает помехи - бесполезна на шумном сигнале

D составляющая исправляет возможные будущие ошибки, анализируя скорость изменения входящего сигнала

Всё вместе #

Соединим всё вышесказанное в виде псевдокода:

error = setpoint - input;
I = I + error * dt;
D = (error - prevError) / dt;
prevError = error;
output = error * Kp + I * Ki + D * Kd;

Это и есть классический дискретный ПИД регулятор.

Реализация на Arduino/C #

Рассмотрим реализацию ПИД регулятора на Arduino для наглядности в виде набора переменных и функции для расчёта. Потом это можно очень просто обернуть например в класс:

float Kp, Ki, Kd;
float integral, pError;

float computePID(float setpoint, float input, float dt) {
    float error = setpoint - input;
    float derivative = (error - pError) / dt;
    pError = error;
    integral += error * dt;
    return error * Kp + integral * Ki + derivative * Kd;
}

Как использовать:

  • Задать коэффициенты
  • Вызывать функцию с некоторым периодом, этот период тоже передать в функцию
  • Результат отправлять на управляющее устройство

Например:

void setup() {
    Kp = 10;
    Ki = 5;
    Kd = 1;
}

void loop() {
    float out = computePID(уставка, сигнал_с_датчика, 0.01);
    analogWrite(пин, out);
    delay(10);
    // 10 мс это 0.01 с
}

Расчёт должен вызываться с указанным dt, чтобы математика регулятора работала корректно (И и Д составляющие). Если нет возможности обеспечить вызов расчёта с одинаковым периодом (блокирующий код, прерывания, глухие циклы), то можно передавать в расчёт фактическое время, прошедшее с прошлого расчёта. Например - вызов по таймеру на миллис с коррекцией:

static uint32_t tmr;
uint32_t dt = millis() - tmr;

if (dt >= 10) {     // каждые 10 мс
    tmr = millis();
    computePID(уставка, сигнал_с_датчика, dt / 1000.0);
}

Настройка регулятора #

Для настройки регулятора нужно варьировать коэффициенты:

  • При увеличении Kp увеличивается скорость выхода на установленное значение, увеличивается управляющий сигнал. Чисто математически система не может прийти ровно к заданному значению, так как при приближении к установке П составляющая пропорционально уменьшается. При дальнейшем увеличении Kp реальная система теряет устойчивость и начинаются колебания
  • При увеличении Ki растёт скорость компенсации накопившейся ошибки, что позволяет вывести систему точно к заданному значению с течением времени. Если система медленная, а Ki слишком большой - интегральная сумма сильно вырастет и произойдёт перерегулирование, которое может иметь характер незатухающих колебаний с большим периодом. Поэтому интегральную сумму в алгоритме регулятора часто ограничивают, чтобы она не могла увеличиваться и уменьшаться до бесконечности (см. ниже)
  • При увеличении Kd растёт стабильность системы, она не даёт системе меняться слишком быстро. В то же время Kd может сам стать причиной неадекватного поведения системы и постоянных скачков управляющего сигнала, если значение с датчика шумит. На каждое резкое изменение сигнала с датчика Д составляющая будет реагировать изменением управляющего сигнала, поэтому сигнал с датчика нужно фильтровать

Вот так выглядит процесс стабилизации при изменении коэффициентов:


Симуляция нагрева, регулятор управляет нагревателем, выходной сигнал ограничен 0-100

Метод Циглера-Никольса #

Начальные коэффициенты для подбора можно получить по следующему алгоритму: сначала выставляем все коэффициенты в 0. Плавно увеличиваем Kp до появления незатухающих колебаний. Значение Kp, при котором они появились, запишем и обозначим как Kp1. Далее замеряем период колебаний системы в секундах - T. Итоговые коэффициенты получим как:

Регулятор Kp Ki Kd
P 0.5 * Kp1
PI 0.45 * Kp1 0.54 * Kp1 / T
PID 0.6 * Kp1 1.2 * Kp1 / T 0.075 * Kp1 * T

Модификации алгоритма #

D по входу #

В классическом PID Д составляющая считается от ошибки, которая равна setpoint - input, поэтому при изменении уставки будет провоцировать резкие выбросы на выходе, что может быть неприемлемо для некоторых систем:

Если сигнал уставки шумит - этот шум усилится и станет выходом регулятора, что сделает систему неустойчивой и очень дёрганой. Решение проблемы - слегка пересмотреть уравнение:

(error) - (prevError) = (setpoint - input) - (setpoint - prevInput) = prevInput - input

То есть брать производную не по ошибке, а по входу. Такая модификация практически всегда используется в промышленных контроллерах и позволяет:

  • Уменьшить чувствительность к скачкам уставки
  • Уменьшить шум в дифференцирующем канале
  • Повысить стабильность и надёжность системы

Д по ошибке (классическая) используется в теоретических моделях и симуляциях, а также в системах, где важна высокая скорость отклика на ошибку. Полная реализация D по входу:

float Kp, Ki, Kd;
float integral, pInput;

float computePID(float setpoint, float input, float dt) {
    float error = setpoint - input;
    float derivative = (pInput - input) / dt;
    pInput = input;
    integral += error * dt;
    return error * Kp + integral * Ki + derivative * Kd;
}

Результат - нет таких сильных выбросов на выходе:

Внесение Ki в интеграл #

В классическом виде интеграл считается по ошибке, а затем умножается на Ki в выражении для выхода. Это приводит к тому, что интеграл живёт сам по себе, независимо от Ki. Например, мы запустили систему с нулевым Ki и увеличили его спустя некоторое время. За это время накопилась огромная интегральная ошибка, стабилизировать систему с ней будет непросто:

Решение - внести Ki в расчёт интеграла:

integral += error * Ki * dt;

Это никак не повлияет на саму математику алгоритма, но теперь интеграл будет зависеть от значения Ki и настраивать регулятор в реальном времени станет гораздо проще:

float Kp, Ki, Kd;
float integral, pError;

float computePID(float setpoint, float input, float dt) {
    float error = setpoint - input;
    float derivative = (error - pError) / dt;
    pError = error;
    integral += error * Ki * dt;
    return error * Kp + integral + derivative * Kd;
}

Однако теперь выход напрямую зависит от интеграла и, если например обнулить Ki в процессе работы регулятора, то сам накопившийся интеграл никуда не денется и будет всё ещё учитываться в расчёте выхода! Это нужно учитывать при разработке и настройке своего регулятора.

Обнуление интеграла #

В некоторых системах может хорошо работать обнуление интегральной суммы при достижении уставки, особенно когда параметры системы часто меняются и нужно избавиться от накопленных ошибок и реагировать быстрее:

error = setpoint - input;
I = I + error * dt;
if (input >= setpoint) I = 0;   // <- если выход регулятора увеличивает input
D = (error - prevError) / dt;
prevError = error;
output = error * Kp + I * Ki + D * Kd;

Насыщение #

Когда регулятор не может достигнуть уставки, интегральная сумма начинает бесконтрольно расти. Если внешние условия изменятся или например уменьшится уставка - интеграл останется очень большим и система отреагирует не сразу. Чтобы уменьшить время реакции в этой ситуации, нужно ограничить рост интегральной суммы. Это можно сделать даже так:

I = I + error * dt;
if (I > Imax) I = Imax;
else if (I < -Imax) I = -Imax;

Но каким выбрать Imax? Сложный вопрос.

Задачу можно решить по-другому - ограничивать рост интеграла, когда система входит в насыщение, т.е. когда выходное значение превышает некий лимит. Классический ПИД регулятор не знает лимита - он выдаёт такой выход, который получился при вычислении. На практике выход всегда имеет ограничение, например 0-255 для Arduino ШИМ или 0-180 градусов для сервопривода. Таким образом, если при расчёте получилось больше максимума или меньше минимума - говорят что выход в насыщении, это условие можно использовать для ограничения интеграла.


Выход в насыщении, интеграл растёт

Conditional Integration #

"Интегрирование по условию" - самый простой подход: не интегрируем I, если выход насыщен и интеграл собирается увеличиться по модулю:

float Kp, Ki, Kd;
float outMax = 0, outMin = 100;
float integral, pError;

float computePID(float setpoint, float input, float dt) {
    float error = setpoint - input;
    float derivative = (error - pError) / dt;
    pError = error;
    float output = error * Kp + integral * Ki + derivative * Kd;

    if (output >= outMax) {
        if (error > 0) return outMax;
    } else if (output <= outMin) {
        if (error < 0) return outMin;
    }

    integral += error * dt;
    return output;
}


Интеграл не растёт из-за интегрирования по условию

Back Calculation #

Второй вариант более хитрый и с настройкой в виде дополнительного коэффициента, назовём его Kbc:

float Kp, Ki, Kd;
float Kbc;
float outMax = 0, outMin = 100;
float integral, pError;

float computePID(float setpoint, float input, float dt) {
    float error = setpoint - input;
    float derivative = (error - pError) / dt;
    pError = error;
    float output = error * Kp + integral * Ki + derivative * Kd;

    if (output >= outMax) {
        error += (outMax - output) * Kbc;
        output = outMax;
    } else if (output <= outMin) {
        error += (outMin - output) * Kbc;
        output = outMin;
    }

    integral += error * dt;
    return output;
}

Коэффициент Kbc подбирается под конкретную систему, начальным значением можно взять текущее Ki.


Влияние Kbc на интегральную сумму при насыщении

P по входу #

Ещё одна интересная модификация, которая используется на практике в промышленных контроллерах (Emerson, Siemens, Honeywell) и часто называется P on Measurement. Она полностью меняет привычную логику работы и поведение регулятора, хотя в уравнении меняется только P составляющая - вместо ошибки используется обратное значение входа:

error = setpoint - input;
I = I + error * dt;
D = (error - prevError) / dt;
prevError = error;
output = -input * Kp + I * Ki + D * Kd;

Если в классике P играет ключевую роль, а I компенсирует оставшуюся ошибку, то в таком регуляторе с "P по входу" основную роль играет уже I составляющая, а P наоборот - тормозит и стабилизирует систему. Преимущества перед "P по ошибке":

  • Снижение резких скачков выхода при скачках уставки
  • Меньшее перерегулирование
  • Более плавный и устойчивый процесс

Алгоритм хорошо работает в медленных и инерционных системах, где недопустимо перерегулирование.

float Kp, Ki, Kd;
float integral, pError;

float computePID(float setpoint, float input, float dt) {
    float error = setpoint - input;
    float derivative = (error - pError) / dt;
    pError = error;
    integral += error * dt;
    return -input * Kp + integral * Ki + derivative * Kd;
}

Feed forward #

Feed forward - использование ПИД регулятора для компенсации ошибки, которая осталась после прямого управления (без обратной связи). Например, мы откалибровали нагреватель и получили зависимость управляющего сигнала от температуры в виде таблицы или аппроксимирующей функции f(T), т.е. функция выдаёт управляющий сигнал в зависимости от указанной температуры. Чтобы использовать ПИД для компенсации ошибки от прямого управления, достаточно делать out = f(Tset) + computePID(Tset, T), т.е. основной сигнал будет давать модель системы, а ПИД только компенсирует остаток ошибки. По сути это более осмысленная замена пропорциональной составляющей, где вместо линейного "коэффициента усиления" используется модель системы.

Такой подход позволяет проще настраивать регулятор, особенно для медленных, но предсказуемых систем. Также часто используется при управлении моторами, т.к. у мотора известна зависимость скорости от напряжения и момента от тока, что позволяет сразу учесть их в feed forward и получить более отзывчивую систему.

Библиотеки #

  • sTune - тюнер коэффициентов, несколько алгоритмов
  • GyverPID - моя старая библиотека со встроенным тюнером коэффициентов
  • uPID - моя новая универсальная библиотека, содержит все модификации из этого урока с настройкой в compile time

Видео #

Дополнительно #

Дополнительный контент доступен владельцам набора GyverKIT и по подписке, подробнее читай здесь. Блок содержит:

  • Интерактивный график

Полезные страницы #

(8 голосов)
Подписаться
Уведомить о
guest

14 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх