navX mxp Newbie - Can't Get it To Work

Hi All,

I’m a new mentor helping our team that has limited FRC programming experience. We are trying to write a simple program to use our NavX MXP to rotate 90 deg, but we are getting sporadic readings. We use the getAngle() to update the SmartDashboard, and see the readings jumping around. They’ll hover around 10, for example, then jump to 170 when the robot has barely rotated.

We are instantiating the NavX in our “DriveTrain” subsystem. We import com.kauailabs.navx.frc.*; then instantiate a driveTrain class variable “gyro”. In our “OperatorDrive” command (which is the driveTrain default), we created a turnRight90() method. If the user presses button 8, it calls a method by the same name in our driveTrain class.

driveTrain.turnRight90() grabs the current Robot.driveTrain.getAngle (which just calls gyro.getAngle()). It does a while loop to use tankDrive to turn right until the angle increases by at least 90.

We added a SmartDashboard putnumber to view the gyro reading in each iteration of the loop, which is where we see it bouncing around while the robot turns. The loop often never ends because the gyro will jump from say 10 to -170, without ever registering having gone 11, 12, 13, etc., so it seems like it thinks it turned left and keeps trying to turn right. Maybe one out of 10 times it seems to exit the loop.

We didn’t update our GIThub today, so don’t have that code handy, but here’s the beginning of the class code for our driveTrain subsystem. Any insights as to how to create a loop to turn right 90 degrees would be helpful! I figure once we have that, everything else we want to do should be relatively straightforward.

// END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=IMPORTS

import com.kauailabs.navx.frc.*;
import edu.wpi.first.wpilibj.SPI;
/**
*
*/
public class DriveTrain extends Subsystem {

// BEGIN AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=CONSTANTS

// END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=CONSTANTS

// BEGIN AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=DECLARATIONS
private VictorSP speedController1;
private VictorSP speedController2;
private DifferentialDrive differentialDrive1;

// END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=DECLARATIONS

double Kp = 0.03;
AHRS gyro = new AHRS(SPI.Port.kMXP);

public DriveTrain() {
    // BEGIN AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=CONSTRUCTORS
    speedController1 = new VictorSP(3);
    addChild("Speed Controller 1",speedController1);
    speedController1.setInverted(false);
    
    speedController2 = new VictorSP(1);
    addChild("Speed Controller 2",speedController2);
    speedController2.setInverted(false);
    
    differentialDrive1 = new DifferentialDrive(speedController1, speedController2);
    addChild("Differential Drive 1",differentialDrive1);
    differentialDrive1.setSafetyEnabled(true);
    differentialDrive1.setExpiration(0.1);
    differentialDrive1.setMaxOutput(1.0);

// END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=CONSTRUCTORS
    gyro.reset();
    
}

Thanks!

Dave

Have you tried recalibrating the navX?

This is quite funny. We were actually having the same problem just today. These steps fixed the problem for us:

(1.) Place the gyro at your desired position on the robot. Make sure that it is parallel to
the earth’s surface.
(2.) Press the reset button on the Roborio.
(3.) Hold down the NavX’s calibrate button; after releasing said button, you should see a light
flash.
(4.) Finally, redeploy code.

Hope this works for you.

Thanks Jdao. I had read that article, and from what I see, calibration is automatic at powerup, and everytime the gyro is still for at least 8 seconds. It does not appear from that article that calibration is necessary beyond that. Please correct me if I’m wrong.

Thanks

Thanks Osprey. Can’t wait to try this on Monday. We did a “reset” on the NavX, but not “Cal”. Also didn’t reset Roborio.

It is parallel to earth, so at least we go that part right!

Thanks

The calibration that occurs on bootup may not be correcting the navx, especially if the ambient temperature at which it was factory calibrated is different from where you are or if it was factory recalibrated incorrectly on accident.

There is a physical button on the navx labeled CAL to do a factory calibration. In the article, it says hold it for 10 seconds and then release. A red led will turn on and stay on until it calibrates. After it turns off, just press the reset button next to it and let it boot up again fully.

Can you post the full drivetrain and the full default command source in here, (or pm them to me). I’m wondering by what you mean in the “while loop” in your default command. My suspicion is that you probably don’t want to do that, you can get loop overrun times since the command based robot is supposed to be operating at 50Hz.

That was one of the biggest mind shifts needed for me (a professional computer engineer with a lot of software experience) to getting the FRC software done correctly. Some of the normal design patterns you’d have in typical software don’t work well for a Command Based FRC Robot.

I would suggest that your default DriveTrain command do nothing but interact with the joysticks for teleop control.

Then, in your OI class where you’ve instantiated your joystick or gamepad, you assign a new command to the button you want to use to turn 90 degrees. Have that command get scheduled and complete, then your robot will just return back to the default command when that is done.

Here is some code from our team this year, the TurnWithoutPID command was written at the end of our build day by a first year student, so it may not work out of the box but I think it will help illustrate the point I was trying to make above.

/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
/* Open Source Software - may be modified and shared by FRC teams. The code   */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project.                                                               */
/*----------------------------------------------------------------------------*/

package frc.robot.commands.drivetrain;

import edu.wpi.first.wpilibj.command.Command;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import frc.robot.Robot;

public class TurnWithoutPID extends Command {
	private double goalAngle = 0.0;
	private boolean isDone = false;
	private double speed;
	private double tolerance = 3;
	private double currentAngle;
	
