Wheel Rotation Not In Sync During Auto [Swerve Drive]

Hello All,

My team have recently been having a problem with our swerve drive. During our autonomous programs, the wheel rotations for our front left module are way behind the other wheels. This causes the wheel to drag and mess up our alignment which is important as we are not using any path following or trajectory PID to correct the drive. We are running Mk4 L1 NEOs, and are using the SDS swerve template from 2021. In our auto, we use a command that drives/turns our robot to a certain encoder/gyro count. This is the command:

package frc.robot.commands.Chassis;

import edu.wpi.first.math.kinematics.ChassisSpeeds;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import frc.robot.subsystems.DrivetrainSubsystem;
import frc.robot.subsystems.VisionTracking;

import com.swervedrivespecialties.swervelib.SwerveModule;
import edu.wpi.first.wpilibj2.command.CommandBase;

public class DriveDistance extends CommandBase
{
private final SwerveModule m_frontLeftModule;
private final SwerveModule m_frontRightModule;
private final SwerveModule m_backLeftModule;
private final SwerveModule m_backRightModule;
private final DrivetrainSubsystem m_drivetrainSubsystem;
private double wheelSpeedX;
private double wheelSpeedY;
private double turnSpeed;
private double encoderTarget;
private Boolean encoderResetTrigger;
private Boolean encoderResetDone;
private double targetAngle;
private boolean angleCalc;
private double angleTarget;
private Boolean turnDone;
public boolean driveDone = false;
public Boolean track;
public Boolean trackDone;
private static int instanceCount = 0;
DriverStation.Alliance color;
double degrees360;
double startDegrees;
double avgEncoderDistance = 0;
Timer m_timer;
Boolean useRamp;
Double rampSpeed;
public DriveDistance(DrivetrainSubsystem driveTrain, VisionTracking vision, double speedY, double speedX, double rotateSpeed, double driveTarget, double angleTarget, Boolean useRamp, Double rampSpeed) {
m_drivetrainSubsystem = driveTrain;
m_frontLeftModule = m_drivetrainSubsystem.getFrontLeft();
m_frontRightModule = m_drivetrainSubsystem.getFrontRight();
m_backRightModule = m_drivetrainSubsystem.getBackRight();
m_backLeftModule = m_drivetrainSubsystem.getBackLeft();
this.useRamp = useRamp;
wheelSpeedX = speedX;
wheelSpeedY = speedY;
turnSpeed = rotateSpeed;
encoderTarget = driveTarget;
this.angleTarget = angleTarget;
this.rampSpeed = rampSpeed;
init();
addRequirements(m_drivetrainSubsystem);
instanceCount++;
SmartDashboard.putNumber(“instance count”, instanceCount);
m_timer = new Timer();
}

private void init() {
    // Sets up variables for each subsystem
    encoderResetTrigger = false;
    encoderResetDone = false;
    angleCalc = false;
    turnDone = false;
    driveDone = false;
}

@Override
public void execute() {
    if (encoderResetTrigger == false) {
        DriverStation.reportWarning("Reseting Encoders", false);
        m_drivetrainSubsystem.resetDriveEncoders();
        m_timer.reset();
        m_timer.start();
        // Sample equation  target = 10.5+5, target = 15.5, 5 more than the first value assuming the specified distance is 5
        encoderResetTrigger = true;
    }

    avgEncoderDistance = (Math.abs(m_backLeftModule.getDriveDistance()) + Math.abs(m_backRightModule.getDriveDistance()) + Math.abs(m_frontLeftModule.getDriveDistance()) + Math.abs(m_frontRightModule.getDriveDistance())) / 4;
    degrees360 = (m_drivetrainSubsystem.getGyroscopeRotation().getDegrees() +360)% 360;
    
    if (!encoderResetDone) {
        if (avgEncoderDistance > 0.05) {
            DriverStation.reportWarning("Encoder Position Average: " + avgEncoderDistance, false);
            return;
        }
        DriverStation.reportWarning("Encoders Reset", false);
        encoderResetDone = true;
        startDegrees = degrees360;
    }

    SmartDashboard.putNumber("auto Target", encoderTarget);
    SmartDashboard.putNumber("auto Avg Encoder Distance", avgEncoderDistance);
    SmartDashboard.putNumber("auto Angle Target", targetAngle);
    SmartDashboard.putNumber("auto Current Rotation", degrees360);
    SmartDashboard.putNumber("auto Front Left encoder", m_frontLeftModule.getDriveDistance());
    SmartDashboard.putNumber("auto Front Right encoder", m_frontRightModule.getDriveDistance());
    SmartDashboard.putNumber("auto Back Left encoder", m_backLeftModule.getDriveDistance());
    SmartDashboard.putNumber("auto Back Right encoder", m_backRightModule.getDriveDistance());

    if (turnSpeed != 0) {
        if (angleCalc == false) {
            DriverStation.reportWarning("Calculating target angle", false);
            targetAngle = (degrees360 + angleTarget) % 360;
            angleCalc = true;
        }
    }
    //One encoder tic = 2.75 feet (old)
    // Makes sure the robot stops when drive is done
    if (driveFinished()) {
        DriverStation.reportWarning("Drive to " + encoderTarget + " Finished", false);
        m_drivetrainSubsystem.drive(new ChassisSpeeds(0, 0, 0));
        driveDone = true;
    }
    if (turnFinished()) {
        DriverStation.reportWarning("Turn Finished", false);
        turnDone = true;
    }
    SmartDashboard.putNumber("Drive Distance", m_frontLeftModule.getDriveDistance());
    SmartDashboard.putBoolean("DriveFinished", driveFinished());

    if (!driveDone && (wheelSpeedX != 0 || wheelSpeedY != 0)) {
        double correctionTurnSpeed = 0;
        if (Math.abs(wheelSpeedX) > 0.7 || Math.abs(wheelSpeedY) > 0.7) {
            if (degrees360 > startDegrees + 1) {
                correctionTurnSpeed = -0.3;
            } else if (degrees360 < startDegrees - 1) {
                correctionTurnSpeed = 0.3;
            }
        }
        
        DriverStation.reportWarning("Driving (" + wheelSpeedX + ", " + wheelSpeedY + ") with correction " + correctionTurnSpeed, false);
        // If first 10% or more than 3/4 of the way through the drive, start rampdown
        if (useRamp == true) {
            if (wheelSpeedX > 0) {
                if (m_frontLeftModule.getDriveVelocity() < wheelSpeedX) {
                    m_drivetrainSubsystem.drive(new ChassisSpeeds(m_timer.get()*rampSpeed+.75, wheelSpeedY, correctionTurnSpeed));
                }
                if (m_frontLeftModule.getDriveVelocity() >= wheelSpeedX) {
                    m_drivetrainSubsystem.drive(new ChassisSpeeds(wheelSpeedX, wheelSpeedY, correctionTurnSpeed));
                }
            }
            if (wheelSpeedX < 0) {
                if (m_frontLeftModule.getDriveVelocity() > wheelSpeedX) {
                    m_drivetrainSubsystem.drive(new ChassisSpeeds(m_timer.get()*-rampSpeed-.75, wheelSpeedY, correctionTurnSpeed));
                }
                if (m_frontLeftModule.getDriveVelocity() <= wheelSpeedX) {
                    m_drivetrainSubsystem.drive(new ChassisSpeeds(wheelSpeedX, wheelSpeedY, correctionTurnSpeed));
                }
            }
        } else {
            m_drivetrainSubsystem.drive(new ChassisSpeeds(wheelSpeedX, wheelSpeedY, correctionTurnSpeed));
        }
        // if (encoderTarget / 4 > encoderTarget - avgEncoderDistance || 0.9 * encoderTarget < encoderTarget - avgEncoderDistance) {
        //     m_drivetrainSubsystem.setOpenloopRate(0.8);
        // } else {
        //     m_drivetrainSubsystem.setOpenloopRate(0);
        // }
    }
    
    if (turnFinished() == false) {
        // Only rotate if driveforward and side speeds are zero
        if (wheelSpeedX == 0 && wheelSpeedY == 0) {
            DriverStation.reportWarning("Turning", false);
            m_drivetrainSubsystem.drive(new ChassisSpeeds(0, 0, turnSpeed));
        } 
    }
        
}
private boolean driveFinished() {
    return encoderResetDone && Math.abs(avgEncoderDistance) >= Math.abs(encoderTarget);
}
private boolean turnFinished() {
    return turnDone || turnSpeed == 0 || degrees360 - targetAngle >= -2 && degrees360 - targetAngle <= 2;
}

@Override
public boolean isFinished(){
    return turnDone == true && driveDone == true && encoderResetDone;
}

@Override
public void end (boolean interrupted)  {
    // Stops the robot and allows the target distance to be calculated again
    DriverStation.reportWarning("DriveDistance command end", false);
    m_drivetrainSubsystem.drive(new ChassisSpeeds(0,0, 0));
    m_drivetrainSubsystem.setOpenloopRate(0);
    m_timer.reset();
}

}

