Skip to content

Desaturating SwerveModuleState[] after ChassisSpeeds.discretize introduces translational drift #7332

Open
@bhall-ctre

Description

@bhall-ctre

If you discretize a ChassisSpeeds, convert the speeds to module states, and then desaturate the module states, translational drift will be introduced to the resulting robot twist. This is how most users currently implement swerve kinematics.

Note

Removing the SwerveDriveKinematics.desaturateWheelSpeeds(states, maxSpeed) does not fix the problem, it simply creates module states that are not achievable, which introduces other issues (including translational drift).

A Java example is shown below:

static ChassisSpeeds undiscretize(ChassisSpeeds speeds, double dtSeconds) {
    // Construct the resulting twist for the timestep
    var twist = new Twist2d(
        speeds.vxMetersPerSecond * dtSeconds,
        speeds.vyMetersPerSecond * dtSeconds,
        speeds.omegaRadiansPerSecond * dtSeconds
    );

    // Find the desired pose after a timestep, relative to the current pose.
    var desiredDeltaPose = Pose2d.kZero.exp(twist);

    // Turn the chassis translation/rotation deltas into average velocities
    return new ChassisSpeeds(
        desiredDeltaPose.getX() / dtSeconds,
        desiredDeltaPose.getY() / dtSeconds,
        desiredDeltaPose.getRotation().getRadians() / dtSeconds
    );
}

static void testDiscretizeAndDesaturate() {
    var kinematics = new SwerveDriveKinematics(
        new Translation2d(0.27, 0.27),
        new Translation2d(0.27, -0.27),
        new Translation2d(-0.27, 0.27),
        new Translation2d(-0.27, -0.27)
    );
    double dt = 0.02;
    double maxSpeed = 4.0;
    // impossible chassis speeds, 0 m/s on Y
    var origSpeeds = new ChassisSpeeds(4, 0, Math.PI * 2.0);

    // discretize our speeds
    var speeds = ChassisSpeeds.discretize(origSpeeds, dt);

    // determine desaturated module states
    var states = kinematics.toSwerveModuleStates(speeds);
    SwerveDriveKinematics.desaturateWheelSpeeds(states, maxSpeed);
    // pull out resulting chassis speeds
    var desatSpeeds = kinematics.toChassisSpeeds(states);

    // print effective chassis Y speeds after undoing discretization
    System.out.println(undiscretize(speeds, dt).vyMetersPerSecond); // -8.67E-17 m/s [GOOD]
    System.out.println(undiscretize(desatSpeeds, dt).vyMetersPerSecond); // -0.056 m/s [BAD]
}

If you instead do desaturate -> discretize -> desaturate, the error is reduced significantly (50x in my testing), but is still less than optimal:

static void testDesaturateDiscretizeDesaturate() {
    var kinematics = new SwerveDriveKinematics(
        new Translation2d(0.27, 0.27),
        new Translation2d(0.27, -0.27),
        new Translation2d(-0.27, 0.27),
        new Translation2d(-0.27, -0.27)
    );
    double dt = 0.02;
    double maxSpeed = 4.0;
    // impossible chassis speeds, 0 m/s on Y
    var origSpeeds = new ChassisSpeeds(4, 0, Math.PI * 2.0);

    // first desaturate module states with the non-discretized speeds
    var tmpStates = kinematics.toSwerveModuleStates(origSpeeds);
    SwerveDriveKinematics.desaturateWheelSpeeds(tmpStates, maxSpeed);
    // convert back to chassis speeds
    var speeds = kinematics.toChassisSpeeds(tmpStates);

    // discretize our speeds
    speeds = ChassisSpeeds.discretize(speeds, dt);

    // determine desaturated module states for the discretized speeds
    var states = kinematics.toSwerveModuleStates(speeds);
    SwerveDriveKinematics.desaturateWheelSpeeds(states, maxSpeed);
    // pull out resulting chassis speeds
    var desatSpeeds = kinematics.toChassisSpeeds(states);

    // print effective chassis Y speeds after undoing discretization
    System.out.println(undiscretize(speeds, dt).vyMetersPerSecond); // 3.53E-15 m/s [GOOD]
    System.out.println(undiscretize(desatSpeeds, dt).vyMetersPerSecond); // -9.1E-4 m/s [OK]
}

I'm not sure what direction WPILib wants to take to address this issue, but at a minimum it should be documented.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions