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}