Major Mecanum Drive Issues

We’ve been pulling our hair out trying to figure out what is wrong with our code. We’ve used Mecanum before, but never had issues like this.

Right now we have no forward and most of the other joystick motions all do the same thing.

We have verified our wiring and CAM addressing.

We’d appreciate if anyone can point to an issue with our code. I’ve cut/pasted the pertinent parts

public class Robot extends TimedRobot {

MecanumDrive driveTrain;

WPI_TalonSRX frontLeftMotor, rearLeftMotor, frontRightMotor, rearRightMotor;

}

public void robotInit() {

   frontLeftMotor = new WPI_TalonSRX(2);
    rearLeftMotor = new WPI_TalonSRX(0);
    frontRightMotor = new WPI_TalonSRX(3);
    rearRightMotor = new WPI_TalonSRX(1);

driveJoystick = new Joystick(0);

driveTrain = new MecanumDrive(frontLeftMotor, rearLeftMotor, frontRightMotor, rearRightMotor);

}

public void teleopPeriodic() {

    SmartDashboard.putBoolean("IMU_Connected", ahrs.isConnected());

    buttonsJoystickButtons.updateState();
    driveJoystickButtons.updateState();
    
    double xSpeed = driveJoystick.getX();
    double ySpeed = driveJoystick.getY();
    double zRotation = driveJoystick.getTwist();

    driveTrain.driveCartesian(ySpeed, xSpeed, zRotation);

}

Put the robot up on blocks and verify that wheels are spinning in the direction you expect them to spin. I don’t necessarily see anything wrong in your code.

We’ve done that.

Forward on the joystick makes the wheels Strakes Right

Joystick Reverse, Turn Left, Strafe Left all make the wheels Turn Right

Can’t help with java but because we have always done our programming in LabView but it sounds like a problem we had when we didn’t reverse the left hand side motors.

So no matter what direction you push the joystick each wheel always spins the same direction? Are any wheels changing direction?

Yes, the wheels change direction. Below is what we have observed as it sat on our test stand

Forward on the joystick makes the wheels Strakes Right

Joystick Reverse, Turn Left, Strafe Left all make the wheels Turn Right

Turn Right and Turn Left are reversed

By “turn right”, you mean that they turn as expected? Not to the right side?

The above description is a bit cryptic. Here’s what you should be observing for a mecanum drive:

STRAFE RIGHT:

when viewing the right side of the robot,
the front wheel should be turning CCW
and
the rear wheel should be turning CW

when viewing the left side of the robot,
the front wheel should be turning CCW
and
the rear wheel should be turning CW

STRAFE LEFT:

when viewing the right side of the robot,
the front wheel should be turning CW
and
the rear wheel should be turning CCW

when viewing the left side of the robot,
the front wheel should be turning CW
and
the rear wheel should be turning CCW

TURN RIGHT:

when viewing the right side of the robot,
the front wheel should be turning CCW
and
the rear wheel should be turning CCW

when viewing the left side of the robot,
the front wheel should be turning CCW
and
the rear wheel should be turning CCW

TURN LEFT:

when viewing the right side of the robot,
the front wheel should be turning CW
and
the rear wheel should be turning CW

when viewing the left side of the robot,
the front wheel should be turning CW
and
the rear wheel should be turning CW

FORWARD:

when viewing the right side of the robot,
the front wheel should be turning CW
and
the rear wheel should be turning CW

when viewing the left side of the robot,
the front wheel should be turning CCW
and
the rear wheel should be turning CCW

REVERSE:

when viewing the right side of the robot,
the front wheel should be turning CCW
and
the rear wheel should be turning CCW

when viewing the left side of the robot,
the front wheel should be turning CW
and
the rear wheel should be turning CW

1 Like

Start with some simple, basic troubleshooting.

Create a new robot project. Add a single motor controller into it. Set that motor controller to 1.

  • Does the wheel you expect rotate in the expected direction?
  • Repeat for each of the 4 wheels

That may turn up some wheels that are wired “backwards”, spinning in the wrong direction, compared to what you expect. It’s easy to reverse them in the code, or swap the wires to reverse them, leaving the code as it is. It may also reveal that some of the motor controllers are hooked up to the wrong ports, or the output leads from the controllers got crossed.

It can be extremely difficult diagnosing an issue with everything all at once - break it up into smaller pieces, and it becomes much easier.

2 Likes

As I read your description, the results are not simply rearranging and inverting motors. If it were simply this, then the following things would be true:

  • When going from joystick forward to joystick reverse*, all wheels would stop and reverse direction
  • Joystick forward and Joystick strafe right would not produce the same result
  • Rotate right would not produce two different results

* or on any other joystick reversal, for that matter

We have to wrap up for the day. Thanks for all the feedback so far!

We’ll try the suggestion above tomorrow and go one wheel at a time

Please keep any other suggestions coming.

We spend about 4 hours today documenting this.

We used our robot from Steamworks that used Mecanum with this years code and changes to the way the Mecanum drive is defined.

