Я думал тема ПИД регуляторов закрыта.
но снова вижу оживление.
придется написать свой (тем более я обещал еще в 20 году).
вот есть тема будем называть ее 1 частью Здесь

итак начнем с маленьких шагов и сделаем большое дело.

ПИД регуляторы имею обширное применение от простейших термостатов до дронов (будь они не лады – последнее время) и 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.

Все это уже идеальный регулятор, который достаточен для большинства применений.

Далее ограничение выхода минимальной и максимальной мощностью рекомендую делать отдельным блоком на выходе регулятора.

В следующий раз напишем блок «Надзиратель» для этого регулятора он будет осуществлять авто настройку коэффициентов.