I can’t seem to figure out what would be making the wheel lag, as we just use the drive function from the swerve template. Do we have to mess around with the PID that is built in on our turn motor to get it to be faster? Here is a video of it messing up. The module in question is the one on the right of the screen.

Video: Swerve Wheels Not in Sync - YouTube

We also have a video of the wheel messing up on this post if it is helpful.

Thanks!

  • Team 85’s Programming Team
1 Like

You’ll have to mess with the PID as the weight of your bot and the weight distribution changes, it shouldn’t need to change too far from the default values and they’re a pretty good starting point. Also I would recommend doing your PID on the controller directly instead of the robot code loop if you’re not already .

1 Like

Just a side note… The wheel that is lagging is not consistent, it is seemingly random which one chooses to lag- and sometimes it just works

Could you use either Advantage Scope or In Control to display what your modules target vs actual behavior are to help identify the problem.

1 Like

Would this working in teleop too or does it just take input from desired positions in auto?

This should work for both, I just took that screenshot while in teleop you just have to make sure you’re logging both your actual current state and your desired one (the one being passed into PID) This will show you if modules don’t line up if the controller is undertuned and will show them wiggle / oscillate if they are overtuned.

1 Like

Awesome! Thanks for helping

1 Like

If you need any help setting it up or have any questions just let me know and hope it helps.

