Falcons "brake" unexpectedly

Hookay, so my programmers went home, so this is now just me trying to look through the code, and I’m, like, well, not a programmer…

We’re trying to use RobotContainer.shoot_Motor.set(ControlMode.PercentOutput, 0.0) but the problem persists.

In our Shoot_Motor.java command, could the line

public boolean isRunning()
{
return m_lead_motor.getControlMode() == ControlMode.Velocity;
}

be overriding that command? Shoot is part of a command group that is controlled by a WhileHeld, and ShooterStop is triggered by a WhenReleased on the same button, but even when it’s no longer held, well, the problem persists.

Rather than have a “whileHeld” command and a “whenReleased” command attached to the same button, I’d suggest doing the actions that you want to take place “whenReleased” in the end() method of the whileHeld command.

Depending upon the details, it may be the case that the whenReleased command is actually getting initiated before the end method of the whileHeld command. As a result, the end() of the whileHeld command may be getting called after the whenReleased command is getting initiated, meaning it may be undoing what is done in the whenReleased command.

1 Like

That’s what we tried first. :confused:

No. That line wouldn’t change the value. = assigns value while == is a relational operator (checks if they are equal and returns true or false).

Hey Pat,

This is a bit hard to diagnose without a full code sample. I’d be happy to take a look through it on Sunday to see if I can spot anything if you want to send me a copy of the repository.

Justin

If we can’t figure it out today, I’ll definitely take you up on that, thanks!

1 Like

No luck so far.

We dropped our commands and subsystems at github.com/pfreivald/1551-2020-Code/tree/master

Clearly we need to organize it into file folders, but if you’d be willing to take a look that would be great.

The shoot command is Shoot_Motor_Command_Group.java, which is a SequentialCommandGroup consisting first of a ParallelRaceGroup to aim the robot and spin up the shooter, which works fine, then a second ParallelRaceGroup to continue with shooter spin-up and run our feed motor when it’s up to speed, which works fine, followed finally by the ShooterStop command to set the PercentOutput to 0.0, which doesn’t work.

I suspect that in this line of code it’s never getting to the ShooterStop, because with the trigger button held it hangs on the second ParallelRaceGroup–which it should–and once released it kicks out of the Command Group and never even sees the ShooterStop command. Seems reasonable.

So we’ve tried a few things, including:

  1. Having a RobotContainer.shoot_Motor.set(SetControlMode.PercentOutput, 0) in the end() of our Shoot command. We thought this would be all that was necessary in the first place. No dice.

  2. Explicitly calling the ShooterStop command at the end of the Shoot_Motor_Command_Group (as noted above). No dice.

  3. Explicitly calling the ShooterStop command with a WhenReleased on the same trigger in RobotContainer. No dice.

  4. Setting ShooterStop to an entirely different WhenPressed button, releasing the trigger that is the WhileHeld for the Shoot_Motor_Command_Group and then pressing that entirely different button to set the motors to PercentOutput 0… No dice.

So either ShooterStop isn’t doing what I think it should be doing (which seems unlikely–it’s a pretty obvious command), it’s never getting called in the first place (also seems unlikely, given the several ways we’ve tried to call it), or something in the rest of the code is overriding it (seems most likely, but I can’t for the life of me figure out what that would be.)

What’s your Falcon FX’s firmware version? Need be updated. The manufacture firmware version may cause the issue you described

My recommendation to your programmers would be to put prints in the places that you set the control mode of that motor to see if your code is setting it in some weird way. If you debug the code with simple System.out.println()s, you’ll be able to see exactly what’s being called. And if you’re brave enough to learn how to use VS Code’s debugger, that might be helpful as well.

On a separate note, in Shoot_Motor you configure the voltage saturation to 12.5 after you enable voltage compensation, which I don’t think will get you the desired results because the enableVoltageCompensation() method says you should call it AFTER you call configVoltageCompSaturation(). This won’t solve your problem, but I thought I’d point that out.

Well, we thought we’d isolated the issue to a bad TalonFX, but after swapping it out we still have the same problem.

Very perplexing.

All firmware is up to date.

Looking at your code, I think the cause of your problem is the setRampRate() feature. When you suddenly command the motor to ControlMode.PercentOutput, 0.0, the controller won’t go immediately to zero, but will ramp down to zero, which is what you are seeing. That is true whether the motor is in ControlMode.Velocity or ControlMode.PercentOutput. Disabling the robot will cause an immediate setting to zero, and won’t obey the ramp.

