308 lines
9.8 KiB
C#
308 lines
9.8 KiB
C#
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参数和输入必须是有限值。");
|
|
}
|
|
}
|
|
}
|
|
}
|