ПИД-регулирование: хорошая идея - добавить задержку перед следующим циклом?

Jan 11 2021

Я реализую ПИД-регулирование на C ++, чтобы робот с дифференциальным приводом поворачивался на точное количество градусов, но у меня много проблем.

Ранний выход из цикла управления из-за быстрого выполнения цикла

Если робот измеряет свою ошибку меньше 0,5 градуса, он выходит из контура управления и считает поворот "завершенным" (0,5 - это случайное значение, которое я могу изменить в какой-то момент). Похоже, что контур управления работает так быстро, что робот может повернуться с очень высокой скоростью, повернуть за заданное значение и выйти из цикла / отключения мощности двигателя, потому что он был на заданном значении в течение короткого момента. Я знаю, что вся цель ПИД-регулирования - точное достижение уставки без превышения, но эта проблема очень затрудняет настройку констант ПИД-регулятора. Например, я пытаюсь найти такое значение kp, чтобы было устойчивое колебание, но никогда не было колебания, потому что робот считает, что он «закончил», как только он пройдет заданное значение. Чтобы исправить это,Я реализовал систему, в которой робот должен находиться в заданной точке в течение определенного периода времени перед выходом, и это было эффективно, позволяя возникать колебания, но проблема раннего выхода из цикла кажется необычной проблемой, и мое решение может быть неверным.

Термин D не действует из-за быстрого времени выполнения

Как только я заставил робота управлять колебаниями, используя только P, я попытался добавить D, чтобы предотвратить перерегулирование. Однако большую часть времени это не имело никакого эффекта, потому что контур управления работает так быстро, что 19 циклов из 20, скорость изменения ошибки равна 0: робот не двигался или двигался недостаточно для этого. быть измеренным в то время. Я распечатал изменение ошибки и производный член для каждого цикла, чтобы подтвердить это, и я мог видеть, что оба они будут равны 0 примерно в течение 20 циклов цикла, прежде чем принять разумное значение, а затем вернуться к 0 еще в течение 20 циклов. Как я уже сказал, я думаю, это связано с тем, что циклы цикла настолько быстрые, что робот буквально не двигался достаточно для какого-либо заметного изменения ошибки.Это было большой проблемой, потому что это означало, что член D практически не влиял на движение робота, потому что он почти всегда был 0. Чтобы решить эту проблему, я попытался использовать последнее ненулевое значение производной вместо любых 0 значений, но это не сработало, и робот будет случайным образом колебаться, если последняя производная не представляет текущую скорость изменения ошибки.

Примечание. Я также использую небольшую прямую связь для статического коэффициента трения и называю ее «f».

Стоит ли добавить задержку?

Я понял, что думаю, что источником обеих этих проблем является цикл, выполняющийся очень-очень быстро, поэтому я подумал о добавлении оператора ожидания в конце цикла. Однако это кажется плохим решением намеренно замедлить цикл. Это хорошая идея?

turnHeading(double finalAngle, double kp, double ki, double kd, double f){
    std::clock_t timer;
    timer = std::clock();

    double pastTime = 0;
    double currentTime = ((std::clock() - timer) / (double)CLOCKS_PER_SEC);

    const double initialHeading = getHeading();
    finalAngle = angleWrapDeg(finalAngle);

    const double initialAngleDiff = initialHeading - finalAngle;
    double error = angleDiff(getHeading(), finalAngle);
    double pastError = error;

    double firstTimeAtSetpoint = 0;
    double timeAtSetPoint = 0;
    bool atSetpoint = false;

    double integral = 0;
    double derivative = 0;
    double lastNonZeroD = 0;

    while (timeAtSetPoint < .05)
    {
        updatePos(encoderL.read(), encoderR.read());
        error = angleDiff(getHeading(), finalAngle);

        currentTime = ((std::clock() - timer) / (double)CLOCKS_PER_SEC);
        double dt = currentTime - pastTime;

        double proportional = error / fabs(initialAngleDiff);
        integral += dt * ((error + pastError) / 2.0);
        double derivative = (error - pastError) / dt;
        
        //FAILED METHOD OF USING LAST NON-0 VALUE OF DERIVATIVE
        // if(epsilonEquals(derivative, 0))
        // {
        //     derivative = lastNonZeroD;
        // }
        // else
        // {
        //     lastNonZeroD = derivative;
        // }

        double power = kp * proportional + ki * integral + kd * derivative;

        if (power > 0)
        {
            setMotorPowers(-power - f, power + f);
        }
        else
        {
            setMotorPowers(-power + f, power - f);
        }

        if (fabs(error) < 2)
        {
            if (!atSetpoint)
            {
                atSetpoint = true;
                firstTimeAtSetpoint = currentTime;
            }
            else //at setpoint
            {
                timeAtSetPoint = currentTime - firstTimeAtSetpoint;
            }
        }
        else //no longer at setpoint
        {
            atSetpoint = false;
            timeAtSetPoint = 0;
        }
        pastTime = currentTime;
        pastError = error;
    }
    setMotorPowers(0, 0);
}

