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.hardware.motor; 006 007import java.util.ArrayList; 008import org.wpilib.hardware.discrete.PWM; 009import org.wpilib.hardware.hal.SimDevice; 010import org.wpilib.hardware.hal.SimDevice.Direction; 011import org.wpilib.hardware.hal.SimDouble; 012import org.wpilib.system.RobotController; 013import org.wpilib.util.sendable.Sendable; 014import org.wpilib.util.sendable.SendableBuilder; 015import org.wpilib.util.sendable.SendableRegistry; 016 017/** Common base class for all PWM Motor Controllers. */ 018@SuppressWarnings("removal") 019public abstract class PWMMotorController extends MotorSafety 020 implements MotorController, Sendable, AutoCloseable { 021 private boolean m_isInverted; 022 private final ArrayList<PWMMotorController> m_followers = new ArrayList<>(); 023 024 /** PWM instances for motor controller. */ 025 protected PWM m_pwm; 026 027 private SimDevice m_simDevice; 028 private SimDouble m_simThrottle; 029 030 private boolean m_eliminateDeadband; 031 private int m_minPwm; 032 private int m_deadbandMinPwm; 033 private int m_centerPwm; 034 private int m_deadbandMaxPwm; 035 private int m_maxPwm; 036 037 /** 038 * Constructor. 039 * 040 * @param name Name to use for SendableRegistry 041 * @param channel The SmartIO channel that the controller is attached to. 042 */ 043 @SuppressWarnings("this-escape") 044 protected PWMMotorController(final String name, final int channel) { 045 m_pwm = new PWM(channel, false); 046 SendableRegistry.add(this, name, channel); 047 048 m_simDevice = SimDevice.create("PWMMotorController", channel); 049 if (m_simDevice != null) { 050 m_simThrottle = m_simDevice.createDouble("Throttle", Direction.OUTPUT, 0.0); 051 m_pwm.setSimDevice(m_simDevice); 052 } 053 } 054 055 /** Free the resource associated with the PWM channel and set the value to 0. */ 056 @Override 057 public void close() { 058 SendableRegistry.remove(this); 059 m_pwm.close(); 060 061 if (m_simDevice != null) { 062 m_simDevice.close(); 063 m_simDevice = null; 064 m_simThrottle = null; 065 } 066 } 067 068 private int getMinPositivePwm() { 069 if (m_eliminateDeadband) { 070 return m_deadbandMaxPwm; 071 } else { 072 return m_centerPwm + 1; 073 } 074 } 075 076 private int getMaxNegativePwm() { 077 if (m_eliminateDeadband) { 078 return m_deadbandMinPwm; 079 } else { 080 return m_centerPwm - 1; 081 } 082 } 083 084 private int getPositiveScaleFactor() { 085 return m_maxPwm - getMinPositivePwm(); 086 } 087 088 private int getNegativeScaleFactor() { 089 return getMaxNegativePwm() - m_minPwm; 090 } 091 092 /** 093 * Takes a duty cycle from -1 to 1 (the sign indicates direction), and outputs it in the 094 * microsecond format. 095 * 096 * @param dutyCycle The duty cycle between -1 and 1 (sign indicates direction). 097 */ 098 protected final void setDutyCycleInternal(double dutyCycle) { 099 if (Double.isFinite(dutyCycle)) { 100 dutyCycle = Math.clamp(dutyCycle, -1.0, 1.0); 101 } else { 102 dutyCycle = 0.0; 103 } 104 105 if (m_simThrottle != null) { 106 m_simThrottle.set(dutyCycle); 107 } 108 109 int rawValue; 110 if (dutyCycle == 0.0) { 111 rawValue = m_centerPwm; 112 } else if (dutyCycle > 0.0) { 113 rawValue = (int) Math.round(dutyCycle * getPositiveScaleFactor()) + getMinPositivePwm(); 114 } else { 115 rawValue = (int) Math.round(dutyCycle * getNegativeScaleFactor()) + getMaxNegativePwm(); 116 } 117 118 m_pwm.setPulseTimeMicroseconds(rawValue); 119 } 120 121 /** 122 * Gets the duty cycle from -1 to 1 (the sign indicates direction), from the currently set pulse 123 * time. 124 * 125 * @return motor controller duty cycle 126 */ 127 protected final double getDutyCycleInternal() { 128 int rawValue = m_pwm.getPulseTimeMicroseconds(); 129 130 if (rawValue == 0) { 131 return 0.0; 132 } else if (rawValue > m_maxPwm) { 133 return 1.0; 134 } else if (rawValue < m_minPwm) { 135 return -1.0; 136 } else if (rawValue > getMinPositivePwm()) { 137 return (rawValue - getMinPositivePwm()) / (double) getPositiveScaleFactor(); 138 } else if (rawValue < getMaxNegativePwm()) { 139 return (rawValue - getMaxNegativePwm()) / (double) getNegativeScaleFactor(); 140 } else { 141 return 0.0; 142 } 143 } 144 145 /** 146 * Sets the bounds in microseconds for the controller. 147 * 148 * @param maxPwm maximum 149 * @param deadbandMaxPwm deadband max 150 * @param centerPwm center 151 * @param deadbandMinPwm deadband min 152 * @param minPwm minimum 153 */ 154 protected final void setBoundsMicroseconds( 155 int maxPwm, int deadbandMaxPwm, int centerPwm, int deadbandMinPwm, int minPwm) { 156 m_maxPwm = maxPwm; 157 m_deadbandMaxPwm = deadbandMaxPwm; 158 m_centerPwm = centerPwm; 159 m_deadbandMinPwm = deadbandMinPwm; 160 m_minPwm = minPwm; 161 } 162 163 @Override 164 public void setThrottle(double throttle) { 165 if (m_isInverted) { 166 throttle = -throttle; 167 } 168 setDutyCycleInternal(throttle); 169 170 for (var follower : m_followers) { 171 follower.setThrottle(throttle); 172 } 173 174 feed(); 175 } 176 177 @Override 178 public double getThrottle() { 179 return getDutyCycleInternal() * (m_isInverted ? -1.0 : 1.0); 180 } 181 182 /** 183 * Gets the voltage output of the motor controller, nominally between -12 V and 12 V. 184 * 185 * @return The voltage of the motor controller, nominally between -12 V and 12 V. 186 */ 187 public double getVoltage() { 188 return getThrottle() * RobotController.getBatteryVoltage(); 189 } 190 191 @Override 192 public void setInverted(boolean isInverted) { 193 m_isInverted = isInverted; 194 } 195 196 @Override 197 public boolean getInverted() { 198 return m_isInverted; 199 } 200 201 @Override 202 public void disable() { 203 m_pwm.setDisabled(); 204 205 if (m_simThrottle != null) { 206 m_simThrottle.set(0.0); 207 } 208 209 for (var follower : m_followers) { 210 follower.disable(); 211 } 212 } 213 214 @Override 215 public void stopMotor() { 216 disable(); 217 } 218 219 @Override 220 public String getDescription() { 221 return "PWM " + getChannel(); 222 } 223 224 /** 225 * Gets the backing PWM handle. 226 * 227 * @return The pwm handle. 228 */ 229 public int getPwmHandle() { 230 return m_pwm.getHandle(); 231 } 232 233 /** 234 * Gets the PWM channel number. 235 * 236 * @return The channel number. 237 */ 238 public int getChannel() { 239 return m_pwm.getChannel(); 240 } 241 242 /** 243 * Optionally eliminate the deadband from a motor controller. 244 * 245 * @param eliminateDeadband If true, set the motor curve for the motor controller to eliminate the 246 * deadband in the middle of the range. Otherwise, keep the full range without modifying any 247 * values. 248 */ 249 public void enableDeadbandElimination(boolean eliminateDeadband) { 250 m_eliminateDeadband = eliminateDeadband; 251 } 252 253 /** 254 * Make the given PWM motor controller follow the output of this one. 255 * 256 * @param follower The motor controller follower. 257 */ 258 public void addFollower(PWMMotorController follower) { 259 m_followers.add(follower); 260 } 261 262 @Override 263 public void initSendable(SendableBuilder builder) { 264 builder.setSmartDashboardType("Motor Controller"); 265 builder.setActuator(true); 266 builder.addDoubleProperty("Value", this::getThrottle, this::setThrottle); 267 } 268}