Если вы пишете прошивку для терморегулятора, стабилизатора скорости мотора или удержания положения сервопривода на 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-регулятором на STM32?
Температура (нагреватель, паяльник)
Скорость или положение мотора
Напряжение или ток (БП, зарядка)
Другое (освещённость, давление и т.п.)

Реализация 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-регулятора проверьте

Выполнено: 0 / 5

Связка регулятора с периферией 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) только тогда, когда базовая схема уже работает устойчиво.