PID-регулятор: теорія та налаштування
Пропорційно-інтегрально-диференціальний (PID) регулятор — це найпоширеніший алгоритм керування на планеті: саме він стабілізує політ дрона, керує суглобами роботизованої руки, підтримує температуру хотенда 3D-принтера, працює в круїз-контролі та промислових технологічних контурах. У цьому посібнику ми зберемо такий регулятор з нуля: розберемо математику кожної складової, напишемо надійну дискретну реалізацію на JavaScript, розглянемо типові помилки початківців і методи налаштування, якими реально користуються інженери.
1. Контур зворотного зв'язку
PID-регулятор веде систему (об'єкт керування) до бажаного значення — уставки (setpoint) — постійно вимірюючи похибку: різницю між тим, де система перебуває зараз, і тим, де вона має бути, і обчислюючи коригувальний керуючий сигнал (напругу, момент двигуна, кут дроселя, шпаруватість ШІМ):
Кожна складова відповідає на своє запитання: пропорційна питає «наскільки я помиляюся зараз?», інтегральна — «наскільки я помилявся протягом часу?», а диференціальна — «наскільки швидко змінюється похибка?». Разом вони дозволяють регулятору реагувати миттєво, усувати статичну похибку та гасити коливання — те, чого жодна окрема складова досягти не може.
2. Пропорційна складова — миттєва реакція
Пропорційна складова видає сигнал, прямо пропорційний поточній похибці. Дрон на 2 м нижче цільової висоти отримує вдвічі більшу коригувальну тягу, ніж дрон на 1 м нижче:
Більше значення Kp реагує швидше, але наближає
систему до нестабільності й перерегулювання. Чистий P-регулятор
майже завжди залишає статичну похибку: для
квадрокоптера, що бореться з гравітацією, тяга зависання, яка
точно компенсує гравітацію, відповідає нульовій похибці — тобто
має існувати певна залишкова похибка, щоб дати саме ту
тягу, яка потрібна для утримання позиції. Саме цей розрив і
покликана усунути інтегральна складова.
3. Інтегральна складова — усунення статичної похибки
Інтегральна складова накопичує похибку в часі, збільшуючи вихідний сигнал доти, доки зберігається будь-яка похибка — навіть дуже мала — аж поки вона не зникне повністю:
Додавання інтегральної дії повністю усуває статичну похибку, але
водночас вносить фазове запізнення: регулятор тепер реагує на
історію похибки, а не лише на її поточне значення, що
збільшує перерегулювання і може дестабілізувати контур, якщо
Ki завелике. Саме інтегральна складова є джерелом
найпоширенішого реального бага PID-регуляторів —
інтегрального накопичення (windup), про яке
йдеться в розділі 6.
4. Диференціальна складова — демпфування та прогноз
Диференціальна складова реагує на швидкість зміни похибки, фактично прогнозуючи, куди рухається похибка, і протидіючи швидкому руху до (або повз) уставки. Саме вона утримує систему від «дзвону»:
Правильно налаштований Kd діє як гальмо: коли система
швидко наближається до уставки, диференціальна складова створює
від'ємну корекцію, яка сповільнює наближення, зменшуючи
перерегулювання та час встановлення. Однак надлишковий
Kd підсилює шум вимірювань — випадковий тремтіння
показань шумного IMU дрона від кадру до кадру диференціюється
прямо в тремтливі команди двигунам.
5. Дискретна реалізація
Реальні регулятори працюють у циклі з фіксованим кроком часу, тому неперервний інтеграл перетворюється на накопичувальну суму, а похідна — на скінченну різницю. Ось повна, придатна для продакшену реалізація класу PID:
class PIDController {
constructor({ kp, ki, kd, outMin = -Infinity, outMax = Infinity }) {
this.kp = kp;
this.ki = ki;
this.kd = kd;
this.outMin = outMin;
this.outMax = outMax;
this.integral = 0;
this.prevMeasurement = null; // для диференціювання за виміром
}
// setpoint, measurement: поточна ціль і показання сенсора
// dt: секунд з моменту попереднього виклику
update(setpoint, measurement, dt) {
const error = setpoint - measurement;
// --- Пропорційна ---
const pTerm = this.kp * error;
// --- Інтегральна (трапецієподібне накопичення) ---
this.integral += error * dt;
let iTerm = this.ki * this.integral;
// --- Диференціювання за виміром (уникає "диференціального удару") ---
const measDeriv = this.prevMeasurement === null
? 0
: (measurement - this.prevMeasurement) / dt;
const dTerm = -this.kd * measDeriv;
this.prevMeasurement = measurement;
// --- Сума та обмеження в межах діапазону виконавчого механізму ---
let output = pTerm + iTerm + dTerm;
const clamped = Math.min(this.outMax, Math.max(this.outMin, output));
// --- Anti-windup через зворотне обчислення (clamping) ---
if (clamped !== output) {
this.integral -= error * dt; // скасовуємо інтегрування, що спричинило насичення
}
return clamped;
}
reset() {
this.integral = 0;
this.prevMeasurement = null;
}
}
// Використання: стабілізація висоти симульованого дрона на 10 м
const altPID = new PIDController({ kp: 2.0, ki: 0.5, kd: 0.8, outMin: 0, outMax: 1 });
function simStep(dt) {
const thrust = altPID.update(10.0, drone.altitude, dt);
drone.applyThrust(thrust);
}
6. Інтегральне накопичення та anti-windup
Будь-який реальний виконавчий механізм має обмеження — двигун не може перевищити 100% шпаруватості, сервопривід роботизованої руки має обмеження за моментом. Якщо похибка залишається великою протягом певного часу (наприклад, рука робота фізично заблокована на шляху до цілі), інтегральна складова продовжує накопичуватися, хоча додатковий сигнал уже не має ефекту, бо виконавчий механізм вже досяг межі. Коли перешкода зникає, весь накопичений інтеграл спрацьовує одразу, спричиняючи значне перерегулювання. Це і є інтегральне накопичення (windup).
Реалізований вище підхід називається anti-windup через обмеження (також — зворотне обчислення): після обмеження вихідного сигналу до фізичного діапазону виконавчого механізму ми скасовуємо ту частину накопичення інтеграла, яка спричинила перевищення межі. Це зупиняє зростання інтегральної складової, щойно виконавчий механізм насичується, тож відновлення відбувається миттєво, а не із запізненням.
this.integral фіксованим діапазоном, наприклад
[-50, 50], одразу після кроку накопичення. Це один
рядок коду, і він добре працює, коли не потрібне ідеально точне
зворотне обчислення.
7. Диференціальний удар і фільтрація
Якщо диференціювати саме похибку, стрибкоподібна зміна уставки спричиняє миттєвий, величезний стрибок похибки за один крок часу — диференціальна складова на один кадр підскакує до величезного значення, різко смикаючи виконавчий механізм. Це і є диференціальний удар. Диференціювання виміру замість похибки (як у реалізації вище) чисто вирішує цю проблему, бо показання сенсора не змінюються стрибкоподібно так, як це може робити уставка.
Друга проблема — підсилення шуму сенсора:
диференціювання є високочастотною операцією, тому будь-який
високочастотний шум у вимірі підсилюється множником
Kd / dt. Стандартне рішення — фільтр низьких частот
першого порядку на диференціальній складовій:
// N = коефіцієнт фільтра (типово: 8-20); більше N = менше згладжування
const rawDeriv = (measurement - prevMeasurement) / dt;
const alpha = dt / (dt + 1 / N);
filteredDeriv = filteredDeriv + alpha * (rawDeriv - filteredDeriv);
const dTerm = -kd * filteredDeriv;
8. Ручне налаштування та метод Зіглера-Нікольса
Ручне налаштування
-
Встановіть
Ki = 0таKd = 0. ЗбільшуйтеKp, доки система не почне реагувати швидко, але видимо коливатися, а потім зменшіть значення приблизно до 50-60% від цього рівня. -
Збільшуйте
Kiвід нуля, доки статична похибка не буде усунена за прийнятний час, водночас стежачи за зростаючими коливаннями — зменшуйте значення, якщо реакція починає «дзвеніти». -
Збільшуйте
Kd, щоб демпфувати перерегулювання та скоротити час встановлення, зменшуючи значення, якщо з'являється високочастотне тремтіння (ознака підсилення шуму — спершу застосуйте фільтрацію похідної).
Метод замкненого контуру Зіглера-Нікольса
Класична, систематична відправна точка: встановіть
Ki = Kd = 0, потім піднімайте Kp, доки
система не почне коливатися зі стійкою, постійною за амплітудою
коливальністю. Зафіксуйте це значення як граничний
коефіцієнт підсилення Ku, а період коливань —
як Tu. Далі застосуйте:
9. Каскадне керування в робототехніці
Окремий PID-контур рідко є повним рішенням у робототехніці. Більшість польотних контролерів і роботизованих рук використовують каскадні PID-контури: зовнішній, повільніший контур керує позицією та видає цільову швидкість, яка подається на внутрішній, швидший контур, що керує безпосередньо швидкістю (або моментом):
Ця структура зустрічається по всій робототехніці: контролер орієнтації квадрокоптера каскадує кут → кутову швидкість → мікшування двигунів; промислова роботизована рука каскадує позицію суглоба → швидкість суглоба → струм/момент. Швидший внутрішній контур дає жорсткіший контроль над швидкими збуреннями, а зовнішній контур керує повільнішою, масштабнішою траєкторією.
10. Типові помилки
-
Змінний крок часу без урахування: якщо
dtне вимірюється й не використовується в обчисленнях інтеграла/похідної (замість цього береться фіксоване припущене значення), коефіцієнти, налаштовані на 60 кадрах/с, поводитимуться неправильно, щойно частота кадрів впаде. -
Перехід через межу кута: для керування
курсом/рисканням наївне віднімання кутів дає величезну похибку
при переході межі ±180°. Завжди нормалізуйте похибку:
error = ((target - current + 540) % 360) - 180. - Відсутність обмеження вихідного сигналу: подача необмеженого виходу PID безпосередньо на драйвер двигуна ризикує задати фізично неможливі значення і посилює накопичення — завжди обмежуйте сигнал реальним діапазоном виконавчого механізму.
-
Забуте скидання стану: коли регулятор
вимикається й вмикається знову (наприклад, дрон приземлився і
злітає повторно), викликайте
reset(), щоб очистити інтегральну складову та попередній вимір, інакше новий запуск успадкує застаріле накопичене значення. - Налаштування в неправильній робочій точці: PID, налаштований для роботизованої руки без вантажу, поводитиметься інакше під навантаженням — динаміка об'єкта (ефективна інерція, тертя) змінилася, тож коефіцієнти, які були стабільними, стають недодемпфованими або коливальними.