Страница 1 из 3 123 ПоследняяПоследняя
Показано с 1 по 10 из 24

Тема: Пишем свой ПИД-регулатор

  1. #1
    Пользователь
    Регистрация
    21.04.2013
    Адрес
    Ижевск
    Сообщений
    179

    По умолчанию Пишем свой ПИД-регулатор

    Я думал тема ПИД регуляторов закрыта.
    но снова вижу оживление.
    придется написать свой (тем более я обещал еще в 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.

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

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

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

  2. #2

    По умолчанию

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

  3. #3

    По умолчанию

    Сначала надо довести ФБ до ума. 1 Работа пидов.jpg
    Проверил последние 2 варианта в эмуляции, задача всех коэффициентов по 1, мгновенно отправляет ПИД в 0 или в максимум.
    Боле менее реально работает, с задачей их от 0.001 до 0.01, спрашивается зачем так сделано? Как их вообще подобрать можно?
    Вариант с Max и Min тоже нормально не работает, ставишь Max=10 или 1, выход перестаёт реагировать вообще.
    Похоже автор вообще не тестировал свои ФБ, даже в эмуляции, не говоря про реальный объект.
    Последний раз редактировалось kondor3000; 16.10.2025 в 20:16.

  4. #4

    По умолчанию

    Коэффициенты приходится делать мизерными, потому что в коде нет учета времени цикла (dt). Без него интеграл на каждом такте прибавляет всю ошибку целиком и мгновенно улетает в насыщение. А Min/Max не работает, потому что логика защиты от насыщения не видит этих ограничений. Интеграл продолжает копиться, думая, что выход свободен, хотя он уже уперся в лимит. В итоге регулятор просто "залипает".

    Еще без хотя бы простого сглаживания D-ветвь превращается в помехоусилитель. Плюс нет зоны нечувствительности, регулятор будет гонять исполнительный механизм даже при ошибке в сотые доли, пытаясь «допилить до идеала». Это красиво на графике, но убивает механику. Ну и форма с Ki/Kd это боль для настройки. Без Ti/Td (в секундах) ни один нормальный метод автоподбора не применим.
    Инженер направления “Контрольно-измерительные приборы”
    e-mail: e.zubkov@owen.ru

  5. #5

    По умолчанию

    Скажу в защиту Ki и Kd - дело в том, применение в форме Ti и Td не совсем отражает, что это не параметры времени - это именно коэффициенты, т.к. они сразу учитывают единицы измерения и диапазон измерений регулируемой величины. Поэтому - это действительно - коэффициенты.
    И для автонастройки, наверное, возможно выполнить пересчёт коэффициентов Ti=1/Ki.

    И для корректной работы интегральной - нужно считать по формуле I := I_prev + (error * Ki * TCycle), где TCycle - время прошедшее от предыдущего вычисления.
    Или выполнять пересчёт строго по таймеру через заданные промежутки времени - и при этом в формуле также учитывать это время.

  6. #6

    По умолчанию

    Цитата Сообщение от FPavel Посмотреть сообщение
    Скажу в защиту Ki и Kd - дело в том, применение в форме Ti и Td не совсем отражает, что это не параметры времени - это именно коэффициенты, т.к. они сразу учитывают единицы измерения и диапазон измерений регулируемой величины. Поэтому - это действительно - коэффициенты.
    И для автонастройки, наверное, возможно выполнить пересчёт коэффициентов Ti=1/Ki.

    И для корректной работы интегральной - нужно считать по формуле I := I_prev + (error * Ki * TCycle), где TCycle - время прошедшее от предыдущего вычисления.
    Или выполнять пересчёт строго по таймеру через заданные промежутки времени - и при этом в формуле также учитывать это время.
    Интересный, свежий взгляд на классику. Ваше наблюдение, что «время интегрирования» по сути не является временем, открывает простор для новой, более образной терминологии. Вероятно, и «время дифференцирования» стоит называть «коэффициентом предчувствия ошибки», чтобы не вводить инженеров в заблуждение простыми физическими величинами.

    Формула Ti=1/Ki также весьма элегантна в своей простоте. Правда, не совсем понятно, что же в этой изящной концепции сталось с пропавшим пропорциональным коэффициентом Kp, но, возможно, это деталь для менее продвинутых пользователей.

    И отдельное спасибо, что акцентировали внимание на необходимости учета времени цикла. Это действительно ключевой момент, с которого, собственно, и началось сообщение выше.
    Инженер направления “Контрольно-измерительные приборы”
    e-mail: e.zubkov@owen.ru

  7. #7

    По умолчанию

    Цитата Сообщение от Евгений Зубков Посмотреть сообщение
    Интересный, свежий взгляд на классику. Ваше наблюдение, что «время интегрирования» по сути не является временем, открывает простор для новой, более образной терминологии. Вероятно, и «время дифференцирования» стоит называть «коэффициентом предчувствия ошибки», чтобы не вводить инженеров в заблуждение простыми физическими величинами.
    Просто, исходя из формулы
    OUT=error*Kp + error*(1/Ti)*TCycle + DeltaError*Td/TCycle
    видно, что размерность слагаемых
    [%]=[EU]*[1] + [EU]/[c]*[c] + [EU]*[c]/[c]
    [%]=[EU] + [EU] + [EU]
    что явно противоречит смыслу
    А значит все параметры ПИД регулятора зависят и от единиц измерения и от их диапазона, т.е. не являются какими либо временами, а лишь коэффициентами с размерностями и времени в том числе.

    Можно сделать эмуляцию ПИД регулятора объекта управления (на основе звена 1-порядка) и сделать два эксперимента - у объекта управления коэффициент умножения 4000 и 0,4 - при смене коэффициента поплывут все настройки - и K и Ti и Td.
    Для OwenLogic как-то делал такой эмулятор
    https://owen.ru/forum/showthread.php...l=1#post447749

  8. #8

    По умолчанию

    Цитата Сообщение от FPavel Посмотреть сообщение
    Просто, исходя из формулы
    OUT=error*Kp + error*(1/Ti)*TCycle + DeltaError*Td/TCycle
    видно, что размерность слагаемых
    [%]=[EU]*[1] + [EU]/[c]*[c] + [EU]*[c]/[c]
    [%]=[EU] + [EU] + [EU]
    что явно противоречит смыслу
    А значит все параметры ПИД регулятора зависят и от единиц измерения и от их диапазона, т.е. не являются какими либо временами, а лишь коэффициентами с размерностями и времени в том числе.

    Можно сделать эмуляцию ПИД регулятора объекта управления (на основе звена 1-порядка) и сделать два эксперимента - у объекта управления коэффициент умножения 4000 и 0,4 - при смене коэффициента поплывут все настройки - и K и Ti и Td.
    Для OwenLogic как-то делал такой эмулятор
    https://owen.ru/forum/showthread.php...l=1#post447749

    У вас при разборе формулы у интегральной и дифференциальной частей по дороге потерялся общий множитель Kp. В классической форме весь ПИД в скобках умножается на Kp, а Ti и Td это реальные времена в секундах. В параллельной форме они связаны как Ki = Kp / Ti и Kd = Kp * Td, поэтому с размерностями там все в порядке: именно Kp отвечает за перевод ошибки из [EU] в [%].

    Ваш эксперимент с усилением объекта 4000 и 0.4, хороший пример того, почему Ti и Td не должны меняться. Вы изменили усиление объекта (Ko), а не его динамику. Усиление определяет, насколько сильно объект реагирует, а динамика - как быстро. Когда меняется Ko, нужно подправить Kp (чтобы не сорвать устойчивость), но времена Ti и Td, отражающие инерционность системы, должны остаться теми же. Физика у объекта не поменялась, он не стал нагреваться или остывать быстрее.

    Если при этом «плывут» все три параметра, это значит, где-то уже смешались разные формы записи. Ki/Kd в вашей реализации, вероятно, уже содержат Kp и/или не учитывают dt. Отсюда и эффект.

    Ваш эмулятор, кстати, отличная идея, как раз помогает увидеть, почему важно разделять статическую настройку (Kp) и динамическую (Ti, Td). Когда это сделано корректно, секунды остаются секундами, а ПИД - ПИДом, а не «коэффициентом философского времени»
    Инженер направления “Контрольно-измерительные приборы”
    e-mail: e.zubkov@owen.ru

  9. #9

    По умолчанию

    Цитата Сообщение от Евгений Зубков Посмотреть сообщение
    В классической форме весь ПИД в скобках умножается на Kp, а Ti и Td это реальные времена в секундах.
    К сожалению, живу и работаю в несовершенном мире, где даже Ваши классические коллеги реализуют неклассические встроенные ПИД регуляторы для ПР в Owen Logic - среди них нет ни одного классического.
    Если не затруднит - Вы уж их (коллег) приведите в классическую форму! Научите ПИД любить!

  10. #10
    Пользователь
    Регистрация
    09.12.2013
    Адрес
    Ставрополь
    Сообщений
    1,898

    По умолчанию

    Цитата Сообщение от FPavel Посмотреть сообщение
    К сожалению, живу и работаю в несовершенном мире, где даже Ваши классические коллеги реализуют неклассические встроенные ПИД регуляторы для ПР в Owen Logic - среди них нет ни одного классического.
    Если не затруднит - Вы уж их (коллег) приведите в классическую форму! Научите ПИД любить!
    А это разве не классический ПИД?
    Безымянный.png

Страница 1 из 3 123 ПоследняяПоследняя

Похожие темы

  1. Пишем музыку
    от МихаилГл в разделе Трёп (Курилка)
    Ответов: 5
    Последнее сообщение: 16.09.2023, 11:28
  2. Пишем музыку
    от МихаилГл в разделе Трёп (Курилка)
    Ответов: 1
    Последнее сообщение: 15.09.2023, 11:32
  3. Свой таймер
    от Спорягин Кирилл в разделе ПЛК1хх
    Ответов: 41
    Последнее сообщение: 28.08.2015, 11:17
  4. Пишем грамотно
    от superMAX в разделе Трёп (Курилка)
    Ответов: 7
    Последнее сообщение: 19.10.2009, 18:18
  5. Пишем грамотно
    от superMAX в разделе Разработки
    Ответов: 0
    Последнее сообщение: 28.07.2009, 03:03

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •