-
Notifications
You must be signed in to change notification settings - Fork 0
Elevator control example
Brendan Burkhart edited this page Oct 3, 2019
·
2 revisions
The elevator on the robot is controlled using motion-profiled PID controllers. The code is described below to aid in creating similar components (position-controlled arm, etc.).
// Sets the package the code is in, this should generally match the folder path - "/src/main/java/com/pigmice/frc/robot/subsystems"
package com.pigmice.frc.robot.subsystems;
// Imports other packages to use in this code
import com.ctre.phoenix.motorcontrol.ControlMode;
import com.ctre.phoenix.motorcontrol.DemandType;
import com.ctre.phoenix.motorcontrol.can.TalonSRX;
import com.pigmice.frc.lib.motion.Setpoint;
import com.pigmice.frc.lib.motion.StaticProfile;
import com.pigmice.frc.lib.motion.execution.StaticProfileExecutor;
import com.pigmice.frc.lib.utils.Range;
import com.pigmice.frc.lib.utils.Utils;
// Creates a component named "Elevator"
public class Elevator {
// tells Java the Elevator component needs to store a reference to a TalonSRX motor controller
// private says the motor controller should only be accessible from inside this component
// "winch" is the name used to reference the motor controller throughout the component.
// By default, the motor controller will be equal to "null" - it has no value yet.
private TalonSRX winch;
// private variable named targetPosition, Double is the type used for decimal numbers
// This stores the position we want the elevator to be at
private Double targetPosition;
// Stores the position the elevator is currently at.
private Double currentPosition;
// Range is a custom type the stores a range of numbers, in this case it stores the minimum and maximum values the
// elevator sensor will read, at the bottom and top of its range of motion respectively.
private Range sensorBounds = new Range(0, 30400.0);
// StaticProfileExecutor is the type holding a motion-profile controller, which plans the elevator motion to make it
// start and stop smoothly.
private StaticProfileExecutor profileExecutor;
// Feedforward coefficient - this is not necessary for a simple controller, but helps boost motor output when the
// controller wants the elevator to be moving quickly
private double kF = 0.85;
// Tuning coefficient to compensate for the fact that gravity makes the elevator move faster down than up
private double gravityCompensation = 0.055;
// Java constructor function - takes a motor as input, and stores it into the "winch" variable.
public Elevator(TalonSRX winchMotor) {
winch = winchMotor;
// Configures the coefficients for the PID controller built into TalonSRX motor controller.
winchMotor.config_kP(0, 0.2, 10);
winchMotor.config_kI(0, 0.0, 10);
winchMotor.config_kD(0, 0.0, 10);
winchMotor.config_kF(0, 0.0, 10);
winchMotor.configNeutralDeadband(0.005, 10);
// When this component is created, the elevator is assumed to resting on the "kickstand" bar, so the sensor is
// reset to 0.138, since the elevator is 13.8% of the way to the top in that position
zeroSensor();
// Sets a target of 13.8% up to it doesn't move right away.
setTargetPosition(0.138);
}
// Function that takes a percent of full power, and applies it to the winch motor.
public void drive(double percent) {
winch.set(ControlMode.PercentOutput, percent);
}
// Takes a number from 0-1, 0 being the bottom and 1 being the top, for the elevator to target.
public void setTargetPosition(double targetPosition) {
// If the profileExecutor variable is set to "null" - empty - or the target position input is different than the
// current target position by more than a rounding error, run the rest of this code.
// This prevents a bunch of code running even when the target position set is the same as the current target.
if (profileExecutor == null || Math.abs(this.targetPosition - targetPosition) > 1e-2) {
// PID controllers remember how much friction they were last compensating for, etc. Since we might be
// drastically changing what the elevator is attempting to do, reset the PID in the motor controller.
resetPID();
// Creates a temporary variable to store a StaticProfile - a custom component that calculates the exact plan
// to move the elevator from its current position to the target position
StaticProfile profile;
// Given current speed, position, target, and some parameters representing how fast the elevator can
// accelerate, move, and decelerate, plan out the motion it will use to get to the target.
profile = new StaticProfile(getVelocity(), currentPosition, targetPosition, 2.0, 1.6, 1.6);
// Store the input target position into the targetPosition variable that is part of the elevator component
// so it stays around after this function finishes running
this.targetPosition = targetPosition;
// a StaticProfileExecutor follows the StaticProfile's calculated motion plan to move the elevator.
profileExecutor = new StaticProfileExecutor(profile, this::output, this::getPosition, 0.02);
profileExecutor.initialize();
}
}
public double getPosition() {
// Raw sensor value in encoder ticks.
double raw = (double) winch.getSelectedSensorPosition();
// Linear interpolation - minimum sensor value is mapped to 0, maximum to 1, everything else falls in-between.
return Utils.lerp(raw, sensorBounds.min(), sensorBounds.max(), 0.0, 1.0);
}
public double getVelocity() {
double raw = (double) winch.getSelectedSensorVelocity();
// Maps from sensor ticks per 100nms to values that make more sense with the position units used.
return Utils.lerp(raw, -sensorBounds.size(), sensorBounds.size(), -1.0, 1.0);
}
public void zeroSensor() {
currentPosition = 0.138;
// Maps from 0.138 back into native sensor ticks and sets the motor sensor to that value.
winch.setSelectedSensorPosition((int) Utils.lerp(0.138, 0, 1.0, sensorBounds.min(), sensorBounds.max()));
}
// Reads the converted sensor value and stores it into this component. This means the sensor can be read just once
// and that value can be reused throughout the component without re-reading and re-converting it many times.
public void updateSensor() {
currentPosition = getPosition();
}
// Tells the StaticProfileExecutor to run an update cycle
public void update() {
profileExecutor.updateNoEnd();
}
public void resetPID() {
winch.setIntegralAccumulator(0.0, 0, 10);
}
// This function is passed into the StaticProfileExecutor. Given an input value of the custom type "Setpoint" which
// contains the speed the elevator should be going, the position it should be at, etc. it gives those values to the
// PID controller in the motor which adjusts the motor output to keep the elevator on track for the planned motion.
private void output(Setpoint sp) {
double lerp = Utils.lerp(sp.getPosition(), 0.0, 1.0, sensorBounds.min(), sensorBounds.max());
winch.set(ControlMode.Position, lerp, DemandType.ArbitraryFeedForward,
gravityCompensation + kF * sp.getVelocity());
}
}