Похоже, вы применяете свою временную базу к интегралу три раза.
Ошибка - это уже накопленная ошибка с момента последней выборки, поэтому вам не нужно умножать ее на deltaTime. Поэтому я бы изменил код на следующий.
SteadyError += error ;
SteadyError - это интеграл или сумма ошибок.
Таким образом, интеграл должен быть просто SteadyError * Ki
float integral = Ki * SteadyError;
Редактировать:
Я снова просмотрел ваш код, и есть несколько других вещей, которые я бы исправил в дополнение к вышеуказанному исправлению.
1) Вы не хотите, чтобы время дельты в миллисекундах. В обычной системе с выборкой дельта-член будет равен единице, но вы вводите значение, равное 50, для частоты 20 Гц, это приводит к увеличению Ki на этот коэффициент и уменьшению Kd также в 50 раз. Если вас беспокоит дрожание, вам нужно преобразовать время дельты в относительное время выборки. Вместо этого я бы использовал формулу.
float deltaTime = (LastCorrectionTime - DateTime.Now.Ticks) / 500000.0
500000.0 - это число ожидаемых тиков на выборку, которое для 20 Гц составляет 50 мс.
2) Держите интеграл в пределах диапазона.
if ( SteadyError > MaxSteadyError ) SteadyError = MaxSteadyError;
if ( SteadyError < MinSteadyError ) SteadyError = MinSteadyError;
3) Измените следующий код, чтобы при ошибке около -180 вы не получили ошибочного шага с небольшим изменением.
if (error < -270) error += 360;
if (error > 270) error -= 360;
4) Убедитесь, что Steering.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);
}