Output PID Penskalaan (Derivatif Integral Proporsional)

8

Saya telah mengimplementasikan fungsi PID menggunakan rumus,

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

Apa yang harus saya lakukan untuk menjaga output saya antara rentang tertentu? katakan 0-255 Jika saya mengabaikan nilai apa pun tidak antara 0 hingga 255 itu menghasilkan perilaku jiggly?

Hamza Yerlikaya
sumber

Jawaban:

7

Anda perlu menangani dua masalah:

  1. melimpah aritmatika
  2. windup integrator

Overflow aritmatika cukup mudah - setiap kali Anda melakukan bilangan bulat matematika, pastikan Anda menggunakan nilai menengah selebar lebih besar: misalnya, jika adan b16-bit, dan Anda menambahkan / mengurangi, gunakan perantara 32-bit nilai, dan batasi pada kisaran nilai 16-bit (0 hingga 65535 untuk yang tidak ditandatangani, -32768 hingga 32767 untuk ditandatangani) sebelum memasukkan kembali ke 16 bit. Jika Anda benar - benar yakin bahwa Anda tidak akan pernah bisa overflow, karena Anda benar - benar yakin tentang rentang variabel input, maka Anda dapat melewati langkah ini, tetapi waspadalah.

Masalah windup integrator lebih halus. Jika Anda memiliki kesalahan besar untuk periode waktu yang lama, sehingga Anda mencapai batas saturasi dari output controller Anda, tetapi kesalahan itu masih nol, maka integrator akan terus mengakumulasi kesalahan, mungkin menjadi jauh lebih besar dari yang seharusnya dicapai stabil. Setelah pengontrol keluar dari saturasi, integrator harus turun kembali, menyebabkan penundaan yang tidak perlu dan mungkin ketidakstabilan dalam respons pengontrol Anda.


Pada catatan lain:

Saya akan sangat menyarankan (ya, saya tahu pertanyaan ini berusia 18 bulan, jadi Anda mungkin sudah selesai dengan tugas Anda, tetapi untuk kepentingan pembaca mari kita berpura-pura tidak) bahwa Anda menghitung istilah integral berbeda: Alih-alih Ki * (kesalahan terintegrasi), hitung integral (kesalahan Ki *).

Ada beberapa alasan untuk melakukannya; Anda dapat membacanya di posting blog yang saya tulis tentang cara menerapkan pengendali PI dengan benar .

Jason S
sumber
6

Saya biasanya hanya membatasi istilah integral (jumlah kesalahan) dan jika Anda tidak dapat menangani dering Anda perlu menjatuhkan gain untuk membuat sistem over damped. Pastikan juga variabel Anda untuk kesalahan, prevError dan (jumlah kesalahan) adalah variabel yang lebih besar yang tidak klip atau meluap.

Ketika Anda hanya klip koreksi dan kemudian memasukkannya kembali ke istilah kesalahan berikutnya itu akan menyebabkan non-linearitas dan loop kontrol akan mendapatkan respons langkah ke dalamnya setiap kali Anda klip yang akan menyebabkan perilaku jiggly Anda.

Rex Logan
sumber
4

Beberapa penyempurnaan yang mungkin ingin Anda pertimbangkan:

  • menghasilkan istilah I dan D yang tepat menggunakan filter yang sesuai daripada hanya menggunakan jumlah dan perbedaan (jika tidak Anda akan sangat rentan terhadap kebisingan, masalah akurasi dan berbagai kesalahan lainnya). NB: pastikan term I Anda memiliki resolusi yang cukup.

  • tentukan band prop di luar yang ketentuan D dan I dinonaktifkan (yaitu kontrol proporsional hanya di luar band prop, kontrol PID di dalam band prop)

Paul R
sumber
2

Nah, seperti kata Jason S, pertanyaan ini sudah lama :). Namun di bawah ini adalah pendekatan saya. Saya telah menerapkan ini pada PIC16F616 yang berjalan pada osilator internal 8MHz, menggunakan kompiler XC8. Kode harus menjelaskan sendiri dalam komentar, jika tidak, tanyakan kepada saya. Juga, saya dapat membagikan seluruh proyek, seperti yang akan saya lakukan di situs web saya nanti.

/*
 * 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.
    }
}
abdullah kahraman
sumber
gunakan typedefs <stdint.h>untuk uint8_tdan uint16_t, bukan unsigned intdan unsigned char.
Jason S
... tapi kenapa kamu menggunakan unsignedvariabel untuk pengontrol PI? Ini menambah banyak kerumitan pada kode Anda; if/elsecase terpisah tidak perlu (kecuali jika Anda menggunakan keuntungan yang berbeda tergantung pada tanda kesalahan) Anda juga menggunakan nilai absolut dari derivatif, yang tidak benar.
Jason S
@JasonS Saya tidak ingat saat ini, tapi saya kira pada saat itu + - 127 tidak cukup untuk saya. Juga, saya tidak mengerti bagaimana saya menggunakan nilai absolut turunannya, bagian mana dari kode yang Anda maksud?
abdullah kahraman
lihat baris Anda yang berisi PID_derivativetugas; Anda mendapatkan nilai yang sama jika Anda beralih PID_errordan PID_lastError. Dan dalam hal ini Anda sudah kehilangan PID_errortanda: jika terakhir kali setMotorSpeed =8dan currentMotorSpeed = 15, kali ini setMotorSpeed = 15dan currentMotorSpeed = 8, maka Anda akan mendapatkan PID_derivativenilai 0, yang salah.
Jason S
Juga kode Anda untuk menghitung produk salah jika unsigned charadalah tipe 8-bit dan tipe unsigned int16-bit: jika PID_kd = 8dan PID_derivative = 32, maka produk mereka akan (unsigned char)256 == 0, karena di C, produk dua bilangan bulat dari tipe T yang sama juga dari itu tipe yang sama T. Jika Anda ingin melakukan 8x8 -> 16 kali, Anda perlu melemparkan salah satu syarat ke angka 16-bit yang tidak ditandatangani sebelum kali, atau menggunakan kompiler intrinsik (MCHP menyebutnya "bawaan") yang dirancang untuk memberi Anda 8x8 -> 16 kali lipat.
Jason S