添加单车底盘仿真平台并完善运动控制与夹臂功能
This commit is contained in:
@@ -6,6 +6,7 @@ using Medulla.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using MyParking.Shared;
|
||||
|
||||
namespace MedullaAdapter
|
||||
{
|
||||
@@ -26,11 +27,16 @@ namespace MedullaAdapter
|
||||
}
|
||||
internal ManualControlMode TransmitterControlMode = ManualControlMode.Normal;
|
||||
internal DateTime TransmitterLastTime = DateTime.Now; // 物理遥控器计算两次实体遥控器指令之间的时间间隔
|
||||
private ManualControlMode? _pendingManualMode;
|
||||
private ManualControlMode? _activeManualMode;
|
||||
#endregion
|
||||
|
||||
#region AsUpperIO
|
||||
[AsUpperIO(desc = "从C上复位")] public bool ResetFromC;
|
||||
[AsUpperIO(desc = "从C将驱动轮下使能")] public bool DisableFromC;
|
||||
[AsUpperIO(desc = "左夹臂下发速度", timeOutReset = true)] public float SpeedLeftArm;
|
||||
[AsUpperIO(desc = "右夹臂下发速度", timeOutReset = true)] public float SpeedRightArm;
|
||||
[AsUpperIO(desc = "夹臂不同步报警")] public bool ClampOutOfSync;
|
||||
#endregion
|
||||
|
||||
#region AsLowerIO
|
||||
@@ -42,6 +48,16 @@ namespace MedullaAdapter
|
||||
[AsLowerIO(desc = "左后右轮实际位置")] public float LRRActualPos;
|
||||
[AsLowerIO(desc = "右后左轮实际位置")] public float RRLActualPos;
|
||||
[AsLowerIO(desc = "右后右轮实际位置")] public float RRRActualPos;
|
||||
[AsLowerIO(desc = "左夹臂实际速度")] public float ActualSpeedLeftArm;
|
||||
[AsLowerIO(desc = "右夹臂实际速度")] public float ActualSpeedRightArm;
|
||||
[AsLowerIO(desc = "左夹臂状态字")] public int LeftArmStateCode;
|
||||
[AsLowerIO(desc = "右夹臂状态字")] public int RightArmStateCode;
|
||||
[AsLowerIO(desc = "左夹臂错误字")] public int LeftArmErrorCode;
|
||||
[AsLowerIO(desc = "右夹臂错误字")] public int RightArmErrorCode;
|
||||
[AsLowerIO(desc = "左夹臂电流")] public float LeftArmElectric;
|
||||
[AsLowerIO(desc = "右夹臂电流")] public float RightArmElectric;
|
||||
[AsLowerIO(desc = "左夹臂实际位置")] public float ActualPosLeftArm;
|
||||
[AsLowerIO(desc = "右夹臂实际位置")] public float ActualPosRightArm;
|
||||
[AsLowerIO(desc = "驱动轮使能状态")] public bool WheelAbleState = true;
|
||||
[AsLowerIO(desc = "电池健康状态")] public float SOH;
|
||||
[AsInitParam(desc = "车号")][AsLowerIO] public int CarNum = 1;
|
||||
@@ -51,6 +67,12 @@ namespace MedullaAdapter
|
||||
[AsInitParam(desc = "MCU端口号")] public string MCUPort = "COM4";
|
||||
[AsInitParam(desc = "遥控器速度上限")] public float TransmitterSpeedUpperLimit = 1.0f;
|
||||
[AsInitParam(desc = "遥控器速度下限")] public float TransmitterSpeedLowerLimit = 0.0f;
|
||||
[AsInitParam(desc = "手动控制夹臂速度系数")] public float ManualArmSpeedFac = 1.0f;
|
||||
[AsInitParam(desc = "左夹臂低限位")][AsLowerIO] public int LeftArmLowerPos = -10000;
|
||||
[AsInitParam(desc = "左夹臂高限位")][AsLowerIO] public int LeftArmUpperPos = 5927610;
|
||||
[AsInitParam(desc = "右夹臂低限位")][AsLowerIO] public int RightArmLowerPos = -17295;
|
||||
[AsInitParam(desc = "右夹臂高限位")][AsLowerIO] public int RightArmUpperPos = 5927610;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -75,6 +97,8 @@ namespace MedullaAdapter
|
||||
[IOObjectMonitor(desc = "左后右驱动器远程帧706")] public byte LRRRemoteCode = 0;
|
||||
[IOObjectMonitor(desc = "右后左驱动器远程帧707")] public byte RRLRemoteCode = 0;
|
||||
[IOObjectMonitor(desc = "右后右驱动器远程帧708")] public byte RRRRemoteCode = 0;
|
||||
[IOObjectMonitor(desc = "左夹臂驱动器远程帧709")] public byte LArmRemoteCode = 0;
|
||||
[IOObjectMonitor(desc = "右夹臂驱动器远程帧70A")] public byte RArmRemoteCode = 0;
|
||||
#endregion
|
||||
|
||||
#region 操作按钮
|
||||
@@ -188,43 +212,193 @@ namespace MedullaAdapter
|
||||
TimeSpan? interval = null)
|
||||
{
|
||||
if (Chassis == null) return;
|
||||
|
||||
var adapter = GetChassisAdapter();
|
||||
if (adapter == null) return;
|
||||
|
||||
// 模式变化时先停车并下发舵轮准备角度;
|
||||
// 在实际舵角到位之前,不开放驱动速度。
|
||||
if (!EnsureManualModeReady(mode, interval))
|
||||
{
|
||||
adapter.StopImmediately();
|
||||
return;
|
||||
}
|
||||
|
||||
var speed = speedThreshold * y;
|
||||
var thPow = (float)Math.Pow(Math.Abs(x), ManualThetaPow) * Math.Sign(x);
|
||||
var frontTh = -thPow * MaxManualTheta;
|
||||
var rearTh = thPow * MaxManualTheta;
|
||||
var omega = CalculateManualOmega(speed, x);
|
||||
ManualMode = (int)mode;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case ManualControlMode.Normal:
|
||||
SetChassisDirection(frontDirection);
|
||||
Chassis.SendMotion(speed, frontTh, rearTh, interval);
|
||||
SendBodyCommand(vx: speed, vy: 0.0, omegaRadiansPerSecond: omega, interval);
|
||||
break;
|
||||
case ManualControlMode.Crab:
|
||||
SetChassisDirection(90);
|
||||
Chassis.SendMotion(speed, frontTh, rearTh, interval);
|
||||
SendBodyCommand(vx: 0.0, vy: speed, omegaRadiansPerSecond: omega, interval);
|
||||
break;
|
||||
case ManualControlMode.Spin:
|
||||
Chassis.SendRotateMotion(speed * MaxAngularSpeed, interval);
|
||||
var spinOmega =
|
||||
speed * MaxAngularSpeed *
|
||||
Math.PI / 180.0;
|
||||
|
||||
SendBodyCommand(
|
||||
vx: 0.0,
|
||||
vy: 0.0,
|
||||
omegaRadiansPerSecond: spinOmega,
|
||||
interval);
|
||||
break;
|
||||
default:
|
||||
ManualMode = -1;
|
||||
Chassis.PredefinedDriveStop();
|
||||
adapter.StopImmediately();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetChassisDirection(float direction)
|
||||
// 停车后切换模式:先预转舵轮,实际角度到位后才允许发送运动命令。
|
||||
private bool EnsureManualModeReady(
|
||||
ManualControlMode mode,
|
||||
TimeSpan? interval)
|
||||
{
|
||||
var origin = Chassis.GetOriginBias();
|
||||
// 方向没有变化时不重复计算轮组几何关系。
|
||||
if (Math.Abs(origin.Z - direction) < 0.001f)
|
||||
var adapter = GetChassisAdapter();
|
||||
if (adapter == null)
|
||||
return false;
|
||||
|
||||
// 当前模式已经完成准备,可以直接接受运动命令。
|
||||
if (_activeManualMode == mode &&
|
||||
_pendingManualMode == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 第一次收到新模式时,停车并下发一次舵轮准备姿态。
|
||||
if (_pendingManualMode != mode)
|
||||
{
|
||||
adapter.StopImmediately();
|
||||
|
||||
var preparationAccepted = mode switch
|
||||
{
|
||||
ManualControlMode.Normal =>
|
||||
adapter.PrepareParallelDirection(0.0),
|
||||
|
||||
ManualControlMode.Crab =>
|
||||
adapter.PrepareParallelDirection(
|
||||
Math.PI / 2.0),
|
||||
|
||||
ManualControlMode.Spin =>
|
||||
adapter.PrepareSpin(interval),
|
||||
|
||||
_ => false
|
||||
};
|
||||
|
||||
if (!preparationAccepted)
|
||||
{
|
||||
_pendingManualMode = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
_pendingManualMode = mode;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 后续控制周期保持停车,并读取实际舵角判断是否到位。
|
||||
adapter.StopImmediately();
|
||||
|
||||
const double toleranceRadians =
|
||||
2.0 * Math.PI / 180.0;
|
||||
|
||||
bool aligned;
|
||||
|
||||
if (mode == ManualControlMode.Spin)
|
||||
{
|
||||
// 自转的四个舵轮目标角不同,等待期间持续刷新其目标。
|
||||
var preparationAccepted =
|
||||
adapter.PrepareSpin(interval);
|
||||
|
||||
aligned =
|
||||
preparationAccepted &&
|
||||
adapter.AreSpinWheelsAligned;
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetDirection = mode ==
|
||||
ManualControlMode.Crab
|
||||
? Math.PI / 2.0
|
||||
: 0.0;
|
||||
|
||||
aligned =
|
||||
adapter.AreParallelWheelsAligned(
|
||||
targetDirection,
|
||||
toleranceRadians);
|
||||
}
|
||||
|
||||
if (!aligned)
|
||||
return false;
|
||||
|
||||
_activeManualMode = mode;
|
||||
_pendingManualMode = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private double CalculateManualOmega(
|
||||
float speed,
|
||||
float steeringInput)
|
||||
{
|
||||
var normalizedSteering =
|
||||
(float)Math.Pow(
|
||||
Math.Abs(steeringInput),
|
||||
ManualThetaPow) *
|
||||
Math.Sign(steeringInput);
|
||||
|
||||
var steeringDegrees =
|
||||
-normalizedSteering * MaxManualTheta;
|
||||
|
||||
var steeringRadians =
|
||||
steeringDegrees * Math.PI / 180.0;
|
||||
|
||||
// CommonUsage中的ControlPointRadius单位为毫米。
|
||||
var halfWheelBaseMeters =
|
||||
Math.Max(
|
||||
Chassis.ControlPointRadius / 1000.0,
|
||||
0.01);
|
||||
|
||||
return speed * Math.Tan(steeringRadians) / halfWheelBaseMeters;
|
||||
}
|
||||
|
||||
internal void SendBodyCommand(double vx, double vy, double omegaRadiansPerSecond, TimeSpan? interval = null)
|
||||
{
|
||||
var adapter = GetChassisAdapter();
|
||||
|
||||
if (adapter == null)
|
||||
return;
|
||||
Chassis.SetOriginBias(
|
||||
origin.X,
|
||||
origin.Y,
|
||||
direction);
|
||||
// 坐标系改变后重新等待四个舵轮对齐。
|
||||
Chassis.AfterDirectionChanged();
|
||||
|
||||
var command = new ChassisCommand(
|
||||
CarNum,
|
||||
new Twist2D(vx, vy, omegaRadiansPerSecond));
|
||||
|
||||
if (!adapter.Send(command, interval))
|
||||
{
|
||||
adapter.StopImmediately();
|
||||
|
||||
Console.WriteLine(
|
||||
"底盘命令分解失败,车辆已经停车:" +
|
||||
adapter.LastFailureReason);
|
||||
}
|
||||
}
|
||||
private MultiWheelChassisAdapter _chassisAdapter;
|
||||
|
||||
private MultiWheelChassisAdapter GetChassisAdapter()
|
||||
{
|
||||
if (Chassis == null)
|
||||
return null;
|
||||
|
||||
if (_chassisAdapter == null ||
|
||||
_chassisAdapter.VehicleId != CarNum)
|
||||
{
|
||||
_chassisAdapter =
|
||||
new MultiWheelChassisAdapter(Chassis, CarNum);
|
||||
}
|
||||
|
||||
return _chassisAdapter;
|
||||
}
|
||||
|
||||
#region MCURoutine兼容参数(暂保留原硬件协议)
|
||||
@@ -237,65 +411,6 @@ namespace MedullaAdapter
|
||||
[AsUpperIO(desc = "多车灯光同步兼容值,-1使用本车灯光")]
|
||||
public int MultiVehicleLightSync = -1;
|
||||
|
||||
// 以下夹臂字段用于兼容原MCU的0x209、0x20A等CAN协议;当前不使用时目标速度保持为0。
|
||||
[AsUpperIO(desc = "左夹臂目标速度", timeOutReset = true)]
|
||||
public float SpeedLeftArm;
|
||||
|
||||
[AsUpperIO(desc = "右夹臂目标速度", timeOutReset = true)]
|
||||
public float SpeedRightArm;
|
||||
|
||||
[AsLowerIO(desc = "左夹臂实际速度")]
|
||||
public float ActualSpeedLeftArm;
|
||||
|
||||
[AsLowerIO(desc = "右夹臂实际速度")]
|
||||
public float ActualSpeedRightArm;
|
||||
|
||||
[AsLowerIO(desc = "左夹臂实际位置")]
|
||||
public float ActualPosLeftArm;
|
||||
|
||||
[AsLowerIO(desc = "右夹臂实际位置")]
|
||||
public float ActualPosRightArm;
|
||||
|
||||
[AsLowerIO(desc = "左夹臂状态字")]
|
||||
public int LeftArmStateCode;
|
||||
|
||||
[AsLowerIO(desc = "右夹臂状态字")]
|
||||
public int RightArmStateCode;
|
||||
|
||||
[AsLowerIO(desc = "左夹臂错误字")]
|
||||
public int LeftArmErrorCode;
|
||||
|
||||
[AsLowerIO(desc = "右夹臂错误字")]
|
||||
public int RightArmErrorCode;
|
||||
|
||||
[AsLowerIO(desc = "左夹臂电流")]
|
||||
public float LeftArmElectric;
|
||||
|
||||
[AsLowerIO(desc = "右夹臂电流")]
|
||||
public float RightArmElectric;
|
||||
|
||||
[AsInitParam(desc = "左夹臂低限位")]
|
||||
[AsLowerIO]
|
||||
public int LeftArmLowerPos = -10000;
|
||||
|
||||
[AsInitParam(desc = "左夹臂高限位")]
|
||||
[AsLowerIO]
|
||||
public int LeftArmUpperPos = 5927610;
|
||||
|
||||
[AsInitParam(desc = "右夹臂低限位")]
|
||||
[AsLowerIO]
|
||||
public int RightArmLowerPos = -17295;
|
||||
|
||||
[AsInitParam(desc = "右夹臂高限位")]
|
||||
[AsLowerIO]
|
||||
public int RightArmUpperPos = 5927610;
|
||||
|
||||
[AsLowerIO(desc = "左夹臂驱动器远程帧709")]
|
||||
public byte LArmRemoteCode;
|
||||
|
||||
[AsLowerIO(desc = "右夹臂驱动器远程帧70A")]
|
||||
public byte RArmRemoteCode;
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user