feat: integrate trajectory tracking controller runtime
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
using System;
|
||||
|
||||
namespace MultiWheelC.Control.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用真实控制周期计算带积分限幅、输出限幅和抗饱和的通用有状态PID输出。
|
||||
/// </summary>
|
||||
public sealed class PidController
|
||||
{
|
||||
private double _integralState;
|
||||
private double _previousError;
|
||||
private double _previousMeasurement;
|
||||
private bool _hasPreviousSample;
|
||||
|
||||
/// <summary>
|
||||
/// 创建具有指定增益、积分输出限制和微分形式的PID控制器。
|
||||
/// </summary>
|
||||
public PidController(
|
||||
double proportionalGain,
|
||||
double integralGainPerSecond,
|
||||
double derivativeGainSeconds,
|
||||
double maximumIntegralOutput,
|
||||
bool derivativeOnMeasurement = true)
|
||||
{
|
||||
EnsureFiniteNonNegative(
|
||||
proportionalGain,
|
||||
nameof(proportionalGain));
|
||||
EnsureFiniteNonNegative(
|
||||
integralGainPerSecond,
|
||||
nameof(integralGainPerSecond));
|
||||
EnsureFiniteNonNegative(
|
||||
derivativeGainSeconds,
|
||||
nameof(derivativeGainSeconds));
|
||||
EnsureFiniteNonNegative(
|
||||
maximumIntegralOutput,
|
||||
nameof(maximumIntegralOutput));
|
||||
|
||||
ProportionalGain = proportionalGain;
|
||||
IntegralGainPerSecond = integralGainPerSecond;
|
||||
DerivativeGainSeconds = derivativeGainSeconds;
|
||||
MaximumIntegralOutput = maximumIntegralOutput;
|
||||
DerivativeOnMeasurement = derivativeOnMeasurement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取比例增益。
|
||||
/// </summary>
|
||||
public double ProportionalGain { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取积分增益,单位为1/s。
|
||||
/// </summary>
|
||||
public double IntegralGainPerSecond { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取微分增益,单位为s。
|
||||
/// </summary>
|
||||
public double DerivativeGainSeconds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取积分项允许产生的最大输出绝对值。
|
||||
/// </summary>
|
||||
public double MaximumIntegralOutput { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取微分项是否作用于测量值,以避免设定值变化产生微分冲击。
|
||||
/// </summary>
|
||||
public bool DerivativeOnMeasurement { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取最近一次设定值减测量值的误差。
|
||||
/// </summary>
|
||||
public double LastError { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取最近一次比例项输出。
|
||||
/// </summary>
|
||||
public double LastProportionalOutput { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取最近一次积分项输出。
|
||||
/// </summary>
|
||||
public double LastIntegralOutput { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取最近一次微分项输出。
|
||||
/// </summary>
|
||||
public double LastDerivativeOutput { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取最近一次经过输出范围限制后的PID输出。
|
||||
/// </summary>
|
||||
public double LastOutput { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 根据设定值、测量值、真实时间间隔和本周期输出范围更新PID。
|
||||
/// </summary>
|
||||
public double Update(
|
||||
double setPoint,
|
||||
double measurement,
|
||||
double deltaTimeSeconds,
|
||||
double minimumOutput,
|
||||
double maximumOutput)
|
||||
{
|
||||
EnsureFinite(setPoint, nameof(setPoint));
|
||||
EnsureFinite(measurement, nameof(measurement));
|
||||
EnsureFinitePositive(
|
||||
deltaTimeSeconds,
|
||||
nameof(deltaTimeSeconds));
|
||||
EnsureFinite(minimumOutput, nameof(minimumOutput));
|
||||
EnsureFinite(maximumOutput, nameof(maximumOutput));
|
||||
|
||||
if (minimumOutput > maximumOutput)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(minimumOutput),
|
||||
"PID最小输出不能大于最大输出。");
|
||||
}
|
||||
|
||||
var error = setPoint - measurement;
|
||||
var proportionalOutput =
|
||||
ProportionalGain * error;
|
||||
var derivativeOutput = CalculateDerivativeOutput(
|
||||
error,
|
||||
measurement,
|
||||
deltaTimeSeconds);
|
||||
|
||||
var candidateIntegralState =
|
||||
_integralState +
|
||||
error * deltaTimeSeconds;
|
||||
var integralOutput = CalculateIntegralOutput(
|
||||
candidateIntegralState);
|
||||
|
||||
// 同步截断积分状态本身,避免积分输出虽已限幅、内部状态仍继续增长。
|
||||
candidateIntegralState =
|
||||
IntegralGainPerSecond > 0.0 &&
|
||||
MaximumIntegralOutput > 0.0
|
||||
? integralOutput /
|
||||
IntegralGainPerSecond
|
||||
: 0.0;
|
||||
|
||||
var unlimitedOutput =
|
||||
proportionalOutput +
|
||||
integralOutput +
|
||||
derivativeOutput;
|
||||
var output = Clamp(
|
||||
unlimitedOutput,
|
||||
minimumOutput,
|
||||
maximumOutput);
|
||||
|
||||
// 根据实际允许输出反算积分项,避免执行器饱和期间继续积累误差。
|
||||
if (IntegralGainPerSecond > 0.0 &&
|
||||
output != unlimitedOutput)
|
||||
{
|
||||
integralOutput = Clamp(
|
||||
output -
|
||||
proportionalOutput -
|
||||
derivativeOutput,
|
||||
-MaximumIntegralOutput,
|
||||
MaximumIntegralOutput);
|
||||
candidateIntegralState =
|
||||
integralOutput /
|
||||
IntegralGainPerSecond;
|
||||
}
|
||||
|
||||
_integralState =
|
||||
IntegralGainPerSecond > 0.0 &&
|
||||
MaximumIntegralOutput > 0.0
|
||||
? candidateIntegralState
|
||||
: 0.0;
|
||||
_previousError = error;
|
||||
_previousMeasurement = measurement;
|
||||
_hasPreviousSample = true;
|
||||
|
||||
LastError = error;
|
||||
LastProportionalOutput = proportionalOutput;
|
||||
LastIntegralOutput = integralOutput;
|
||||
LastDerivativeOutput = derivativeOutput;
|
||||
LastOutput = output;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除积分、历史采样和最近一次PID诊断输出。
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_integralState = 0.0;
|
||||
_previousError = 0.0;
|
||||
_previousMeasurement = 0.0;
|
||||
_hasPreviousSample = false;
|
||||
LastError = 0.0;
|
||||
LastProportionalOutput = 0.0;
|
||||
LastIntegralOutput = 0.0;
|
||||
LastDerivativeOutput = 0.0;
|
||||
LastOutput = 0.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用测量值微分或误差微分计算本周期微分项输出。
|
||||
/// </summary>
|
||||
private double CalculateDerivativeOutput(
|
||||
double error,
|
||||
double measurement,
|
||||
double deltaTimeSeconds)
|
||||
{
|
||||
if (!_hasPreviousSample ||
|
||||
DerivativeGainSeconds <= 0.0)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if (DerivativeOnMeasurement)
|
||||
{
|
||||
return -DerivativeGainSeconds *
|
||||
(measurement - _previousMeasurement) /
|
||||
deltaTimeSeconds;
|
||||
}
|
||||
|
||||
return DerivativeGainSeconds *
|
||||
(error - _previousError) /
|
||||
deltaTimeSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据积分状态计算经过绝对值限制的积分项输出。
|
||||
/// </summary>
|
||||
private double CalculateIntegralOutput(
|
||||
double integralState)
|
||||
{
|
||||
if (IntegralGainPerSecond <= 0.0 ||
|
||||
MaximumIntegralOutput <= 0.0)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return Clamp(
|
||||
IntegralGainPerSecond * integralState,
|
||||
-MaximumIntegralOutput,
|
||||
MaximumIntegralOutput);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将数值限制在指定闭区间内。
|
||||
/// </summary>
|
||||
private static double Clamp(
|
||||
double value,
|
||||
double minimum,
|
||||
double maximum)
|
||||
{
|
||||
return Math.Max(
|
||||
minimum,
|
||||
Math.Min(maximum, value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查参数是否为正有限值。
|
||||
/// </summary>
|
||||
private static void EnsureFinitePositive(
|
||||
double value,
|
||||
string parameterName)
|
||||
{
|
||||
EnsureFinite(value, parameterName);
|
||||
|
||||
if (value <= 0.0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
parameterName,
|
||||
"PID时间间隔必须是正有限值。");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查参数是否为非负有限值。
|
||||
/// </summary>
|
||||
private static void EnsureFiniteNonNegative(
|
||||
double value,
|
||||
string parameterName)
|
||||
{
|
||||
EnsureFinite(value, parameterName);
|
||||
|
||||
if (value < 0.0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
parameterName,
|
||||
"PID增益和积分输出限幅必须是非负有限值。");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查参数是否为有限值。
|
||||
/// </summary>
|
||||
private static void EnsureFinite(
|
||||
double value,
|
||||
string parameterName)
|
||||
{
|
||||
if (double.IsNaN(value) ||
|
||||
double.IsInfinity(value))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
parameterName,
|
||||
"PID参数和输入必须是有限值。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user