Đầu ra quy mô PID (đạo hàm tích phân tỷ lệ)


8

Tôi đã thực hiện một chức năng PID bằng cách sử dụng công thức,

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

Tôi nên làm gì để giữ đầu ra của mình giữa một phạm vi nhất định? nói 0-255 Nếu tôi bỏ qua bất kỳ giá trị nào không nằm trong khoảng từ 0 đến 255 thì nó có tạo ra hành vi đùa giỡn không?

Câu trả lời:


7

Bạn cần xử lý hai vấn đề:

  1. tràn số học
  2. tích hợp Windup

Tràn số học khá đơn giản - bất cứ khi nào bạn đang làm toán số nguyên, hãy đảm bảo rằng bạn sử dụng các giá trị trung gian có chiều rộng lớn hơn: ví dụ: nếu ablà 16 bit và bạn thêm / bớt chúng, hãy sử dụng trung gian 32 bit và giới hạn nó trong phạm vi của giá trị 16 bit (0 đến 65535 đối với dấu không dấu, -32768 đến 32767 đối với chữ ký) trước khi truyền xuống 16 bit. Nếu bạn hoàn toàn chắc chắn rằng bạn không bao giờ có thể bị tràn, bởi vì bạn hoàn toàn chắc chắn về phạm vi của các biến đầu vào, thì bạn có thể bỏ qua bước này, nhưng hãy cẩn thận.

Các vấn đề tích hợp Windup là tinh tế hơn. Nếu bạn có một lỗi lớn trong một khoảng thời gian dài, để bạn đạt đến giới hạn bão hòa của đầu ra bộ điều khiển, nhưng lỗi vẫn là khác không, thì bộ tích hợp sẽ tiếp tục tích lũy lỗi, có thể sẽ lớn hơn nhiều so với mức cần đạt được trạng thái ổn định. Khi bộ điều khiển hết bão hòa, bộ tích hợp phải quay trở lại, gây ra độ trễ không cần thiết và có thể không ổn định trong phản ứng của bộ điều khiển.


Trên một ghi chú khác:

Tôi thực sự muốn giới thiệu (vâng, tôi biết câu hỏi này đã 18 tháng tuổi, vì vậy bạn có thể đã hoàn thành nhiệm vụ của mình, nhưng vì lợi ích của độc giả, hãy giả vờ không phải vậy) rằng bạn tính thuật ngữ tích phân khác nhau: Thay vì Ki * (lỗi tích hợp), tính tích phân của (lỗi Ki *).

Có một số lý do để làm như vậy; bạn có thể đọc chúng trong một bài đăng trên blog tôi đã viết về cách triển khai bộ điều khiển PI một cách chính xác .


6

Tôi thường chỉ giới hạn thuật ngữ tích phân (tổng các lỗi) và nếu bạn không thể xử lý đổ chuông, bạn cần bỏ mức tăng để làm cho hệ thống bị ẩm. Ngoài ra, hãy đảm bảo các biến của bạn có lỗi, trước đó và (tổng lỗi) là các biến lớn hơn không cắt hoặc tràn.

Khi bạn chỉ chỉnh sửa và sau đó đưa nó vào cụm từ lỗi tiếp theo, nó sẽ gây ra sự không tuyến tính và vòng điều khiển sẽ nhận được phản hồi từng bước vào nó mỗi khi bạn tạo ra hành vi gây cười của mình.


4

Một vài tinh chỉnh bạn có thể muốn xem xét:

  • tạo các thuật ngữ I và D thích hợp bằng cách sử dụng các bộ lọc phù hợp thay vì chỉ sử dụng tổng và chênh lệch (nếu không, bạn sẽ rất dễ bị nhiễu, các vấn đề chính xác và các lỗi khác). NB: đảm bảo thuật ngữ I của bạn có đủ độ phân giải.

  • xác định một băng chống đỡ bên ngoài mà các thuật ngữ D và I bị vô hiệu hóa (nghĩa là điều khiển chỉ theo tỷ lệ bên ngoài dải prop, điều khiển PID bên trong dải prop)


2

Chà, như Jason S đã nói, câu hỏi này đã cũ :). Nhưng dưới đây là cách tiếp cận của tôi. Tôi đã thực hiện điều này trên PIC16F616 chạy ở bộ dao động nội bộ 8 MHz, sử dụng trình biên dịch XC8. Các mã nên giải thích chính nó trong các ý kiến, nếu không, hãy hỏi tôi. Ngoài ra, tôi có thể chia sẻ toàn bộ dự án, như tôi sẽ làm trong trang web của mình sau này.

/*
 * 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.
    }
}

sử dụng typedefs trong <stdint.h>cho uint8_tuint16_t, chứ không phải là unsigned intunsigned char.
Jason S

... nhưng tại sao bạn lại sử dụng unsignedbiến cho bộ điều khiển PI? Điều này thêm rất nhiều phức tạp vào mã của bạn; các if/elsetrường hợp riêng biệt là không cần thiết (trừ khi bạn sử dụng các mức tăng khác nhau tùy thuộc vào dấu hiệu lỗi) Bạn cũng đang sử dụng giá trị tuyệt đối của công cụ phái sinh, không chính xác.
Jason S

@JasonS Tôi không nhớ vào lúc này, nhưng tôi đoán tại thời điểm đó + - 127 là không đủ cho tôi. Ngoài ra, tôi không hiểu làm thế nào tôi sử dụng giá trị tuyệt đối của công cụ phái sinh, ý bạn là phần nào của mã?
abdullah kahraman

nhìn vào dòng của bạn có chứa PID_derivativebài tập; bạn nhận được cùng một giá trị nếu bạn chuyển đổi PID_errorPID_lastError. Và cho rằng vấn đề mà bạn đã bị mất PID_errordấu 's: thời gian nếu cuối cùng setMotorSpeed =8currentMotorSpeed = 15, và lần này setMotorSpeed = 15currentMotorSpeed = 8, sau đó bạn sẽ nhận được một PID_derivativegiá trị từ 0, đó là sai.
Jason S

Ngoài ra mã của bạn cho các sản phẩm máy tính là sai nếu unsigned charlà loại 8 bit và unsigned intlà loại 16 bit: nếu PID_kd = 8PID_derivative = 32, thì sản phẩm của họ sẽ là (unsigned char)256 == 0, bởi vì trong C, sản phẩm của hai số nguyên cùng loại T cũng thuộc loại đó cùng loại T. Nếu bạn muốn nhân bội số 8 -> 16, bạn cần truyền một trong các thuật ngữ thành số 16 bit không dấu trước khi nhân hoặc sử dụng một trình biên dịch nội tại (MCHP gọi chúng là "nội dung") được thiết kế để cung cấp cho bạn một số nhân 8 -> 16.
Jason S
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.