Skip to content

Commit

Permalink
advanced-controls/controllers done (minus RLI)
Browse files Browse the repository at this point in the history
  • Loading branch information
falOn-Dev committed Sep 6, 2024
1 parent cd18605 commit 176e601
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ Since a bang-bang controller does not have any gains, it does not need any const
controller = BangBangController()
```



## Using a BangBangController

.. warning:: Bang-bang control is an extremely aggressive algorithm that relies on response asymmetry to remain stable. Be *absolutely certain* that your motor controllers have been set to "coast mode" before attempting to control them with a bang-bang controller, or else the braking action will fight the controller and cause potentially destructive oscillation.
Expand Down Expand Up @@ -65,8 +63,6 @@ Using a bang-bang controller is easy:
motor.set(controller.calculate(encoder.getRate(), setpoint))
```



## Combining Bang Bang Control with Feedforward

Like a PID controller, best results are obtained in conjunction with a :ref:`feedforward <docs/software/advanced-controls/controllers/feedforward:Feedforward Control in WPILib>` controller that provides the necessary voltage to sustain the system output at the desired speed, so that the bang-bang controller is only responsible for rejecting disturbances. Since the bang-bang controller can *only* correct in the forward direction, however, it may be preferable to use a slightly conservative feedforward estimate to ensure that the shooter does not over-speed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ Users may add any feedforward they like to the output of the controller before s
motor.setVoltage(pid.calculate(encoder.getDistance(), setpoint) + feedforward);
```

```kotlin
// Adds a feedforward to the loop output before sending it to the motor
motor.setVoltage(pid.calculate(encoder.distance, setpoint) + feedforward)
```

```c++
// Adds a feedforward to the loop output before sending it to the motor
motor.SetVoltage(pid.Calculate(encoder.GetDistance(), setpoint) + feedforward);
Expand Down Expand Up @@ -48,6 +53,16 @@ What might a more complete example of combined feedforward/PID control look like
}
```

```kotlin
fun tankDriveWithFeedforwardPID(leftVelocitySetpoint: Double, rightVelocitySetpoint: Double) {
leftMotor.setVoltage(feedforward.calculate(leftVelocitySetpoint)
+ leftPID.calculate(leftEncoder.rate, leftVelocitySetpoint))
rightMotor.setVoltage(feedforward.calculate(rightVelocitySetpoint)
+ rightPID.calculate(rightEncoder.rate, rightVelocitySetpoint))
}
```


```c++
void TankDriveWithFeedforwardPID(units::meters_per_second_t leftVelocitySetpoint,
units::meters_per_second_t rightVelocitySetpoint) {
Expand Down
35 changes: 35 additions & 0 deletions source/docs/software/advanced-controls/controllers/feedforward.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ To create a ``SimpleMotorFeedforward``, simply construct it with the required ga
SimpleMotorFeedforward feedforward = new SimpleMotorFeedforward(kS, kV, kA);
```

```kotlin
// Create a new SimpleMotorFeedforward with gains kS, kV, and kA
val feedforward = SimpleMotorFeedforward(kS, kV, kA)
```

```c++
// Create a new SimpleMotorFeedforward with gains kS, kV, and kA
// Distance is measured in meters
Expand All @@ -66,6 +71,12 @@ To calculate the feedforward, simply call the ``calculate()`` method with the de
feedforward.calculate(10, 20);
```

```kotlin
// Calculates the feedforward for a velocity of 10 units/second and an acceleration of 20 units/second^2
// Units are determined by the units of the gains passed in at construction.
feedforward.calculate(10, 20)
```

```c++
// Calculates the feedforward for a velocity of 10 meters/second and an acceleration of 20 meters/second^2
// Output is in volts
Expand Down Expand Up @@ -99,6 +110,11 @@ To create an ``ArmFeedforward``, simply construct it with the required gains:
ArmFeedforward feedforward = new ArmFeedforward(kS, kG, kV, kA);
```

```kotlin
// Create a new ArmFeedforward with gains kS, kG, kV, and kA
val feedforward = ArmFeedforward(kS, kG, kV, kA)
```

```c++
// Create a new ArmFeedforward with gains kS, kG, kV, and kA
frc::ArmFeedforward feedforward(kS, kG, kV, kA);
Expand All @@ -123,6 +139,13 @@ To calculate the feedforward, simply call the ``calculate()`` method with the de
feedforward.calculate(1, 2, 3);
```

```kotlin
// Calculates the feedforward for a position of 1 units, a velocity of 2 units/second, and
// an acceleration of 3 units/second^2
// Units are determined by the units of the gains passed in at construction.
feedforward.calculate(1, 2, 3)
```

```c++
// Calculates the feedforward for a position of 1 radians, a velocity of 2 radians/second, and
// an acceleration of 3 radians/second^2
Expand Down Expand Up @@ -158,6 +181,11 @@ To create a ``ElevatorFeedforward``, simply construct it with the required gains
ElevatorFeedforward feedforward = new ElevatorFeedforward(kS, kG, kV, kA);
```

```kotlin
// Create a new ElevatorFeedforward with gains kS, kG, kV, and kA
val feedforward = ElevatorFeedforward(kS, kG, kV, kA)
```

```c++
// Create a new ElevatorFeedforward with gains kS, kV, and kA
// Distance is measured in meters
Expand Down Expand Up @@ -213,6 +241,13 @@ Feedforward control can be used entirely on its own, without a feedback controll
}
```

```kotlin
fun tankDriveWithFeedforward(leftVelocity: Double, rightVelocity: Double) {
leftMotor.setVoltage(feedforward.calculate(leftVelocity))
rightMotor.setVoltage(feedForward.calculate(rightVelocity))
}
```

```c++
void TankDriveWithFeedforward(units::meters_per_second_t leftVelocity,
units::meters_per_second_t rightVelocity) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ In order to use WPILib's PID control functionality, users must first construct a
PIDController pid = new PIDController(kP, kI, kD);
```

```kotlin
// Creates a PIDController with gains kP, kI, and kD
val pid = PIDController(kP, kI, kD)
```

```c++
// Creates a PIDController with gains kP, kI, and kD
frc::PIDController pid{kP, kI, kD};
Expand Down Expand Up @@ -48,6 +53,12 @@ Using the constructed ``PIDController`` is simple: simply call the ``calculate()
motor.set(pid.calculate(encoder.getDistance(), setpoint));
```

```kotlin
// Calculates the output of the PID algorithm based on the sensor reading
// and sends it to a motor
motor.set(pid.calculate(encoder.distance, setpoint))
```

```c++
// Calculates the output of the PID algorithm based on the sensor reading
// and sends it to a motor
Expand Down Expand Up @@ -88,6 +99,15 @@ To do this, we first must specify the tolerances with the ``setTolerance()`` met
pid.atSetpoint();
```

```kotlin
// Sets the error tolerance to 5, and the error derivative tolerance to 10 per second
pid.setTolerance(5, 10)
// Or to set it on initialization
val pid = PIDController(kP, kI, kD).apply { setTolerance(5, 10) }
// Returns true if the error is less than 5 units, and the
// error derivative is less than 10 units
```

```c++
// Sets the error tolerance to 5, and the error derivative tolerance to 10 per second
pid.SetTolerance(5, 10);
Expand Down Expand Up @@ -126,6 +146,14 @@ The range limits may be increased or decreased using the ``setIntegratorRange()`
pid.setIntegratorRange(-0.5, 0.5);
```

