47. Регулирование: обратная связь, П- и ПИД-регулятор
ШИМ задаёт скважность, но не результат. Стоит подключить нагрузку — и напряжение просядет; стоит открыть форточку — и нагреватель не выйдет на заданную температуру. Управление без обратной связи держит не величину, а только команду. Регулятор замыкает контур: измеряет то, чем управляет, и правит команду по разнице.
Раздел — про самый распространённый регулятор: пропорционально- интегрально-дифференциальный. Что делает каждая из трёх частей, почему почти всегда хватает двух, где он ломается и как записать его так, чтобы он работал на микроконтроллере без блока плавающей точки.
Как это называется в документации
Оригинал Перевод closed loop, feedback замкнутый контур, обратная связь setpoint (SP) задание, целевое значение process variable (PV) измеренная величина error ошибка, рассогласование steady-state error статическая ошибка: то, что остаётся навсегда overshoot перерегулирование, выброс выше задания settling time время выхода на режим integral windup накопление интеграла при упёршемся выходе anti-windup ограничение накопления derivative kick бросок производной при смене задания bumpless transfer плавный ввод регулятора в работу saturation, clamping насыщение, ограничение plant объект управления sampling period период дискретизации, он же период регулирования
47.1 Контур
Всё, что видит регулятор, — ошибка: разность между заданием и измерением. Он не знает, чем управляет: нагревателем, мотором или напряжением. Поэтому одна и та же программа годится всюду — меняются только три коэффициента и период.
Разбор ведётся на модели с понятными числами: ШИМ через RC-цепь с постоянной времени 10 мс, период регулирования 1 мс, питание 3,3 В, цель 2,000 В. За один период выход проходит 9,5 % пути до нового уровня.
47.2 П: пропорциональная часть и её неустранимая ошибка
Самое простое: выход тем больше, чем больше ошибка.
u = kP · (задание − измерение)
И сразу главное свойство: у такого объекта ошибка не уходит в ноль. Этой RC-цепи, как нагревателю или мотору под нагрузкой, для удержания уровня нужен ненулевой выход. Если бы ошибка ушла в ноль, выход стал бы нулевым и объект пошёл бы вниз. Равновесие устанавливается там, где остатка ошибки хватает ровно на удержание:
| kP | Установилось | Недобор |
|---|---|---|
| 1 | 1,535 В | 465 мВ |
| 3 | 1,817 В | 183 мВ |
| 5 | 1,886 В | 114 мВ |
Модель сходится с формулой v = kP·Vcc·цель / (1 + kP·Vcc): чем больше
kP, тем меньше остаток, но в ноль он не приходит никогда.
Объект, который сам интегрирует, — положение вала, уровень в баке без
утечки — ведёт себя иначе: чтобы стоять на месте, ему нужен нулевой выход,
и П-регулятор приводит его к нулевой ошибке без интегральной части.
Остаток можно снять и постоянной добавкой, u = u0 + kP·e, где u0 —
выход, который держит цель (здесь 2 / 3,3 ≈ 61 % ШИМ). Но добавка верна
только для одного режима: сменились нагрузка или цель — и остаток
вернулся. Интегральная часть (47.3) подбирает её сама.
⚠️ Коэффициент нельзя поднимать сколько угодно. У этого объекта при периоде 1 мс устойчивость держится, пока
kP < 6,07. При kP = 7 выход перестаёт устанавливаться вовсе: незатухающие колебания размахом 177 мВ вокруг цели. Признак на осциллограмме однозначный — ровная «пила» или синусоида, которая не затухает.
Оценка kP для начала. При максимальной ошибке регулятор имеет право
выдать полный ход: kP ≈ ход / диапазон. При ARR = 1000 и шкале
3,3 В это kP ≈ 300, если считать в вольтах, и 0,3 — если в милливольтах.
Коэффициент не физическая величина: он связывает числа, и его значение
зависит от того, в чём меряют вход и выход. Записывая kP, всегда
записывайте и единицы.
47.3 И: интегральная часть убирает остаток
Ошибку копят. Пока она есть, накопитель растёт, и выход растёт вместе с ним — до тех пор, пока ошибка не станет нулём.
накопитель += kI · ошибка · dt
u = kP · ошибка + накопитель
С kP = 3 и kI = 50 модель встаёт ровно на 2,000 В за 67 мс без выброса. Учетверить kI — выйдет за 38 мс, но с перерегулированием 5,8 %. Это и есть основной размен: быстрее — с выбросом, плавно — дольше.
Приращённая форма — это тоже интегратор. Запись
u += kP · ошибкас kP = 0,05 часто называют пропорциональным регулированием, но она накапливает, а значит, интегрирует: в модели она выходит точно на 2,000 В. Тот же kP = 0,05 в позиционной формеu = kP · ошибкадаёт всего 0,283 В. Формы не взаимозаменяемы, и коэффициенты у них означают разное: в приращённой форме kP имеет смысл kI·dt.
Накопление при недостижимой цели. Если цель за пределами возможностей (задали 3,5 В при питании 3,3 В), ошибка не уходит, а накопитель растёт бесконечно. В модели выход регулятора дорастает до 22,3 (сам накопитель — до 21,7) — в двадцать два раза больше полного хода. Когда цель снова становится достижимой, регулятор ещё долго «отрабатывает» накопленное:
| Возврат к нормальной работе | |
|---|---|
| без ограничения накопителя | 431 мс |
| с ограничением | 28 мс |
Разница в пятнадцать раз. Поэтому ограничивают не только выход, но и сам накопитель, отдельно — типовой источник «регулятор завис на максимуме» в готовых устройствах.
47.4 Д: дифференциальная часть и почему её обычно нет
Производная реагирует не на величину ошибки, а на скорость её изменения: помогает поймать момент, когда нагрузка появилась, ещё до того, как ошибка выросла.
Плата — шум. Производная усиливает его в разы:
| Размах на установившемся режиме | |
|---|---|
| П + И | 19,5 мВ |
| П + И + Д | 164,9 мВ |
| П + И + Д с фильтром | 17,6 мВ |
| П + И с тем же фильтром | 11,2 мВ |
При шуме измерения ±10 мВ (обычный АЦП без усреднения) добавление производной ухудшает результат в восемь раз. Фильтр на измерении возвращает 17,6 мВ, но он стоит на измерении целиком и сглаживает шум и для П, и для И: П + И с тем же фильтром дают 11,2 мВ. Так что на шумном сигнале дифференциальная часть здесь вредна и с фильтром. Фильтр к тому же добавляет запаздывание: после его установки заново проверяют предел kP. Если фильтр нужен только против шума производной, его ставят на одно дифференциальное слагаемое.
Фильтр — апериодическое звено первого порядка, одна строка:
фильтр += k · (измерение − фильтр)
Коэффициент — отношение периода к желаемой постоянной времени:
k = Tд / Tф. При 1 мс и 20 мс это 0,05; фильтр проходит 63 % скачка за
20 тактов. k = 1 — фильтра нет вовсе.
Производную берут по измерению, а не по ошибке. Иначе скачок задания даёт бросок: при переходе с 0 на 2,000 В производная по ошибке выдаёт 100 единиц выхода мгновенно — насыщение и рывок исполнительного механизма. Производная по измерению на смену задания не реагирует вообще: измерение-то не прыгало.
На практике в большинстве задач останавливаются на ПИ-регуляторе. Дифференциальную часть добавляют, когда объект инерционный и медленный (нагрев, крупная механика), а измерение — чистое.
47.5 Наброс нагрузки: зачем всё это
Проверка, ради которой строится регулятор. На 2000-м такте нагрузка отбирает четверть выхода:
| Просадка | Чем закончилось | |
|---|---|---|
| только П | 237 мВ | так и осталось 1,763 В |
| П + И | 58 мВ | вернулось на 2,000 В за 74 мс |
Пропорциональный регулятор ослабляет удар, но не устраняет его. Интегральная часть возвращает величину туда, где она должна быть, — это её единственная, но незаменимая работа.
47.6 🔧 Регулятор в коде
Три вещи, о которые спотыкаются чаще всего: постоянный период, ограничение накопителя и производная по измерению. Всё это — в двадцати строках.
typedef struct {
int32_t kp, ki, kd; /* Q16 */
int32_t integ; /* Q16, накопитель */
int32_t integ_max; /* Q16, свой предел — не общий с выходом */
int32_t prev_meas; /* коды АЦП: производная берётся по измерению */
int32_t filt; /* Q16, фильтр измерения */
int32_t k_filt; /* Q16, коэффициент фильтра */
int32_t out_min, out_max; /* пределы выхода в шагах ШИМ */
} pid_t;
/* Один шаг. Вызывается строго с постоянным периодом — dt «зашит»
в коэффициенты, и переменный период молча испортит настройку. */
int32_t pid_step(pid_t *p, int32_t target, int32_t meas)
{
int32_t err, deriv, out;
/* Фильтр измерения: без него дифференциальная часть работает по шуму. */
p->filt += MUL_Q(p->k_filt, (meas << Q) - p->filt);
meas = p->filt >> Q;
err = target - meas;
/* Интеграл со своим ограничителем: иначе при недостижимой цели
он растёт без предела и потом долго «рассасывается». */
p->integ = clamp(p->integ + p->ki * err, -p->integ_max, p->integ_max);
/* Производная по измерению, а не по ошибке: смена задания не даёт броска. */
deriv = -(p->kd * (meas - p->prev_meas));
p->prev_meas = meas;
out = ((p->kp * err) + p->integ + deriv) >> Q;
return clamp(out, p->out_min, p->out_max);
}
Где его вызывать. В обработчике прерывания того же таймера, что задаёт ШИМ, либо по завершении преобразования АЦП, запущенного этим таймером (раздел 14.5). Не в основном цикле: там период «плавает», а все коэффициенты посчитаны под фиксированный.
Плавный ввод в работу. Если регулятор включают, когда объект уже работает, мало зарядить накопитель текущим выходом. Фильтр и прошлое измерение стоят на нуле: первый шаг отфильтрует измерение от нуля (при k = 0,05 выйдет 5 % настоящего значения) и возьмёт производную −kD · (измерение − 0) — ровно тот бросок, от которого предостерегает 47.4. А при ненулевой ошибке к выходу добавится ещё kP · ошибка. Поэтому заряжают три вещи:
void pid_preload(pid_t *p, int32_t current_out, int32_t target, int32_t meas)
{
p->filt = meas << Q; /* фильтр — с текущего измерения, а не с нуля */
p->prev_meas = meas; /* первая производная — ноль */
p->integ = (current_out << Q) - p->kp * (target - meas); /* П-часть + накопитель = current_out */
p->integ = clamp(p->integ, -p->integ_max, p->integ_max);
}
Первый шаг после такого ввода начинает ровно с current_out и прибавляет
к нему лишь обычный шаг интеграла kI · ошибка.
47.7 Целые числа против вещественных
Величины держат в кодах АЦП, коэффициенты — в формате Q16: обычное целое, у которого младшие 16 бит считаются дробной частью. Умножение — это умножение со сдвигом на 16 вправо, деление на степень двойки — сдвиг.
#define Q 16
#define TO_Q(x) ((int32_t)((x) * (1 << Q)))
#define MUL_Q(a, b) ((int32_t)(((int64_t)(a) * (b)) >> Q))
Разрядность считают, а не подбирают: kP = 3 в Q16 — это 196 608,
и произведение на полную шкалу АЦП (4095) ещё умещается в знаковые
32 бита, а в формате Q20 то же произведение уже переполняет.
Отдельная мина — целочисленное деление отбрасывает остаток. Цель
2,000 В в кодах: 2000 × 4095 / 3300 даёт 2481 вместо 2482 — цель
занижена на 0,8 кода — около 0,66 мВ, и регулятор честно отработает эту ошибку. Лечится
добавлением половины делителя: (a + b/2) / b.
Стоит ли возиться, если можно писать float? Измерено сборкой одного
и того же регулятора:
| Ядро | Целые числа | Вещественные |
|---|---|---|
| Cortex-M3 (F103, без FPU) | 584 байта | 1868 байт |
| Cortex-M4F (F407, G474) | 616 байт | 600 байт |
На ядре с блоком плавающей точки разницы почти нет — пишите float. На ядре
без него вещественная арифметика тянет за собой библиотеку и утраивает
код, а по времени проигрывает ещё заметнее. И double не используйте
даже на M4F: аппаратный блок там одинарной точности, double считается
программно.
47.8 Как настраивать
Метод, который работает без теории:
- Обнулить kI и kD. Поднимать kP, пока выход не начнёт колебаться незатухающе. Запомнить это значение — назовём его критическим.
- Взять kP около половины критического. Смотреть, какая осталась статическая ошибка.
- Добавлять kI с малого значения, пока ошибка не уходит за приемлемое время. Появился выброс — уменьшить kI либо kP.
- kD — только если нужно и только вместе с фильтром измерения. Поднимать до первых признаков «дрожания» выхода, затем уменьшить вдвое.
- Проверить крайние режимы: наброс и сброс нагрузки, скачок задания, недостижимая цель, включение регулятора на ходу.
Смотреть при этом надо не на регулируемую величину, а на три слагаемых по отдельности — их удобно выводить по SWO (раздел 21) или в отладчик: сразу видно, какая часть даёт колебания, а какая упёрлась в ограничитель.
Про период. Он должен быть заметно меньше постоянной времени объекта — раз в десять. Здесь 1 мс против 10 мс. Опрашивать нагреватель с постоянной времени в минуты тысячу раз в секунду бессмысленно: только шум и лишняя работа.
47.9 Грабли
Величина не доходит до задания. Только пропорциональная часть, а объекту для удержания нужен ненулевой выход. Статическая ошибка — её свойство, а не неисправность; нужна интегральная.
Незатухающие колебания. kP выше предела устойчивости объекта либо период регулирования слишком велик.
Регулятор «завис» на максимуме. Накопился интеграл при недостижимой цели. Ограничивать накопитель отдельно от выхода.
Выброс при пуске. Накопитель стартует с нуля и разгоняется, пока объект догоняет. Ограничить накопитель, уменьшить kI либо при включении зарядить накопитель, фильтр и прошлое измерение (47.6).
Рывок при включении регулятора на ходу. Заряжен только накопитель, а фильтр и прошлое измерение остались нулями: первый шаг берёт производную от нуля до текущего измерения.
Рывок при смене задания. Производная по ошибке. Брать по измерению.
Выход дрожит. Дифференциальная часть без фильтра или шум АЦП. Фильтровать измерение, усреднять выборки, проверить землю и опору (раздел 14).
Коэффициенты «не те», хотя формула правильная. Забыты единицы:
вольты против милливольт, коды против процентов, dt внутри kI или
снаружи. Записывайте единицы рядом с коэффициентом.
Работает на столе, не работает в изделии. Период регулирования плавает: вызов из основного цикла вместо прерывания, или обработчик задерживается более длинным прерыванием (раздел 7.3).
Переполнение в целочисленной арифметике. Произведение коэффициента на полную шкалу не помещается. Считать худший случай, промежуточное произведение брать 64-разрядным.
Ошибка на полшага из ниоткуда. Целочисленное деление без округления.
47.10 Итоги
- Регулятор видит только ошибку — поэтому одна программа управляет чем угодно; специфика объекта прячется в трёх коэффициентах и периоде.
- Пропорциональная часть оставляет статическую ошибку, если объекту для удержания нужен ненулевой выход (нагрев, RC-цепь); поднимать kP до нуля ошибки нельзя — раньше наступит потеря устойчивости.
- Интегральная часть убирает ошибку и возвращает величину после наброса нагрузки; за скорость платят перерегулированием.
- Накопитель ограничивают отдельно от выхода — иначе возврат к работе затягивается на порядок.
- Дифференциальная часть без фильтра усиливает шум в разы; производную берут по измерению, не по ошибке; в большинстве задач хватает ПИ.
- Вызывать строго с постоянным периодом, примерно в десять раз меньшим постоянной времени объекта.
- На ядре без FPU целочисленный регулятор втрое компактнее вещественного; на M4F разницы почти нет.
- Настройка — по шагам от kP к kI, с проверкой крайних режимов и наблюдением за каждым слагаемым отдельно.
Дальше: раздел 48 «Собственный загрузчик и обновление прошивки» — как менять программу в устройстве, которое уже уехало к заказчику.