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 java.util.Objects.requireNonNull;
008
009import edu.wpi.first.hal.FRCNetComm.tInstances;
010import edu.wpi.first.hal.FRCNetComm.tResourceType;
011import edu.wpi.first.hal.HAL;
012import edu.wpi.first.math.MathUtil;
013import edu.wpi.first.util.sendable.Sendable;
014import edu.wpi.first.util.sendable.SendableBuilder;
015import edu.wpi.first.util.sendable.SendableRegistry;
016import edu.wpi.first.wpilibj.SpeedController;
017
018/**
019 * A class for driving differential drive/skid-steer drive platforms such as the Kit of Parts drive
020 * base, "tank drive", or West Coast Drive.
021 *
022 * <p>These drive bases typically have drop-center / skid-steer with two or more wheels per side
023 * (e.g., 6WD or 8WD). This class takes a MotorController per side. For four and six motor
024 * drivetrains, construct and pass in {@link
025 * edu.wpi.first.wpilibj.motorcontrol.MotorControllerGroup} instances as follows.
026 *
027 * <p>Four motor drivetrain:
028 *
029 * <pre><code>
030 * public class Robot {
031 *   MotorController m_frontLeft = new PWMVictorSPX(1);
032 *   MotorController m_rearLeft = new PWMVictorSPX(2);
033 *   MotorControllerGroup m_left = new MotorControllerGroup(m_frontLeft, m_rearLeft);
034 *
035 *   MotorController m_frontRight = new PWMVictorSPX(3);
036 *   MotorController m_rearRight = new PWMVictorSPX(4);
037 *   MotorControllerGroup m_right = new MotorControllerGroup(m_frontRight, m_rearRight);
038 *
039 *   DifferentialDrive m_drive = new DifferentialDrive(m_left, m_right);
040 * }
041 * </code></pre>
042 *
043 * <p>Six motor drivetrain:
044 *
045 * <pre><code>
046 * public class Robot {
047 *   MotorController m_frontLeft = new PWMVictorSPX(1);
048 *   MotorController m_midLeft = new PWMVictorSPX(2);
049 *   MotorController m_rearLeft = new PWMVictorSPX(3);
050 *   MotorControllerGroup m_left = new MotorControllerGroup(m_frontLeft, m_midLeft, m_rearLeft);
051 *
052 *   MotorController m_frontRight = new PWMVictorSPX(4);
053 *   MotorController m_midRight = new PWMVictorSPX(5);
054 *   MotorController m_rearRight = new PWMVictorSPX(6);
055 *   MotorControllerGroup m_right = new MotorControllerGroup(m_frontRight, m_midRight, m_rearRight);
056 *
057 *   DifferentialDrive m_drive = new DifferentialDrive(m_left, m_right);
058 * }
059 * </code></pre>
060 *
061 * <p>A differential drive robot has left and right wheels separated by an arbitrary width.
062 *
063 * <p>Drive base diagram:
064 *
065 * <pre>
066 * |_______|
067 * | |   | |
068 *   |   |
069 * |_|___|_|
070 * |       |
071 * </pre>
072 *
073 * <p>Each drive() function provides different inverse kinematic relations for a differential drive
074 * robot. Motor outputs for the right side are negated, so motor direction inversion by the user is
075 * usually unnecessary.
076 *
077 * <p>This library uses the NED axes convention (North-East-Down as external reference in the world
078 * frame): http://www.nuclearprojects.com/ins/images/axis_big.png.
079 *
080 * <p>The positive X axis points ahead, the positive Y axis points right, and the positive Z axis
081 * points down. Rotations follow the right-hand rule, so clockwise rotation around the Z axis is
082 * positive.
083 *
084 * <p>Inputs smaller then {@value edu.wpi.first.wpilibj.drive.RobotDriveBase#kDefaultDeadband} will
085 * be set to 0, and larger values will be scaled so that the full range is still used. This deadband
086 * value can be changed with {@link #setDeadband}.
087 */
088@SuppressWarnings("removal")
089public class DifferentialDrive extends RobotDriveBase implements Sendable, AutoCloseable {
090  private static int instances;
091
092  private final SpeedController m_leftMotor;
093  private final SpeedController m_rightMotor;
094
095  private boolean m_reported;
096
097  @SuppressWarnings("MemberName")
098  public static class WheelSpeeds {
099    public double left;
100    public double right;
101
102    /** Constructs a WheelSpeeds with zeroes for left and right speeds. */
103    public WheelSpeeds() {}
104
105    /**
106     * Constructs a WheelSpeeds.
107     *
108     * @param left The left speed.
109     * @param right The right speed.
110     */
111    public WheelSpeeds(double left, double right) {
112      this.left = left;
113      this.right = right;
114    }
115  }
116
117  /**
118   * Construct a DifferentialDrive.
119   *
120   * <p>To pass multiple motors per side, use a {@link
121   * edu.wpi.first.wpilibj.motorcontrol.MotorControllerGroup}. If a motor needs to be inverted, do
122   * so before passing it in.
123   *
124   * @param leftMotor Left motor.
125   * @param rightMotor Right motor.
126   */
127  public DifferentialDrive(SpeedController leftMotor, SpeedController rightMotor) {
128    requireNonNull(leftMotor, "Left motor cannot be null");
129    requireNonNull(rightMotor, "Right motor cannot be null");
130
131    m_leftMotor = leftMotor;
132    m_rightMotor = rightMotor;
133    SendableRegistry.addChild(this, m_leftMotor);
134    SendableRegistry.addChild(this, m_rightMotor);
135    instances++;
136    SendableRegistry.addLW(this, "DifferentialDrive", instances);
137  }
138
139  @Override
140  public void close() {
141    SendableRegistry.remove(this);
142  }
143
144  /**
145   * Arcade drive method for differential drive platform. The calculated values will be squared to
146   * decrease sensitivity at low speeds.
147   *
148   * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
149   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Clockwise is
150   *     positive.
151   */
152  @SuppressWarnings("ParameterName")
153  public void arcadeDrive(double xSpeed, double zRotation) {
154    arcadeDrive(xSpeed, zRotation, true);
155  }
156
157  /**
158   * Arcade drive method for differential drive platform.
159   *
160   * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
161   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Clockwise is
162   *     positive.
163   * @param squareInputs If set, decreases the input sensitivity at low speeds.
164   */
165  @SuppressWarnings("ParameterName")
166  public void arcadeDrive(double xSpeed, double zRotation, boolean squareInputs) {
167    if (!m_reported) {
168      HAL.report(
169          tResourceType.kResourceType_RobotDrive, tInstances.kRobotDrive2_DifferentialArcade, 2);
170      m_reported = true;
171    }
172
173    xSpeed = MathUtil.applyDeadband(xSpeed, m_deadband);
174    zRotation = MathUtil.applyDeadband(zRotation, m_deadband);
175
176    var speeds = arcadeDriveIK(xSpeed, zRotation, squareInputs);
177
178    m_leftMotor.set(speeds.left * m_maxOutput);
179    m_rightMotor.set(speeds.right * m_maxOutput);
180
181    feed();
182  }
183
184  /**
185   * Curvature drive method for differential drive platform.
186   *
187   * <p>The rotation argument controls the curvature of the robot's path rather than its rate of
188   * heading change. This makes the robot more controllable at high speeds.
189   *
190   * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
191   * @param zRotation The normalized curvature [-1.0..1.0]. Clockwise is positive.
192   * @param allowTurnInPlace If set, overrides constant-curvature turning for turn-in-place
193   *     maneuvers. zRotation will control turning rate instead of curvature.
194   */
195  @SuppressWarnings("ParameterName")
196  public void curvatureDrive(double xSpeed, double zRotation, boolean allowTurnInPlace) {
197    if (!m_reported) {
198      HAL.report(
199          tResourceType.kResourceType_RobotDrive, tInstances.kRobotDrive2_DifferentialCurvature, 2);
200      m_reported = true;
201    }
202
203    xSpeed = MathUtil.applyDeadband(xSpeed, m_deadband);
204    zRotation = MathUtil.applyDeadband(zRotation, m_deadband);
205
206    var speeds = curvatureDriveIK(xSpeed, zRotation, allowTurnInPlace);
207
208    m_leftMotor.set(speeds.left * m_maxOutput);
209    m_rightMotor.set(speeds.right * m_maxOutput);
210
211    feed();
212  }
213
214  /**
215   * Tank drive method for differential drive platform. The calculated values will be squared to
216   * decrease sensitivity at low speeds.
217   *
218   * @param leftSpeed The robot's left side speed along the X axis [-1.0..1.0]. Forward is positive.
219   * @param rightSpeed The robot's right side speed along the X axis [-1.0..1.0]. Forward is
220   *     positive.
221   */
222  public void tankDrive(double leftSpeed, double rightSpeed) {
223    tankDrive(leftSpeed, rightSpeed, true);
224  }
225
226  /**
227   * Tank drive method for differential drive platform.
228   *
229   * @param leftSpeed The robot left side's speed along the X axis [-1.0..1.0]. Forward is positive.
230   * @param rightSpeed The robot right side's speed along the X axis [-1.0..1.0]. Forward is
231   *     positive.
232   * @param squareInputs If set, decreases the input sensitivity at low speeds.
233   */
234  public void tankDrive(double leftSpeed, double rightSpeed, boolean squareInputs) {
235    if (!m_reported) {
236      HAL.report(
237          tResourceType.kResourceType_RobotDrive, tInstances.kRobotDrive2_DifferentialTank, 2);
238      m_reported = true;
239    }
240
241    leftSpeed = MathUtil.applyDeadband(leftSpeed, m_deadband);
242    rightSpeed = MathUtil.applyDeadband(rightSpeed, m_deadband);
243
244    var speeds = tankDriveIK(leftSpeed, rightSpeed, squareInputs);
245
246    m_leftMotor.set(speeds.left * m_maxOutput);
247    m_rightMotor.set(speeds.right * m_maxOutput);
248
249    feed();
250  }
251
252  /**
253   * Arcade drive inverse kinematics for differential drive platform.
254   *
255   * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
256   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Clockwise is
257   *     positive.
258   * @param squareInputs If set, decreases the input sensitivity at low speeds.
259   * @return Wheel speeds.
260   */
261  @SuppressWarnings("ParameterName")
262  public static WheelSpeeds arcadeDriveIK(double xSpeed, double zRotation, boolean squareInputs) {
263    xSpeed = MathUtil.clamp(xSpeed, -1.0, 1.0);
264    zRotation = MathUtil.clamp(zRotation, -1.0, 1.0);
265
266    // Square the inputs (while preserving the sign) to increase fine control
267    // while permitting full power.
268    if (squareInputs) {
269      xSpeed = Math.copySign(xSpeed * xSpeed, xSpeed);
270      zRotation = Math.copySign(zRotation * zRotation, zRotation);
271    }
272
273    double leftSpeed;
274    double rightSpeed;
275
276    double maxInput = Math.copySign(Math.max(Math.abs(xSpeed), Math.abs(zRotation)), xSpeed);
277
278    if (xSpeed >= 0.0) {
279      // First quadrant, else second quadrant
280      if (zRotation >= 0.0) {
281        leftSpeed = maxInput;
282        rightSpeed = xSpeed - zRotation;
283      } else {
284        leftSpeed = xSpeed + zRotation;
285        rightSpeed = maxInput;
286      }
287    } else {
288      // Third quadrant, else fourth quadrant
289      if (zRotation >= 0.0) {
290        leftSpeed = xSpeed + zRotation;
291        rightSpeed = maxInput;
292      } else {
293        leftSpeed = maxInput;
294        rightSpeed = xSpeed - zRotation;
295      }
296    }
297
298    // Normalize the wheel speeds
299    double maxMagnitude = Math.max(Math.abs(leftSpeed), Math.abs(rightSpeed));
300    if (maxMagnitude > 1.0) {
301      leftSpeed /= maxMagnitude;
302      rightSpeed /= maxMagnitude;
303    }
304
305    return new WheelSpeeds(leftSpeed, rightSpeed);
306  }
307
308  /**
309   * Curvature drive inverse kinematics for differential drive platform.
310   *
311   * <p>The rotation argument controls the curvature of the robot's path rather than its rate of
312   * heading change. This makes the robot more controllable at high speeds.
313   *
314   * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
315   * @param zRotation The normalized curvature [-1.0..1.0]. Clockwise is positive.
316   * @param allowTurnInPlace If set, overrides constant-curvature turning for turn-in-place
317   *     maneuvers. zRotation will control rotation rate around the Z axis instead of curvature.
318   * @return Wheel speeds.
319   */
320  @SuppressWarnings("ParameterName")
321  public static WheelSpeeds curvatureDriveIK(
322      double xSpeed, double zRotation, boolean allowTurnInPlace) {
323    xSpeed = MathUtil.clamp(xSpeed, -1.0, 1.0);
324    zRotation = MathUtil.clamp(zRotation, -1.0, 1.0);
325
326    double leftSpeed;
327    double rightSpeed;
328
329    if (allowTurnInPlace) {
330      leftSpeed = xSpeed + zRotation;
331      rightSpeed = xSpeed - zRotation;
332    } else {
333      leftSpeed = xSpeed + Math.abs(xSpeed) * zRotation;
334      rightSpeed = xSpeed - Math.abs(xSpeed) * zRotation;
335    }
336
337    // Normalize wheel speeds
338    double maxMagnitude = Math.max(Math.abs(leftSpeed), Math.abs(rightSpeed));
339    if (maxMagnitude > 1.0) {
340      leftSpeed /= maxMagnitude;
341      rightSpeed /= maxMagnitude;
342    }
343
344    return new WheelSpeeds(leftSpeed, rightSpeed);
345  }
346
347  /**
348   * Tank drive inverse kinematics for differential drive platform.
349   *
350   * @param leftSpeed The robot left side's speed along the X axis [-1.0..1.0]. Forward is positive.
351   * @param rightSpeed The robot right side's speed along the X axis [-1.0..1.0]. Forward is
352   *     positive.
353   * @param squareInputs If set, decreases the input sensitivity at low speeds.
354   * @return Wheel speeds.
355   */
356  public static WheelSpeeds tankDriveIK(double leftSpeed, double rightSpeed, boolean squareInputs) {
357    leftSpeed = MathUtil.clamp(leftSpeed, -1.0, 1.0);
358    rightSpeed = MathUtil.clamp(rightSpeed, -1.0, 1.0);
359
360    // Square the inputs (while preserving the sign) to increase fine control
361    // while permitting full power.
362    if (squareInputs) {
363      leftSpeed = Math.copySign(leftSpeed * leftSpeed, leftSpeed);
364      rightSpeed = Math.copySign(rightSpeed * rightSpeed, rightSpeed);
365    }
366
367    return new WheelSpeeds(leftSpeed, rightSpeed);
368  }
369
370  @Override
371  public void stopMotor() {
372    m_leftMotor.stopMotor();
373    m_rightMotor.stopMotor();
374    feed();
375  }
376
377  @Override
378  public String getDescription() {
379    return "DifferentialDrive";
380  }
381
382  @Override
383  public void initSendable(SendableBuilder builder) {
384    builder.setSmartDashboardType("DifferentialDrive");
385    builder.setActuator(true);
386    builder.setSafeState(this::stopMotor);
387    builder.addDoubleProperty("Left Motor Speed", m_leftMotor::get, m_leftMotor::set);
388    builder.addDoubleProperty(
389        "Right Motor Speed", () -> m_rightMotor.get(), x -> m_rightMotor.set(x));
390  }
391}