This is how our robot works:

The Z rotation of the joystick controls Turn Left, Turn Right
The X axis of the joystick controls Forward/Reverse
The Y axis of the joystick controls Strafe Left, Strafe Right.

We tried every permutation of inversion of pairs of motors to get this to work the way we remembered Mecanum to work (Z strafes, Y forward/reverse, X turn LR)

We verified our CAN addressing to the motors.

We coded arcade drive to confirm the joystick function properly.

We even tried changing the order that the Z, X and Y are feed into the driveTrain.driveCartesian(ySpeed, xSpeed, zRotation); statement - that made things weird too.

Below is the code deployed to this robot:

package org.usfirst.frc.team5000.robot;

import edu.wpi.first.wpilibj.TimedRobot;

import edu.wpi.first.wpilibj.smartdashboard.SendableChooser;

import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;

// import edu.wpi.first.wpilibj.drive.DifferentialDrive;

import edu.wpi.first.wpilibj.drive.MecanumDrive;

// import edu.wpi.cscore.UsbCamera;

import edu.wpi.first.cameraserver.CameraServer;

import edu.wpi.first.wpilibj.Joystick;

import edu.wpi.first.wpilibj.PowerDistributionPanel;

// import edu.wpi.first.wpilibj.Timer;

import edu.wpi.first.wpilibj.DoubleSolenoid;

// import com.ctre.phoenix.motorcontrol.ControlMode;

import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX;

import com.ctre.phoenix.motorcontrol.ControlMode;

import com.ctre.phoenix.motorcontrol.can.VictorSPX;

// import edu.wpi.first.wpilibj.PWMSpeedController;

import edu.wpi.first.wpilibj.Spark;

// import edu.wpi.first.wpilibj.SpeedController;

import edu.wpi.first.wpilibj.SpeedControllerGroup;

import com.kauailabs.navx.frc.AHRS;

// import edu.wpi.first.wpilibj.SerialPort;

import edu.wpi.first.wpilibj.I2C;

import edu.wpi.first.wpilibj.Encoder;

/**

  • The VM is configured to automatically run this class, and to call the

  • functions corresponding to each mode, as described in the IterativeRobot

  • documentation. If you change the name of this class or the package after

  • creating this project, you must also update the manifest file in the resource

  • directory.

*/