Getting rid of all of the setRampRate() calls in your code, except an initial setRampRate(false) command in the Shoot_Motor.java constructor would be one way to do this. Note that you have some of these calls in Shoot.java

To test my theory, I would suggest changing line 40 in Shoot_Motor.java from the below

   // setRampRate(true);

to instead being as below:

   setRampRate(false);

Also, for the time being change the setRampRate calls in Shoot.java, at line 33 (in the initialize method) to be setRampRate(false):

  public void initialize() {
      RobotContainer.shoot_Motor.setRampRate(true);
      System.out.println("===================");
  }

The code that you have at Shoot.java lines 44-46 is what is causing the change in behavior at 5000 native units. (Given your constants, 1500rpm ~ 5000 native units). While debugging, I would comment out / remove those lines, too.

Also a few other incidental things I noticed when looking at the code:

In “Shoot_Motor.java” you have a pair of lines commented out at lines 23-24.

 // m_lead_motor.configFactoryDefault();
 // m_follow_motor.configFactoryDefault();

These are best to leave in as a best practice, so that the controllers are always configured correctly when the robot program runs and don’t have any parameters left over from prior usage (such as when replaced due to a hardware failure, or with parameters left over from another application, such as PhoenixTuner).

Lines 30 and 31 should be reversed, as mentioned by another user.

    m_lead_motor.enableVoltageCompensation(true);
    m_lead_motor.configVoltageCompSaturation(12.5);
3 Likes

Patrick, have you been in contact with CTRE?

@ozrien

I don’t think I’ve narrowed down to a single thing, but some concerns I have:

  1. You may be converting units wrong. I don’t know. I’m not immediately sure what the 10 * 60 is supposed to represent in encoder -> RPM conversions (because I don’t know your gearbox either). Maybe its too early in the morning. I tried digging through Javadocs trying to find a way to natively convert units so that you don’t have to do it, but I couldn’t find anything obvious.

  2. I would take off the ramp rate. Make sure you aren’t just commenting it out. I’m not sure if the Falcon will remember a ramp rate of 1 between settings (which is a very high value anyway - try going much lower - I think 2791 2018 dt had a ramp rate of 0.05), but you should explicitly set it to zero. Instead, I’d put a current limit of like 30A on it.

  3. I would take what you can find for a barebones closed loop velocity controller on the system, taking all other robot code off. Start a completely fresh project and try to copy paste in stuff from CTRE example code, but I spent an hour looking for TalonFX example code and didn’t really find anything.

  4. You have this line in the Shoot::execute function:

      if (RobotContainer.shoot_Motor.getRPM() > 1500){
          RobotContainer.shoot_Motor.setRampRate(false);
      }

which scares me. The current limit as I mentioned in 2 will do what I think you are trying to achieve here.


Looks like Ken mostly beat me to it.

We aren’t using Falcons on our shooter so I can’t give you our shooter code to copy-paste to check if it works, but I can give you our NEO-based code as maybe a vague template you can throw Falcon code at: https://gist.github.com/tervay/1241905d5af46aaafe63e0000c3760ff

1 Like

For any others following along, the unit conversion in question is in the following snippet of code:

public double getRPM() {
    return (m_lead_motor.getSelectedSensorVelocity() * 10 * 60 / Constants.SHOOTER_PULSES_PER_ROTATION);
}

which converts from CTRE Phoenix native units (which are in counts / 100 msec) for a Falcon.

Basically, the conversion from CTRE Phoenix native units is as follows:

Starting with N (in native units, which is counts / 100msec),
multiply by 10 (in 100msec / second)
multiply be 60 (in seconds / minute)
divide by 2048 (in counts / rotation).

If one keeps track of the units in the above calculation, the counts cancel, the 100msecs cancel, the seconds cancel, and one ends up with rotations / minute (RPM).

3 Likes

The two commands in the second parallel race group in Shoot_Motor_Command_Group (that is, Shoot and Feed) both have this odd looking isFinished implementation:

      public boolean isFinished() {
         return rightJoystick.getRawButtonReleased(1);
      }

If that button is never pressed, these will end immediately, ShooterStop will run and immediately end and then, because you are doing a whileHeld, the whole command group will start again.

If that button is pressed but never released before the group’s whileHeld button is released, then that race group will never end (until the group’s whileHeld is released causing the group to be cancelled) and ShooterStop will never get a chance to run.

I think either of these could cause the behavior you are seeing.