1 Like

Hello! My name is Aedan and I am also from Team 85, the team that created this post. I have a question that you can hopefully answer.

So looking into Advantage Scope (GitHub code, Document, the sorts), my team as well as I don’t exactly know where to start to implement this software into our robot and was wondering if you could give a simple rundown on how to get started? (Things like if we need to implement code into our program, what do we need to implement)

Thank you for your time whenever you respond to this!

(Also for reference, we used a modified template version of the SwerveDriveSpecialties by a person named Democat, so we have all of the basics for a swerve to function)

Team 85, Built on Brains

Does the wheel “lagging” only happen in Auto and then is fixed in Teleop?

We had what sounds like a similar-ish problem, which we thankfully tracked down early in build season. Our driver named is “slow robot syndrome”. To cut to the end, the problem is that the CANcoders will not always initialize quickly and give bad results until they do. This means that, if you try to synchronize the turn motor encoders to the absolute encoder (the CANcoders) too early in the robot boot, it will mis-align the module and the wheel will drag. We experienced this with the standard SDS library code.

We fixed it with this:

  • we separated the CANcoder/SparkMAX encoder synchronization into a separate routine.
  • we made a pass through all 4 modules at the very end of the Drivetrain constructor
  • in Robot::disabledPeriodic(), we sync the angles if the drivetrain has been idle for 10 sec (to make sure it is not still moving).

You can find our code here:GitHub - ligerbots/ChargedUp2023: 2023 Season code. Imported from swervePractice

4 Likes

To do it simply the implimentation requires adding advantage kit and then implementing the following code

package handlers.logging;

import edu.wpi.first.math.kinematics.SwerveModuleState;
import frc.robot.Robot;
import handlers.configs.Constants.LoggingConstants;
import org.littletonrobotics.junction.Logger;
import org.littletonrobotics.junction.networktables.NT4Publisher;
import org.littletonrobotics.junction.wpilog.WPILOGWriter;

public class AdvantageKitLogger {
    public static Logger logger;

    public static void configureDataReceivers() {
        logger = Logger.getInstance();
        // Set up data receivers & replay source
        if (Robot.isReal()) {
            logger.addDataReceiver(new WPILOGWriter("/media/sda1/")); // Log to a USB stick
            logger.addDataReceiver(new NT4Publisher()); // Publish data to NetworkTables
        } else if (Robot.isSimulation() & LoggingConstants.k_simulationLogging) {
            logger.addDataReceiver(new WPILOGWriter("logs/")); // Log to a USB stick
            logger.addDataReceiver(new NT4Publisher()); // Publish data to NetworkTables
        } else if (Robot.isSimulation()) {
            logger.addDataReceiver(new NT4Publisher()); // Publish data to NetworkTables
        }
    }

