Keldish
15.10.2025, 12:28
Я думал тема ПИД регуляторов закрыта.
но снова вижу оживление.
придется написать свой (тем более я обещал еще в 20 году).
вот есть тема будем называть ее 1 частью Здесь (https://owen.ru/forum/showthread.php?t=34012)
итак начнем с маленьких шагов и сделаем большое дело.
ПИД регуляторы имею обширное применение от простейших термостатов до дронов (будь они не лады – последнее время) и 3Д принтеров.
почему ПИД -ПИД
Пропорциональный
Интегральный
Дифференциальный
регулятор
вычисляются эти три составляющие, суммируются и получаем итоговое воздействие на систему для достижения требуемого результата
Пропорциональная - Реагирует на текущую ошибку. Чем она больше, тем быстрее система, но слишком большое значение приводит к перерегулированию и колебаниям.
Интегральная - Накапливает прошлые ошибки. Позволяет устранить статическую ошибку (когда система стабилизируется не точно на уставке). Чрезмерное увеличение может вызвать "интегральное насыщение" и большие перерегулирования.
Дифференциальная - Предсказывает будущее поведение ошибки, учитывая скорость ее изменения. "Сглаживает" переходный процесс и помогает уменьшить перерегулирование. Чувствительна к шуму.
С теорией все, перейдем к практике
имеем
setpoint - уставку (то что мы хотим получить)
feedback – текущее значение (то что есть сейчас)
Коэффициенты Kp, Ki, Kd – это задаваемые нами значения которые задают пропорцию влияния каждого из расчетов на конечный результат (к ним мы вернемся позже)
вычисляем error – ошибку = setpoint – feedback
P - пропорциональная составляющая = error * Kp
I – интегральная составляющая = I_prev + error * Ki (тут I_prev это I сохраненное с предыдущего вызова макроса, при первом цикле = 0)
D – дифференциальная составляющая = Kd * (error – error_prev) (тут error_prev это error сохраненное с предыдущего вызова макроса, при первом цикле = 0)
Out - ну и наконец выход нашего блока = P+I+D
вот и готов наш первый ПИД-регулятор
вот его код на ST
function_block first_PID
var_input
setpoint: real;
feedback: real;
Kp: real;
Ki: real;
Kd: real;
end_var
var_output
Out : real;
end_var
var
error : real;
I_prev : real;
error_prev : real;
P : real;
I : real;
D : real;
end_var
error := setpoint - feedback;
P := error * Kp;
I := I_prev + (error * Ki);
// назовем это место в программе как (точка 1)
D := Kd * (error - error_prev);
Out := P+I+D;
// назовем это место в программе как (точка 2)
I_prev := I;
error_prev := error;
end_function_block
Но его практически не возможно использовать
когда измеренное значение будет рядом с заданным интегральная составляющая будет накапливаться для устранения ошибки и в какой то момент может стать очень большой и для обратного хода при изменений уставки или смене измеренного значения ей потребуется время для «смотки» назад, а нам нужен регулятор с быстрым реагированием.
по тому ограничим величину I максимальным и минимальным возможным значением выхода. Вставим в точку 1 – этот код
IF I > 1 then
I := 1;
elsif I < -1 then
I := -1;
end_IF
тоже самое с выходом расчитанное значение может превышать возможные значения на выходе и их нужно ограничить. Вставим в точку 2 – этот код
IF Out > 1 then
Out := 1;
elsif Out < -1 then
Out := -1;
end_IF
Или интегральную составляющую можно сохранять только если выход не перенасыщен. Вот новый код
function_block first_PID
var_input
setpoint: real;
feedback: real;
Kp: real;
Ki: real;
Kd: real;
end_var
var_output
Out : real;
end_var
var
error : real;
I_prev : real;
error_prev : real;
P : real;
I : real;
D : real;
output_saturated: bool;
end_var
error := setpoint - feedback;
P := error * Kp;
I := I_prev + (error * Ki);
D := kD * (error - error_prev);
Out := P+I+D;
IF Out > 1 then
Out := 1;
output_saturated := true;
elsif Out < -1 then
Out := -1;
output_saturated := true;
else
output_saturated := false;
end_IF
// назовем это место в программе как (точка 3)
if not output_saturated then
I_prev := I;
end_if;
error_prev := error;
end_function_block
он практически единетичен первому с внесенными правками. Но есть и отличия мы ограничиваем I только когда регулятор не может повлиять на установку из за физических ограничений не смотря на максимальный выход (например отапливание 100 ватной лампочкой комнаты в 30 м2)
Вот это уже вполне правильный и рабочий вариант.
Сейчас выходной сигнал у нас в диапазоне -1…1. Давайте приведем его в диапазон 0..100(%), для этого вставим в точку 3 следующий код Out := (Out+1) * 50;
Хотя это спорный вопрос
выход -1…1 (симетричный диапазон) удобен для управления двигателями (вперед/назад) и реверсивными приводами
выход 0..100 для насосов, вентиляторов, нагревателей
Я не думаю что ограничение диапазона нужно вносить в сам регулятор, но приведем НАШ регулятор в соответствие с библиотечным и добавим параметры «Минимальная мощность» и «Максимальная мощность».
Вот итоговый код:
function_block first_PID
var_input
setpoint: real;
feedback: real;
Kp: real;
Ki: real;
Kd: real;
min_P: real;
max_P: real;
end_var
var_output
Out : real;
end_var
var
error : real;
I_prev : real;
error_prev : real;
P : real;
I : real;
D : real;
output_saturated: bool;
end_var
error := setpoint - feedback;
P := error * Kp;
I := I_prev + (error * Ki);
D := Kd * (error - error_prev);
Out := P+I+D;
IF Out > 1 then
Out := 1;
output_saturated := true;
elsif Out < -1 then
Out := -1;
output_saturated := true;
else
output_saturated := false;
end_IF
Out := (Out+1) * 50;
IF Out > max_P then Out := max_P; elsif Out < min_P then Out := min_P; end_IF
if not output_saturated then I_prev := I; end_if;
error_prev := error;
end_function_block
а вот разрешение работы или «Ручной режим», что по моему одно и тоже лучше внести в регулятор.
Сразу приведу итоговый код (в нем я отказался от последних изменений, уже говорил что по моему им здесь не место, и далее приведем более красивое решение)
function_block first_PID
var_input
setpoint: real;
feedback: real;
Kp: real;
Ki: real;
Kd: real;
EN_manual: bool;
Set_manual: real;
end_var
var_output
Out : real;
end_var
var
error : real;
I_prev : real;
error_prev : real;
P : real;
I : real;
D : real;
output_saturated: bool;
raw_output: real;
end_var
IF EN_manual THEN
raw_output := Set_manual;
I_prev := Set_manual;
error_prev := setpoint - feedback;
ELSE
error := setpoint - feedback;
P := error * Kp;
I := I_prev + (error * Ki);
D := Kd * (error - error_prev);
raw_output := P+I+D;
IF raw_output > 1 then
raw_output := 1;
output_saturated := true;
elsif raw_output< -1 then
raw_output := -1;
output_saturated := true;
else
output_saturated := false;
end_IF
if not output_saturated then I_prev := I; end_if;
error_prev := error;
end_IF
Out := (raw_output+1) * 50;
end_function_block
здесь в ручном режиме мы сохраняем старую ошибку для дифференциальной составляющей и интегральную составляющую делаем равной ручному заданию для плавного подхвата регулятором при выходе из ручного режима.
подхват можно сделать и еще более плавным заменив строку
I_prev := Set_manual; на I_prev := Set_manual - (error * Kp);
Эти входы можно использовать и как в библиотечном регуляторе задать жесткое значение выхода при отключенном регуляторе в Set_manual и включать/выключать регулирование через инверсию EN_manual.
Все это уже идеальный регулятор, который достаточен для большинства применений.
Далее ограничение выхода минимальной и максимальной мощностью рекомендую делать отдельным блоком на выходе регулятора.
В следующий раз напишем блок «Надзиратель» для этого регулятора он будет осуществлять авто настройку коэффициентов.
но снова вижу оживление.
придется написать свой (тем более я обещал еще в 20 году).
вот есть тема будем называть ее 1 частью Здесь (https://owen.ru/forum/showthread.php?t=34012)
итак начнем с маленьких шагов и сделаем большое дело.
ПИД регуляторы имею обширное применение от простейших термостатов до дронов (будь они не лады – последнее время) и 3Д принтеров.
почему ПИД -ПИД
Пропорциональный
Интегральный
Дифференциальный
регулятор
вычисляются эти три составляющие, суммируются и получаем итоговое воздействие на систему для достижения требуемого результата
Пропорциональная - Реагирует на текущую ошибку. Чем она больше, тем быстрее система, но слишком большое значение приводит к перерегулированию и колебаниям.
Интегральная - Накапливает прошлые ошибки. Позволяет устранить статическую ошибку (когда система стабилизируется не точно на уставке). Чрезмерное увеличение может вызвать "интегральное насыщение" и большие перерегулирования.
Дифференциальная - Предсказывает будущее поведение ошибки, учитывая скорость ее изменения. "Сглаживает" переходный процесс и помогает уменьшить перерегулирование. Чувствительна к шуму.
С теорией все, перейдем к практике
имеем
setpoint - уставку (то что мы хотим получить)
feedback – текущее значение (то что есть сейчас)
Коэффициенты Kp, Ki, Kd – это задаваемые нами значения которые задают пропорцию влияния каждого из расчетов на конечный результат (к ним мы вернемся позже)
вычисляем error – ошибку = setpoint – feedback
P - пропорциональная составляющая = error * Kp
I – интегральная составляющая = I_prev + error * Ki (тут I_prev это I сохраненное с предыдущего вызова макроса, при первом цикле = 0)
D – дифференциальная составляющая = Kd * (error – error_prev) (тут error_prev это error сохраненное с предыдущего вызова макроса, при первом цикле = 0)
Out - ну и наконец выход нашего блока = P+I+D
вот и готов наш первый ПИД-регулятор
вот его код на ST
function_block first_PID
var_input
setpoint: real;
feedback: real;
Kp: real;
Ki: real;
Kd: real;
end_var
var_output
Out : real;
end_var
var
error : real;
I_prev : real;
error_prev : real;
P : real;
I : real;
D : real;
end_var
error := setpoint - feedback;
P := error * Kp;
I := I_prev + (error * Ki);
// назовем это место в программе как (точка 1)
D := Kd * (error - error_prev);
Out := P+I+D;
// назовем это место в программе как (точка 2)
I_prev := I;
error_prev := error;
end_function_block
Но его практически не возможно использовать
когда измеренное значение будет рядом с заданным интегральная составляющая будет накапливаться для устранения ошибки и в какой то момент может стать очень большой и для обратного хода при изменений уставки или смене измеренного значения ей потребуется время для «смотки» назад, а нам нужен регулятор с быстрым реагированием.
по тому ограничим величину I максимальным и минимальным возможным значением выхода. Вставим в точку 1 – этот код
IF I > 1 then
I := 1;
elsif I < -1 then
I := -1;
end_IF
тоже самое с выходом расчитанное значение может превышать возможные значения на выходе и их нужно ограничить. Вставим в точку 2 – этот код
IF Out > 1 then
Out := 1;
elsif Out < -1 then
Out := -1;
end_IF
Или интегральную составляющую можно сохранять только если выход не перенасыщен. Вот новый код
function_block first_PID
var_input
setpoint: real;
feedback: real;
Kp: real;
Ki: real;
Kd: real;
end_var
var_output
Out : real;
end_var
var
error : real;
I_prev : real;
error_prev : real;
P : real;
I : real;
D : real;
output_saturated: bool;
end_var
error := setpoint - feedback;
P := error * Kp;
I := I_prev + (error * Ki);
D := kD * (error - error_prev);
Out := P+I+D;
IF Out > 1 then
Out := 1;
output_saturated := true;
elsif Out < -1 then
Out := -1;
output_saturated := true;
else
output_saturated := false;
end_IF
// назовем это место в программе как (точка 3)
if not output_saturated then
I_prev := I;
end_if;
error_prev := error;
end_function_block
он практически единетичен первому с внесенными правками. Но есть и отличия мы ограничиваем I только когда регулятор не может повлиять на установку из за физических ограничений не смотря на максимальный выход (например отапливание 100 ватной лампочкой комнаты в 30 м2)
Вот это уже вполне правильный и рабочий вариант.
Сейчас выходной сигнал у нас в диапазоне -1…1. Давайте приведем его в диапазон 0..100(%), для этого вставим в точку 3 следующий код Out := (Out+1) * 50;
Хотя это спорный вопрос
выход -1…1 (симетричный диапазон) удобен для управления двигателями (вперед/назад) и реверсивными приводами
выход 0..100 для насосов, вентиляторов, нагревателей
Я не думаю что ограничение диапазона нужно вносить в сам регулятор, но приведем НАШ регулятор в соответствие с библиотечным и добавим параметры «Минимальная мощность» и «Максимальная мощность».
Вот итоговый код:
function_block first_PID
var_input
setpoint: real;
feedback: real;
Kp: real;
Ki: real;
Kd: real;
min_P: real;
max_P: real;
end_var
var_output
Out : real;
end_var
var
error : real;
I_prev : real;
error_prev : real;
P : real;
I : real;
D : real;
output_saturated: bool;
end_var
error := setpoint - feedback;
P := error * Kp;
I := I_prev + (error * Ki);
D := Kd * (error - error_prev);
Out := P+I+D;
IF Out > 1 then
Out := 1;
output_saturated := true;
elsif Out < -1 then
Out := -1;
output_saturated := true;
else
output_saturated := false;
end_IF
Out := (Out+1) * 50;
IF Out > max_P then Out := max_P; elsif Out < min_P then Out := min_P; end_IF
if not output_saturated then I_prev := I; end_if;
error_prev := error;
end_function_block
а вот разрешение работы или «Ручной режим», что по моему одно и тоже лучше внести в регулятор.
Сразу приведу итоговый код (в нем я отказался от последних изменений, уже говорил что по моему им здесь не место, и далее приведем более красивое решение)
function_block first_PID
var_input
setpoint: real;
feedback: real;
Kp: real;
Ki: real;
Kd: real;
EN_manual: bool;
Set_manual: real;
end_var
var_output
Out : real;
end_var
var
error : real;
I_prev : real;
error_prev : real;
P : real;
I : real;
D : real;
output_saturated: bool;
raw_output: real;
end_var
IF EN_manual THEN
raw_output := Set_manual;
I_prev := Set_manual;
error_prev := setpoint - feedback;
ELSE
error := setpoint - feedback;
P := error * Kp;
I := I_prev + (error * Ki);
D := Kd * (error - error_prev);
raw_output := P+I+D;
IF raw_output > 1 then
raw_output := 1;
output_saturated := true;
elsif raw_output< -1 then
raw_output := -1;
output_saturated := true;
else
output_saturated := false;
end_IF
if not output_saturated then I_prev := I; end_if;
error_prev := error;
end_IF
Out := (raw_output+1) * 50;
end_function_block
здесь в ручном режиме мы сохраняем старую ошибку для дифференциальной составляющей и интегральную составляющую делаем равной ручному заданию для плавного подхвата регулятором при выходе из ручного режима.
подхват можно сделать и еще более плавным заменив строку
I_prev := Set_manual; на I_prev := Set_manual - (error * Kp);
Эти входы можно использовать и как в библиотечном регуляторе задать жесткое значение выхода при отключенном регуляторе в Set_manual и включать/выключать регулирование через инверсию EN_manual.
Все это уже идеальный регулятор, который достаточен для большинства применений.
Далее ограничение выхода минимальной и максимальной мощностью рекомендую делать отдельным блоком на выходе регулятора.
В следующий раз напишем блок «Надзиратель» для этого регулятора он будет осуществлять авто настройку коэффициентов.