001/*----------------------------------------------------------------------------*/
002/* Copyright (c) 2008-2018 FIRST. All Rights Reserved.                        */
003/* Open Source Software - may be modified and shared by FRC teams. The code   */
004/* must be accompanied by the FIRST BSD license file in the root directory of */
005/* the project.                                                               */
006/*----------------------------------------------------------------------------*/
007
008package edu.wpi.first.wpilibj.drive;
009
010import edu.wpi.first.wpilibj.SpeedController;
011import edu.wpi.first.wpilibj.hal.FRCNetComm.tInstances;
012import edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType;
013import edu.wpi.first.wpilibj.hal.HAL;
014import edu.wpi.first.wpilibj.smartdashboard.SendableBuilder;
015
016/**
017 * A class for driving differential drive/skid-steer drive platforms such as the Kit of Parts drive
018 * base, "tank drive", or West Coast Drive.
019 *
020 * <p>These drive bases typically have drop-center / skid-steer with two or more wheels per side
021 * (e.g., 6WD or 8WD). This class takes a SpeedController per side. For four and
022 * six motor drivetrains, construct and pass in {@link edu.wpi.first.wpilibj.SpeedControllerGroup}
023 * instances as follows.
024 *
025 * <p>Four motor drivetrain:
026 * <pre><code>
027 * public class Robot {
028 *   Spark m_frontLeft = new Spark(1);
029 *   Spark m_rearLeft = new Spark(2);
030 *   SpeedControllerGroup m_left = new SpeedControllerGroup(m_frontLeft, m_rearLeft);
031 *
032 *   Spark m_frontRight = new Spark(3);
033 *   Spark m_rearRight = new Spark(4);
034 *   SpeedControllerGroup m_right = new SpeedControllerGroup(m_frontRight, m_rearRight);
035 *
036 *   DifferentialDrive m_drive = new DifferentialDrive(m_left, m_right);
037 * }
038 * </code></pre>
039 *
040 * <p>Six motor drivetrain:
041 * <pre><code>
042 * public class Robot {
043 *   Spark m_frontLeft = new Spark(1);
044 *   Spark m_midLeft = new Spark(2);
045 *   Spark m_rearLeft = new Spark(3);
046 *   SpeedControllerGroup m_left = new SpeedControllerGroup(m_frontLeft, m_midLeft, m_rearLeft);
047 *
048 *   Spark m_frontRight = new Spark(4);
049 *   Spark m_midRight = new Spark(5);
050 *   Spark m_rearRight = new Spark(6);
051 *   SpeedControllerGroup m_right = new SpeedControllerGroup(m_frontRight, m_midRight, m_rearRight);
052 *
053 *   DifferentialDrive m_drive = new DifferentialDrive(m_left, m_right);
054 * }
055 * </code></pre>
056 *
057 * <p>A differential drive robot has left and right wheels separated by an arbitrary width.
058 *
059 * <p>Drive base diagram:
060 * <pre>
061 * |_______|
062 * | |   | |
063 *   |   |
064 * |_|___|_|
065 * |       |
066 * </pre>
067 *
068 * <p>Each drive() function provides different inverse kinematic relations for a differential drive
069 * robot. Motor outputs for the right side are negated, so motor direction inversion by the user is
070 * usually unnecessary.
071 *
072 * <p>This library uses the NED axes convention (North-East-Down as external reference in the world
073 * frame): http://www.nuclearprojects.com/ins/images/axis_big.png.
074 *
075 * <p>The positive X axis points ahead, the positive Y axis points right, and the positive Z axis
076 * points down. Rotations follow the right-hand rule, so clockwise rotation around the Z axis is
077 * positive.
078 *
079 * <p>Inputs smaller then {@value edu.wpi.first.wpilibj.drive.RobotDriveBase#kDefaultDeadband} will
080 * be set to 0, and larger values will be scaled so that the full range is still used. This
081 * deadband value can be changed with {@link #setDeadband}.
082 *
083 * <p>RobotDrive porting guide:
084 * <br>{@link #tankDrive(double, double)} is equivalent to
085 * {@link edu.wpi.first.wpilibj.RobotDrive#tankDrive(double, double)} if a deadband of 0 is used.
086 * <br>{@link #arcadeDrive(double, double)} is equivalent to
087 * {@link edu.wpi.first.wpilibj.RobotDrive#arcadeDrive(double, double)} if a deadband of 0 is used
088 * and the the rotation input is inverted eg arcadeDrive(y, -rotation)
089 * <br>{@link #curvatureDrive(double, double, boolean)} is similar in concept to
090 * {@link edu.wpi.first.wpilibj.RobotDrive#drive(double, double)} with the addition of a quick turn
091 * mode. However, it is not designed to give exactly the same response.
092 */
093public class DifferentialDrive extends RobotDriveBase {
094  public static final double kDefaultQuickStopThreshold = 0.2;
095  public static final double kDefaultQuickStopAlpha = 0.1;
096
097  private static int instances = 0;
098
099  private SpeedController m_leftMotor;
100  private SpeedController m_rightMotor;
101
102  private double m_quickStopThreshold = kDefaultQuickStopThreshold;
103  private double m_quickStopAlpha = kDefaultQuickStopAlpha;
104  private double m_quickStopAccumulator = 0.0;
105  private boolean m_reported = false;
106
107  /**
108   * Construct a DifferentialDrive.
109   *
110   * <p>To pass multiple motors per side, use a {@link SpeedControllerGroup}. If a motor needs to be
111   * inverted, do so before passing it in.
112   */
113  public DifferentialDrive(SpeedController leftMotor, SpeedController rightMotor) {
114    m_leftMotor = leftMotor;
115    m_rightMotor = rightMotor;
116    addChild(m_leftMotor);
117    addChild(m_rightMotor);
118    instances++;
119    setName("DifferentialDrive", instances);
120  }
121
122  /**
123   * Arcade drive method for differential drive platform.
124   * The calculated values will be squared to decrease sensitivity at low speeds.
125   *
126   * @param xSpeed    The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
127   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Clockwise is
128   *                  positive.
129   */
130  @SuppressWarnings("ParameterName")
131  public void arcadeDrive(double xSpeed, double zRotation) {
132    arcadeDrive(xSpeed, zRotation, true);
133  }
134
135  /**
136   * Arcade drive method for differential drive platform.
137   *
138   * @param xSpeed        The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
139   * @param zRotation     The robot's rotation rate around the Z axis [-1.0..1.0]. Clockwise is
140   *                      positive.
141   * @param squaredInputs If set, decreases the input sensitivity at low speeds.
142   */
143  @SuppressWarnings("ParameterName")
144  public void arcadeDrive(double xSpeed, double zRotation, boolean squaredInputs) {
145    if (!m_reported) {
146      HAL.report(tResourceType.kResourceType_RobotDrive, 2, tInstances.kRobotDrive_ArcadeStandard);
147      m_reported = true;
148    }
149
150    xSpeed = limit(xSpeed);
151    xSpeed = applyDeadband(xSpeed, m_deadband);
152
153    zRotation = limit(zRotation);
154    zRotation = applyDeadband(zRotation, m_deadband);
155
156    // Square the inputs (while preserving the sign) to increase fine control
157    // while permitting full power.
158    if (squaredInputs) {
159      xSpeed = Math.copySign(xSpeed * xSpeed, xSpeed);
160      zRotation = Math.copySign(zRotation * zRotation, zRotation);
161    }
162
163    double leftMotorOutput;
164    double rightMotorOutput;
165
166    double maxInput = Math.copySign(Math.max(Math.abs(xSpeed), Math.abs(zRotation)), xSpeed);
167
168    if (xSpeed >= 0.0) {
169      // First quadrant, else second quadrant
170      if (zRotation >= 0.0) {
171        leftMotorOutput = maxInput;
172        rightMotorOutput = xSpeed - zRotation;
173      } else {
174        leftMotorOutput = xSpeed + zRotation;
175        rightMotorOutput = maxInput;
176      }
177    } else {
178      // Third quadrant, else fourth quadrant
179      if (zRotation >= 0.0) {
180        leftMotorOutput = xSpeed + zRotation;
181        rightMotorOutput = maxInput;
182      } else {
183        leftMotorOutput = maxInput;
184        rightMotorOutput = xSpeed - zRotation;
185      }
186    }
187
188    m_leftMotor.set(limit(leftMotorOutput) * m_maxOutput);
189    m_rightMotor.set(-limit(rightMotorOutput) * m_maxOutput);
190
191    m_safetyHelper.feed();
192  }
193
194  /**
195   * Curvature drive method for differential drive platform.
196   *
197   * <p>The rotation argument controls the curvature of the robot's path rather than its rate of
198   * heading change. This makes the robot more controllable at high speeds. Also handles the
199   * robot's quick turn functionality - "quick turn" overrides constant-curvature turning for
200   * turn-in-place maneuvers.
201   *
202   * @param xSpeed      The robot's speed along the X axis [-1.0..1.0]. Forward is positive.
203   * @param zRotation   The robot's rotation rate around the Z axis [-1.0..1.0]. Clockwise is
204   *                    positive.
205   * @param isQuickTurn If set, overrides constant-curvature turning for
206   *                    turn-in-place maneuvers.
207   */
208  @SuppressWarnings("ParameterName")
209  public void curvatureDrive(double xSpeed, double zRotation, boolean isQuickTurn) {
210    if (!m_reported) {
211      // HAL.report(tResourceType.kResourceType_RobotDrive, 2, tInstances.kRobotDrive_Curvature);
212      m_reported = true;
213    }
214
215    xSpeed = limit(xSpeed);
216    xSpeed = applyDeadband(xSpeed, m_deadband);
217
218    zRotation = limit(zRotation);
219    zRotation = applyDeadband(zRotation, m_deadband);
220
221    double angularPower;
222    boolean overPower;
223
224    if (isQuickTurn) {
225      if (Math.abs(xSpeed) < m_quickStopThreshold) {
226        m_quickStopAccumulator = (1 - m_quickStopAlpha) * m_quickStopAccumulator
227            + m_quickStopAlpha * limit(zRotation) * 2;
228      }
229      overPower = true;
230      angularPower = zRotation;
231    } else {
232      overPower = false;
233      angularPower = Math.abs(xSpeed) * zRotation - m_quickStopAccumulator;
234
235      if (m_quickStopAccumulator > 1) {
236        m_quickStopAccumulator -= 1;
237      } else if (m_quickStopAccumulator < -1) {
238        m_quickStopAccumulator += 1;
239      } else {
240        m_quickStopAccumulator = 0.0;
241      }
242    }
243
244    double leftMotorOutput = xSpeed + angularPower;
245    double rightMotorOutput = xSpeed - angularPower;
246
247    // If rotation is overpowered, reduce both outputs to within acceptable range
248    if (overPower) {
249      if (leftMotorOutput > 1.0) {
250        rightMotorOutput -= leftMotorOutput - 1.0;
251        leftMotorOutput = 1.0;
252      } else if (rightMotorOutput > 1.0) {
253        leftMotorOutput -= rightMotorOutput - 1.0;
254        rightMotorOutput = 1.0;
255      } else if (leftMotorOutput < -1.0) {
256        rightMotorOutput -= leftMotorOutput + 1.0;
257        leftMotorOutput = -1.0;
258      } else if (rightMotorOutput < -1.0) {
259        leftMotorOutput -= rightMotorOutput + 1.0;
260        rightMotorOutput = -1.0;
261      }
262    }
263
264    m_leftMotor.set(leftMotorOutput * m_maxOutput);
265    m_rightMotor.set(-rightMotorOutput * m_maxOutput);
266
267    m_safetyHelper.feed();
268  }
269
270  /**
271   * Tank drive method for differential drive platform.
272   * The calculated values will be squared to decrease sensitivity at low speeds.
273   *
274   * @param leftSpeed  The robot's left side speed along the X axis [-1.0..1.0]. Forward is
275   *                   positive.
276   * @param rightSpeed The robot's right side speed along the X axis [-1.0..1.0]. Forward is
277   *                   positive.
278   */
279  public void tankDrive(double leftSpeed, double rightSpeed) {
280    tankDrive(leftSpeed, rightSpeed, true);
281  }
282
283  /**
284   * Tank drive method for differential drive platform.
285   *
286   * @param leftSpeed     The robot left side's speed along the X axis [-1.0..1.0]. Forward is
287   *                      positive.
288   * @param rightSpeed    The robot right side's speed along the X axis [-1.0..1.0]. Forward is
289   *                      positive.
290   * @param squaredInputs If set, decreases the input sensitivity at low speeds.
291   */
292  public void tankDrive(double leftSpeed, double rightSpeed, boolean squaredInputs) {
293    if (!m_reported) {
294      HAL.report(tResourceType.kResourceType_RobotDrive, 2, tInstances.kRobotDrive_Tank);
295      m_reported = true;
296    }
297
298    leftSpeed = limit(leftSpeed);
299    leftSpeed = applyDeadband(leftSpeed, m_deadband);
300
301    rightSpeed = limit(rightSpeed);
302    rightSpeed = applyDeadband(rightSpeed, m_deadband);
303
304    // Square the inputs (while preserving the sign) to increase fine control
305    // while permitting full power.
306    if (squaredInputs) {
307      leftSpeed = Math.copySign(leftSpeed * leftSpeed, leftSpeed);
308      rightSpeed = Math.copySign(rightSpeed * rightSpeed, rightSpeed);
309    }
310
311    m_leftMotor.set(leftSpeed * m_maxOutput);
312    m_rightMotor.set(-rightSpeed * m_maxOutput);
313
314    m_safetyHelper.feed();
315  }
316
317  /**
318   * Sets the QuickStop speed threshold in curvature drive.
319   *
320   * <p>QuickStop compensates for the robot's moment of inertia when stopping after a QuickTurn.
321   *
322   * <p>While QuickTurn is enabled, the QuickStop accumulator takes on the rotation rate value
323   * outputted by the low-pass filter when the robot's speed along the X axis is below the
324   * threshold. When QuickTurn is disabled, the accumulator's value is applied against the computed
325   * angular power request to slow the robot's rotation.
326   *
327   * @param threshold X speed below which quick stop accumulator will receive rotation rate values
328   *                  [0..1.0].
329   */
330  public void setQuickStopThreshold(double threshold) {
331    m_quickStopThreshold = threshold;
332  }
333
334  /**
335   * Sets the low-pass filter gain for QuickStop in curvature drive.
336   *
337   * <p>The low-pass filter filters incoming rotation rate commands to smooth out high frequency
338   * changes.
339   *
340   * @param alpha Low-pass filter gain [0.0..2.0]. Smaller values result in slower output changes.
341   *              Values between 1.0 and 2.0 result in output oscillation. Values below 0.0 and
342   *              above 2.0 are unstable.
343   */
344  public void setQuickStopAlpha(double alpha) {
345    m_quickStopAlpha = alpha;
346  }
347
348  @Override
349  public void stopMotor() {
350    m_leftMotor.stopMotor();
351    m_rightMotor.stopMotor();
352    m_safetyHelper.feed();
353  }
354
355  @Override
356  public String getDescription() {
357    return "DifferentialDrive";
358  }
359
360  @Override
361  public void initSendable(SendableBuilder builder) {
362    builder.setSmartDashboardType("DifferentialDrive");
363    builder.addDoubleProperty("Left Motor Speed", m_leftMotor::get, m_leftMotor::set);
364    builder.addDoubleProperty(
365        "Right Motor Speed",
366        () -> -m_rightMotor.get(),
367        x -> m_rightMotor.set(-x));
368  }
369}