That fixed the problem! We are now coasting rather than turning our Falcons into battery-destroying generators. Thanks so much! Not sure why that worked in code but didn’t work when I did a Factory Reset in Phoenix Tuner, but hey, I learned something. Thank you again.

It of course means that we have to set current limits in our code rather than in the Falcons via Tuner, and that doesn’t appear to be working.

Right now we have, in Shoot_Motor.java, with the last four lines being the relevant ones,

    setRampRate(false); // Added this line and commented out all other instances of RampRate.
    m_lead_motor = new TalonFX(Constants.SHOOT_MOTOR_2_ID);
    m_lead_motorConfig = new TalonFXConfiguration();
    m_follow_motor = new TalonFX(Constants.SHOOT_MOTOR_1_ID);
    m_follow_motorConfig = new TalonFXConfiguration();
    m_lead_motor.configFactoryDefault();
    m_follow_motor.configFactoryDefault();
    m_follow_motor.follow(m_lead_motor);

    m_lead_motor.setNeutralMode(NeutralMode.Coast);
    m_follow_motor.setNeutralMode(NeutralMode.Coast);
    
    m_lead_motor.configVoltageCompSaturation(12.5);
    m_lead_motor.enableVoltageCompensation(true); //PATRICK SCREWING AROUND I SWITCHED THE ORDER OF THESE
 
    // shooter.configSelectedFeedbackSensor(FeedbackDevice.QuadEncoder);

    m_lead_motor.setInverted(true);
    m_follow_motor.setInverted(true);
    m_lead_motor.setSelectedSensorPosition(0);
    m_lead_motor.setSensorPhase(false);

    m_lead_motor.config_kP(0, Constants.SHOOTER_P);
    m_lead_motor.config_kI(0, Constants.SHOOTER_I);
    m_lead_motor.config_kD(0, Constants.SHOOTER_D);
    m_lead_motor.config_kF(0, Constants.SHOOTER_F);
    
    StatorCurrentLimitConfiguration statorLimit = new StatorCurrentLimitConfiguration(true, 40, 60, .001);
    m_lead_motorConfig.statorCurrLimit = statorLimit;
    StatorCurrentLimitConfiguration statorLimitfollow = new StatorCurrentLimitConfiguration(true, 40, 60, .001);
    m_follow_motorConfig.statorCurrLimit = statorLimitfollow;
}

Changing the (true, 40, 60, .001) to (true, 5, 5, .001) for both does nothing; the shooter is still spinning up with maximum possible voltage (and dropping our battery voltage a great deal, of course), which implies to me that these lines of code aren’t doing anything.

Thoughts?

So glad to hear that worked for you!

The above use of the StatorCurrentLimitConfiguration is unfamiliar to me. We haven’t used that before.

We haven’t used current limits in a few years, but going back to our 2017 code, below is how we did it for our 2017 shooter. (We haven’t gotten to the point of putting a shooter current limit in place for 2020, although we’ll want to do something similar this year, I think.)

Seems from the latest API docs that the below should still work. The below code sets a limit if the current exceeds 15A for 1 second (1000ms). This allows momentary surges above 15A (e.g. initial startup from stall or the sudden surge for recovering speed after a shot) but doesn’t let that condition persist for more than 1 second.

  m_Wheel.configContinuousCurrentLimit(15, 1000);
  m_Wheel.enableCurrentLimit(true);

Good luck!

I just took a look at the Phoenix example code for current limiting, and it seems that in 2017, we weren’t fully configuring the limit.

I’d suggest looking at the official CTRE Phoenix example at the URL below:

