[hal, wpilib] PWM Rewrite (#7845)

The HAL will only contain the output period and the raw microseconds. Higher level things such as SimDevice can handle everything else.
This commit is contained in:
Thad House
2025-03-20 19:23:22 -07:00
committed by GitHub
parent 2e21a41f87
commit e2cc9e0059
96 changed files with 1037 additions and 2453 deletions

View File

@@ -5,8 +5,8 @@
package edu.wpi.first.wpilibj;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.hal.PWMConfigDataResult;
import edu.wpi.first.hal.PWMJNI;
import edu.wpi.first.hal.SimDevice;
import edu.wpi.first.util.sendable.Sendable;
import edu.wpi.first.util.sendable.SendableBuilder;
import edu.wpi.first.util.sendable.SendableRegistry;
@@ -19,14 +19,14 @@ import edu.wpi.first.util.sendable.SendableRegistry;
* sent to the FPGA, and the update occurs at the next FPGA cycle (5.05ms). There is no delay.
*/
public class PWM implements Sendable, AutoCloseable {
/** Represents the amount to multiply the minimum servo-pulse pwm period by. */
public enum PeriodMultiplier {
/** Period Multiplier: don't skip pulses. PWM pulses occur every 5.05 ms */
k1X,
/** Period Multiplier: skip every other pulse. PWM pulses occur every 10.10 ms */
k2X,
/** Period Multiplier: skip three out of four pulses. PWM pulses occur every 20.20 ms */
k4X
/** Represents the output period in microseconds. */
public enum OutputPeriod {
/** Pulse every 5ms. */
k5Ms,
/** Pulse every 10ms. */
k10Ms,
/** Pulse every 20ms. */
k20Ms
}
private final int m_channel;
@@ -62,8 +62,6 @@ public class PWM implements Sendable, AutoCloseable {
setDisabled();
PWMJNI.setPWMEliminateDeadband(m_handle, false);
HAL.reportUsage("IO", channel, "PWM");
if (registerSendable) {
SendableRegistry.add(this, "PWM", channel);
@@ -82,44 +80,6 @@ public class PWM implements Sendable, AutoCloseable {
m_handle = 0;
}
/**
* Optionally eliminate the deadband from a motor controller.
*
* @param eliminateDeadband If true, set the motor curve for the motor controller to eliminate the
* deadband in the middle of the range. Otherwise, keep the full range without modifying any
* values.
*/
public void enableDeadbandElimination(boolean eliminateDeadband) {
PWMJNI.setPWMEliminateDeadband(m_handle, eliminateDeadband);
}
/**
* Set the bounds on the PWM pulse widths. This sets the bounds on the PWM values for a particular
* type of controller. The values determine the upper and lower speeds as well as the deadband
* bracket.
*
* @param max The max PWM pulse width in us
* @param deadbandMax The high end of the deadband range pulse width in us
* @param center The center (off) pulse width in us
* @param deadbandMin The low end of the deadband pulse width in us
* @param min The minimum pulse width in us
*/
public void setBoundsMicroseconds(
int max, int deadbandMax, int center, int deadbandMin, int min) {
PWMJNI.setPWMConfigMicroseconds(m_handle, max, deadbandMax, center, deadbandMin, min);
}
/**
* Gets the bounds on the PWM pulse widths. This gets the bounds on the PWM values for a
* particular type of controller. The values determine the upper and lower speeds as well as the
* deadband bracket.
*
* @return The bounds on the PWM pulse widths.
*/
public PWMConfigDataResult getBoundsMicroseconds() {
return PWMJNI.getPWMConfigMicroseconds(m_handle);
}
/**
* Gets the channel number associated with the PWM Object.
*
@@ -129,54 +89,6 @@ public class PWM implements Sendable, AutoCloseable {
return m_channel;
}
/**
* Set the PWM value based on a position.
*
* <p>This is intended to be used by servos.
*
* @param pos The position to set the servo between 0.0 and 1.0.
* @pre setBoundsMicroseconds() called.
*/
public void setPosition(double pos) {
PWMJNI.setPWMPosition(m_handle, pos);
}
/**
* Get the PWM value in terms of a position.
*
* <p>This is intended to be used by servos.
*
* @return The position the servo is set to between 0.0 and 1.0.
* @pre setBoundsMicroseconds() called.
*/
public double getPosition() {
return PWMJNI.getPWMPosition(m_handle);
}
/**
* Set the PWM value based on a speed.
*
* <p>This is intended to be used by motor controllers.
*
* @param speed The speed to set the motor controller between -1.0 and 1.0.
* @pre setBoundsMicroseconds() called.
*/
public void setSpeed(double speed) {
PWMJNI.setPWMSpeed(m_handle, speed);
}
/**
* Get the PWM value in terms of speed.
*
* <p>This is intended to be used by motor controllers.
*
* @return The most recently set speed between -1.0 and 1.0.
* @pre setBoundsMicroseconds() called.
*/
public double getSpeed() {
return PWMJNI.getPWMSpeed(m_handle);
}
/**
* Set the PWM value directly to the hardware.
*
@@ -201,33 +113,23 @@ public class PWM implements Sendable, AutoCloseable {
/** Temporarily disables the PWM output. The next set call will re-enable the output. */
public final void setDisabled() {
PWMJNI.setPWMDisabled(m_handle);
setPulseTimeMicroseconds(0);
}
/**
* Slow down the PWM signal for old devices.
* Sets the PWM output period.
*
* @param mult The period multiplier to apply to this channel
* @param mult The output period to apply to this channel
*/
public void setPeriodMultiplier(PeriodMultiplier mult) {
public void setOutputPeriod(OutputPeriod mult) {
int scale =
switch (mult) {
case k4X -> 3; // Squelch 3 out of 4 outputs
case k2X -> 1; // Squelch 1 out of 2 outputs
case k1X -> 0; // Don't squelch any outputs
case k20Ms -> 3;
case k10Ms -> 1;
case k5Ms -> 0;
};
PWMJNI.setPWMPeriodScale(m_handle, scale);
}
/** Latches PWM to zero. */
public void setZeroLatch() {
PWMJNI.latchPWMZero(m_handle);
}
/** Sets the PWM output to be a continuous high signal while enabled. */
public void setAlwaysHighMode() {
PWMJNI.setAlwaysHighMode(m_handle);
PWMJNI.setPWMOutputPeriod(m_handle, scale);
}
/**
@@ -239,13 +141,20 @@ public class PWM implements Sendable, AutoCloseable {
return m_handle;
}
/**
* Indicates this input is used by a simulated device.
*
* @param device simulated device handle
*/
public void setSimDevice(SimDevice device) {
PWMJNI.setPWMSimDevice(m_handle, device.getNativeHandle());
}
@Override
public void initSendable(SendableBuilder builder) {
builder.setSmartDashboardType("PWM");
builder.setActuator(true);
builder.addDoubleProperty(
"Value", this::getPulseTimeMicroseconds, value -> setPulseTimeMicroseconds((int) value));
builder.addDoubleProperty("Speed", this::getSpeed, this::setSpeed);
builder.addDoubleProperty("Position", this::getPosition, this::setPosition);
}
}

View File

@@ -5,8 +5,14 @@
package edu.wpi.first.wpilibj;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.hal.SimDevice;
import edu.wpi.first.hal.SimDevice.Direction;
import edu.wpi.first.hal.SimDouble;
import edu.wpi.first.math.MathUtil;
import edu.wpi.first.util.sendable.Sendable;
import edu.wpi.first.util.sendable.SendableBuilder;
import edu.wpi.first.util.sendable.SendableRegistry;
import edu.wpi.first.wpilibj.PWM.OutputPeriod;
/**
* Standard hobby style servo.
@@ -14,13 +20,21 @@ import edu.wpi.first.util.sendable.SendableRegistry;
* <p>The range parameters default to the appropriate values for the Hitec HS-322HD servo provided
* in the FIRST Kit of Parts in 2008.
*/
public class Servo extends PWM {
public class Servo implements Sendable, AutoCloseable {
private static final double kMaxServoAngle = 180.0;
private static final double kMinServoAngle = 0.0;
private static final int kDefaultMaxServoPWM = 2400;
private static final int kDefaultMinServoPWM = 600;
private final PWM m_pwm;
private SimDevice m_simDevice;
private SimDouble m_simPosition;
private static final int m_minPwm = kDefaultMinServoPWM;
private static final int m_maxPwm = kDefaultMaxServoPWM;
/**
* Constructor.
*
@@ -32,12 +46,31 @@ public class Servo extends PWM {
*/
@SuppressWarnings("this-escape")
public Servo(final int channel) {
super(channel);
setBoundsMicroseconds(kDefaultMaxServoPWM, 0, 0, 0, kDefaultMinServoPWM);
setPeriodMultiplier(PeriodMultiplier.k4X);
m_pwm = new PWM(channel, false);
SendableRegistry.add(this, "Servo", channel);
HAL.reportUsage("IO", getChannel(), "Servo");
SendableRegistry.setName(this, "Servo", getChannel());
m_pwm.setOutputPeriod(OutputPeriod.k20Ms);
HAL.reportUsage("IO", channel, "Servo");
m_simDevice = SimDevice.create("Servo", channel);
if (m_simDevice != null) {
m_simPosition = m_simDevice.createDouble("Position", Direction.kOutput, 0.0);
m_pwm.setSimDevice(m_simDevice);
}
}
/** Free the resource associated with the PWM channel and set the value to 0. */
@Override
public void close() {
SendableRegistry.remove(this);
m_pwm.close();
if (m_simDevice != null) {
m_simDevice.close();
m_simDevice = null;
m_simPosition = null;
}
}
/**
@@ -48,7 +81,15 @@ public class Servo extends PWM {
* @param value Position from 0.0 to 1.0.
*/
public void set(double value) {
setPosition(value);
value = MathUtil.clamp(value, 0.0, 1.0);
if (m_simPosition != null) {
m_simPosition.set(value);
}
int rawValue = (int) ((value * getFullRangeScaleFactor()) + m_minPwm);
m_pwm.setPulseTimeMicroseconds(rawValue);
}
/**
@@ -61,7 +102,14 @@ public class Servo extends PWM {
* @return Position from 0.0 to 1.0.
*/
public double get() {
return getPosition();
int rawValue = m_pwm.getPulseTimeMicroseconds();
if (rawValue < m_minPwm) {
return 0.0;
} else if (rawValue > m_maxPwm) {
return 1.0;
}
return (rawValue - m_minPwm) / getFullRangeScaleFactor();
}
/**
@@ -83,7 +131,7 @@ public class Servo extends PWM {
degrees = kMaxServoAngle;
}
setPosition((degrees - kMinServoAngle) / getServoAngleRange());
set((degrees - kMinServoAngle) / getServoAngleRange());
}
/**
@@ -95,7 +143,29 @@ public class Servo extends PWM {
* @return The angle in degrees to which the servo is set.
*/
public double getAngle() {
return getPosition() * getServoAngleRange() + kMinServoAngle;
return get() * getServoAngleRange() + kMinServoAngle;
}
/**
* Gets the backing PWM handle.
*
* @return The pwm handle.
*/
public int getPwmHandle() {
return m_pwm.getHandle();
}
/**
* Gets the PWM channel number.
*
* @return The channel number.
*/
public int getChannel() {
return m_pwm.getChannel();
}
private double getFullRangeScaleFactor() {
return m_maxPwm - m_minPwm;
}
private double getServoAngleRange() {

View File

@@ -4,6 +4,10 @@
package edu.wpi.first.wpilibj.motorcontrol;
import edu.wpi.first.hal.SimDevice;
import edu.wpi.first.hal.SimDevice.Direction;
import edu.wpi.first.hal.SimDouble;
import edu.wpi.first.math.MathUtil;
import edu.wpi.first.util.sendable.Sendable;
import edu.wpi.first.util.sendable.SendableBuilder;
import edu.wpi.first.util.sendable.SendableRegistry;
@@ -22,6 +26,16 @@ public abstract class PWMMotorController extends MotorSafety
/** PWM instances for motor controller. */
protected PWM m_pwm;
private SimDevice m_simDevice;
private SimDouble m_simSpeed;
private boolean m_eliminateDeadband;
private int m_minPwm;
private int m_deadbandMinPwm;
private int m_centerPwm;
private int m_deadbandMaxPwm;
private int m_maxPwm;
/**
* Constructor.
*
@@ -33,6 +47,12 @@ public abstract class PWMMotorController extends MotorSafety
protected PWMMotorController(final String name, final int channel) {
m_pwm = new PWM(channel, false);
SendableRegistry.add(this, name, channel);
m_simDevice = SimDevice.create("PWMMotorController", channel);
if (m_simDevice != null) {
m_simSpeed = m_simDevice.createDouble("Speed", Direction.kOutput, 0.0);
m_pwm.setSimDevice(m_simDevice);
}
}
/** Free the resource associated with the PWM channel and set the value to 0. */
@@ -40,6 +60,105 @@ public abstract class PWMMotorController extends MotorSafety
public void close() {
SendableRegistry.remove(this);
m_pwm.close();
if (m_simDevice != null) {
m_simDevice.close();
m_simDevice = null;
m_simSpeed = null;
}
}
private int getMinPositivePwm() {
if (m_eliminateDeadband) {
return m_deadbandMaxPwm;
} else {
return m_centerPwm + 1;
}
}
private int getMaxNegativePwm() {
if (m_eliminateDeadband) {
return m_deadbandMinPwm;
} else {
return m_centerPwm - 1;
}
}
private int getPositiveScaleFactor() {
return m_maxPwm - getMinPositivePwm();
}
private int getNegativeScaleFactor() {
return getMaxNegativePwm() - m_minPwm;
}
/**
* Takes a speed from -1 to 1, and outputs it in the microsecond format.
*
* @param speed the speed to output
*/
protected final void setSpeed(double speed) {
if (Double.isFinite(speed)) {
speed = MathUtil.clamp(speed, -1.0, 1.0);
} else {
speed = 0.0;
}
if (m_simSpeed != null) {
m_simSpeed.set(speed);
}
int rawValue;
if (speed == 0.0) {
rawValue = m_centerPwm;
} else if (speed > 0.0) {
rawValue = (int) Math.round(speed * getPositiveScaleFactor()) + getMinPositivePwm();
} else {
rawValue = (int) Math.round(speed * getNegativeScaleFactor()) + getMaxNegativePwm();
}
m_pwm.setPulseTimeMicroseconds(rawValue);
}
/**
* Gets the speed from -1 to 1, from the currently set pulse time.
*
* @return motor controller speed
*/
protected final double getSpeed() {
int rawValue = m_pwm.getPulseTimeMicroseconds();
if (rawValue == 0) {
return 0.0;
} else if (rawValue > m_maxPwm) {
return 1.0;
} else if (rawValue < m_minPwm) {
return -1.0;
} else if (rawValue > getMinPositivePwm()) {
return (rawValue - getMinPositivePwm()) / (double) getPositiveScaleFactor();
} else if (rawValue < getMaxNegativePwm()) {
return (rawValue - getMaxNegativePwm()) / (double) getNegativeScaleFactor();
} else {
return 0.0;
}
}
/**
* Sets the bounds in microseconds for the controller.
*
* @param maxPwm maximum
* @param deadbandMaxPwm deadband max
* @param centerPwm center
* @param deadbandMinPwm deadmand min
* @param minPwm minimum
*/
protected final void setBoundsMicroseconds(
int maxPwm, int deadbandMaxPwm, int centerPwm, int deadbandMinPwm, int minPwm) {
m_maxPwm = maxPwm;
m_deadbandMaxPwm = deadbandMaxPwm;
m_centerPwm = centerPwm;
m_deadbandMinPwm = deadbandMinPwm;
m_minPwm = minPwm;
}
/**
@@ -55,7 +174,7 @@ public abstract class PWMMotorController extends MotorSafety
if (m_isInverted) {
speed = -speed;
}
m_pwm.setSpeed(speed);
setSpeed(speed);
for (var follower : m_followers) {
follower.set(speed);
@@ -65,15 +184,13 @@ public abstract class PWMMotorController extends MotorSafety
}
/**
* Get the recently set value of the PWM. This value is affected by the inversion property. If you
* want the value that is sent directly to the MotorController, use {@link
* edu.wpi.first.wpilibj.PWM#getSpeed()} instead.
* Get the recently set value of the PWM. This value is affected by the inversion property.
*
* @return The most recently set value for the PWM between -1.0 and 1.0.
*/
@Override
public double get() {
return m_pwm.getSpeed() * (m_isInverted ? -1.0 : 1.0);
return getSpeed() * (m_isInverted ? -1.0 : 1.0);
}
/**
@@ -99,6 +216,10 @@ public abstract class PWMMotorController extends MotorSafety
public void disable() {
m_pwm.setDisabled();
if (m_simSpeed != null) {
m_simSpeed.set(0.0);
}
for (var follower : m_followers) {
follower.disable();
}
@@ -107,7 +228,7 @@ public abstract class PWMMotorController extends MotorSafety
@Override
public void stopMotor() {
// Don't use set(0) as that will feed the watch kitty
m_pwm.setSpeed(0);
m_pwm.setPulseTimeMicroseconds(0);
for (var follower : m_followers) {
follower.stopMotor();
@@ -145,7 +266,7 @@ public abstract class PWMMotorController extends MotorSafety
* values.
*/
public void enableDeadbandElimination(boolean eliminateDeadband) {
m_pwm.enableDeadbandElimination(eliminateDeadband);
m_eliminateDeadband = eliminateDeadband;
}
/**

View File

@@ -0,0 +1,41 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj.simulation;
import edu.wpi.first.hal.SimDouble;
import edu.wpi.first.wpilibj.motorcontrol.PWMMotorController;
/** Class to control a simulated PWM motor controller. */
public class PWMMotorControllerSim {
private final SimDouble m_simSpeed;
/**
* Constructor.
*
* @param motorctrl The device to simulate
*/
public PWMMotorControllerSim(PWMMotorController motorctrl) {
this(motorctrl.getChannel());
}
/**
* Constructor.
*
* @param channel The channel the motor controller is attached to.
*/
public PWMMotorControllerSim(int channel) {
SimDeviceSim simDevice = new SimDeviceSim("PWMMotorController", channel);
m_simSpeed = simDevice.getDouble("Speed");
}
/**
* Gets the motor speed set.
*
* @return Speed
*/
public double getSpeed() {
return m_simSpeed.get();
}
}

View File

@@ -7,7 +7,6 @@ package edu.wpi.first.wpilibj.simulation;
import edu.wpi.first.hal.simulation.NotifyCallback;
import edu.wpi.first.hal.simulation.PWMDataJNI;
import edu.wpi.first.wpilibj.PWM;
import edu.wpi.first.wpilibj.motorcontrol.PWMMotorController;
/** Class to control a simulated PWM output. */
public class PWMSim {
@@ -22,15 +21,6 @@ public class PWMSim {
m_index = pwm.getChannel();
}
/**
* Constructs from a PWMMotorController object.
*
* @param motorctrl PWMMotorController to simulate
*/
public PWMSim(PWMMotorController motorctrl) {
m_index = motorctrl.getChannel();
}
/**
* Constructs from a PWM channel number.
*
@@ -101,66 +91,6 @@ public class PWMSim {
PWMDataJNI.setPulseMicrosecond(m_index, microsecondPulseTime);
}
/**
* Register a callback to be run when the PWM speed changes.
*
* @param callback the callback
* @param initialNotify whether to run the callback with the initial value
* @return the {@link CallbackStore} object associated with this callback.
*/
public CallbackStore registerSpeedCallback(NotifyCallback callback, boolean initialNotify) {
int uid = PWMDataJNI.registerSpeedCallback(m_index, callback, initialNotify);
return new CallbackStore(m_index, uid, PWMDataJNI::cancelSpeedCallback);
}
/**
* Get the PWM speed.
*
* @return the PWM speed (-1.0 to 1.0)
*/
public double getSpeed() {
return PWMDataJNI.getSpeed(m_index);
}
/**
* Set the PWM speed.
*
* @param speed the PWM speed (-1.0 to 1.0)
*/
public void setSpeed(double speed) {
PWMDataJNI.setSpeed(m_index, speed);
}
/**
* Register a callback to be run when the PWM position changes.
*
* @param callback the callback
* @param initialNotify whether to run the callback with the initial value
* @return the {@link CallbackStore} object associated with this callback.
*/
public CallbackStore registerPositionCallback(NotifyCallback callback, boolean initialNotify) {
int uid = PWMDataJNI.registerPositionCallback(m_index, callback, initialNotify);
return new CallbackStore(m_index, uid, PWMDataJNI::cancelPositionCallback);
}
/**
* Get the PWM position.
*
* @return the PWM position (0.0 to 1.0)
*/
public double getPosition() {
return PWMDataJNI.getPosition(m_index);
}
/**
* Set the PWM position.
*
* @param position the PWM position (0.0 to 1.0)
*/
public void setPosition(double position) {
PWMDataJNI.setPosition(m_index, position);
}
/**
* Register a callback to be run when the PWM period scale changes.
*
@@ -168,9 +98,10 @@ public class PWMSim {
* @param initialNotify whether to run the callback with the initial value
* @return the {@link CallbackStore} object associated with this callback.
*/
public CallbackStore registerPeriodScaleCallback(NotifyCallback callback, boolean initialNotify) {
int uid = PWMDataJNI.registerPeriodScaleCallback(m_index, callback, initialNotify);
return new CallbackStore(m_index, uid, PWMDataJNI::cancelPeriodScaleCallback);
public CallbackStore registerOutputPeriodCallback(
NotifyCallback callback, boolean initialNotify) {
int uid = PWMDataJNI.registerOutputPeriodCallback(m_index, callback, initialNotify);
return new CallbackStore(m_index, uid, PWMDataJNI::cancelOutputPeriodCallback);
}
/**
@@ -178,47 +109,17 @@ public class PWMSim {
*
* @return the PWM period scale
*/
public int getPeriodScale() {
return PWMDataJNI.getPeriodScale(m_index);
public int getOutputPeriod() {
return PWMDataJNI.getOutputPeriod(m_index);
}
/**
* Set the PWM period scale.
*
* @param periodScale the PWM period scale
* @param period the PWM period scale
*/
public void setPeriodScale(int periodScale) {
PWMDataJNI.setPeriodScale(m_index, periodScale);
}
/**
* Register a callback to be run when the PWM zero latch state changes.
*
* @param callback the callback
* @param initialNotify whether to run the callback with the initial state
* @return the {@link CallbackStore} object associated with this callback.
*/
public CallbackStore registerZeroLatchCallback(NotifyCallback callback, boolean initialNotify) {
int uid = PWMDataJNI.registerZeroLatchCallback(m_index, callback, initialNotify);
return new CallbackStore(m_index, uid, PWMDataJNI::cancelZeroLatchCallback);
}
/**
* Check whether the PWM is zero latched.
*
* @return true if zero latched
*/
public boolean getZeroLatch() {
return PWMDataJNI.getZeroLatch(m_index);
}
/**
* Define whether the PWM has been zero latched.
*
* @param zeroLatch true to indicate zero latched
*/
public void setZeroLatch(boolean zeroLatch) {
PWMDataJNI.setZeroLatch(m_index, zeroLatch);
public void setOutputPeriod(int period) {
PWMDataJNI.setOutputPeriod(m_index, period);
}
/** Reset all simulation data. */

View File

@@ -0,0 +1,50 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj.simulation;
import edu.wpi.first.hal.SimDouble;
import edu.wpi.first.wpilibj.Servo;
/** Class to control a simulated Servo. */
public class ServoSim {
private final SimDouble m_simPosition;
/**
* Constructor.
*
* @param servo The device to simulate
*/
public ServoSim(Servo servo) {
this(servo.getChannel());
}
/**
* Constructor.
*
* @param channel The channel the servo is attached to.
*/
public ServoSim(int channel) {
SimDeviceSim simDevice = new SimDeviceSim("Servo", channel);
m_simPosition = simDevice.getDouble("Position");
}
/**
* Gets the position set.
*
* @return Position
*/
public double getPosition() {
return m_simPosition.get();
}
/**
* Gets the angle set.
*
* @return Angle
*/
public double getAngle() {
return getPosition() * 180.0;
}
}