    public static void updateSwerveModuleStates(SwerveModuleState[] desiredStates, SwerveModuleState[] actualStates, double rotation) {
        Logger logger = Logger.getInstance();
        logger.recordOutput("Swerve/Display/Target Swerve Module States", desiredStates);
        logger.recordOutput("Swerve/Display/Actual Swerve Module States", actualStates);
        logger.recordOutput("Swerve/Display/Rotation", rotation);
    }
}

Note not all of that is required but it makes things pretty easy

AdvantageKitLogger.updateSwerveModuleStates(getModuleDesiredStates(), getModuleActualStates(), getCurEstPose().getRotation().getRadians());

To update in a periodic loop
and you will need to have

AdvantageKitLogger.configureDataReceivers();
Logger.getInstance().start();

to trigger everything if you use the format above, note to get advantage kit into an existing project you will need to go to build.gradle and swap it out for this

plugins {
    id "java"
    id "edu.wpi.first.GradleRIO" version "2023.4.3"
    id "com.peterabeles.gversion" version "1.10"
}

sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11

def ROBOT_MAIN_CLASS = "frc.robot.Main"

// Define my targets (RoboRIO) and artifacts (deployable files)
// This is added by GradleRIO's backing project DeployUtils.
deploy {
    targets {
        roborio(getTargetTypeClass('RoboRIO')) {
            // Team number is loaded either from the .wpilib/wpilib_preferences.json
            // or from command line. If not found an exception will be thrown.
            // You can use getTeamOrDefault(team) instead of getTeamNumber if you
            // want to store a team number in this file.
            team = project.frc.getTeamNumber()
            debug = project.frc.getDebugOrDefault(false)

            artifacts {
                // First part is artifact name, 2nd is artifact type
                // getTargetTypeClass is a shortcut to get the class type using a string

                frcJava(getArtifactTypeClass('FRCJavaArtifact')) {
                    gcType = edu.wpi.first.gradlerio.deploy.roborio.GarbageCollectorType.Other
                    jvmArgs << '-XX:+UseG1GC'
                }

                // Static files artifact
                frcStaticFileDeploy(getArtifactTypeClass('FileTreeArtifact')) {
                    files = project.fileTree('src/main/deploy')
                    directory = '/home/lvuser/deploy'
                }
            }
        }
    }
}

def deployArtifact = deploy.targets.roborio.artifacts.frcJava

// Set to true to use debug for JNI.
wpi.java.debugJni = false

// Set this to true to enable desktop support.
def includeDesktopSupport = true

// Configuration for AdvantageKit
repositories {
    maven {
        url = uri("https://maven.pkg.github.com/Mechanical-Advantage/AdvantageKit")
        credentials {
            username = "Mechanical-Advantage-Bot"
            password = "\u0067\u0068\u0070\u005f\u006e\u0056\u0051\u006a\u0055\u004f\u004c\u0061\u0079\u0066\u006e\u0078\u006e\u0037\u0051\u0049\u0054\u0042\u0032\u004c\u004a\u006d\u0055\u0070\u0073\u0031\u006d\u0037\u004c\u005a\u0030\u0076\u0062\u0070\u0063\u0051"
        }
    }
    mavenLocal()
}

configurations.all {
    exclude group: "edu.wpi.first.wpilibj"
}

// Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries.
// Also defines JUnit 4.
dependencies {
    implementation wpi.java.deps.wpilib()
    implementation wpi.java.vendor.java()

    roborioDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.roborio)
    roborioDebug wpi.java.vendor.jniDebug(wpi.platforms.roborio)

    roborioRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.roborio)
    roborioRelease wpi.java.vendor.jniRelease(wpi.platforms.roborio)

    nativeDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.desktop)
    nativeDebug wpi.java.vendor.jniDebug(wpi.platforms.desktop)
    simulationDebug wpi.sim.enableDebug()

    nativeRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.desktop)
    nativeRelease wpi.java.vendor.jniRelease(wpi.platforms.desktop)
    simulationRelease wpi.sim.enableRelease()

    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
    testImplementation 'org.junit.jupiter:junit-jupiter-params:5.4.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'

    def akitJson = new groovy.json.JsonSlurper().parseText(new File(projectDir.getAbsolutePath() + "/vendordeps/AdvantageKit.json").text)
    annotationProcessor "org.littletonrobotics.akit.junction:junction-autolog:$akitJson.version"
}