public class Robot extends TimedRobot {

final String defaultAuto = “Default”;

final String customAuto = “My Auto”;

String autoSelected;

SendableChooser<String> chooser = new SendableChooser<>();

WPI_TalonSRX frontLeftMotor, rearLeftMotor, frontRightMotor, rearRightMotor;

VictorSPX intakeMotor;

Joystick driveJoystick, buttonsJoystick;

HHJoystickButtons buttonsJoystickButtons;

HHJoystickButtons driveJoystickButtons;

PowerDistributionPanel pdp;

CameraServer cameraServer;

MecanumDrive driveTrain;

// DifferentialDrive driveTrain;

Spark liftA, liftB;

// UsbCamera usbCamera;

SpeedControllerGroup lift;

static final Double liftPower = .5;

static final Double driveDeadband = 0.0;

Encoder enc;

DoubleSolenoid intake, kicker, portal;

static final int forwardChannel1 = 0;

static final int reverseChannel1 = 1;

static final int intakeUp = 2;

static final int intakeDown = 3;

static final int forwardChannel2 = 2;

static final int reverseChannel2 = 3;

static final int kickerOut = 4;

static final int kickerIn = 5;

static final int forwardChannel3 = 4;

static final int reverseChannel3 = 5;

static final int portalOut = 6;

static final int portalIn = 7;

static final int intakeMotorOn = 1;

static final int intakeMotorOff = 2;

static final double intakeMotorPower = .25;

double joystickB = .2;

double joystickA;

double joystickY;

double joystickZ;

AHRS ahrs;

/**

 * This function is run when the robot is first started up and should be

 * used for any initialization code.

 */

@Override

public void robotInit() {

    chooser.setDefaultOption("Default Auto", defaultAuto);

    chooser.addOption("My Auto", customAuto);

    SmartDashboard.putData("Auto choices", chooser);

/**frontLeftMotor = new WPI_TalonSRX(2);

    rearLeftMotor = new WPI_TalonSRX(0);

    frontRightMotor = new WPI_TalonSRX(3);

    rearRightMotor = new WPI_TalonSRX(1);

    intakeMotor = new VictorSPX(0);

    */

    frontLeftMotor = new WPI_TalonSRX(2);

    rearLeftMotor = new WPI_TalonSRX(3);

    frontRightMotor = new WPI_TalonSRX(1);

    rearRightMotor = new WPI_TalonSRX(0);

    liftA = new Spark(0);

    liftB = new Spark(1);

    lift = new SpeedControllerGroup((liftA), (liftB));

// SpeedControllerGroup Right = new SpeedControllerGroup(frontRightMotor, rearRightMotor);

// SpeedControllerGroup Left = new SpeedControllerGroup(frontLeftMotor, rearLeftMotor);

    cameraServer = CameraServer.getInstance();

    cameraServer.startAutomaticCapture("cam0", 0);

    enc = new Encoder(0, 1, false, Encoder.EncodingType.k4X);

    enc.reset();

    driveJoystick = new Joystick(0);        

    buttonsJoystick = new Joystick(1);

    buttonsJoystickButtons = new HHJoystickButtons(buttonsJoystick, 10);

    driveJoystickButtons = new HHJoystickButtons(driveJoystick, 10);

    pdp = new PowerDistributionPanel();

// driveTrain.setDeadband(driveDeadband);

// driveTrain.setRightSideInverted(true);

// frontLeftMotor.setInverted(true);

// rearLeftMotor.setInverted(true);

// frontRightMotor.setInverted(true);

// rearRightMotor.setInverted(true);

 driveTrain = new MecanumDrive(frontLeftMotor, rearLeftMotor, frontRightMotor, rearRightMotor);

// driveTrain = new DifferentialDrive(Left, Right);

    intake = new DoubleSolenoid(forwardChannel1,reverseChannel1);

    kicker = new DoubleSolenoid(forwardChannel2,reverseChannel2);

    portal = new DoubleSolenoid(forwardChannel3, reverseChannel3);

// ahrs = new AHRS(SerialPort.Port.kMXP.kUSB);

    ahrs = new AHRS(I2C.Port.kOnboard);

}

/**

 * This autonomous (along with the chooser code above) shows how to select

 * between different autonomous modes using the dashboard. The sendable

 * chooser code works with the Java SmartDashboard. If you prefer the

 * LabVIEW Dashboard, remove all of the chooser code and uncomment the

 * getString line to get the auto name from the text box below the Gyro

 *

 * You can add additional auto modes by adding additional comparisons to the

 * switch structure below with additional strings. If using the

 * SendableChooser make sure to add them to the chooser code above as well.

 */

@Override

public void autonomousInit() {

    autoSelected = chooser.getSelected();

// autoSelected = SmartDashboard.getString(“Auto Selector”,

// defaultAuto);

    System.out.println("Auto selected: " + autoSelected);

}

/**

 * This function is called periodically during autonomous

 */

@Override

public void autonomousPeriodic() {

switch (autoSelected) {

case customAuto:

// Put custom auto code here

break;

case defaultAuto:

default:

// Put default auto code here

break;

    }

}

/** Need to add teleopInit JF

*/

@Override

public void teleopInit() {

}

/**

 * This function is called periodically during operator control

 */

@Override

public void teleopPeriodic() {

    SmartDashboard.putBoolean("IMU_Connected", ahrs.isConnected());

    buttonsJoystickButtons.updateState();

    driveJoystickButtons.updateState();

double xSpeed = driveJoystick.getX();

double ySpeed = driveJoystick.getY();

double zRotation = driveJoystick.getTwist();

    driveTrain.driveCartesian(ySpeed, xSpeed, zRotation);

// driveTrain.arcadeDrive(xSpeed, zRotation);

    intake();   

    lift();

// kicker();

}

void intake() {

if (buttonsJoystickButtons.isPressed(intakeDown)) {

        intakeMotor.set(ControlMode.PercentOutput, 0);

        intake.set(DoubleSolenoid.Value.kReverse);

    } else if (driveJoystickButtons.isPressed(intakeUp)) {

            intake.set(DoubleSolenoid.Value.kForward);

            intakeMotor.set(ControlMode.PercentOutput, intakeMotorPower);

        }

}

void lift() {

double X = buttonsJoystick.getY();

double x1 = X * liftPower;

    lift.set(x1);

double Current = pdp.getCurrent(14);//change # for actual robot

    SmartDashboard.putNumber("Lift Current", Current);

    SmartDashboard.putNumber("Encoder ",enc.getRaw());

}

/**

 * This function is called periodically during test mode

 */

@Override

public void testPeriodic() {

}

}

In case someone stumbles on this thread, our issue ended up being a worn out joystick!

We also feed the X and Y inputs into the Yspeed and Xspeed in that order to get us what we believe is the right way the inputs should make the robot respond.

Here is the code inside the driveCartesian() method:

double[] wheelSpeeds = new double[4];
    wheelSpeeds[MotorType.kFrontLeft.value] = input.x + input.y + zRotation;
    wheelSpeeds[MotorType.kFrontRight.value] = -input.x + input.y - zRotation;
    wheelSpeeds[MotorType.kRearLeft.value] = -input.x + input.y + zRotation;
    wheelSpeeds[MotorType.kRearRight.value] = input.x + input.y - zRotation;

In the future you can verify very quickly, at the individual motor level, what a given input should be and how the output should correspond.

This is what we do with our students creating our DriveTrain class…we don’t use the built-in WPILib classes, as that sort of masks some of what is being done. So, we heavily borrow from the open source wpilib classes but we have our own implementations for arcadeDrive() so our students get used to tracking down simplicity like Input X => Output Y…and does it make sense for what we want.

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