turnHeading(90, .37, 0, .00004, .12);

Ответы

3 UgoPattacini Jan 12 2021 at 05:14

Не отключайте контроллер.

Цель контроллера - не только направить вашу систему к желаемой уставке в соответствии с заранее определенным динамическим откликом, но и противодействовать потенциальным внешним факторам, которые могут препятствовать этой задаче. Подумайте о возмущении, которое уведет систему далеко от заданного значения, как только оно будет достигнуто. Таким образом, контроллер должен быть всегда в рабочем состоянии (если вам не нужно менять саму задачу и, как следствие, менять и контроллер).

С этой целью вам, безусловно, потребуется сложить интегральную часть, которая отвечает за достижение нулевой установившейся ошибки при наличии немоделируемых величин и внешних возмущений.

Это довольно связанный ресурс: https://robotics.stackexchange.com/a/19198/6941.

Оставьте производную.

95% ПИД-регуляторов в промышленности являются ПИ-регуляторами (см. «Системы обратной связи», Астром, Мюррей), поскольку часть D может играть значительную роль только в медленных процессах (например, связанных с регулированием температуры и уровня в резервуаре). Это определенно не ваш случай. Каким-то образом связанные ресурсы о трудностях производных терминов:

  • https://robotics.stackexchange.com/a/21556/6941.
  • https://robotics.stackexchange.com/a/21555/6941.

Как только я заставил робота управлять колебаниями, используя только P, я попытался добавить D, чтобы предотвратить перерегулирование.

Что ж, похоже, вы следуете предписаниям Циглера-Николса по настройке вашего контроллера. Вам нужно придерживаться таблиц, которые дают вам оценку прироста. Однако это эвристический характер, и, скорее всего, в вашем случае это не сработает.

Просто отбросьте часть D и сосредоточьтесь на контроллере PI. Существует также другой вариант ZN, который не полагается на преднамеренно генерируемые колебания:https://robotics.stackexchange.com/a/21616/6941.

Никогда не добавляйте задержки в систему.

Задержки - это плохие парни, с которыми приходится иметь дело в контуре управления, и с худшими зверями, с которыми инженеры должны бороться ( смотрите это 🎥 ), поскольку они значительно уменьшают запас по фазе, подталкивая систему в целом к ​​нестабильности.

Если вы считаете, что ваш цикл слишком быстрый, то примените формирование входных данных к заданному значению (например, с траекторией минимального рывка ), чтобы сгладить резкий скачок. Система с обратной связью отреагирует более изящно. Другая возможность состоит в том, что ваше усиление P слишком велико: тогда просто отрегулируйте контроллер.

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

1 TimWescott Jan 12 2021 at 08:36

Было бы намного лучше определить фиксированную частоту дискретизации для вашего контроллера. Очень грубое практическое правило состоит в том, что какое бы время установки вы ни выбрали для выхода из контура, когда он работает в линейном режиме , ваш интервал выборки должен быть в 10-100 раз меньше, чем время установления. Другими словами, частота дискретизации должна быть в 10–100 раз быстрее, чем желаемая полоса пропускания контура.

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

Предпочтительно, частота дискретизации должна определяться аппаратными средствами, например, аппаратным таймером, а еще лучше выборка позиции должна выполняться аппаратными средствами, запускаемыми таймером. Это значительно снижает ограничения программного обеспечения в реальном времени.

Если вы используете разумную частоту дискретизации и в основном не регистрируете изменения в кодировщике, значит, у кодировщика недостаточно шагов.

Я собираюсь не согласиться с @Ugo_Pattachini в том, что, если цикл включает двигатель, а ваш контроллер достаточно чистый, некоторые дифференциальные действия могут быть полезны. Но есть большая вероятность, что он должен быть ограничен по полосе (то есть вам нужен контроллер опережения и запаздывания), и если вы выполняете настройку ПИД-регулятора в штатном режиме, есть большая вероятность, что у вас нет инструментов для правильно установите ограничение полосы частот.

Однако, если вы не можете установить очень жесткое время для выборки кодировщика, даже не пытайтесь использовать производное управление. Нерегулярное время выборки плюс некоторая скорость на вашем двигателе создают дополнительный шум, а производные контроллеры имеют тенденцию усиливать шум. Если вы не можете организовать чистый сигнал для управления, производное управление не подойдет.