PID控制器积分术语导致极端不稳定
-
29-09-2019 - |
题
我有一个PID控制器在机器人上运行,该机器人旨在使机器人转向指南针。 PID校正以20Hz的速度重新计算/应用。
尽管PID控制器在PD模式下运行良好(即,具有整体术语为零),即使是最小的积分也会迫使输出不稳定,以使转向执行器将转向执行器推向左或右极端。
代码:
private static void DoPID(object o)
{
// Bring the LED up to signify frame start
BoardLED.Write(true);
// Get IMU heading
float currentHeading = (float)RazorIMU.Yaw;
// We just got the IMU heading, so we need to calculate the time from the last correction to the heading read
// *immediately*. The units don't so much matter, but we are converting Ticks to milliseconds
int deltaTime = (int)((LastCorrectionTime - DateTime.Now.Ticks) / 10000);
// Calculate error
// (let's just assume CurrentHeading really is the current GPS heading, OK?)
float error = (TargetHeading - currentHeading);
LCD.Lines[0].Text = "Heading: "+ currentHeading.ToString("F2");
// We calculated the error, but we need to make sure the error is set so that we will be correcting in the
// direction of least work. For example, if we are flying a heading of 2 degrees and the error is a few degrees
// to the left of that ( IE, somewhere around 360) there will be a large error and the rover will try to turn all
// the way around to correct, when it could just turn to the right a few degrees.
// In short, we are adjusting for the fact that a compass heading wraps around in a circle instead of continuing
// infinity on a line
if (error < -180)
error = error + 360;
else if (error > 180)
error = error - 360;
// Add the error calculated in this frame to the running total
SteadyError = SteadyError + (error * deltaTime);
// We need to allow for a certain amount of tolerance.
// If the abs(error) is less than the set amount, we will
// set error to 0, effectively telling the equation that the
// rover is perfectly on course.
if (MyAbs(error) < AllowError)
error = 0;
LCD.Lines[2].Text = "Error: " + error.ToString("F2");
// Calculate proportional term
float proportional = Kp * error;
// Calculate integral term
float integral = Ki * (SteadyError * deltaTime);
// Calculate derivative term
float derivative = Kd * ((error - PrevError) / deltaTime);
// Add them all together to get the correction delta
// Set the steering servo to the correction
Steering.Degree = 90 + proportional + integral + derivative;
// We have applied the correction, so we need to *immediately* record the
// absolute time for generation of deltaTime in the next frame
LastCorrectionTime = DateTime.Now.Ticks;
// At this point, the current PID frame is finished
// ------------------------------------------------------------
// Now, we need to setup for the next PID frame and close out
// The "current" error is now the previous error
// (Remember, we are done with the current frame, so in
// relative terms, the previous frame IS the "current" frame)
PrevError = error;
// Done
BoardLED.Write(false);
}
有人知道为什么会发生这种情况或如何解决吗?
解决方案
看来您将时间表应用于整数三次。错误已经是累积错误,因为最后一个示例,因此您不需要乘以Deltatime Time。因此,我会将代码更改为以下内容。
SteadyError += error ;
SteadyError是误差总和。
因此,积分应该是稳定的 * ki
float integral = Ki * SteadyError;
编辑:
我再次浏览了您的代码,除上述修复程序外,还有其他一些项目还可以修复。
1)您不想以毫秒为单位的三角洲时间。在正常的采样系统中,增量项将是一个,但对于20Hz速率,您将其视为50,这具有增加Ki的效果,而Ki则增加了KI,并且KD也将KD降低50倍。如果您担心抖动,则需要将增量时间转换为相对样本时间。我将使用公式。
float deltaTime = (LastCorrectionTime - DateTime.Now.Ticks) / 500000.0
500000.0是每个样品的预期刻度数,为20Hz为50ms。
2)将整数术语保持在范围内。
if ( SteadyError > MaxSteadyError ) SteadyError = MaxSteadyError;
if ( SteadyError < MinSteadyError ) SteadyError = MinSteadyError;
3)更改以下代码,以便当错误约为-180左右时,您不会在较小的更改中获得错误。
if (error < -270) error += 360;
if (error > 270) error -= 360;
4)验证转向。Degree正在接收正确的分辨率和符号。
5)最后,您可能只能将deltatime全部放在一起,并以以下方式计算差异术语。
float derivative = Kd * (error - PrevError);
所有这些都变成了代码。
private static void DoPID(object o)
{
// Bring the LED up to signify frame start
BoardLED.Write(true);
// Get IMU heading
float currentHeading = (float)RazorIMU.Yaw;
// Calculate error
// (let's just assume CurrentHeading really is the current GPS heading, OK?)
float error = (TargetHeading - currentHeading);
LCD.Lines[0].Text = "Heading: "+ currentHeading.ToString("F2");
// We calculated the error, but we need to make sure the error is set
// so that we will be correcting in the
// direction of least work. For example, if we are flying a heading
// of 2 degrees and the error is a few degrees
// to the left of that ( IE, somewhere around 360) there will be a
// large error and the rover will try to turn all
// the way around to correct, when it could just turn to the right
// a few degrees.
// In short, we are adjusting for the fact that a compass heading wraps
// around in a circle instead of continuing infinity on a line
if (error < -270) error += 360;
if (error > 270) error -= 360;
// Add the error calculated in this frame to the running total
SteadyError += error;
if ( SteadyError > MaxSteadyError ) SteadyError = MaxSteadyError;
if ( SteadyError < MinSteadyError ) SteadyError = MinSteadyError;
LCD.Lines[2].Text = "Error: " + error.ToString("F2");
// Calculate proportional term
float proportional = Kp * error;
// Calculate integral term
float integral = Ki * SteadyError ;
// Calculate derivative term
float derivative = Kd * (error - PrevError) ;
// Add them all together to get the correction delta
// Set the steering servo to the correction
Steering.Degree = 90 + proportional + integral + derivative;
// At this point, the current PID frame is finished
// ------------------------------------------------------------
// Now, we need to setup for the next PID frame and close out
// The "current" error is now the previous error
// (Remember, we are done with the current frame, so in
// relative terms, the previous frame IS the "current" frame)
PrevError = error;
// Done
BoardLED.Write(false);
}
其他提示
整数术语已经累积了,随着时间的推移,乘以三角洲将使它以时间平方的速度积累。实际上,由于已经通过将误差乘以三角洲的误差已经错误地计算出来,因此已被时间内!
在SteadyError中,如果您试图弥补上的周围更新,最好修复Aperiodicition。但是,无论如何,计算都是有缺陷的。您已经以错误/时间单位进行了计算,而您只需要错误单位。如果真正需要的话,算术的正确方法可以补偿定时抖动:
SteadyError += (error * 50.0f/deltaTime);
如果三角洲保留为毫秒,名义更新率为20Hz。但是,如果您要检测到的正时抖动,则会更好地计算出浮子或根本不转换为毫秒。您不必丢弃精度。无论哪种方式,您需要的是通过名义时间与实际时间的比率修改错误值。
一个很好的阅读是 没有博士学位的PID
我不确定您的代码为什么不起作用,但是我几乎是肯定的,您也无法测试它以了解原因。您可以注入计时器服务,以便您嘲笑它,看看发生了什么:
public interace ITimer
{
long GetCurrentTicks()
}
public class Timer : ITimer
{
public long GetCurrentTicks()
{
return DateTime.Now.Ticks;
}
}
public class TestTimer : ITimer
{
private bool firstCall = true;
private long last;
private int counter = 1000000000;
public long GetCurrentTicks()
{
if (firstCall)
last = counter * 10000;
else
last += 3500; //ticks; not sure what a good value is here
//set up for next call;
firstCall = !firstCall;
counter++;
return last;
}
}
然后,将两个呼叫替换为 DateTime.Now.Ticks
和 GetCurrentTicks()
, ,您可以浏览代码,看看值的外观。