    public TurnWithoutPID(double speed, double givenAngle) {
        // Use requires() here to declare subsystem dependencies
        // eg. requires(chassis);
    	requires(Robot.drivetrain);
    	goalAngle = givenAngle;
    	this.speed = speed;
    	isDone = false;
    }

    // Called just before this Command runs the first time
    protected void initialize() {
    	Robot.drivetrain.resetAHRSGyro();
    	isDone = false;
 

    	//Robot.drivetrain.setAHRSAdjustment(80.0);
    }

    // Called repeatedly when this Command is scheduled to run
    protected void execute() {
    	currentAngle = Robot.drivetrain.getAHRSGyroAngle();
    	SmartDashboard.putNumber("Gyro: ", currentAngle);
    	if(Math.abs(goalAngle - currentAngle) < tolerance) {  //if within tolerance
    		Robot.drivetrain.arcadeDrive(0, 0);
    		isDone = true;
    	} else if(currentAngle < goalAngle) {  //If left of target angle
    		Robot.drivetrain.arcadeDrive(0, speed);  //turn clockwise
    	} else if(currentAngle > goalAngle){  //If right of target angle
    		Robot.drivetrain.arcadeDrive(0, -speed);  //turn counterclockwise
    	}
    }

    // Make this return true when this Command no longer needs to run execute()
    protected boolean isFinished() {
    	return isDone;
    }

    // Called once after isFinished returns true
    protected void end() {
    	Robot.drivetrain.setAHRSAdjustment(0.0);
    }

    // Called when another command which requires one or more of the same
    // subsystems is scheduled to run
    protected void interrupted() {
    	Robot.drivetrain.arcadeDrive(0, 0);
    	isDone = true;
    }
}

Obviously in that code you would call tank drive rather than our arcade drive but the rest of the logic is sort of intuitive enough I think.

Here’s the relevant OI code that assigns the command to the button, we use Xbox style controllers rather than joysticks:

public F310 gamepad;
	public F310 gamepad2;
	JoystickButton driver_b;
	