test {
    useJUnitPlatform()
    systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true'
}

// Simulation configuration (e.g. environment variables).
wpi.sim.addGui().defaultEnabled = true
wpi.sim.addDriverstation()

// Setting up my Jar File. In this case, adding all libraries into the main jar ('fat jar')
// in order to make them all available at runtime. Also adding the manifest so WPILib
// knows where to look for our Robot Class.
jar {
    from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
    manifest edu.wpi.first.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS)
    duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

// Configure jar and deploy tasks
deployArtifact.jarTask = jar
wpi.java.configureExecutableTasks(jar)
wpi.java.configureTestTasks(test)

// Configure string concat to always inline compile
tasks.withType(JavaCompile) {
    options.compilerArgs.add '-XDstringConcat=inline'
}

// Create version file
project.compileJava.dependsOn(createVersionFile)
gversion {
    srcDir = "src/main/java/"
    classPackage = "handlers.logging"
    className = "BuildMetadata"
    dateFormat = "yyyy-MM-dd HH:mm:ss z"
    timeZone = "America/New_York"
    indent = "  "
}

If you want an example implementation of this that you can just copy and move things over I can share a project for it thatyou’d just need to copy build.gradle, the handlers package and then just call

AdvantageKitLogger.configureDataReceivers();
Logger.getInstance().start();

In robot init & then call

AdvantageKitLogger.updateSwerveModuleStates(getModuleDesiredStates(), getModuleActualStates(), getCurEstPose().getRotation().getRadians());

In periodic, democats swerve library should be fully supported as I believe it uses WPIlibs SwerveModuleState datatype.

You don’t need advantage kit to use advantage scope. Just log your swerve states to smart dashboard and pull them into the swerve tab in advantage scope.

Also, we occasionally have this issue with our front left module as well and are trying to figure it out. Might have to diagnose more at worlds unfortunately. A roborio reboot fixes it, if needed on the fly.

When it happens, can you check the status light on the turning Sparkmax? Does it change to red or green or stay blue/purple when you try to move? That’s what ours does. It’s like the Spark just stops greeting commands.

I’m using YAGSL not democat, but the symptom of the lagging turn motor sounds exactly the same. My current theory is related to CAN communication getting ‘bound’ up somehow. When I check my swerve states in advantage, the front left module turning state spins continuously around like it’s trying to catch up but since no commands are going to the motor or controller, it’s unable to get to its desired state. If I can’t figure out a way to fix this, some sort of module disable might be the only bandaid solution.

2 Likes

i have no real idea of what it could be but are all of your motor controllers and encoders on one can bus? i’ve heard that can cause control issues where the can gets overused and starts failing

This is exactly what is happening, yes. It seems we forgot to specify that it only happens in Auto and not Tele-Op, but it is nice to see that we are not the only ones with said problem. Our team will look into this and see if it works and then get back to you. Thank you from Team 85

Hi there, so I am also trying to use advantage scope, and I have 2 doubts.

S

  1. So you say that I need to send the module states to the shuffle board, but I don’t know how to do that, since I cannot use “SmartDashboard.putData()” or “SmartDashboard.putNumber()” or how are you sending the module states to the shuffle board

  2. The same question, but how do I send the data of the robot pose so i can drag it in the odometry tab in advantage scope, thank you!!

  1. I’m using a library called YAGSL. I guess, to make it compatible with AdvantageScope, you’d have to log it similar to how this class does it in YAGSL. https://github.com/BroncBotz3481/YAGSL-Example/blob/main/src/main/java/swervelib/telemetry/SwerveDriveTelemetry.java
    And then update it similar to this:
    https://github.com/BroncBotz3481/YAGSL-Example/blob/main/src/main/java/swervelib/SwerveDrive.java#L170

  2. I think you just update the robot pose on your Field2d object? Look at how this code uses the Field2d object. https://github.com/BroncBotz3481/YAGSL-Example/blob/main/src/main/java/swervelib/SwerveDrive.java#L152

If that doesn’t help, my apologies. There may be others with better answers.

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.