Если вы пишете прошивку для терморегулятора, стабилизатора скорости мотора или удержания положения сервопривода на STM32, рано или поздно потребуется PID-регулятор — и типичная первая ошибка здесь заключается в вызове расчёта регулятора из главного цикла while(1) без фиксированного шага по времени, из-за чего интегральная и дифференциальная составляющие ведут себя непредсказуемо. Проверьте, считается ли регулятор с постоянным периодом: именно это первое, что стоит проверить при «плавающем» выходе.
В этой статье разберём, как работает пропорционально-интегрально-дифференциальный регулятор, как корректно реализовать его на микроконтроллерах STM32 (F1, F4, G4 и других серий), какие подводные камни встречаются чаще всего и как подобрать коэффициенты без лабораторного стенда. Материал ориентирован на практику: минимум теории, максимум рабочего кода и проверяемых действий.
Как работает PID-регулятор: краткая теория
PID-регулятор вычисляет управляющее воздействие на основе ошибки — разницы между заданным значением (setpoint) и измеренным значением процесса. Классическая формула в непрерывном виде: u(t) = Kp·e(t) + Ki·∫e(t)dt + Kd·de(t)/dt. Каждая составляющая отвечает за свой аспект поведения системы.
Пропорциональная часть реагирует на текущую ошибку: чем больше отклонение, тем сильнее воздействие. Интегральная накапливает ошибку во времени и устраняет статическую ошибку — ситуацию, когда система стабилизировалась, но не достигла цели. Дифференциальная реагирует на скорость изменения ошибки и демпфирует колебания, предотвращая перерегулирование.
- 🔧 Kp — задаёт скорость реакции; слишком большое значение вызывает колебания
- 🧮 Ki — убирает остаточную ошибку; избыток приводит к медленным раскачкам и насыщению
- 📉 Kd — гасит перерегулирование; чувствителен к шуму датчика
- ⏱️ Период дискретизации
dtдолжен быть стабильным и в несколько раз меньше характерного времени процесса
В микроконтроллере формула дискретизируется: интеграл заменяется суммой, производная — разностью соседних значений. Именно дискретная форма и реализуется в коде.
Организация периодического вызова на STM32
Ключевое требование — фиксированный период расчёта. Наиболее надёжный способ на STM32 — использовать аппаратный таймер (например, TIM6 или TIM7) с прерыванием по обновлению либо тик SysTick / планировщик FreeRTOS с задачей фиксированного периода через vTaskDelayUntil(). Вызов регулятора из while(1) с «примерной» задержкой недопустим: джиттер периода напрямую искажает интегральную и дифференциальную части.
// Пример: флаг выставляется в прерывании таймера
volatile uint8_t pid_tick = 0;
void TIM6_DAC_IRQHandler(void)
{
if (__HAL_TIM_GET_FLAG(&htim6, TIM_FLAG_UPDATE)) {
__HAL_TIM_CLEAR_FLAG(&htim6, TIM_FLAG_UPDATE);
pid_tick = 1;
}
}
В обработчике прерывания следует выполнять минимум работы — только установку флага или чтение датчика. Сам расчёт лучше выполнять в основном цикле или задаче, чтобы не блокировать другие прерывания. Период выбирайте исходя из динамики объекта: для температурных процессов обычно достаточно периодов в сотни миллисекунд, для моторов — единицы миллисекунд. Точные значения зависят от вашего объекта, их стоит подбирать экспериментально.
⚠️ Внимание: не выполняйте длительные вычисления с плавающей точкой и операции вроде printf внутри обработчика прерывания таймера — это нарушит периодичность и может вызвать пропуски тиков.
Реализация PID-регулятора на C
Ниже — компактная реализация с защитой от насыщения интеграла (anti-windup), ограничением выхода и производной по измерению (чтобы избежать «дифференциального удара» при смене уставки). Структура хранит состояние регулятора, что позволяет использовать несколько регуляторов одновременно.
typedef struct {
float kp, ki, kd;
float dt;
float integral;
float prev_measurement;
float out_min, out_max;
float int_min, int_max;
} PID_t;
float PID_Update(PID_t *pid, float setpoint, float measurement)
{
float error = setpoint - measurement;
// Пропорциональная часть
float p = pid->kp * error;
// Интегральная часть с ограничением (anti-windup)
pid->integral += error * pid->dt;
if (pid->integral > pid->int_max) pid->integral = pid->int_max;
if (pid->integral < pid->int_min) pid->integral = pid->int_min;
float i = pid->ki * pid->integral;
// Дифференциальная часть по измерению
float d = -pid->kd * (measurement - pid->prev_measurement) / pid->dt;
pid->prev_measurement = measurement;
float out = p + i + d;
if (out > pid->out_max) out = pid->out_max;
if (out < pid->out_min) out = pid->out_min;
return out;
}
Обратите внимание на знак минус в дифференциальной части: производная берётся по измерению, а не по ошибке. Это стандартный приём, устраняющий скачок выхода при резкой смене уставки. Ограничение интеграла (int_min/int_max) не даёт ему «наматываться», пока выход насыщен — без этого система будет долго выходить из перерегулирования.
Для микроконтроллеров без FPU (например, STM32F1) вычисления с float выполняются программно и заметно медленнее. Если производительности не хватает, регулятор можно перевести на целочисленную арифметику с фиксированной точкой, но для большинства задач с периодом в миллисекунды это не требуется. На сериях с FPU (F4, G4, H7) убедитесь, что в проекте включено аппаратное использование FPU.
☑️ Перед запуском PID-регулятора проверьте
Связка регулятора с периферией STM32
Типовая схема: измерение через АЦП (ADC) или цифровой датчик, расчёт регулятора, выдача управляющего воздействия через ШИМ (канал таймера, например TIM3_CH1) на силовой ключ. Для терморегулятора ШИМ с периодом в секунды можно формировать программно (time-proportioning), для мотора — аппаратный ШИМ на частоте выше слышимого диапазона, если это критично для вашего применения.
Чтение АЦП удобно выполнять по DMA с усреднением нескольких выборок — это снижает шум, который особенно вреден для дифференциальной составляющей. Дополнительно применяют простой фильтр: скользящее среднее или экспоненциальный фильтр вида y = y + alpha * (x - y). Определение коэффициента сглаживания подбирается под частоту опроса и уровень шума конкретного датчика.
Выводите через UART текущие значения ошибки, интеграла и выхода регулятора в виде чисел или в инструмент построения графиков — визуализация переходного процесса ускоряет настройку коэффициентов в разы.
Настройка коэффициентов Kp, Ki, Kd
Универсальных значений коэффициентов не существует — они зависят от инерционности объекта, диапазонов измерения и выхода. Практичный ручной порядок настройки выглядит так:
- 🎯 Начните с
Ki = 0иKd = 0, увеличивайтеKpдо появления устойчивых колебаний, затем уменьшите примерно вдвое - 🧲 Добавьте
Kiмалыми шагами, пока статическая ошибка не исчезнет за приемлемое время; следите за перерегулированием - 🛡️ Введите
Kdтолько если нужно подавить перерегулирование; на шумных датчиках D-часть часто лучше не использовать вовсе - 📊 Оценивайте результат по графику переходного процесса: время установления, перерегулирование, отсутствие затухающих колебаний
Существуют и формальные методы (например, метод Зиглера—Никольса по критическому усилению), но для нагревателей и моторов ручная настройка с графиком обычно даёт результат быстрее. Самая частая причина «неработающего» PID — не коэффициенты, а неверный знак обратной связи: выход увеличивает ошибку вместо её уменьшения, и система уходит в насыщение. Проверяется это элементарно: подайте небольшое фиксированное воздействие и убедитесь, что измеряемая величина растёт в ожидаемую сторону.
Почему регулятор «залипает» после перерегулирования
Это классический эффект насыщения интеграла (integral windup). Пока выход ограничен (например, ШИМ уже на максимуме), ошибка продолжает накапливаться в интеграле. Когда процесс наконец пересекает уставку, огромный накопленный интеграл продолжает толкать систему дальше. Лечится ограничением интеграла, остановкой интегрирования при насыщенном выходе или схемой back-calculation.
Типичные ошибки и их диагностика
Даже корректный код может давать плохой результат из-за ошибок интеграции. Ниже — симптомы, с которыми чаще всего сталкиваются разработчики, и их вероятные причины.
| Симптом | Вероятная причина | Что проверить |
|---|---|---|
| Система уходит в насыщение и не возвращается | Неверный знак обратной связи | Направление реакции объекта на выход |
| Медленные раскачки вокруг уставки | Избыточный Ki, windup | Ограничение интеграла, уменьшение Ki |
| Высокочастотный шум на выходе | Kd усиливает шум датчика | Фильтрация АЦП, уменьшение или отключение Kd |
| Выход «плавает» без видимой закономерности | Нестабильный период расчёта dt | Вызов по таймеру, а не из while(1) |
| Статическая ошибка не уходит | Ki = 0 или ограничен выход | Диапазон выхода, значение Ki |
⚠️ Внимание: при управлении нагревателями, моторами и другими силовыми нагрузками всегда реализуйте программную защиту: ограничение максимальной мощности, аварийное отключение при обрыве датчика (проверка на неправдоподобные значения АЦП) и сторожевой таймер IWDG. Зависшая прошивка с включённым нагревателем — реальная пожарная опасность.
Отдельного упоминания заслуживает обработка обрыва датчика. Термопара или NTC-резистор при обрыве могут давать значение, которое регулятор интерпретирует как «холодно», и выкрутит нагрев на максимум. Добавьте проверку диапазона измерений и перевод выхода в безопасное состояние при выходе за него.
Стабильный период расчёта, правильный знак обратной связи и ограничение интеграла — три вещи, которые решают большинство проблем с PID на STM32 ещё до подбора коэффициентов.
Оптимизация и продвинутые приёмы
Когда базовый регулятор работает, можно улучшить качество регулирования. Для сильно нелинейных объектов применяют кусочно-заданные коэффициенты (gain scheduling): разные наборы Kp/Ki/Kd для разных диапазонов уставки. Для каскадных систем — например, положение и скорость мотора — строят вложенные контуры: внутренний быстрый контур скорости и внешний медленный контур положения.
Полезен также режим bumpless transfer — плавный переход между ручным и автоматическим управлением: при переключении интеграл инициализируется так, чтобы выход не изменился скачком. Для задач с фиксированной точкой или жёсткими требованиями по времени расчёта на ядрах без FPU рассмотрите целочисленную реализацию с масштабированием коэффициентов, но предварительно проверьте диапазоны на переполнение.
Храните коэффициенты во Flash или EEPROM (в сериях, где она эмулируется через Flash) с контрольной суммой — так настройки переживут перезагрузку, а повреждённые данные можно заменить значениями по умолчанию.
Часто задаваемые вопросы
Какой период дискретизации выбрать для PID на STM32?
Ориентируйтесь на динамику объекта: период должен быть заметно меньше характерного времени процесса. Для нагревателей обычно достаточно сотен миллисекунд, для моторов — единиц миллисекунд. Точное значение подбирается экспериментально; важнее всего, чтобы период был строго постоянным.
Можно ли использовать HAL для таймера и ШИМ вместе с PID?
Да. HAL предоставляет всё необходимое: настройка таймера на прерывание по обновлению для периода расчёта и генерация ШИМ через HAL_TIM_PWM_Start с изменением скважности через __HAL_TIM_SET_COMPARE. Для критичных по времени приложений можно перейти на LL или прямую работу с регистрами.
Нужна ли дифференциальная составляющая для терморегулятора?
Часто нет. Температурные процессы медленные, а шум измерения делает D-часть скорее вредной. Для нагревателей обычно хватает PI-регулятора с anti-windup. D-часть оправдана в быстрых системах вроде управления моторами.
Почему выход регулятора дрожит даже при стабильном измерении?
Проверьте источник тактирования расчёта: если регулятор вызывается из главного цикла, джиттер периода искажает интеграл и производную. Также убедитесь, что АЦП даёт стабильные значения — при необходимости добавьте усреднение по DMA или программный фильтр.
Как перенести настройки PID между разными экземплярами устройства?
Коэффициенты зависят от разброса параметров объекта: мощности нагревателя, тепловой инерции, характеристик мотора. Для серийных устройств может потребоваться процедура автонастройки или калибровка каждого экземпляра. Храните коэффициенты в энергонезависимой памяти с возможностью сброса к заводским значениям.
PID-регулятор на STM32 — задача вполне посильная даже начинающему разработчику встраиваемых систем, если соблюсти три ключевых условия: стабильный период расчёта, корректный знак обратной связи и защита от насыщения интеграла. Начните с простого PI-варианта, визуализируйте переходный процесс и добавляйте сложность (D-часть, каскады, gain scheduling) только тогда, когда базовая схема уже работает устойчиво.