001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.wpilibj.drive;
006
007import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
008
009import edu.wpi.first.hal.HAL;
010import edu.wpi.first.math.MathUtil;
011import edu.wpi.first.util.sendable.Sendable;
012import edu.wpi.first.util.sendable.SendableBuilder;
013import edu.wpi.first.util.sendable.SendableRegistry;
014import edu.wpi.first.wpilibj.motorcontrol.MotorController;
015import java.util.function.DoubleConsumer;
016
017/**
018 * A class for driving differential drive/skid-steer drive platforms such as the Kit of Parts drive
019 * base, "tank drive", or West Coast Drive.
020 *
021 * <p>These drive bases typically have drop-center / skid-steer with two or more wheels per side
022 * (e.g., 6WD or 8WD). This class takes a setter per side. For four and six motor drivetrains, use
023 * CAN motor controller followers or {@link
024 * edu.wpi.first.wpilibj.motorcontrol.PWMMotorController#addFollower(PWMMotorController)}.
025 *
026 * <p>A differential drive robot has left and right wheels separated by an arbitrary width.
027 *
028 * <p>Drive base diagram:
029 *
030 * <pre>
031 * |_______|
032 * | |   | |
033 *   |   |
034 * |_|___|_|
035 * |       |
036 * </pre>
037 *
038 * <p>Each drive function provides different inverse kinematic relations for a differential drive
039 * robot.
040 *
041 * <p>This library uses the NWU axes convention (North-West-Up as external reference in the world
042 * frame). The positive X axis points ahead, the positive Y axis points to the left, and the
043 * positive Z axis points up. Rotations follow the right-hand rule, so counterclockwise rotation
044 * around the Z axis is positive.
045 *
046 * <p>Inputs smaller then {@value edu.wpi.first.wpilibj.drive.RobotDriveBase#kDefaultDeadband} will
047 * be set to 0, and larger values will be scaled so that the full range is still used. This deadband
048 * value can be changed with {@link #setDeadband}.
049 *
050 * <p>{@link edu.wpi.first.wpilibj.MotorSafety} is enabled by default. The tankDrive, arcadeDrive,
051 * or curvatureDrive methods should be called periodically to avoid Motor Safety timeouts.
052 */
053public class DifferentialDrive extends RobotDriveBase implements Sendable, AutoCloseable {
054  private static int instances;
055
056  private final DoubleConsumer m_leftMotor;
057  private final DoubleConsumer m_rightMotor;
058
059  // Used for Sendable property getters
060  private double m_leftOutput;
061  private double m_rightOutput;
062
063  private boolean m_reported;
064
065  /**
066   * Wheel speeds for a differential drive.
067   *
068   * <p>Uses normalized voltage [-1.0..1.0].
069   */
070  @SuppressWarnings("MemberName")
071  public static class WheelSpeeds {
072    /** Left wheel speed. */
073    public double left;
074
075    /** Right wheel speed. */
076    public double right;
077
078    /** Constructs a WheelSpeeds with zeroes for left and right speeds. */
079    public WheelSpeeds() {}
080
081    /**
082     * Constructs a WheelSpeeds.
083     *
084     * @param left The left speed [-1.0..1.0].
085     * @param right The right speed [-1.0..1.0].
086     */
087    public WheelSpeeds(double left, double right) {
088      this.left = left;
089      this.right = right;
090    }
091  }
092
093  /**
094   * Construct a DifferentialDrive.
095   *
096   * <p>To pass multiple motors per side, use CAN motor controller followers or {@link
097   * edu.wpi.first.wpilibj.motorcontrol.PWMMotorController#addFollower(PWMMotorController)}. If a
098   * motor needs to be inverted, do so before passing it in.
099   *
100   * @param leftMotor Left motor.
101   * @param rightMotor Right motor.
102   */
103  @SuppressWarnings({"removal", "this-escape"})
104  public DifferentialDrive(MotorController leftMotor, MotorController rightMotor) {
105    this((double output) -> leftMotor.set(output), (double output) -> rightMotor.set(output));
106    SendableRegistry.addChild(this, leftMotor);
107    SendableRegistry.addChild(this, rightMotor);
108  }
109
110  /**
111   * Construct a DifferentialDrive.
112   *
113   * <p>To pass multiple motors per side, use CAN motor controller followers or {@link
114   * edu.wpi.first.wpilibj.motorcontrol.PWMMotorController#addFollower(PWMMotorController)}. If a
115   * motor needs to be inverted, do so before passing it in.
116   *
117   * @param leftMotor Left motor setter.
118   * @param rightMotor Right motor setter.
119   */
120  @SuppressWarnings("this-escape")
121  public DifferentialDrive(DoubleConsumer leftMotor, DoubleConsumer rightMotor) {
122    requireNonNullParam(leftMotor, "leftMotor", "DifferentialDrive");
123    requireNonNullParam(rightMotor, "rightMotor", "DifferentialDrive");
124
125    m_leftMotor = leftMotor;
126    m_rightMotor = rightMotor;
127    instances++;
128    SendableRegistry.add(this, "DifferentialDrive", instances);
129  }
130
131  @Override
132  public void close() {
133    SendableRegistry.remove(this);
134  }
135
136  /**
137   * Arcade drive method for differential drive platform. The calculated values will be squared to
138   * decrease sensitivity at low speeds.
139   *
140   * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
141   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is
142   *     positive.
143   */
144  public void arcadeDrive(double xSpeed, double zRotation) {
145    arcadeDrive(xSpeed, zRotation, true);
146  }
147
148  /**
149   * Arcade drive method for differential drive platform.
150   *
151   * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
152   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is
153   *     positive.
154   * @param squareInputs If set, decreases the input sensitivity at low speeds.
155   */
156  public void arcadeDrive(double xSpeed, double zRotation, boolean squareInputs) {
157    if (!m_reported) {
158      HAL.reportUsage("RobotDrive", "DifferentialArcade");
159      m_reported = true;
160    }
161
162    xSpeed = MathUtil.applyDeadband(xSpeed, m_deadband);
163    zRotation = MathUtil.applyDeadband(zRotation, m_deadband);
164
165    var speeds = arcadeDriveIK(xSpeed, zRotation, squareInputs);
166
167    m_leftOutput = speeds.left * m_maxOutput;
168    m_rightOutput = speeds.right * m_maxOutput;
169
170    m_leftMotor.accept(m_leftOutput);
171    m_rightMotor.accept(m_rightOutput);
172
173    feed();
174  }
175
176  /**
177   * Curvature drive method for differential drive platform.
178   *
179   * <p>The rotation argument controls the curvature of the robot's path rather than its rate of
180   * heading change. This makes the robot more controllable at high speeds.
181   *
182   * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
183   * @param zRotation The normalized curvature [-1.0..1.0]. Counterclockwise is positive.
184   * @param allowTurnInPlace If set, overrides constant-curvature turning for turn-in-place
185   *     maneuvers. zRotation will control turning rate instead of curvature.
186   */
187  public void curvatureDrive(double xSpeed, double zRotation, boolean allowTurnInPlace) {
188    if (!m_reported) {
189      HAL.reportUsage("RobotDrive", "DifferentialCurvature");
190      m_reported = true;
191    }
192
193    xSpeed = MathUtil.applyDeadband(xSpeed, m_deadband);
194    zRotation = MathUtil.applyDeadband(zRotation, m_deadband);
195
196    var speeds = curvatureDriveIK(xSpeed, zRotation, allowTurnInPlace);
197
198    m_leftOutput = speeds.left * m_maxOutput;
199    m_rightOutput = speeds.right * m_maxOutput;
200
201    m_leftMotor.accept(m_leftOutput);
202    m_rightMotor.accept(m_rightOutput);
203
204    feed();
205  }
206
207  /**
208   * Tank drive method for differential drive platform. The calculated values will be squared to
209   * decrease sensitivity at low speeds.
210   *
211   * @param leftSpeed The robot's left side speed along the X axis [-1.0..1.0]. Forward is positive.
212   * @param rightSpeed The robot's right side speed along the X axis [-1.0..1.0]. Forward is
213   *     positive.
214   */
215  public void tankDrive(double leftSpeed, double rightSpeed) {
216    tankDrive(leftSpeed, rightSpeed, true);
217  }
218
219  /**
220   * Tank drive method for differential drive platform.
221   *
222   * @param leftSpeed The robot left side's speed along the X axis [-1.0..1.0]. Forward is positive.
223   * @param rightSpeed The robot right side's speed along the X axis [-1.0..1.0]. Forward is
224   *     positive.
225   * @param squareInputs If set, decreases the input sensitivity at low speeds.
226   */
227  public void tankDrive(double leftSpeed, double rightSpeed, boolean squareInputs) {
228    if (!m_reported) {
229      HAL.reportUsage("RobotDrive", "DifferentialTank");
230      m_reported = true;
231    }
232
233    leftSpeed = MathUtil.applyDeadband(leftSpeed, m_deadband);
234    rightSpeed = MathUtil.applyDeadband(rightSpeed, m_deadband);
235
236    var speeds = tankDriveIK(leftSpeed, rightSpeed, squareInputs);
237
238    m_leftOutput = speeds.left * m_maxOutput;
239    m_rightOutput = speeds.right * m_maxOutput;
240
241    m_leftMotor.accept(m_leftOutput);
242    m_rightMotor.accept(m_rightOutput);
243
244    feed();
245  }
246
247  /**
248   * Arcade drive inverse kinematics for differential drive platform.
249   *
250   * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
251   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is
252   *     positive.
253   * @param squareInputs If set, decreases the input sensitivity at low speeds.
254   * @return Wheel speeds [-1.0..1.0].
255   */
256  public static WheelSpeeds arcadeDriveIK(double xSpeed, double zRotation, boolean squareInputs) {
257    xSpeed = MathUtil.clamp(xSpeed, -1.0, 1.0);
258    zRotation = MathUtil.clamp(zRotation, -1.0, 1.0);
259
260    // Square the inputs (while preserving the sign) to increase fine control
261    // while permitting full power.
262    if (squareInputs) {
263      xSpeed = MathUtil.copySignPow(xSpeed, 2);
264      zRotation = MathUtil.copySignPow(zRotation, 2);
265    }
266
267    double leftSpeed = xSpeed - zRotation;
268    double rightSpeed = xSpeed + zRotation;
269
270    // Find the maximum possible value of (throttle + turn) along the vector
271    // that the joystick is pointing, then desaturate the wheel speeds
272    double greaterInput = Math.max(Math.abs(xSpeed), Math.abs(zRotation));
273    double lesserInput = Math.min(Math.abs(xSpeed), Math.abs(zRotation));
274    if (greaterInput == 0.0) {
275      return new WheelSpeeds(0.0, 0.0);
276    }
277    double saturatedInput = (greaterInput + lesserInput) / greaterInput;
278    leftSpeed /= saturatedInput;
279    rightSpeed /= saturatedInput;
280
281    return new WheelSpeeds(leftSpeed, rightSpeed);
282  }
283
284  /**
285   * Curvature drive inverse kinematics for differential drive platform.
286   *
287   * <p>The rotation argument controls the curvature of the robot's path rather than its rate of
288   * heading change. This makes the robot more controllable at high speeds.
289   *
290   * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
291   * @param zRotation The normalized curvature [-1.0..1.0]. Counterclockwise is positive.
292   * @param allowTurnInPlace If set, overrides constant-curvature turning for turn-in-place
293   *     maneuvers. zRotation will control rotation rate around the Z axis instead of curvature.
294   * @return Wheel speeds [-1.0..1.0].
295   */
296  public static WheelSpeeds curvatureDriveIK(
297      double xSpeed, double zRotation, boolean allowTurnInPlace) {
298    xSpeed = MathUtil.clamp(xSpeed, -1.0, 1.0);
299    zRotation = MathUtil.clamp(zRotation, -1.0, 1.0);
300
301    double leftSpeed;
302    double rightSpeed;
303
304    if (allowTurnInPlace) {
305      leftSpeed = xSpeed - zRotation;
306      rightSpeed = xSpeed + zRotation;
307    } else {
308      leftSpeed = xSpeed - Math.abs(xSpeed) * zRotation;
309      rightSpeed = xSpeed + Math.abs(xSpeed) * zRotation;
310    }
311
312    // Desaturate wheel speeds
313    double maxMagnitude = Math.max(Math.abs(leftSpeed), Math.abs(rightSpeed));
314    if (maxMagnitude > 1.0) {
315      leftSpeed /= maxMagnitude;
316      rightSpeed /= maxMagnitude;
317    }
318
319    return new WheelSpeeds(leftSpeed, rightSpeed);
320  }
321
322  /**
323   * Tank drive inverse kinematics for differential drive platform.
324   *
325   * @param leftSpeed The robot left side's speed along the X axis [-1.0..1.0]. Forward is positive.
326   * @param rightSpeed The robot right side's speed along the X axis [-1.0..1.0]. Forward is
327   *     positive.
328   * @param squareInputs If set, decreases the input sensitivity at low speeds.
329   * @return Wheel speeds [-1.0..1.0].
330   */
331  public static WheelSpeeds tankDriveIK(double leftSpeed, double rightSpeed, boolean squareInputs) {
332    leftSpeed = MathUtil.clamp(leftSpeed, -1.0, 1.0);
333    rightSpeed = MathUtil.clamp(rightSpeed, -1.0, 1.0);
334
335    // Square the inputs (while preserving the sign) to increase fine control
336    // while permitting full power.
337    if (squareInputs) {
338      leftSpeed = MathUtil.copySignPow(leftSpeed, 2);
339      rightSpeed = MathUtil.copySignPow(rightSpeed, 2);
340    }
341
342    return new WheelSpeeds(leftSpeed, rightSpeed);
343  }
344
345  @Override
346  public void stopMotor() {
347    m_leftOutput = 0.0;
348    m_rightOutput = 0.0;
349
350    m_leftMotor.accept(0.0);
351    m_rightMotor.accept(0.0);
352
353    feed();
354  }
355
356  @Override
357  public String getDescription() {
358    return "DifferentialDrive";
359  }
360
361  @Override
362  public void initSendable(SendableBuilder builder) {
363    builder.setSmartDashboardType("DifferentialDrive");
364    builder.setActuator(true);
365    builder.addDoubleProperty("Left Motor Speed", () -> m_leftOutput, m_leftMotor);
366    builder.addDoubleProperty("Right Motor Speed", () -> m_rightOutput, m_rightMotor);
367  }
368}