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}