Mentor need help programming a shooter + BangBangController

Hi!

As a mentor, this is my first time dealing with a shooter. I’d like to get my head around PID and/or bang-bang controllers when it comes to shooters before providing assistance to students.

What I want: implement a command group to shoot a cargo using the following sequence

  1. spin the wheels (motors are in coast mode)
  2. feed a cargo
  3. wait 1s. for the cargo to be shot
  4. stop the wheels

The code to implement this command is provided below.

My questions:

  1. What is supposed to maintain the velocity of the wheels once the BangBangController reaches its setpoint, and the spin command ends?
  2. Should the spin command be a DefaultCommand of the shooter instead? This way the BangBangController would constantly maintain the velocity of the wheels throughout a match.
  3. Is there any advantage in using a PID/banb-bang controller over a fixed output voltage for shooting?

Any help of this would be really appreciated. Thank you!

My implementation:

The command implemented using a SequentialCommandGroup:

import edu.wpi.first.wpilibj2.command.InstantCommand;
import edu.wpi.first.wpilibj2.command.SequentialCommandGroup;
import edu.wpi.first.wpilibj2.command.WaitCommand;
import frc.robot.subsystems.Shooter;

public class BangBangShoot extends SequentialCommandGroup {

    public BangBangShoot(Shooter shooter) {
        addCommands(
                new BangBangSpin(shooter),
                new InstantCommand(shooter::feed, shooter),
                new WaitCommand(1.0),
                new InstantCommand(shooter::stop, shooter));
    }
}

And the spin command using a BangBangController :

import edu.wpi.first.math.controller.BangBangController;
import edu.wpi.first.wpilibj2.command.CommandBase;
import frc.robot.subsystems.Shooter;

public class BangBangSpin extends CommandBase {

    private Shooter m_shooter;
    private BangBangController m_rpmController;

    public BangBangSpin(Shooter shooter) {
        m_shooter = shooter;

        m_rpmController = new BangBangController();
        m_rpmController.setSetpoint(3000.0);    // 3000 RPM.
        m_rpmController.setTolerance(50.0);     // 50 RPM.

        addRequirements(shooter);
    }

    @Override
    public void execute() {
        m_shooter.spin(m_rpmController.calculate(m_shooter.getRpm()));
    }

    @Override
    public boolean isFinished() {
        return m_rpmController.atSetpoint();
    }

    @Override
    public void end(boolean interrupted) {
        if (interrupted) {
            m_shooter.stop();
        }

        // ??? What is supposed to maintain the velocity of the wheels ???
    }
}

The way we usually do it is we keep the controller inside the Subsystem, there we update the PID in the Periodic functions and implement methods to change the setpoint and see if the goal has been reached.

This makes sure that the Shooter is constantly correcting its velocity. We then create commands that change the setpoint and finish whenever the target is reached.

Here is the Shooter subsystem for another team I mentor:

And the Shoot command:

1 Like

For any closed loop control, the control loop needs to run as long as the output is controlled. So the spin command can’t end when the flywheel meets its desired velocity the first time.

That’s one option, but There’s plenty of ways to skin this cat. I typically like to be able to turn it off and only turn it on when needed. Doing the logic in the subystem periodic like mentioned earlier is another similar option, but I don’t like that because it makes it harder to switch out the logic for different cases (for example, if you need to revert to voltage control). I would instead move the BangBang controller to the Subsystem, but call it from the execute method of the command rather then the periodic method). I would then run the BangBangSpin command in parallel with the rest of your logic in the command group. Then I would write a wait for shooter at speed command that can be used to gate your intake logic.

Lets assume that your shooting percent is 75%. Assuming that your flywheel has a decent amount of inertia, it will take time to spin up to the nominal speed at 75%. You can decrease that time by increasing the voltage while it’s spinning up, like the bang bang controller does. Another issue is that if your battery is 12v, 75% is 9v. But if you’re also driving and running the compressor and doing other high current things then the battery might be at 10v, and 75% is only 7.5v. You can limit the effect of this by measuring the voltage and choosing the right percent to get the desired voltage. However, this won’t take into account device wear and other changes that might change what voltage creates a desired speed.

1 Like

It makes sense!

I’ve stumbled upon PIDSubsystem recently and was surprised since we exclusively used PID controllers in commands before (ie. PIDCommand). Thanks a lot @Darksainor and @Joe_Ross.

So using closed-loop control is mostly about consistency and not so much about about having a shorter cycle time when shooting (we still have to wait for the shooter to reach its setpoint before feeding a cargo).

Here are some follow-up questions:

  • Any good resource for computing the feed-forward for a shooter? I’m not sure what kA, kS and kV represent.
  • Can a feed-forward be determined empirically? So instead of having kA, kS and kV, the feed-forward would simply be a known constant that usually brings the wheels of the shooter near the desired setpoint?
  • Using ReCalc, what is the difference between a shooter wheel and a flywheel? (my background is purely about software development)
1 Like

You can use Sysid to characterize your Flywheel, we use the Simple mechanism and it has worked well for us, just beware that if you want to use the PID values it calculates you may need to manually tune them: Introduction to System Identification — FIRST Robotics Competition documentation

These are separate questions, but the answer to both is “yes” (though clearly the latter approach will only work if you’re trying to achieve a single constant setpoint).

I guess it would still work, but the controller might require more time to reach its setpoint based on the setpoint value to reach.

I’m not sure what you mean by this? A well-tuned constant feedforward should be very close to the value of an accurate kS/kV/kA-style feedforward at the setpoint it’s tuned around - so it should not matter which you use, as long as you are only trying to achieve that particular setpoint.

I mean that even if a constant feed-forward is not necessarily tuned for a setpoint, as long as the feed-forward is set low enough to not overshoot, eventually the control-loop is going to reach its setpoint. It might simply require more time to do so.

With a bang bang controller, the ultimate behavior is oscillatory and the oscillation will be wider the further the feedforward is from the “correct” value.

If you use a proportional controller and your gain is small enough you can achieve a stable steady-state behavior, but the equilibrium point will be somewhere between the speed your feedforward is tuned around and the setpoint you are trying to achieve.

Got it. Thanks @Oblarg! I guess my next step is to get more familiar with SysId.

1 Like

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