	public OI() {
		gamepad = new F310(RobotMap.GAMEPAD_PORT);
		gamepad2 = new F310(RobotMap.GAMEPAD2_PORT);

		driver_x = new JoystickButton(gamepad, F310.X);  // F310.X represents the int of the button
		driver_x.whenPressed(new TurnWithoutPid(90); // This should rotate the robot 90 degrees to the right
		
	}

I recommend this a lot here on CD, but if you’re wishing for a good overview on how the scheduler works for a command-based robot in FRC I recommend checking out this introduction from TripleHelix, 2363.

1 Like

Do you get the right readings when you run the navx Data Monitor example? https://pdocs.kauailabs.com/navx-mxp/examples/data-monitor/

Take the path NewtonCrosby is pointing you down. I think it will prove to be your quickest path to success and will teach you quite a bit that will be applicable to other parts of your programming.

Steve

1 Like

This is great stuff! Thanks so much for sharing.

I too had been a professional programmer for over 20 years (though no longer), so I’m used to doing this kind of thing in a WHILE loop. Counterintuitive for an application programmer like me, but I think I’m starting to understand how it works on the robot. Makes sense. I’ll definitely study that intro from TripleHelix.

Thanks again!

Thanks again NewtonCrosby.

Based on the presentation you suggested and your comments, I tried to assign “button8” to run a command called “AutoRotate”. When I press button8, it calls AutoRotate, but doesn’t call the execute. Any ideas?

package org.usfirst.frc4097.DeepSpace;

import org.usfirst.frc4097.DeepSpace.commands.*;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import edu.wpi.first.wpilibj.Joystick;
import edu.wpi.first.wpilibj.buttons.JoystickButton;

import org.usfirst.frc4097.DeepSpace.subsystems.*;


/**
 * This class is the glue that binds the controls on the physical operator
 * interface to the commands and command groups that allow control of the robot.
 */
public class OI {
 
    public Joystick joystick1;

    // END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=DECLARATIONS

    public OI() {
        // BEGIN AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=CONSTRUCTORS

        joystick1 = new Joystick(0);
        


        // SmartDashboard Buttons
        SmartDashboard.putData("Autonomous Command", new AutonomousCommand());
        SmartDashboard.putData("OperatorDrive", new OperatorDrive());

        // END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=CONSTRUCTORS
        
        JoystickButton button8 = new JoystickButton(joystick1, 8);
        button8.whenPressed(new AutoRotate());
        
    }

    // BEGIN AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=FUNCTIONS
    public Joystick getJoystick1() {
        return joystick1;
    }


    // END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=FUNCTIONS
}

Here’s the code for AutoRotate:

/----------------------------------------------------------------------------/
/* Copyright (c) 2018 FIRST. All Rights Reserved. /
/
Open Source Software - may be modified and shared by FRC teams. The code /
/
must be accompanied by the FIRST BSD license file in the root directory of /
/
the project. /
/
----------------------------------------------------------------------------*/

package org.usfirst.frc4097.DeepSpace.commands;

import org.usfirst.frc4097.DeepSpace.Robot;
import org.usfirst.frc4097.DeepSpace.subsystems.DriveTrain;

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

public class AutoRotate extends Command {

double startAngle;

public AutoRotate() {
// Use requires() here to declare subsystem dependencies
requires(Robot.driveTrain);
// eg. requires(chassis);
}

// Called just before this Command runs the first time
@Override
protected void initialize() {
startAngle = Robot.driveTrain.getAngle();
SmartDashboard.putString(“AutoRotate”, “hello”);
SmartDashboard.putNumber(“StartAngle”,this.startAngle);
}

// Called repeatedly when this Command is scheduled to run
@Override
protected void execute() {
Robot.driveTrain.tankDrive(.4, -.4);
SmartDashboard.putString(“Executing”,“hello”);
}

// Make this return true when this Command no longer needs to run execute()
@Override
protected boolean isFinished() {
boolean done;

if (Math.abs(this.startAngle - Robot.driveTrain.getAngle()) >= 90){
  done = true;
} 
else {
  done = false;
}
return done;

}

// Called once after isFinished returns true
@Override
protected void end() {
Robot.driveTrain.stop();
}

// Called when another command which requires one or more of the same
// subsystems is scheduled to run
@Override
protected void interrupted() {
}
}

Finally, here’s our DriveTrain code:

// RobotBuilder Version: 2.0
//
// This file was generated by RobotBuilder. It contains sections of
// code that are automatically generated and assigned by robotbuilder.
// These sections will be updated in the future when you export to
// Java from RobotBuilder. Do not put any code or make any change in
// the blocks indicating autogenerated code or it will be lost on an
// update. Deleting the comments indicating the section will prevent
// it from being updated in the future.

package org.usfirst.frc4097.DeepSpace.subsystems;

import org.usfirst.frc4097.DeepSpace.commands.*;
import edu.wpi.first.wpilibj.livewindow.LiveWindow;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import edu.wpi.first.wpilibj.command.Subsystem;
import edu.wpi.first.wpilibj.PIDOutput;
import edu.wpi.first.wpilibj.PIDSource;
// BEGIN AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=IMPORTS
import edu.wpi.first.wpilibj.SpeedController;
import edu.wpi.first.wpilibj.VictorSP;
import edu.wpi.first.wpilibj.drive.DifferentialDrive;
import edu.wpi.first.wpilibj.interfaces.Gyro;

// END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=IMPORTS

import com.kauailabs.navx.frc.*;
import edu.wpi.first.wpilibj.SPI;
/**
*
*/
public class DriveTrain extends Subsystem {

// BEGIN AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=CONSTANTS

// END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=CONSTANTS

// BEGIN AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=DECLARATIONS
private VictorSP speedController1;
private VictorSP speedController2;
private DifferentialDrive differentialDrive1;

// END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=DECLARATIONS

double Kp = 0.03;
AHRS gyro = new AHRS(SPI.Port.kMXP);

public DriveTrain() {
    // BEGIN AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=CONSTRUCTORS
    speedController1 = new VictorSP(3);
    addChild("Speed Controller 1",speedController1);
    speedController1.setInverted(false);
    
    speedController2 = new VictorSP(1);
    addChild("Speed Controller 2",speedController2);
    speedController2.setInverted(false);
    
    differentialDrive1 = new DifferentialDrive(speedController1, speedController2);
    addChild("Differential Drive 1",differentialDrive1);
    differentialDrive1.setSafetyEnabled(true);
    differentialDrive1.setExpiration(0.1);
    differentialDrive1.setMaxOutput(1.0);

// END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=CONSTRUCTORS
    gyro.reset();
    
}

@Override
public void initDefaultCommand() {
    // BEGIN AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=DEFAULT_COMMAND

    setDefaultCommand(new OperatorDrive());

// END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=DEFAULT_COMMAND

    // Set the default command for a subsystem here.
    // setDefaultCommand(new MySpecialCommand());
}

@Override
public void periodic() {
    // Put code here to be run every loop

}


public void arcadeDrive(double xSpeed, double zRotation) {
    differentialDrive1.arcadeDrive(xSpeed, zRotation);
}

public void driveStraight(double moveSpeed){
    //used double movespeed as parameter
    //double currentAngle = this.getAngle();
    //arcadeDrive(moveSpeed, -currentAngle*Kp);
    //the above, when called with a conditional, caused it to make a snap sound and turn slightly
    //differentialDrive1.arcadeDrive(moveSpeed, 0);
    differentialDrive1.tankDrive(moveSpeed, moveSpeed);
}

public void curvatureDrive(double xSpeed, double zRotation) {
    differentialDrive1.curvatureDrive(xSpeed, zRotation, true);
}

public void tankDrive(double leftSpeed, double rightSpeed){
    tankDrive(leftSpeed, rightSpeed);
}

public double getAngle(){
    double z = gyro.getAngle();
    if (z>=360){
        z=z-360;
        //makes sure that angle output is always between 0 and 360
    }
    else if (z<=-360){
        z=z+360;
    }
    return z;
}

public void stop() {
    tankDrive(0,0);
}

// BEGIN AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=CMDPIDGETTERS


// END AUTOGENERATED CODE, SOURCE=ROBOTBUILDER ID=CMDPIDGETTERS

// Put methods for controlling this subsystem
// here. Call these from Commands.

}

Thanks for your help!

Dave

Hmm, are you sure that the command is getting scheduled?

The only thing that I see that could be out of whack just looking at the code here briefly is that I don’t know what the behavior would be when you instantiate another Default Drive command by putting it on the SmartDashboard.

So, the nature of the default command should be only instantiated once, and should never return true for isFinished(). That way it remains the command that is on the bottom of the scheduler stack, and therefore only runs when there isn’t any other command to run.

The ordering in robotInit() should be:

  1. Instantiate all the subsystems
  2. Instantiate the OI last

When you do that you ensure that all the instantiations assigned to triggers/buttons/etc don’t have requires statements that will interfere with an incomplete subsystem.

You should also be able to fire the command from the button that is placed on the SmartDashboard as well. So, instead of

SmartDashboard.putData("Autonomous Command", new AutonomousCommand()); SmartDashboard.putData("OperatorDrive", new OperatorDrive());

Try:

SmartDashboard.putData("Autonomous Command", new AutonomousCommand()); SmartDashboard.putData("AutoRotate", new AutoRotate());

That way you can eliminate the oddity of whether or not instantiating the other default DriveTrain command will mess things up with the scheduler.

Be sure to check out their excellent examples. Here they use a simple PID to rotate to angle: https://pdocs.kauailabs.com/navx-mxp/examples/rotate-to-angle-2/

Thanks again NewtonCrosby. I’m going to try this. THanks for all your help.

Happy to help. I decided to make our github public for this year, I have shared it enough times on CD that I figured it would be easier for new teams (or new mentors) to follow along as we go if they so desire.

We don’t do very much that is super complicated, our team is very much a student-led team so we’re basically at the mercy of who shows up each year. This year over half of our programming team have no experience at all.

However, I think we do a decent job of teaching them the basics of command-based FRC programming.