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 org.wpilib.drive; 006 007import static org.wpilib.util.ErrorMessages.requireNonNullParam; 008 009import java.util.function.DoubleConsumer; 010import org.wpilib.hardware.hal.HAL; 011import org.wpilib.hardware.motor.MotorController; 012import org.wpilib.math.util.MathUtil; 013import org.wpilib.util.sendable.Sendable; 014import org.wpilib.util.sendable.SendableBuilder; 015import org.wpilib.util.sendable.SendableRegistry; 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 * org.wpilib.hardware.motor.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 org.wpilib.drive.RobotDriveBase#kDefaultDeadband} will be set to 047 * 0, and larger values will be scaled so that the full range is still used. This deadband value can 048 * be changed with {@link #setDeadband}. 049 * 050 * <p>{@link org.wpilib.hardware.motor.MotorSafety} is enabled by default. The tankDrive, 051 * arcadeDrive, or curvatureDrive methods should be called periodically to avoid Motor Safety 052 * timeouts. 053 */ 054public class DifferentialDrive extends RobotDriveBase implements Sendable, AutoCloseable { 055 private static int instances; 056 057 private final DoubleConsumer m_leftMotor; 058 private final DoubleConsumer m_rightMotor; 059 060 // Used for Sendable property getters 061 private double m_leftOutput; 062 private double m_rightOutput; 063 064 private boolean m_reported; 065 066 /** 067 * Wheel velocities for a differential drive. 068 * 069 * <p>Uses normalized voltage [-1.0..1.0]. 070 */ 071 @SuppressWarnings("MemberName") 072 public static class WheelVelocities { 073 /** Left wheel velocity. */ 074 public double left; 075 076 /** Right wheel velocity. */ 077 public double right; 078 079 /** Constructs a WheelVelocities with zeroes for left and right velocities. */ 080 public WheelVelocities() {} 081 082 /** 083 * Constructs a WheelVelocities. 084 * 085 * @param left The left velocity [-1.0..1.0]. 086 * @param right The right velocity [-1.0..1.0]. 087 */ 088 public WheelVelocities(double left, double right) { 089 this.left = left; 090 this.right = right; 091 } 092 } 093 094 /** 095 * Construct a DifferentialDrive. 096 * 097 * <p>To pass multiple motors per side, use CAN motor controller followers or {@link 098 * org.wpilib.hardware.motor.PWMMotorController#addFollower(PWMMotorController)}. If a motor needs 099 * to be inverted, do so before passing it in. 100 * 101 * @param leftMotor Left motor. 102 * @param rightMotor Right motor. 103 */ 104 @SuppressWarnings({"removal", "this-escape"}) 105 public DifferentialDrive(MotorController leftMotor, MotorController rightMotor) { 106 this( 107 (double output) -> leftMotor.setDutyCycle(output), 108 (double output) -> rightMotor.setDutyCycle(output)); 109 SendableRegistry.addChild(this, leftMotor); 110 SendableRegistry.addChild(this, rightMotor); 111 } 112 113 /** 114 * Construct a DifferentialDrive. 115 * 116 * <p>To pass multiple motors per side, use CAN motor controller followers or {@link 117 * org.wpilib.hardware.motor.PWMMotorController#addFollower(PWMMotorController)}. If a motor needs 118 * to be inverted, do so before passing it in. 119 * 120 * @param leftMotor Left motor setter. 121 * @param rightMotor Right motor setter. 122 */ 123 @SuppressWarnings("this-escape") 124 public DifferentialDrive(DoubleConsumer leftMotor, DoubleConsumer rightMotor) { 125 requireNonNullParam(leftMotor, "leftMotor", "DifferentialDrive"); 126 requireNonNullParam(rightMotor, "rightMotor", "DifferentialDrive"); 127 128 m_leftMotor = leftMotor; 129 m_rightMotor = rightMotor; 130 instances++; 131 SendableRegistry.add(this, "DifferentialDrive", instances); 132 } 133 134 @Override 135 public void close() { 136 SendableRegistry.remove(this); 137 } 138 139 /** 140 * Arcade drive method for differential drive platform. The calculated values will be squared to 141 * decrease sensitivity at low velocities. 142 * 143 * @param xVelocity The robot's velocity along the X axis [-1.0..1.0]. Forward is positive. 144 * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is 145 * positive. 146 */ 147 public void arcadeDrive(double xVelocity, double zRotation) { 148 arcadeDrive(xVelocity, zRotation, true); 149 } 150 151 /** 152 * Arcade drive method for differential drive platform. 153 * 154 * @param xVelocity The robot's velocity along the X axis [-1.0..1.0]. Forward is positive. 155 * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is 156 * positive. 157 * @param squareInputs If set, decreases the input sensitivity at low velocities. 158 */ 159 public void arcadeDrive(double xVelocity, double zRotation, boolean squareInputs) { 160 if (!m_reported) { 161 HAL.reportUsage("RobotDrive", "DifferentialArcade"); 162 m_reported = true; 163 } 164 165 xVelocity = MathUtil.applyDeadband(xVelocity, m_deadband); 166 zRotation = MathUtil.applyDeadband(zRotation, m_deadband); 167 168 var velocities = arcadeDriveIK(xVelocity, zRotation, squareInputs); 169 170 m_leftOutput = velocities.left * m_maxOutput; 171 m_rightOutput = velocities.right * m_maxOutput; 172 173 m_leftMotor.accept(m_leftOutput); 174 m_rightMotor.accept(m_rightOutput); 175 176 feed(); 177 } 178 179 /** 180 * Curvature drive method for differential drive platform. 181 * 182 * <p>The rotation argument controls the curvature of the robot's path rather than its rate of 183 * heading change. This makes the robot more controllable at high velocities. 184 * 185 * @param xVelocity The robot's velocity along the X axis [-1.0..1.0]. Forward is positive. 186 * @param zRotation The normalized curvature [-1.0..1.0]. Counterclockwise is positive. 187 * @param allowTurnInPlace If set, overrides constant-curvature turning for turn-in-place 188 * maneuvers. zRotation will control turning rate instead of curvature. 189 */ 190 public void curvatureDrive(double xVelocity, double zRotation, boolean allowTurnInPlace) { 191 if (!m_reported) { 192 HAL.reportUsage("RobotDrive", "DifferentialCurvature"); 193 m_reported = true; 194 } 195 196 xVelocity = MathUtil.applyDeadband(xVelocity, m_deadband); 197 zRotation = MathUtil.applyDeadband(zRotation, m_deadband); 198 199 var velocities = curvatureDriveIK(xVelocity, zRotation, allowTurnInPlace); 200 201 m_leftOutput = velocities.left * m_maxOutput; 202 m_rightOutput = velocities.right * m_maxOutput; 203 204 m_leftMotor.accept(m_leftOutput); 205 m_rightMotor.accept(m_rightOutput); 206 207 feed(); 208 } 209 210 /** 211 * Tank drive method for differential drive platform. The calculated values will be squared to 212 * decrease sensitivity at low velocities. 213 * 214 * @param leftVelocity The robot's left side velocity along the X axis [-1.0..1.0]. Forward is 215 * positive. 216 * @param rightVelocity The robot's right side velocity along the X axis [-1.0..1.0]. Forward is 217 * positive. 218 */ 219 public void tankDrive(double leftVelocity, double rightVelocity) { 220 tankDrive(leftVelocity, rightVelocity, true); 221 } 222 223 /** 224 * Tank drive method for differential drive platform. 225 * 226 * @param leftVelocity The robot left side's velocity along the X axis [-1.0..1.0]. Forward is 227 * positive. 228 * @param rightVelocity The robot right side's velocity along the X axis [-1.0..1.0]. Forward is 229 * positive. 230 * @param squareInputs If set, decreases the input sensitivity at low velocities. 231 */ 232 public void tankDrive(double leftVelocity, double rightVelocity, boolean squareInputs) { 233 if (!m_reported) { 234 HAL.reportUsage("RobotDrive", "DifferentialTank"); 235 m_reported = true; 236 } 237 238 leftVelocity = MathUtil.applyDeadband(leftVelocity, m_deadband); 239 rightVelocity = MathUtil.applyDeadband(rightVelocity, m_deadband); 240 241 var velocities = tankDriveIK(leftVelocity, rightVelocity, squareInputs); 242 243 m_leftOutput = velocities.left * m_maxOutput; 244 m_rightOutput = velocities.right * m_maxOutput; 245 246 m_leftMotor.accept(m_leftOutput); 247 m_rightMotor.accept(m_rightOutput); 248 249 feed(); 250 } 251 252 /** 253 * Arcade drive inverse kinematics for differential drive platform. 254 * 255 * @param xVelocity The robot's velocity 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]. Counterclockwise is 257 * positive. 258 * @param squareInputs If set, decreases the input sensitivity at low velocities. 259 * @return Wheel velocities [-1.0..1.0]. 260 */ 261 public static WheelVelocities arcadeDriveIK( 262 double xVelocity, double zRotation, boolean squareInputs) { 263 xVelocity = Math.clamp(xVelocity, -1.0, 1.0); 264 zRotation = Math.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 xVelocity = MathUtil.copyDirectionPow(xVelocity, 2); 270 zRotation = MathUtil.copyDirectionPow(zRotation, 2); 271 } 272 273 double leftVelocity = xVelocity - zRotation; 274 double rightVelocity = xVelocity + zRotation; 275 276 // Find the maximum possible value of (throttle + turn) along the vector 277 // that the joystick is pointing, then desaturate the wheel velocities 278 double greaterInput = Math.max(Math.abs(xVelocity), Math.abs(zRotation)); 279 double lesserInput = Math.min(Math.abs(xVelocity), Math.abs(zRotation)); 280 if (greaterInput == 0.0) { 281 return new WheelVelocities(0.0, 0.0); 282 } 283 double saturatedInput = (greaterInput + lesserInput) / greaterInput; 284 leftVelocity /= saturatedInput; 285 rightVelocity /= saturatedInput; 286 287 return new WheelVelocities(leftVelocity, rightVelocity); 288 } 289 290 /** 291 * Curvature drive inverse kinematics for differential drive platform. 292 * 293 * <p>The rotation argument controls the curvature of the robot's path rather than its rate of 294 * heading change. This makes the robot more controllable at high velocities. 295 * 296 * @param xVelocity The robot's velocity along the X axis [-1.0..1.0]. Forward is positive. 297 * @param zRotation The normalized curvature [-1.0..1.0]. Counterclockwise is positive. 298 * @param allowTurnInPlace If set, overrides constant-curvature turning for turn-in-place 299 * maneuvers. zRotation will control rotation rate around the Z axis instead of curvature. 300 * @return Wheel velocities [-1.0..1.0]. 301 */ 302 public static WheelVelocities curvatureDriveIK( 303 double xVelocity, double zRotation, boolean allowTurnInPlace) { 304 xVelocity = Math.clamp(xVelocity, -1.0, 1.0); 305 zRotation = Math.clamp(zRotation, -1.0, 1.0); 306 307 double leftVelocity; 308 double rightVelocity; 309 310 if (allowTurnInPlace) { 311 leftVelocity = xVelocity - zRotation; 312 rightVelocity = xVelocity + zRotation; 313 } else { 314 leftVelocity = xVelocity - Math.abs(xVelocity) * zRotation; 315 rightVelocity = xVelocity + Math.abs(xVelocity) * zRotation; 316 } 317 318 // Desaturate wheel velocities 319 double maxMagnitude = Math.max(Math.abs(leftVelocity), Math.abs(rightVelocity)); 320 if (maxMagnitude > 1.0) { 321 leftVelocity /= maxMagnitude; 322 rightVelocity /= maxMagnitude; 323 } 324 325 return new WheelVelocities(leftVelocity, rightVelocity); 326 } 327 328 /** 329 * Tank drive inverse kinematics for differential drive platform. 330 * 331 * @param leftVelocity The robot left side's velocity along the X axis [-1.0..1.0]. Forward is 332 * positive. 333 * @param rightVelocity The robot right side's velocity along the X axis [-1.0..1.0]. Forward is 334 * positive. 335 * @param squareInputs If set, decreases the input sensitivity at low velocities. 336 * @return Wheel velocities [-1.0..1.0]. 337 */ 338 public static WheelVelocities tankDriveIK( 339 double leftVelocity, double rightVelocity, boolean squareInputs) { 340 leftVelocity = Math.clamp(leftVelocity, -1.0, 1.0); 341 rightVelocity = Math.clamp(rightVelocity, -1.0, 1.0); 342 343 // Square the inputs (while preserving the sign) to increase fine control 344 // while permitting full power. 345 if (squareInputs) { 346 leftVelocity = MathUtil.copyDirectionPow(leftVelocity, 2); 347 rightVelocity = MathUtil.copyDirectionPow(rightVelocity, 2); 348 } 349 350 return new WheelVelocities(leftVelocity, rightVelocity); 351 } 352 353 @Override 354 public void stopMotor() { 355 m_leftOutput = 0.0; 356 m_rightOutput = 0.0; 357 358 m_leftMotor.accept(0.0); 359 m_rightMotor.accept(0.0); 360 361 feed(); 362 } 363 364 @Override 365 public String getDescription() { 366 return "DifferentialDrive"; 367 } 368 369 @Override 370 public void initSendable(SendableBuilder builder) { 371 builder.setSmartDashboardType("DifferentialDrive"); 372 builder.setActuator(true); 373 builder.addDoubleProperty("Left Motor Velocity", () -> m_leftOutput, m_leftMotor); 374 builder.addDoubleProperty("Right Motor Velocity", () -> m_rightOutput, m_rightMotor); 375 } 376}