Simple Arm Example
Below is an example of a simple arm subsystem. Visualize the arm from the side as overlaying unit circle, where zeroed arm lies on the +x axis.
public class Arm extends Subsystem {
// CAN ID = 4
public static int canId = 4;
// How many inches the elavator tranvels
// per radian that the motor turns
public static double gearRatio = 17.06;
// Thrify Nova driving NEO
private ThriftyNova pivot;
// From NEO internal encoder units, to radians
private Conversion converter;
// Enum which contains setpoint data from arm
// assume zero position is straight out in front and up is +rotation
// visualize as unit circle where zeroed arm lies on the +x axis
enum SetPoint {
TOP(67),
MIDDLE(27),
OTHER_SIDE(254),
BOTTOM(-33);
final Rotation2d theta;
private SetPoint(double deg) { this(Rotation2d.fromDegrees(deg)); }
private SetPoint(Rotation2d theta) { this.theta = theta; }
}
public SetPoint setPoint = SetPoint.BOTTOM;
// Some constraints to not let the arm rotate past
private static final Rotation2d LOW_CONSTRAINT = Rotation2d.fromDegrees(-33);
private static final Rotation2d HIGH_CONSTRAINT = Rotation2d.fromDegrees(330);
// Storing the real angle of the arm
private double realTheta = 0;
private Arm() {
converter = new Conversion(PositionUnit.RADIANS, EncoderType.INTERNAL);
pivot = new ThriftyNova(canId) // create with CAN ID
.setBrakeMode(true) // brake mode
.setInverted(false) // not inverted
.setRampUp(0.25) // 1/4 second ramp up
.setRampDown(0.05) // tiny ramp dowm
.setMaxOutput(1, 0.25) // full power for forward because the
// system is fighting against gravity
// limits power on reverse because the
// system is falling with gravity
// constrain the motor
.setSoftLimits(
converter.toMotor(LOW_CONSTRAINT.getRadians * gearRatio),
converter.toMotor(HIGH_CONSTRAINT.getRadians * gearRatio)
)
.enableSoftLimits(true) // enable the soft limits
.setMaxCurrent(CurrentType.SUPPLY, 50) // set a 50amp current limit
// on supply side
.useEncoderType(EncoderType.INTERNAL) // use internal NEO encoder
.usePIDSlot(PIDSlot.SLOT0); // use the first PID slot
// Configure the first PID slot
pivot.pid0.setP(0.5)
.setI(0)
.setD(0)
.setFF(1.18);
// Iterate through errors and check them
for (var err : pivot.getErrors()) {
// The user can handle the errors
System.err.println("Error", err.toString());
}
// Clear errors here
pivot.clearErrors();
}
// This method is called periodically
public void update() {
// Set the position based on the setpoint
pivot.setPosition(converter.toMotor(setPoint.pos * gearRatio));
// Get the real position from the encoder
realTheta = converter.fromMotor(pivot.getPosition()) / gearRatio;
// Log the real position and the current draw
SmartDashboard.putNumber("Arm Position", realTheta);
SmartDashboard.putNumber("Arm Current", pivot.getSupplyCurrent());
}
// Is the arm at the correct setpoint
public boolean atSetpoint() {
// Return true if the absolute distance is under the 2 deg tolerance
return Math.abs(realTheta - setPoint.theta.getDegrees()) < 2;
}
// Begin singleton boilerplate
private static volatile Arm instance;
public static synchronized Arm getInstance() {
return instance == null ? instance = new Arm() : instance;
}
// End singleton boilerplate
}
Last updated