我已经使用公式实现了一个 PID 函数,
correction = Kp * error + Kd * (error - prevError) + kI * (sum of errors)
我应该怎么做才能将输出保持在一定范围内?说 0-255 如果我忽略不介于 0 到 255 之间的任何值,它会产生不稳定的行为吗?
我已经使用公式实现了一个 PID 函数,
correction = Kp * error + Kd * (error - prevError) + kI * (sum of errors)
我应该怎么做才能将输出保持在一定范围内?说 0-255 如果我忽略不介于 0 到 255 之间的任何值,它会产生不稳定的行为吗?
你需要处理两个问题:
算术溢出相当简单——无论何时进行整数数学运算,确保使用更大宽度的中间值:例如,如果a
和b
是 16 位,并且添加/减去它们,请使用 32 位中间值值,并将其限制在 16 位值的范围内(0 到 65535 表示无符号,-32768 到 32767 表示有符号),然后再回滚到 16 位。如果您绝对确定永远不会溢出,因为您绝对确定输入变量的范围,那么您可以跳过这一步,但要小心。
积分器结束问题更为微妙。如果你在很长一段时间内有一个很大的误差,以至于你达到了控制器输出的饱和极限,但误差仍然非零,那么积分器将继续累积误差,可能会比它应该达到的大得多稳定状态。一旦控制器脱离饱和状态,积分器就必须降下来,从而导致不必要的延迟,并可能导致控制器响应不稳定。
另一个注意事项:
我强烈建议(是的,我知道这个问题已经 18 个月了,所以你可能已经完成了你的任务,但为了读者的利益,我们假设它没有)你以不同的方式计算积分项:而不是 Ki * (integrated error),计算 (Ki*error) 的积分。
这样做有几个原因;您可以在我写的关于如何正确实现 PI 控制器的博客文章中阅读它们。
我通常只限制积分项(误差总和),如果您无法处理振铃,则需要降低增益以使系统过度阻尼。还要确保您的 error、prevError 和 (sum of error ) 变量是不会剪辑或溢出的较大变量。
当您只是剪辑校正然后将其反馈到下一个误差项时,它将导致非线性,并且每次剪辑时控制回路都会对其进行阶跃响应,这将导致您的行为不稳定。
您可能需要考虑一些改进:
使用合适的滤波器生成适当的 I 和 D 项,而不仅仅是使用和和差(否则您将很容易出现噪声、精度问题和各种其他错误)。注意:确保您的 I 术语具有足够的分辨率。
定义 D 和 I 项被禁用的支撑带(即在支撑带外仅比例控制,在支撑带内进行 PID 控制)
好吧,正如 Jason S 所说,这个问题很老了:)。但下面是我的方法。我已经使用 XC8 编译器在以 8MHz 内部振荡器运行的 PIC16F616 上实现了这一点。代码应该在评论中解释自己,如果没有,问我。此外,我可以分享整个项目,就像我稍后在我的网站上所做的那样。
/*
* 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.
}
}