重构横向控制器输出前后GCP目标转角并简化命令分配器

This commit is contained in:
2026-08-07 18:13:55 +08:00
parent bc37e71ad0
commit 88f651e0c2
25 changed files with 310 additions and 105 deletions
Binary file not shown.
@@ -3,37 +3,62 @@ using System;
namespace MultiWheelC.Control.Abstractions namespace MultiWheelC.Control.Abstractions
{ {
/// <summary> /// <summary>
/// 表示横向控制器生成的车体中心目标曲率命令 /// 表示横向控制器生成的前、后GCP目标转角,单位为rad,逆时针为正
/// </summary> /// </summary>
public readonly struct LateralControlCommand public readonly struct LateralControlCommand
{ {
/// <summary> /// <summary>
/// 创建统一使用SI单位和左转为正约定的横向控制命令。 /// 创建前、后GCP目标转角命令。
/// </summary> /// </summary>
public LateralControlCommand( public LateralControlCommand(
double targetCurvaturePerMeter) double frontGcpAngleRadians,
double rearGcpAngleRadians)
{ {
EnsureFinite( EnsureFinite(
targetCurvaturePerMeter, frontGcpAngleRadians,
nameof(targetCurvaturePerMeter)); nameof(frontGcpAngleRadians));
EnsureFinite(
rearGcpAngleRadians,
nameof(rearGcpAngleRadians));
TargetCurvaturePerMeter = FrontGcpAngleRadians =
targetCurvaturePerMeter; frontGcpAngleRadians;
RearGcpAngleRadians =
rearGcpAngleRadians;
} }
/// <summary> /// <summary>
/// 获取车体中心目标轨迹曲率,单位为1/m,左转为正、右转为负 /// 获取前GCP目标转角,单位为rad,逆时针为正
/// </summary> /// </summary>
public double TargetCurvaturePerMeter { get; } public double FrontGcpAngleRadians { get; }
/// <summary> /// <summary>
/// 创建保持直线行驶的零曲率命令 /// 获取后GCP目标转角,单位为rad,逆时针为正
/// </summary>
public double RearGcpAngleRadians { get; }
/// <summary>
/// 获取前后GCP的共同转角分量,主要用于横向平移修正。
/// </summary>
public double CommonAngleRadians =>
(FrontGcpAngleRadians +
RearGcpAngleRadians) / 2.0;
/// <summary>
/// 获取前后GCP的差动转角分量,主要用于曲率前馈和航向修正。
/// </summary>
public double DifferentialAngleRadians =>
(FrontGcpAngleRadians -
RearGcpAngleRadians) / 2.0;
/// <summary>
/// 创建前后GCP均保持车头方向的直线命令。
/// </summary> /// </summary>
public static LateralControlCommand Straight => public static LateralControlCommand Straight =>
new LateralControlCommand(0.0); new LateralControlCommand(0.0, 0.0);
/// <summary> /// <summary>
/// 检查横向曲率命令是否为有限值。 /// 检查GCP目标转角是否为有限值。
/// </summary> /// </summary>
private static void EnsureFinite( private static void EnsureFinite(
double value, double value,
@@ -44,7 +69,7 @@ namespace MultiWheelC.Control.Abstractions
{ {
throw new ArgumentOutOfRangeException( throw new ArgumentOutOfRangeException(
parameterName, parameterName,
"横向控制目标曲率必须是有限值。"); "GCP目标转角必须是有限值。");
} }
} }
} }
@@ -4,57 +4,36 @@ using MultiWheelC.Control.Abstractions;
namespace MultiWheelC.Control.Allocation namespace MultiWheelC.Control.Allocation
{ {
/// <summary> /// <summary>
/// 将车体中心目标曲率按对称前后转向策略转换为旧版底盘的前后GCP方向 /// 独立限制前后GCP目标转角并与纵向速度组合成底盘运动命令
/// </summary> /// </summary>
public sealed class AckermannGcpAllocator public sealed class GcpCommandAllocator
{ {
/// <summary> /// <summary>
/// 创建使用指定GCP半间距和最大GCP转角的对称转向分配器。 /// 创建使用指定前后GCP最大转角的命令分配器。
/// </summary> /// </summary>
public AckermannGcpAllocator( public GcpCommandAllocator(double maximumGcpAngleRadians)
double controlPointRadiusMeters,
double maximumGcpAngleRadians)
{ {
EnsureFinitePositive(
controlPointRadiusMeters,
nameof(controlPointRadiusMeters));
EnsureFinitePositive( EnsureFinitePositive(
maximumGcpAngleRadians, maximumGcpAngleRadians,
nameof(maximumGcpAngleRadians)); nameof(maximumGcpAngleRadians));
if (maximumGcpAngleRadians >= if (maximumGcpAngleRadians >= Math.PI / 2.0)
Math.PI / 2.0)
{ {
throw new ArgumentOutOfRangeException( throw new ArgumentOutOfRangeException(
nameof(maximumGcpAngleRadians), nameof(maximumGcpAngleRadians),
"最大GCP转角必须小于π/2,避免曲率换算出现奇异值。"); "最大GCP转角必须小于π/2。");
} }
ControlPointRadiusMeters = MaximumGcpAngleRadians = maximumGcpAngleRadians;
controlPointRadiusMeters;
MaximumGcpAngleRadians =
maximumGcpAngleRadians;
} }
/// <summary>
/// 获取车体中心到前、后GCP的距离,单位为m。
/// </summary>
public double ControlPointRadiusMeters { get; }
/// <summary> /// <summary>
/// 获取前后GCP允许的最大转角绝对值,单位为rad。 /// 获取前后GCP允许的最大转角绝对值,单位为rad。
/// </summary> /// </summary>
public double MaximumGcpAngleRadians { get; } public double MaximumGcpAngleRadians { get; }
/// <summary> /// <summary>
/// 获取当前GCP几何和转角限制允许的最大车体中心曲率,单位为1/m /// 将纵向速度和前后GCP转角组合为底盘运动命令
/// </summary>
public double MaximumCurvaturePerMeter =>
Math.Tan(MaximumGcpAngleRadians) /
ControlPointRadiusMeters;
/// <summary>
/// 将纵向命令速度和车体中心目标曲率分配为前后GCP运动命令。
/// </summary> /// </summary>
public GcpMotionCommand Allocate( public GcpMotionCommand Allocate(
double speedMetersPerSecond, double speedMetersPerSecond,
@@ -64,19 +43,12 @@ namespace MultiWheelC.Control.Allocation
speedMetersPerSecond, speedMetersPerSecond,
nameof(speedMetersPerSecond)); nameof(speedMetersPerSecond));
var limitedCurvaturePerMeter = var frontAngleRadians = ClampSymmetric(
Clamp( lateralCommand.FrontGcpAngleRadians,
lateralCommand MaximumGcpAngleRadians);
.TargetCurvaturePerMeter, var rearAngleRadians = ClampSymmetric(
-MaximumCurvaturePerMeter, lateralCommand.RearGcpAngleRadians,
MaximumCurvaturePerMeter); MaximumGcpAngleRadians);
var frontAngleRadians =
Math.Atan(
limitedCurvaturePerMeter *
ControlPointRadiusMeters);
var rearAngleRadians =
-frontAngleRadians;
return new GcpMotionCommand( return new GcpMotionCommand(
speedMetersPerSecond, speedMetersPerSecond,
@@ -85,16 +57,15 @@ namespace MultiWheelC.Control.Allocation
} }
/// <summary> /// <summary>
/// 将数值限制在指定闭区间内。 /// 将数值按正负对称方式限制在指定绝对值内。
/// </summary> /// </summary>
private static double Clamp( private static double ClampSymmetric(
double value, double value,
double minimum, double maximumAbsoluteValue)
double maximum)
{ {
return Math.Max( return Math.Max(
minimum, -maximumAbsoluteValue,
Math.Min(maximum, value)); Math.Min(maximumAbsoluteValue, value));
} }
/// <summary> /// <summary>
@@ -33,7 +33,7 @@ namespace MultiWheelC.Control.Execution
private readonly IVehicleStateProvider _stateProvider; private readonly IVehicleStateProvider _stateProvider;
private readonly ILateralController _lateralController; private readonly ILateralController _lateralController;
private readonly ILongitudinalController _longitudinalController; private readonly ILongitudinalController _longitudinalController;
private readonly AckermannGcpAllocator _gcpAllocator; private readonly GcpCommandAllocator _gcpAllocator;
private readonly GcpCommandExecutor _commandExecutor; private readonly GcpCommandExecutor _commandExecutor;
private Trajectory2D _trajectory; private Trajectory2D _trajectory;
@@ -45,7 +45,7 @@ namespace MultiWheelC.Control.Execution
IVehicleStateProvider stateProvider, IVehicleStateProvider stateProvider,
ILateralController lateralController, ILateralController lateralController,
ILongitudinalController longitudinalController, ILongitudinalController longitudinalController,
AckermannGcpAllocator gcpAllocator, GcpCommandAllocator gcpAllocator,
GcpCommandExecutor commandExecutor, GcpCommandExecutor commandExecutor,
double finishDistanceMeters = 0.03, double finishDistanceMeters = 0.03,
double finishSpeedMetersPerSecond = 0.02, double finishSpeedMetersPerSecond = 0.02,
@@ -4,22 +4,23 @@ using MultiWheelC.Control.Abstractions;
namespace MultiWheelC.Control.Lateral namespace MultiWheelC.Control.Lateral
{ {
/// <summary> /// <summary>
/// 使用参考曲率前馈、航向误差和向误差计算车体中心目标曲率 /// 参考曲率、横向误差和向误差分别转换为前、后GCP目标转角
/// </summary> /// </summary>
public sealed class StanleyLateralController : ILateralController public sealed class StanleyLateralController : ILateralController
{ {
private const double MaximumMathematicalAngleRadians =
Math.PI / 2.0 - 1e-3;
/// <summary> /// <summary>
/// 创建使用指定GCP几何、Stanley增益和低速保护参数的横向控制器。 /// 创建使用指定GCP几何、Stanley增益和转角保护参数的横向控制器。
/// </summary> /// </summary>
public StanleyLateralController( public StanleyLateralController(
double controlPointRadiusMeters, double controlPointRadiusMeters,
double crossTrackGainPerSecond, double crossTrackGainPerSecond,
double headingErrorGain, double headingErrorGain,
double minimumSpeedMetersPerSecond, double minimumSpeedMetersPerSecond,
bool useActualSpeedForGain = true) bool useActualSpeedForGain = true,
double maximumCrossTrackCorrectionRadians =
10.0 * Math.PI / 180.0,
double maximumHeadingCorrectionRadians =
10.0 * Math.PI / 180.0)
{ {
EnsureFinitePositive( EnsureFinitePositive(
controlPointRadiusMeters, controlPointRadiusMeters,
@@ -33,12 +34,22 @@ namespace MultiWheelC.Control.Lateral
EnsureFinitePositive( EnsureFinitePositive(
minimumSpeedMetersPerSecond, minimumSpeedMetersPerSecond,
nameof(minimumSpeedMetersPerSecond)); nameof(minimumSpeedMetersPerSecond));
EnsureFinitePositive(
maximumCrossTrackCorrectionRadians,
nameof(maximumCrossTrackCorrectionRadians));
EnsureFinitePositive(
maximumHeadingCorrectionRadians,
nameof(maximumHeadingCorrectionRadians));
ControlPointRadiusMeters = controlPointRadiusMeters; ControlPointRadiusMeters = controlPointRadiusMeters;
CrossTrackGainPerSecond = crossTrackGainPerSecond; CrossTrackGainPerSecond = crossTrackGainPerSecond;
HeadingErrorGain = headingErrorGain; HeadingErrorGain = headingErrorGain;
MinimumSpeedMetersPerSecond = minimumSpeedMetersPerSecond; MinimumSpeedMetersPerSecond = minimumSpeedMetersPerSecond;
UseActualSpeedForGain = useActualSpeedForGain; UseActualSpeedForGain = useActualSpeedForGain;
MaximumCrossTrackCorrectionRadians =
maximumCrossTrackCorrectionRadians;
MaximumHeadingCorrectionRadians =
maximumHeadingCorrectionRadians;
} }
/// <summary> /// <summary>
@@ -62,12 +73,22 @@ namespace MultiWheelC.Control.Lateral
public double MinimumSpeedMetersPerSecond { get; } public double MinimumSpeedMetersPerSecond { get; }
/// <summary> /// <summary>
/// 获取是否优先使用Detour估算的实际纵向速度计算横向误差项 /// 获取是否优先使用Detour估算的实际纵向速度计算横向修正
/// </summary> /// </summary>
public bool UseActualSpeedForGain { get; } public bool UseActualSpeedForGain { get; }
/// <summary> /// <summary>
/// 根据参考曲率、航向误差和横向误差计算车体中心目标曲率 /// 获取横向误差共同转角分量的最大绝对值,单位为rad
/// </summary>
public double MaximumCrossTrackCorrectionRadians { get; }
/// <summary>
/// 获取航向误差差动转角分量的最大绝对值,单位为rad。
/// </summary>
public double MaximumHeadingCorrectionRadians { get; }
/// <summary>
/// 分别计算横向共同转角以及曲率和航向差动转角,并生成前后GCP命令。
/// </summary> /// </summary>
public LateralControlCommand Compute( public LateralControlCommand Compute(
PathTrackingContext context) PathTrackingContext context)
@@ -78,33 +99,40 @@ namespace MultiWheelC.Control.Lateral
MinimumSpeedMetersPerSecond); MinimumSpeedMetersPerSecond);
var travelDirection = SelectTravelDirection(context); var travelDirection = SelectTravelDirection(context);
// 参考曲率提供前馈;没有跟踪误差时也能沿曲线行驶。 // 参考曲率决定前后反向的差动转角,使无跟踪误差时也能沿曲线行驶。
var feedforwardAngleRadians = Math.Atan( var feedforwardAngleRadians = Math.Atan(
context.ReferenceCurvaturePerMeter * context.ReferenceCurvaturePerMeter *
ControlPointRadiusMeters); ControlPointRadiusMeters);
// 轨迹位于车辆左侧时横向误差为正,对应正的左转修正 // 横向误差生成前后同向的共同转角,使四舵轮车辆平稳靠近轨迹
var crossTrackCorrectionRadians = Math.Atan( var crossTrackCorrectionRadians =
CrossTrackGainPerSecond * ClampSymmetric(
context.LateralErrorMeters / Math.Atan(
speedMagnitude); CrossTrackGainPerSecond *
context.LateralErrorMeters /
speedMagnitude),
MaximumCrossTrackCorrectionRadians);
// 倒车时需要反转反馈修正方向;参考曲率前馈仍由轨迹本身决定 // 航向误差生成前后反向的差动转角,只负责调整车身朝向
var feedbackAngleRadians = travelDirection * var headingCorrectionRadians =
(HeadingErrorGain * context.HeadingErrorRadians + ClampSymmetric(
crossTrackCorrectionRadians); HeadingErrorGain *
context.HeadingErrorRadians,
MaximumHeadingCorrectionRadians);
// 这里只避开tan奇点,实际GCP机械限制由AckermannGcpAllocator处理。 var commonAngleRadians =
var targetEquivalentAngleRadians = Clamp( travelDirection *
feedforwardAngleRadians + feedbackAngleRadians, crossTrackCorrectionRadians;
-MaximumMathematicalAngleRadians, var differentialAngleRadians =
MaximumMathematicalAngleRadians); feedforwardAngleRadians +
var targetCurvaturePerMeter = Math.Tan( travelDirection *
targetEquivalentAngleRadians) / headingCorrectionRadians;
ControlPointRadiusMeters;
return new LateralControlCommand( return new LateralControlCommand(
targetCurvaturePerMeter); commonAngleRadians +
differentialAngleRadians,
commonAngleRadians -
differentialAngleRadians);
} }
/// <summary> /// <summary>
@@ -115,7 +143,7 @@ namespace MultiWheelC.Control.Lateral
} }
/// <summary> /// <summary>
/// 选择Stanley横向误差项使用的实际速度或旧版参考速度。 /// 选择Stanley横向误差项使用的实际速度或参考速度。
/// </summary> /// </summary>
private double SelectSpeedForGain( private double SelectSpeedForGain(
PathTrackingContext context) PathTrackingContext context)
@@ -131,7 +159,7 @@ namespace MultiWheelC.Control.Lateral
} }
/// <summary> /// <summary>
/// 根据有符号参考速度确定前进或倒车的反馈修正方向。 /// 根据有符号参考速度确定前进或倒车的反馈修正方向。
/// </summary> /// </summary>
private static double SelectTravelDirection( private static double SelectTravelDirection(
PathTrackingContext context) PathTrackingContext context)
@@ -158,16 +186,15 @@ namespace MultiWheelC.Control.Lateral
} }
/// <summary> /// <summary>
/// 将数值限制在指定闭区间内。 /// 将数值按正负对称方式限制在指定绝对值内。
/// </summary> /// </summary>
private static double Clamp( private static double ClampSymmetric(
double value, double value,
double minimum, double maximumAbsoluteValue)
double maximum)
{ {
return Math.Max( return Math.Max(
minimum, -maximumAbsoluteValue,
Math.Min(maximum, value)); Math.Min(maximumAbsoluteValue, value));
} }
/// <summary> /// <summary>
@@ -183,7 +210,7 @@ namespace MultiWheelC.Control.Lateral
{ {
throw new ArgumentOutOfRangeException( throw new ArgumentOutOfRangeException(
parameterName, parameterName,
"Stanley控制器的几何尺寸和最小速度必须是正有限值。"); "Stanley控制器的几何尺寸、速度和角度限制必须是正有限值。");
} }
} }
@@ -47,7 +47,7 @@ namespace MultiWheelC
/// <summary> /// <summary>
/// 获取或设置参考速度减速度,单位为m/s²。 /// 获取或设置参考速度减速度,单位为m/s²。
/// </summary> /// </summary>
public double DecelerationMetersPerSecondSquared = 0.12; public double DecelerationMetersPerSecondSquared = 0.10;
/// <summary> /// <summary>
/// 获取或设置离散轨迹点间距,单位为m。 /// 获取或设置离散轨迹点间距,单位为m。
+15 -2
View File
@@ -173,7 +173,12 @@ namespace MultiWheelC
foreach (var keepRunning in movement.Get()) foreach (var keepRunning in movement.Get())
{ {
yield return keepRunning; if (!keepRunning)
{
break;
}
yield return true;
} }
continue; continue;
@@ -219,7 +224,12 @@ namespace MultiWheelC
foreach (var keepRunning in movement.Get()) foreach (var keepRunning in movement.Get())
{ {
yield return keepRunning; if (!keepRunning)
{
break;
}
yield return true;
} }
continue; continue;
@@ -228,6 +238,9 @@ namespace MultiWheelC
throw new NotSupportedException( throw new NotSupportedException(
$"组合运动计划不支持动作段类型:{segment.GetType().FullName}。"); $"组合运动计划不支持动作段类型:{segment.GetType().FullName}。");
} }
// 所有子动作均已完成后,才向外层DriveTask发送组合计划结束信号。
yield return false;
} }
} }
} }
@@ -55,6 +55,18 @@ namespace MultiWheelC
/// </summary> /// </summary>
public bool StanleyUsesActualSpeed = true; public bool StanleyUsesActualSpeed = true;
/// <summary>
/// Stanley横向误差共同转角分量的最大绝对值,单位为rad。
/// </summary>
public double MaximumCrossTrackCorrectionRadians =
AngleMath.DegreesToRadians(10.0);
/// <summary>
/// Stanley航向误差差动转角分量的最大绝对值,单位为rad。
/// </summary>
public double MaximumHeadingCorrectionRadians =
AngleMath.DegreesToRadians(10.0);
/// <summary> /// <summary>
/// 纵向速度外环比例增益。 /// 纵向速度外环比例增益。
/// </summary> /// </summary>
@@ -163,7 +175,9 @@ namespace MultiWheelC
StanleyCrossTrackGainPerSecond, StanleyCrossTrackGainPerSecond,
StanleyHeadingErrorGain, StanleyHeadingErrorGain,
StanleyMinimumSpeedMetersPerSecond, StanleyMinimumSpeedMetersPerSecond,
StanleyUsesActualSpeed); StanleyUsesActualSpeed,
MaximumCrossTrackCorrectionRadians,
MaximumHeadingCorrectionRadians);
var longitudinalController = var longitudinalController =
new PidLongitudinalController( new PidLongitudinalController(
LongitudinalKp, LongitudinalKp,
@@ -173,8 +187,7 @@ namespace MultiWheelC
MaximumCommandSpeedMetersPerSecond, MaximumCommandSpeedMetersPerSecond,
LongitudinalSpeedErrorDeadbandMetersPerSecond); LongitudinalSpeedErrorDeadbandMetersPerSecond);
var gcpAllocator = var gcpAllocator =
new AckermannGcpAllocator( new GcpCommandAllocator(
controlPointRadiusMeters,
MaximumGcpAngleRadians); MaximumGcpAngleRadians);
var commandExecutor = var commandExecutor =
new GcpCommandExecutor( new GcpCommandExecutor(
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,17 @@
第一次:
ok
第二次:
: * (Exception):DriveTask failed, msg=车辆已在终点零速参考处停稳,但终点精度不满足要求:位置误差=0.031m,航向误差=0.03°。, stack:
at ClumsyCore.DriveTask.Wait() in D:\MDCS\Source\Core\Clumsy\ClumsyCore\DriveTask.cs:line 205
at MultiWheelC.NewControllerStraight4mTest.Test() in D:\Users\Desktop\入职培训\停车机器人\MyParking\MultiWheelC\Experiments\NewControllerTrackingTests.cs:line 146
at ClumsyLite.ClumsyLiteUI.<>c__DisplayClass19_1.<MainPanelHandler>b__21() in D:\MDCS\Source\Core\Clumsy\ClumsyLite\ClumsyLiteUI.cs:line 592
*p.InnerException * (InvalidOperationException):车辆已在终点零速参考处停稳,但终点精度不满足要求:位置误差=0.031m,航向误差=0.03°。, stack:
at MultiWheelC.TrajectoryTrackingMovement.Get()+MoveNext() in D:\Users\Desktop\入职培训\停车机器人\MyParking\MultiWheelC\Movements\TrajectoryTrackingMovement.cs:line 257
at ClumsyCore.DriveTask.<>c__DisplayClass9_0.<.ctor>b__2() in D:\MDCS\Source\Core\Clumsy\ClumsyCore\DriveTask.cs:line 112
第三次:
ok
@@ -0,0 +1,9 @@
put meobj `clumsy_detour_backplate_lines` @ 95c33350
apply gltf class `basic_car`, vtx=19456
put meobj `CarInWorld` @ 965e05e0
组合运动开始第1段:TrackMotionPlanSegment
put meobj `CompositeStopTurnGoTest_pc` @ a511aa30
put meobj `CompositeStopTurnGoTest_lines` @ a5281610
轨迹实验数据已保存:C:\Users\Administrator\Desktop\MDCS2\Clumsy自动\TrackingExperiments\20260807_134158_374_NewStanleyPidComposite_SmoothTurnStopRotateStraight_Trial1.csv
Declare P2 on T0(local)
P2(on T0) decide to close
@@ -0,0 +1,29 @@
第一次
: * (Exception):DriveTask failed, msg=车辆已在终点零速参考处停稳,但终点精度不满足要求:位置误差=0.033m,航向误差=0.75°。, stack:
at ClumsyCore.DriveTask.Wait() in D:\MDCS\Source\Core\Clumsy\ClumsyCore\DriveTask.cs:line 205
at MultiWheelC.CompositeStopTurnGoTest.Test() in D:\Users\Desktop\入职培训\停车机器人\MyParking\MultiWheelC\Experiments\CompositeMotionPlanTests.cs:line 187
at ClumsyLite.ClumsyLiteUI.<>c__DisplayClass19_1.<MainPanelHandler>b__21() in D:\MDCS\Source\Core\Clumsy\ClumsyLite\ClumsyLiteUI.cs:line 592
*p.InnerException * (InvalidOperationException):车辆已在终点零速参考处停稳,但终点精度不满足要求:位置误差=0.033m,航向误差=0.75°。, stack:
at MultiWheelC.TrajectoryTrackingMovement.Get()+MoveNext() in D:\Users\Desktop\入职培训\停车机器人\MyParking\MultiWheelC\Movements\TrajectoryTrackingMovement.cs:line 244
at MultiWheelC.MotionPlanExecutor.Get()+MoveNext() in D:\Users\Desktop\入职培训\停车机器人\MyParking\MultiWheelC\Movements\MotionPlanExecutor.cs:line 174
at ClumsyCore.DriveTask.<>c__DisplayClass9_0.<.ctor>b__2() in D:\MDCS\Source\Core\Clumsy\ClumsyCore\DriveTask.cs:line 112
第二次:
: * (Exception):DriveTask failed, msg=车辆已在终点零速参考处停稳,但终点精度不满足要求:位置误差=0.041m,航向误差=0.24°。, stack:
at ClumsyCore.DriveTask.Wait() in D:\MDCS\Source\Core\Clumsy\ClumsyCore\DriveTask.cs:line 205
at MultiWheelC.CompositeStopTurnGoTest.Test() in D:\Users\Desktop\入职培训\停车机器人\MyParking\MultiWheelC\Experiments\CompositeMotionPlanTests.cs:line 187
at ClumsyLite.ClumsyLiteUI.<>c__DisplayClass19_1.<MainPanelHandler>b__21() in D:\MDCS\Source\Core\Clumsy\ClumsyLite\ClumsyLiteUI.cs:line 592
*p.InnerException * (InvalidOperationException):车辆已在终点零速参考处停稳,但终点精度不满足要求:位置误差=0.041m,航向误差=0.24°。, stack:
at MultiWheelC.TrajectoryTrackingMovement.Get()+MoveNext() in D:\Users\Desktop\入职培训\停车机器人\MyParking\MultiWheelC\Movements\TrajectoryTrackingMovement.cs:line 244
at MultiWheelC.MotionPlanExecutor.Get()+MoveNext() in D:\Users\Desktop\入职培训\停车机器人\MyParking\MultiWheelC\Movements\MotionPlanExecutor.cs:line 174
at ClumsyCore.DriveTask.<>c__DisplayClass9_0.<.ctor>b__2() in D:\MDCS\Source\Core\Clumsy\ClumsyCore\DriveTask.cs:line 112
第三次:
ok
+94
View File
@@ -0,0 +1,94 @@
是的,强烈建议做系统辨识,尤其是你这种要把电机反馈和 SLAM 融合的场景。
为什么需要系统辨识?
卡尔曼滤波(或 EKF)的效果很大程度上取决于过程模型有多准。模型不准的话,会出现:
预测步持续往错误方向跑
滤波器过度依赖测量(SLAM),或者反过来过度信任错误的模型
速度估计系统性偏大/偏小
原地自转时航向纠正效果变差
你现在已经知道电机反馈“偏大”,这本身就是典型的模型参数问题(可能是轮胎半径、减速比、编码器标定、打滑补偿等)。
建议辨识的主要参数
针对四轮差速车,优先考虑这些:
运动学参数
有效轮半径(或左右轮半径)
轮距(左右轮中心距离)
可能的轴距相关项(如果不是纯差速)
速度相关
电机速度到实际车速的缩放系数(你提到的“偏大”问题)
左右轮速度的不对称性(如果存在)
低速段的非线性(静摩擦、死区)
可选但有用的
速度偏置 / 零点漂移
简单的打滑系数(如果经常在特定地面跑)
怎么做比较实际?
不需要一开始就上很复杂的在线辨识,可以分两步:
离线标定(推荐先做)
让车在平坦地面以不同速度直线行驶、原地旋转,记录:
四个电机反馈速度
SLAM 给出的位置和航向变化
用最小二乘或简单优化,拟合出轮半径、轮距、速度缩放系数等
原地自转特别适合标定轮距和左右轮差异
在线微调(可选)
把关键参数(比如速度缩放系数、轮半径)也放进状态向量里一起估计(Augmented EKF
或者用简单的自适应方法,定期用 SLAM 和电机速度的一致性去微调缩放因子
实际建议顺序
先建立基本运动学模型 + EKF 框架
做一轮离线系统辨识,把明显偏差(尤其是速度偏大)修正掉
再跑融合,看残差(innovation)是否变小、速度估计是否更合理
如果还有残余误差,再考虑把个别参数做成在线估计
总结:建完模型后做系统辨识,是让卡尔曼滤波真正好用的关键一步。不做的话,即使融合了电机和 SLAM,效果也容易打折扣。需要的话,我可以给你一个针对差速车的简单离线标定思路(需要记录哪些数据、怎么拟合)。
有没有必要做卡尔曼/传感器融合?
分两层看:
如果只是为了速度闭环,且当前绿色线已经能让车跑得比较稳
→ 性价比一般。
你现在的处理已经解决了最严重的问题,继续花时间做完整的 EKF + 电机融合,短期内收益可能没有那么大。可以先把绿色线再优化一下(比如调整低通截止频率、加一点自适应滤波,或者用更温和的滑动平均),看能不能把残余抖动再压低一点。
如果你对速度精度、平滑度要求比较高,或者后面还要做更精细的控制(轨迹跟踪、力控、高精度定位等)
→ 值得做,性价比会变高。
原因是:
绿色线本质还是“从位置差分出来的速度”,天然会有延迟和残余噪声。
电机反馈是更直接的速度源,频率高、相位更好。
把电机速度和经过处理的 SLAM 差分速度做融合(哪怕是简单的互补滤波或一阶卡尔曼),通常能同时得到:
更低的噪声
更小的滞后
对电机“偏大”问题的一定校正能力
实际建议(按性价比排序)
先低成本优化现有绿色线(最快)
检查低通滤波的截止频率是否合适(太低会滞后,太高会留噪声)。
可以尝试一阶低通 + 中值滤波组合,或者简单的互补滤波(位置差分 + 电机速度)。
看看闭环时车的实际表现,如果抖动已经可接受,就先用着。
中等成本:做简单融合(推荐优先考虑)
不用完整 EKF,先做一个互补滤波或一维卡尔曼只融速度:
高频信任电机反馈速度
低频用处理后的 SLAM 差分速度去校正偏差
实现简单,效果通常比单纯低通好一截,滞后也更小。
完整 EKF + 系统辨识
只有在你需要同时提升位置、航向、速度的整体一致性,或者后面要上更复杂的控制时,再上这个。
工作量明显更大,但长期收益最高。
总结
从你这张图来看,目前绿色线已经能用,不是“必须立刻上卡尔曼”的紧急情况。
但如果你觉得绿色线的残余抖动还是影响闭环效果,或者想要更干净、滞后更小的速度,那么把四个电机反馈融进去(哪怕先做简单融合)是性价比不错的下一步,比继续死磕低通滤波更有前途。
你现在速度闭环时,车实际表现如何?是觉得绿色线的抖动已经导致控制不稳,还是只是看着不舒服?这能帮助判断要不要马上上融合。
+7
View File
@@ -55,3 +55,10 @@ ParkingGeometricController.cs
增加起步航向对齐状态。 增加起步航向对齐状态。
第一阶段建议采用第二种,简单、安全。 第一阶段建议采用第二种,简单、安全。
把前后GCP转角分解成两个模态:
共同转角 = (前GCP转角 + 后GCP转角) / 2
差动转角 = (前GCP转角 - 后GCP转角) / 2
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.