Масштабирование PID (Пропорционально-Интегральная Производная) Выход

8

Я реализовал функцию PID по формуле:

correction = Kp * error + Kd * (error - prevError) + kI * (sum of errors)

Что я должен сделать, чтобы мой вывод находился в определенном диапазоне? скажем 0-255 Если я игнорирую какое-либо значение не в диапазоне от 0 до 255, это вызывает колебательное поведение?

Хамза Ерликая
источник

Ответы:

7

Вам нужно решить две проблемы:

  1. арифметическое переполнение
  2. интегратор windup

Арифметическое переполнение довольно просто - всякий раз, когда вы выполняете целочисленную математику, убедитесь, что вы используете промежуточные значения большей ширины: например, если aи b16-битные, и вы добавляете / вычитаете их, используйте 32-битное промежуточное значение значение, и ограничьте его диапазоном 16-битного значения (от 0 до 65535 для без знака, от -32768 до 32767 для со знаком) перед приведением к 16 битам. Если вы абсолютно уверены, что вы никогда не получите переполнение, потому что вы абсолютно уверены в диапазонах входных переменных, тогда вы можете пропустить этот шаг, но будьте осторожны.

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


На другой ноте:

Я настоятельно рекомендую (да, я знаю, что этому вопросу исполнилось 18 месяцев, так что вы, вероятно, выполнили свою задачу, но для удобства читателей притворимся, что это не так), чтобы вы вычислили интегральный термин по-другому: вместо Ки * (интегрированная ошибка), вычислить интеграл от (Ki * error).

Есть несколько причин для этого; Вы можете прочитать их в блоге, который я написал о том, как правильно реализовать PI-контроллеры .

Джейсон С
источник
6

Я обычно просто ограничиваю интегральный член (сумму ошибок), и если вы не можете справиться со звонком, вам нужно уменьшить усиление, чтобы система перегружалась. Также убедитесь, что ваши переменные для error, prevError и (sum of error) являются большими переменными, которые не обрезаются и не переполняются.

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

Рекс Логан
источник
4

Пару уточнений, которые вы можете рассмотреть:

  • генерировать правильные термины I и D, используя подходящие фильтры, а не просто суммы и различия (в противном случае вы будете очень подвержены шуму, проблемам с точностью и другим ошибкам). NB: убедитесь, что ваш термин имеет достаточное разрешение.

  • определить зону поддержки, за пределами которой запрещены термины D и I (т. е. только пропорциональное управление за пределами зоны поддержки, ПИД-управление внутри зоны поддержки)

Пол Р
источник
2

Ну, как сказал Джейсон С., этот вопрос старый :). Но ниже мой подход. Я реализовал это на PIC16F616, работающем на внутреннем генераторе 8 МГц, используя компилятор XC8. Код должен объяснять себя в комментариях, если нет, спросите меня. Кроме того, я могу поделиться всем проектом, как я буду делать на моем сайте позже.

/*
 * applyEncoder Task:
 * -----------------
 * Calculates the PID (proportional-integral-derivative) to set the motor
 * speed.
 *
 * PID_error = setMotorSpeed - currentMotorSpeed
 * PID_sum = PID_Kp * (PID_error) + PID_Ki * ∫(PID_error) + PID_Kd * (ΔPID_error)
 *
 * or if the motor is speedier than it is set;
 *
 * PID_error = currentMotorSpeed - setMotorSpeed
 * PID_sum = - PID_Kp * (PID_error) - PID_Ki * ∫(PID_error) - PID_Kd * (ΔPID_error)
 *
 * Maximum value of PID_sum will be about:
 * 127*255 + 63*Iul + 63*255 = 65500
 *
 * Where Iul is Integral upper limit and is about 250.
 * 
 * If we divide by 256, we scale that down to about 0 to 255, that is the scale
 * of the PWM value.
 *
 * This task takes about 750us. Real figure is at the debug pin.
 * 
 * This task will fire when the startPID bit is set. This happens when a
 * sample is taken, about every 50 ms. When the startPID bit is not set,
 * the task yields the control of the CPU for other tasks' use.
 */
