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.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.motorcontrol.MotorController; 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. 075 * 076 * <p>This library uses the NWU axes convention (North-West-Up as external reference in the world 077 * frame). The positive X axis points ahead, the positive Y axis points to the left, and the 078 * positive Z axis points up. Rotations follow the right-hand rule, so counterclockwise rotation 079 * around the Z axis is positive. 080 * 081 * <p>Inputs smaller then {@value edu.wpi.first.wpilibj.drive.RobotDriveBase#kDefaultDeadband} will 082 * be set to 0, and larger values will be scaled so that the full range is still used. This deadband 083 * value can be changed with {@link #setDeadband}. 084 * 085 * <p>{@link edu.wpi.first.wpilibj.MotorSafety} is enabled by default. The tankDrive, arcadeDrive, 086 * or curvatureDrive methods should be called periodically to avoid Motor Safety timeouts. 087 */ 088public class DifferentialDrive extends RobotDriveBase implements Sendable, AutoCloseable { 089 private static int instances; 090 091 private final MotorController m_leftMotor; 092 private final MotorController m_rightMotor; 093 094 private boolean m_reported; 095 096 /** 097 * Wheel speeds for a differential drive. 098 * 099 * <p>Uses normalized voltage [-1.0..1.0]. 100 */ 101 @SuppressWarnings("MemberName") 102 public static class WheelSpeeds { 103 public double left; 104 public double right; 105 106 /** Constructs a WheelSpeeds with zeroes for left and right speeds. */ 107 public WheelSpeeds() {} 108 109 /** 110 * Constructs a WheelSpeeds. 111 * 112 * @param left The left speed [-1.0..1.0]. 113 * @param right The right speed [-1.0..1.0]. 114 */ 115 public WheelSpeeds(double left, double right) { 116 this.left = left; 117 this.right = right; 118 } 119 } 120 121 /** 122 * Construct a DifferentialDrive. 123 * 124 * <p>To pass multiple motors per side, use a {@link 125 * edu.wpi.first.wpilibj.motorcontrol.MotorControllerGroup}. If a motor needs to be inverted, do 126 * so before passing it in. 127 * 128 * @param leftMotor Left motor. 129 * @param rightMotor Right motor. 130 */ 131 @SuppressWarnings("this-escape") 132 public DifferentialDrive(MotorController leftMotor, MotorController rightMotor) { 133 requireNonNullParam(leftMotor, "leftMotor", "DifferentialDrive"); 134 requireNonNullParam(rightMotor, "rightMotor", "DifferentialDrive"); 135 136 m_leftMotor = leftMotor; 137 m_rightMotor = rightMotor; 138 SendableRegistry.addChild(this, m_leftMotor); 139 SendableRegistry.addChild(this, m_rightMotor); 140 instances++; 141 SendableRegistry.addLW(this, "DifferentialDrive", instances); 142 } 143 144 @Override 145 public void close() { 146 SendableRegistry.remove(this); 147 } 148 149 /** 150 * Arcade drive method for differential drive platform. The calculated values will be squared to 151 * decrease sensitivity at low speeds. 152 * 153 * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive. 154 * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is 155 * positive. 156 */ 157 public void arcadeDrive(double xSpeed, double zRotation) { 158 arcadeDrive(xSpeed, zRotation, true); 159 } 160 161 /** 162 * Arcade drive method for differential drive platform. 163 * 164 * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive. 165 * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is 166 * positive. 167 * @param squareInputs If set, decreases the input sensitivity at low speeds. 168 */ 169 public void arcadeDrive(double xSpeed, double zRotation, boolean squareInputs) { 170 if (!m_reported) { 171 HAL.report( 172 tResourceType.kResourceType_RobotDrive, tInstances.kRobotDrive2_DifferentialArcade, 2); 173 m_reported = true; 174 } 175 176 xSpeed = MathUtil.applyDeadband(xSpeed, m_deadband); 177 zRotation = MathUtil.applyDeadband(zRotation, m_deadband); 178 179 var speeds = arcadeDriveIK(xSpeed, zRotation, squareInputs); 180 181 m_leftMotor.set(speeds.left * m_maxOutput); 182 m_rightMotor.set(speeds.right * m_maxOutput); 183 184 feed(); 185 } 186 187 /** 188 * Curvature drive method for differential drive platform. 189 * 190 * <p>The rotation argument controls the curvature of the robot's path rather than its rate of 191 * heading change. This makes the robot more controllable at high speeds. 192 * 193 * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive. 194 * @param zRotation The normalized curvature [-1.0..1.0]. Counterclockwise is positive. 195 * @param allowTurnInPlace If set, overrides constant-curvature turning for turn-in-place 196 * maneuvers. zRotation will control turning rate instead of curvature. 197 */ 198 public void curvatureDrive(double xSpeed, double zRotation, boolean allowTurnInPlace) { 199 if (!m_reported) { 200 HAL.report( 201 tResourceType.kResourceType_RobotDrive, tInstances.kRobotDrive2_DifferentialCurvature, 2); 202 m_reported = true; 203 } 204 205 xSpeed = MathUtil.applyDeadband(xSpeed, m_deadband); 206 zRotation = MathUtil.applyDeadband(zRotation, m_deadband); 207 208 var speeds = curvatureDriveIK(xSpeed, zRotation, allowTurnInPlace); 209 210 m_leftMotor.set(speeds.left * m_maxOutput); 211 m_rightMotor.set(speeds.right * m_maxOutput); 212 213 feed(); 214 } 215 216 /** 217 * Tank drive method for differential drive platform. The calculated values will be squared to 218 * decrease sensitivity at low speeds. 219 * 220 * @param leftSpeed The robot's left side speed along the X axis [-1.0..1.0]. Forward is positive. 221 * @param rightSpeed The robot's right side speed along the X axis [-1.0..1.0]. Forward is 222 * positive. 223 */ 224 public void tankDrive(double leftSpeed, double rightSpeed) { 225 tankDrive(leftSpeed, rightSpeed, true); 226 } 227 228 /** 229 * Tank drive method for differential drive platform. 230 * 231 * @param leftSpeed The robot left side's speed along the X axis [-1.0..1.0]. Forward is positive. 232 * @param rightSpeed The robot right side's speed along the X axis [-1.0..1.0]. Forward is 233 * positive. 234 * @param squareInputs If set, decreases the input sensitivity at low speeds. 235 */ 236 public void tankDrive(double leftSpeed, double rightSpeed, boolean squareInputs) { 237 if (!m_reported) { 238 HAL.report( 239 tResourceType.kResourceType_RobotDrive, tInstances.kRobotDrive2_DifferentialTank, 2); 240 m_reported = true; 241 } 242 243 leftSpeed = MathUtil.applyDeadband(leftSpeed, m_deadband); 244 rightSpeed = MathUtil.applyDeadband(rightSpeed, m_deadband); 245 246 var speeds = tankDriveIK(leftSpeed, rightSpeed, squareInputs); 247 248 m_leftMotor.set(speeds.left * m_maxOutput); 249 m_rightMotor.set(speeds.right * m_maxOutput); 250 251 feed(); 252 } 253 254 /** 255 * Arcade drive inverse kinematics for differential drive platform. 256 * 257 * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive. 258 * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is 259 * positive. 260 * @param squareInputs If set, decreases the input sensitivity at low speeds. 261 * @return Wheel speeds [-1.0..1.0]. 262 */ 263 public static WheelSpeeds arcadeDriveIK(double xSpeed, double zRotation, boolean squareInputs) { 264 xSpeed = MathUtil.clamp(xSpeed, -1.0, 1.0); 265 zRotation = MathUtil.clamp(zRotation, -1.0, 1.0); 266 267 // Square the inputs (while preserving the sign) to increase fine control 268 // while permitting full power. 269 if (squareInputs) { 270 xSpeed = Math.copySign(xSpeed * xSpeed, xSpeed); 271 zRotation = Math.copySign(zRotation * zRotation, zRotation); 272 } 273 274 double leftSpeed = xSpeed - zRotation; 275 double rightSpeed = xSpeed + zRotation; 276 277 // Find the maximum possible value of (throttle + turn) along the vector 278 // that the joystick is pointing, then desaturate the wheel speeds 279 double greaterInput = Math.max(Math.abs(xSpeed), Math.abs(zRotation)); 280 double lesserInput = Math.min(Math.abs(xSpeed), Math.abs(zRotation)); 281 if (greaterInput == 0.0) { 282 return new WheelSpeeds(0.0, 0.0); 283 } 284 double saturatedInput = (greaterInput + lesserInput) / greaterInput; 285 leftSpeed /= saturatedInput; 286 rightSpeed /= saturatedInput; 287 288 return new WheelSpeeds(leftSpeed, rightSpeed); 289 } 290 291 /** 292 * Curvature drive inverse kinematics for differential drive platform. 293 * 294 * <p>The rotation argument controls the curvature of the robot's path rather than its rate of 295 * heading change. This makes the robot more controllable at high speeds. 296 * 297 * @param xSpeed The robot's speed along the X axis [-1.0..1.0]. Forward is positive. 298 * @param zRotation The normalized curvature [-1.0..1.0]. Counterclockwise is positive. 299 * @param allowTurnInPlace If set, overrides constant-curvature turning for turn-in-place 300 * maneuvers. zRotation will control rotation rate around the Z axis instead of curvature. 301 * @return Wheel speeds [-1.0..1.0]. 302 */ 303 public static WheelSpeeds curvatureDriveIK( 304 double xSpeed, double zRotation, boolean allowTurnInPlace) { 305 xSpeed = MathUtil.clamp(xSpeed, -1.0, 1.0); 306 zRotation = MathUtil.clamp(zRotation, -1.0, 1.0); 307 308 double leftSpeed; 309 double rightSpeed; 310 311 if (allowTurnInPlace) { 312 leftSpeed = xSpeed - zRotation; 313 rightSpeed = xSpeed + zRotation; 314 } else { 315 leftSpeed = xSpeed - Math.abs(xSpeed) * zRotation; 316 rightSpeed = xSpeed + Math.abs(xSpeed) * zRotation; 317 } 318 319 // Desaturate wheel speeds 320 double maxMagnitude = Math.max(Math.abs(leftSpeed), Math.abs(rightSpeed)); 321 if (maxMagnitude > 1.0) { 322 leftSpeed /= maxMagnitude; 323 rightSpeed /= maxMagnitude; 324 } 325 326 return new WheelSpeeds(leftSpeed, rightSpeed); 327 } 328 329 /** 330 * Tank drive inverse kinematics for differential drive platform. 331 * 332 * @param leftSpeed The robot left side's speed along the X axis [-1.0..1.0]. Forward is positive. 333 * @param rightSpeed The robot right side's speed along the X axis [-1.0..1.0]. Forward is 334 * positive. 335 * @param squareInputs If set, decreases the input sensitivity at low speeds. 336 * @return Wheel speeds [-1.0..1.0]. 337 */ 338 public static WheelSpeeds tankDriveIK(double leftSpeed, double rightSpeed, boolean squareInputs) { 339 leftSpeed = MathUtil.clamp(leftSpeed, -1.0, 1.0); 340 rightSpeed = MathUtil.clamp(rightSpeed, -1.0, 1.0); 341 342 // Square the inputs (while preserving the sign) to increase fine control 343 // while permitting full power. 344 if (squareInputs) { 345 leftSpeed = Math.copySign(leftSpeed * leftSpeed, leftSpeed); 346 rightSpeed = Math.copySign(rightSpeed * rightSpeed, rightSpeed); 347 } 348 349 return new WheelSpeeds(leftSpeed, rightSpeed); 350 } 351 352 @Override 353 public void stopMotor() { 354 m_leftMotor.stopMotor(); 355 m_rightMotor.stopMotor(); 356 feed(); 357 } 358 359 @Override 360 public String getDescription() { 361 return "DifferentialDrive"; 362 } 363 364 @Override 365 public void initSendable(SendableBuilder builder) { 366 builder.setSmartDashboardType("DifferentialDrive"); 367 builder.setActuator(true); 368 builder.setSafeState(this::stopMotor); 369 builder.addDoubleProperty("Left Motor Speed", m_leftMotor::get, m_leftMotor::set); 370 builder.addDoubleProperty("Right Motor Speed", m_rightMotor::get, m_rightMotor::set); 371 } 372}