```kotlin
// The integral gain term will never add or subtract more than 0.5 from
// the total loop output
pid.setIntegratorRange(-0.5, 0.5)
// Or to set it on initialization
val pid = PIDController(kP, kI, kD).apply { setIntegratorRange(-0.5, 0.5) }
```

```c++
// The integral gain term will never add or subtract more than 0.5 from
// the total loop output
Expand Down Expand Up @@ -156,6 +184,16 @@ By default, ``IZone`` is disabled.
pid.setIZone(2);
```

```kotlin
// Disable IZone
pid.iZone = Double.POSITIVE_INFINITY
// Integral gain will not be applied if the absolute value of the error is
// more than 2
pid.iZone = 2
// Or to set it on initialization
val pid = PIDController(kP, kI, kD).apply { iZone = 2 }
```

```c++
// Disable IZone
pid.SetIZone(std::numeric_limits<double>::infinity());
Expand Down Expand Up @@ -187,6 +225,14 @@ To configure a ``PIDController`` to automatically do this, use the ``enableConti
pid.enableContinuousInput(-180, 180);
```

```kotlin
// Enables continuous input on a range from -180 to 180
pid.enableContinuousInput(-180, 180)
// Or to set it on initialization
val pid = PIDController(kP, kI, kD).apply { enableContinuousInput(-180, 180) }
```


```c++
// Enables continuous input on a range from -180 to 180
pid.EnableContinuousInput(-180, 180);
Expand All @@ -206,6 +252,11 @@ To configure a ``PIDController`` to automatically do this, use the ``enableConti
MathUtil.clamp(pid.calculate(encoder.getDistance(), setpoint), -0.5, 0.5);
```

```kotlin
// Clamps the controller output to between -0.5 and 0.5
MathUtil.clamp(pid.calculate(encoder.distance, setpoint), -0.5, 0.5)
```