void applyPID(void)
{
    static unsigned int PID_sum = 0; // Sum of all PID terms.
    static unsigned int PID_integral = 0; // Integral for the integral term.
    static unsigned char PID_derivative = 0; // PID derivative term.
    static unsigned char PID_error; // Error term.
    static unsigned char PID_lastError = 0; // Record of the previous error term.
    static unsigned int tmp1; // Temporary register for holding miscellaneous stuff.
    static unsigned int tmp2; // Temporary register for holding miscellaneous stuff.
    OS_initializeTask(); // Initialize the task. Needed by RTOS. See RTOS header file for the details.
    while (1)
    {
        while (!startPID) // Wait for startPID bit to be 1.
        {
            OS_yield(); // If startPID is not 1, yield the CPU to other tasks in the mean-time.
        }
        DebugPin = 1; // We will measure how much time it takes to implement a PID controller.


        if (currentMotorSpeed > setMotorSpeed) // If the motor is speedier than it is set,
        {
            // PID error is the difference between set value and current value.
            PID_error = (unsigned char) (currentMotorSpeed - setMotorSpeed);

            // Integrate errors by subtracting them from the PID_integral variable.
            if (PID_error < PID_integral) // If the subtraction will not underflow,
                PID_integral -= PID_error; // Subtract the error from the current error integration.
            else
                PID_integral = 0; // If the subtraction will underflow, then set it to zero.
            // Integral term is: Ki * ∫error
            tmp1 = PID_Ki * PID_integral;
            // Check if PID_sum will overflow in the addition of integral term.
            tmp2 = 0xFFFF - tmp1;
            if (PID_sum < tmp2)
                PID_sum += tmp1; // If it will not overflow, then add it.
            else
                PID_sum = 0xFFFF; // If it will, then saturate it.

            if (PID_error >= PID_lastError) // If current error is bigger than last error,
                PID_derivative = (unsigned char) (PID_error - PID_lastError);
                // then calculate the derivative by subtracting them.
            else
                PID_derivative = (unsigned char) (PID_lastError - PID_error);
            // Derivative term is : Kd * d(Δerror)
            tmp1 = PID_Kd * PID_derivative;
            // Check if PID_sum will overflow in the addition of derivative term.
            if (tmp1 < PID_sum) // Check if subtraction will underflow PID_sum
                PID_sum -= tmp1;
            else PID_sum = 0; // If the subtraction will underflow, then set it to zero.

            // Proportional term is: Kp * error
            tmp1 = PID_Kp * PID_error; // Calculate the proportional term.
            if (tmp1 < PID_sum) // Check if subtraction will underflow PID_sum
                PID_sum -= tmp1;
            else PID_sum = 0; // If the subtraction will underflow, then set it to zero.
        }
        else // If the motor is slower than it is set,
        {
            PID_error = (unsigned char) (setMotorSpeed - currentMotorSpeed);
            // Proportional term is: Kp * error
            PID_sum = PID_Kp * PID_error;

            PID_integral += PID_error; // Add the error to the integral term.
            if (PID_integral > PID_integralUpperLimit) // If we have reached the upper limit of the integral,
                PID_integral = PID_integralUpperLimit; // then limit it there.
            // Integral term is: Ki * ∫error
            tmp1 = PID_Ki * PID_integral;
            // Check if PID_sum will overflow in the addition of integral term.
            tmp2 = 0xFFFF - tmp1;
            if (PID_sum < tmp2)
                PID_sum += tmp1; // If it will not overflow, then add it.
            else
                PID_sum = 0xFFFF; // If it will, then saturate it.

            if (PID_error >= PID_lastError) // If current error is bigger than last error,
                PID_derivative = (unsigned char) (PID_error - PID_lastError);
                // then calculate the derivative by subtracting them.
            else
                PID_derivative = (unsigned char) (PID_lastError - PID_error);
            // Derivative term is : Kd * d(Δerror)
            tmp1 = PID_Kd * PID_derivative;
            // Check if PID_sum will overflow in the addition of derivative term.
            tmp2 = 0xFFFF - tmp1;
            if (PID_sum < tmp2)
                PID_sum += tmp1; // If it will not overflow, then add it.
            else
                PID_sum = 0xFFFF; // If it will, then saturate it.
        }

        // Scale the sum to 0 - 255 from 0 - 65535 , dividing by 256, or right shifting 8.
        PID_sum >>= 8;

        // Set the duty cycle to the calculated and scaled PID_sum.
        PWM_dutyCycle = (unsigned char) PID_sum;
        PID_lastError = PID_error; // Make the current error the last error, since it is old now.

        startPID = 0; // Clear the flag. That will let this task wait for the flag.
        DebugPin = 0; // We are finished with the PID control block.
    }
}
Абдулла Кахраман
источник
используйте typedefs <stdint.h>для uint8_tи uint16_t, а не unsigned intи unsigned char.
Джейсон С
... но unsignedкакого чёрта вы используете переменные для ПИ-контроллера? Это добавляет сложности вашему коду; отдельные if/elseслучаи не нужны (если вы не используете разные коэффициенты усиления в зависимости от знака ошибки). Вы также используете абсолютное значение производной, что неверно.
Джейсон С
@JasonS Я не помню в данный момент, но я думаю, что в то время + - 127 было недостаточно для меня. Кроме того, я не понимаю, как я использую абсолютное значение производной, какую часть кода вы имеете в виду?
Абдулла Кахраман
посмотрите на ваши строки, содержащие PID_derivativeназначение; вы получите то же значение, если вы переключитесь PID_errorи PID_lastError. И в этом отношении вы уже потеряли PID_errorзнак: если в прошлый раз setMotorSpeed =8и currentMotorSpeed = 15, а в этот раз setMotorSpeed = 15и currentMotorSpeed = 8тогда вы получите PID_derivativeзначение 0, что неверно.
Джейсон С
Кроме того, ваш код для вычислительных продуктов неправильный, если unsigned charон является 8-битным типом и unsigned int16-битным типом: если PID_kd = 8и PID_derivative = 32, то их продукт будет (unsigned char)256 == 0, потому что в C произведение двух целых чисел одного типа T также того же типа T. Если вы хотите сделать умножение 8x8 -> 16, вам нужно привести одно из слагаемых к 16-разрядному числу без знака перед умножением или использовать встроенный компилятор (MCHP называет их «встроенными»), предназначенные для дать вам 8x8 -> 16 умножить.
Джейсон С