We have a similar setup (2X F500s), this is what out code looks like for reference:

    private TalonFX _motorOne;
    private TalonFX _motorTwo;
 
    // private final boolean _motorOneReversed = false;
    // private final boolean _motorTwoReversed = false;
 
    private StatorCurrentLimitConfiguration talonCurrentLimit;
    private final boolean ENABLE_CURRENT_LIMIT = true;
    private final double CONTINUOUS_CURRENT_LIMIT = 60; //amps
    private final double TRIGGER_THRESHOLD_LIMIT = 70; //amp
    private final double TRIGGER_THRESHOLD_TIME = 100; //ms
 
    private static Shooter _instance;
 
    /**
     * Which PID slot to pull gains from. Starting 2018, you can choose from
     * 0,1,2 or 3. Only the first two (0,1) are visible in web-based
     * configuration.
     */
    public static final int kSlotIdx = 0;
 
    /**
     * Talon SRX/ Victor SPX will supported multiple (cascaded) PID loops. For
     * now we just want the primary one.
     */
    public static final int kPIDLoopIdx = 0;
 
    /**
     * Set to zero to skip waiting for confirmation, set to nonzero to wait and
     * report to DS if action fails.
     */
    public static final int kTimeoutMs = 30;
 
    public static final double max_velocity = 8000.0; //TODO set (measured ~18,000 units/1000ms at full stick)
 
    /** Invert Directions for Left and Right */
    TalonFXInvertType _motorOneInvert = TalonFXInvertType.Clockwise;
    TalonFXInvertType _motorTwoInvert = TalonFXInvertType.CounterClockwise;
 
    /** Config Objects for motor controllers */
    TalonFXConfiguration _leftConfig = new TalonFXConfiguration();
    TalonFXConfiguration _rightConfig = new TalonFXConfiguration();
 
    private static final double TICKS_PER_REV = 2048.0; //one event per edge on each quadrature channel
    private static final double TICKS_PER_100MS = TICKS_PER_REV / 10.0;
    private static final double GEAR_RATIO = 18.0/24.0;  // motor pulley/shooter wheel pulley
    private static final double SECS_PER_MIN = 60.0;
 
    /**
     * PID Gains may have to be adjusted based on the responsiveness of control loop.
     * kF: 1023 represents output value to Talon at 100%, 7200 represents Velocity units at 100% output
     * 
     * 	                                      kP    kI    kD          kF               Iz   PeakOut
     */
    final Gains kGains_Velocity = new Gains( 0.5, 0.000, 0.0, 1.00*1023.0/19225.0,  300,  1.00); // kF = 75% * 1023.0 / max_vel in sensor ticks, kP = 3.6, kD = 160.0, kF  = 
    
    private double setPointVelocity_sensorUnits;


private Shooter() {
    _motorOne = new TalonFX(RobotMap.SHOOTER_MOTOR_ONE_PDP);
    _motorTwo = new TalonFX(RobotMap.SHOOTER_MOTOR_TWO_PDP);
 
    /* Factory Default all hardware to prevent unexpected behaviour */
    _motorOne.configFactoryDefault();
    _motorTwo.configFactoryDefault();
 
    talonCurrentLimit = new StatorCurrentLimitConfiguration(ENABLE_CURRENT_LIMIT,
    CONTINUOUS_CURRENT_LIMIT, TRIGGER_THRESHOLD_LIMIT, TRIGGER_THRESHOLD_TIME);
 
    _motorOne.configStatorCurrentLimit(talonCurrentLimit);
    _motorTwo.configStatorCurrentLimit(talonCurrentLimit);
  
    _motorOne.setNeutralMode(NeutralMode.Coast);
    _motorTwo.setNeutralMode(NeutralMode.Coast);
 
    /* Configure output and sensor direction */
    _motorOne.setInverted(_motorOneInvert);
    _motorTwo.setInverted(_motorTwoInvert);
 
    //set second motor as a follower
    _motorTwo.follow(_motorOne, FollowerType.PercentOutput);
    
    /* Config neutral deadband to be the smallest possible */
    _motorOne.configNeutralDeadband(0.01);
 
    /* Config sensor used for Primary PID [Velocity] */
    _motorOne.configSelectedFeedbackSensor(TalonFXFeedbackDevice.IntegratedSensor,
                                        kPIDLoopIdx, 
                                        kTimeoutMs);
                                        
 
    /* Config the peak and nominal outputs */
    _motorOne.configNominalOutputForward(0.0, kTimeoutMs);
    _motorOne.configNominalOutputReverse(0.0, kTimeoutMs);
    _motorOne.configPeakOutputForward(1.0, kTimeoutMs);
    _motorOne.configPeakOutputReverse(0.0, kTimeoutMs); //set so that the shooter CANNOT run backwards
 
    /* Config the Velocity closed loop gains in slot0 */
    _motorOne.config_kF(kPIDLoopIdx, kGains_Velocity.kF, kTimeoutMs);
    _motorOne.config_kP(kPIDLoopIdx, kGains_Velocity.kP, kTimeoutMs);
    _motorOne.config_kI(kPIDLoopIdx, kGains_Velocity.kI, kTimeoutMs);
    _motorOne.config_kD(kPIDLoopIdx, kGains_Velocity.kD, kTimeoutMs);
    _motorOne.config_IntegralZone(kPIDLoopIdx, kGains_Velocity.kIzone, kTimeoutMs);
 
}