```c++
// Clamps the controller output to between -0.5 and 0.5
std::clamp(pid.Calculate(encoder.GetDistance(), setpoint), -0.5, 0.5);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ Creating a ``ProfiledPIDController`` is nearly identical to :ref:`creating a PID
new TrapezoidProfile.Constraints(5, 10));
```

```kotlin
// Creates a ProfiledPIDController
// Max velocity is 5 meters per second
// Max acceleration is 10 meters per second
val controller = ProfiledPIDController(
kP, kI, kD,
TrapezoidProfile.Constraints(5, 10))
```

```c++
// Creates a ProfiledPIDController
// Max velocity is 5 meters per second
Expand Down Expand Up @@ -62,6 +71,12 @@ A major difference between a standard ``PIDController`` and a ``ProfiledPIDContr
motor.set(controller.calculate(encoder.getDistance(), goal));
```

```kotlin
// Calculates the output of the PID algorithm based on the sensor reading
// and sends it to a motor
motor.set(controller.calculate(encoder.distance, goal))
```

```c++
// Calculates the output of the PID algorithm based on the sensor reading
// and sends it to a motor
Expand Down Expand Up @@ -100,6 +115,23 @@ The returned setpoint might then be used as in the following example:
}
```

```kotlin
var lastSpeed = 0.0
var lastTime = Timer.getFPGATimestamp()
// Controls a simple motor's position using a SimpleMotorFeedforward
// and a ProfiledPIDController
fun goToPosition(goalPosition: Double) {
val pidVal = controller.calculate(encoder.distance, goalPosition)
val acceleration = (controller.setpoint.velocity - lastSpeed) / (Timer.getFPGATimestamp() - lastTime)
motor.setVoltage(
pidVal + feedforward.calculate(controller.setpoint.velocity, acceleration)
)
lastSpeed = controller.setpoint.velocity
lastTime = Timer.getFPGATimestamp()
}
```

```c++
units::meters_per_second_t lastSpeed = 0_mps;
units::second_t lastTime = frc2::Timer::GetFPGATimestamp();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ In order to create a trapezoidal motion profile, we must first impose some const
new TrapezoidProfile.Constraints(10, 20);
```

```kotlin
// Creates a new set of trapezoidal motion profile constraints
// Max velocity of 10 meters per second
// Max acceleration of 20 meters per second squared
TrapezoidProfile.Constraints(10, 20)
```

```c++
// Creates a new set of trapezoidal motion profile constraints
// Max velocity of 10 meters per second
Expand Down Expand Up @@ -58,6 +65,12 @@ Next, we must specify the desired starting and ending states for our mechanisms
new TrapezoidProfile.State(5, 0);
```

```kotlin
// Creates a new state with a position of 5 meters
// and a velocity of 0 meters per second
TrapezoidProfile.State(5, 0)
```

```c++
// Creates a new state with a position of 5 meters
// and a velocity of 0 meters per second
Expand Down Expand Up @@ -86,6 +99,13 @@ Now that we know how to create a set of constraints and the desired start/end st
TrapezoidProfile profile = new TrapezoidProfile(new TrapezoidProfile.Constraints(5, 10));
```

```kotlin
// Creates a new TrapezoidProfile
// Profile will have a max vel of 5 meters per second
// Profile will have a max acceleration of 10 meters per second squared
val profile = TrapezoidProfile(TrapezoidProfile.Constraints(5, 10))
```

```c++
// Creates a new TrapezoidProfile
// Profile will have a max vel of 5 meters per second
Expand Down Expand Up @@ -117,6 +137,13 @@ Once we've created a ``TrapezoidProfile``, using it is very simple: to get the p
profile.calculate(5, new TrapezoidProfile.State(0, 0), new TrapezoidProfile.State(5, 0));
```

```kotlin
// Profile will start stationary at zero position
// Profile will end stationary at 5 meters
// Returns the motion profile state after 5 seconds of motion
profile.calculate(5, TrapezoidProfile.State(0, 0), TrapezoidProfile.State(5, 0))
```

```c++
// Profile will start stationary at zero position
// Profile will end stationary at 5 meters
Expand Down Expand Up @@ -144,6 +171,11 @@ The ``calculate`` method returns a ``TrapezoidProfile.State`` class (the same on
controller.calculate(encoder.getDistance(), setpoint.position);
```

```kotlin
val setpoint = profile.calculate(elapsedTime, initialState, goalState)
controller.calculate(encoder.distance, setpoint.position)
```

```c++
auto setpoint = profile.Calculate(elapsedTime, initialState, goalState);
controller.Calculate(encoder.GetDistance(), setpoint.position.value());
Expand Down

0 comments on commit 176e601

Please sign in to comment.