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;
006
007import edu.wpi.first.hal.FRCNetComm.tResourceType;
008import edu.wpi.first.hal.HAL;
009import edu.wpi.first.hal.PWMConfigDataResult;
010import edu.wpi.first.hal.PWMJNI;
011import edu.wpi.first.util.sendable.Sendable;
012import edu.wpi.first.util.sendable.SendableBuilder;
013import edu.wpi.first.util.sendable.SendableRegistry;
014
015/**
016 * Class implements the PWM generation in the FPGA.
017 *
018 * <p>The values supplied as arguments for PWM outputs range from -1.0 to 1.0. They are mapped to
019 * the microseconds to keep the pulse high, with a range of 0 (off) to 4096. Changes are immediately
020 * sent to the FPGA, and the update occurs at the next FPGA cycle (5.05ms). There is no delay.
021 */
022public class PWM implements Sendable, AutoCloseable {
023  /** Represents the amount to multiply the minimum servo-pulse pwm period by. */
024  public enum PeriodMultiplier {
025    /** Period Multiplier: don't skip pulses. PWM pulses occur every 5.05 ms */
026    k1X,
027    /** Period Multiplier: skip every other pulse. PWM pulses occur every 10.10 ms */
028    k2X,
029    /** Period Multiplier: skip three out of four pulses. PWM pulses occur every 20.20 ms */
030    k4X
031  }
032
033  private final int m_channel;
034
035  private int m_handle;
036
037  /**
038   * Allocate a PWM given a channel.
039   *
040   * <p>Checks channel value range and allocates the appropriate channel. The allocation is only
041   * done to help users ensure that they don't double assign channels.
042   *
043   * <p>By default, adds itself to SendableRegistry and LiveWindow.
044   *
045   * @param channel The PWM channel number. 0-9 are on-board, 10-19 are on the MXP port
046   */
047  public PWM(final int channel) {
048    this(channel, true);
049  }
050
051  /**
052   * Allocate a PWM given a channel.
053   *
054   * @param channel The PWM channel number. 0-9 are on-board, 10-19 are on the MXP port
055   * @param registerSendable If true, adds this instance to SendableRegistry and LiveWindow
056   */
057  @SuppressWarnings("this-escape")
058  public PWM(final int channel, final boolean registerSendable) {
059    SensorUtil.checkPWMChannel(channel);
060    m_channel = channel;
061
062    m_handle = PWMJNI.initializePWMPort(HAL.getPort((byte) channel));
063
064    setDisabled();
065
066    PWMJNI.setPWMEliminateDeadband(m_handle, false);
067
068    HAL.report(tResourceType.kResourceType_PWM, channel + 1);
069    if (registerSendable) {
070      SendableRegistry.addLW(this, "PWM", channel);
071    }
072  }
073
074  /** Free the resource associated with the PWM channel and set the value to 0. */
075  @Override
076  public void close() {
077    SendableRegistry.remove(this);
078    if (m_handle == 0) {
079      return;
080    }
081    setDisabled();
082    PWMJNI.freePWMPort(m_handle);
083    m_handle = 0;
084  }
085
086  /**
087   * Optionally eliminate the deadband from a motor controller.
088   *
089   * @param eliminateDeadband If true, set the motor curve for the motor controller to eliminate the
090   *     deadband in the middle of the range. Otherwise, keep the full range without modifying any
091   *     values.
092   */
093  public void enableDeadbandElimination(boolean eliminateDeadband) {
094    PWMJNI.setPWMEliminateDeadband(m_handle, eliminateDeadband);
095  }
096
097  /**
098   * Set the bounds on the PWM pulse widths. This sets the bounds on the PWM values for a particular
099   * type of controller. The values determine the upper and lower speeds as well as the deadband
100   * bracket.
101   *
102   * @param max The max PWM pulse width in us
103   * @param deadbandMax The high end of the deadband range pulse width in us
104   * @param center The center (off) pulse width in us
105   * @param deadbandMin The low end of the deadband pulse width in us
106   * @param min The minimum pulse width in us
107   */
108  public void setBoundsMicroseconds(
109      int max, int deadbandMax, int center, int deadbandMin, int min) {
110    PWMJNI.setPWMConfigMicroseconds(m_handle, max, deadbandMax, center, deadbandMin, min);
111  }
112
113  /**
114   * Gets the bounds on the PWM pulse widths. This gets the bounds on the PWM values for a
115   * particular type of controller. The values determine the upper and lower speeds as well as the
116   * deadband bracket.
117   *
118   * @return The bounds on the PWM pulse widths.
119   */
120  public PWMConfigDataResult getBoundsMicroseconds() {
121    return PWMJNI.getPWMConfigMicroseconds(m_handle);
122  }
123
124  /**
125   * Gets the channel number associated with the PWM Object.
126   *
127   * @return The channel number.
128   */
129  public int getChannel() {
130    return m_channel;
131  }
132
133  /**
134   * Set the PWM value based on a position.
135   *
136   * <p>This is intended to be used by servos.
137   *
138   * @param pos The position to set the servo between 0.0 and 1.0.
139   * @pre setBoundsMicroseconds() called.
140   */
141  public void setPosition(double pos) {
142    PWMJNI.setPWMPosition(m_handle, pos);
143  }
144
145  /**
146   * Get the PWM value in terms of a position.
147   *
148   * <p>This is intended to be used by servos.
149   *
150   * @return The position the servo is set to between 0.0 and 1.0.
151   * @pre setBoundsMicroseconds() called.
152   */
153  public double getPosition() {
154    return PWMJNI.getPWMPosition(m_handle);
155  }
156
157  /**
158   * Set the PWM value based on a speed.
159   *
160   * <p>This is intended to be used by motor controllers.
161   *
162   * @param speed The speed to set the motor controller between -1.0 and 1.0.
163   * @pre setBoundsMicroseconds() called.
164   */
165  public void setSpeed(double speed) {
166    PWMJNI.setPWMSpeed(m_handle, speed);
167  }
168
169  /**
170   * Get the PWM value in terms of speed.
171   *
172   * <p>This is intended to be used by motor controllers.
173   *
174   * @return The most recently set speed between -1.0 and 1.0.
175   * @pre setBoundsMicroseconds() called.
176   */
177  public double getSpeed() {
178    return PWMJNI.getPWMSpeed(m_handle);
179  }
180
181  /**
182   * Set the PWM value directly to the hardware.
183   *
184   * <p>Write a microsecond pulse value to a PWM channel.
185   *
186   * @param microsecondPulseTime Microsecond pulse PWM value. Range 0 - 4096.
187   */
188  public void setPulseTimeMicroseconds(int microsecondPulseTime) {
189    PWMJNI.setPulseTimeMicroseconds(m_handle, microsecondPulseTime);
190  }
191
192  /**
193   * Get the PWM value directly from the hardware.
194   *
195   * <p>Read a raw value from a PWM channel.
196   *
197   * @return Microsecond pulse PWM control value. Range: 0 - 4096.
198   */
199  public int getPulseTimeMicroseconds() {
200    return PWMJNI.getPulseTimeMicroseconds(m_handle);
201  }
202
203  /** Temporarily disables the PWM output. The next set call will re-enable the output. */
204  public final void setDisabled() {
205    PWMJNI.setPWMDisabled(m_handle);
206  }
207
208  /**
209   * Slow down the PWM signal for old devices.
210   *
211   * @param mult The period multiplier to apply to this channel
212   */
213  public void setPeriodMultiplier(PeriodMultiplier mult) {
214    switch (mult) {
215      case k4X:
216        // Squelch 3 out of 4 outputs
217        PWMJNI.setPWMPeriodScale(m_handle, 3);
218        break;
219      case k2X:
220        // Squelch 1 out of 2 outputs
221        PWMJNI.setPWMPeriodScale(m_handle, 1);
222        break;
223      case k1X:
224        // Don't squelch any outputs
225        PWMJNI.setPWMPeriodScale(m_handle, 0);
226        break;
227      default:
228        // Cannot hit this, limited by PeriodMultiplier enum
229    }
230  }
231
232  /** Latches PWM to zero. */
233  public void setZeroLatch() {
234    PWMJNI.latchPWMZero(m_handle);
235  }
236
237  /** Sets the PWM output to be a continuous high signal while enabled. */
238  public void setAlwaysHighMode() {
239    PWMJNI.setAlwaysHighMode(m_handle);
240  }
241
242  /**
243   * Get the underlying handle.
244   *
245   * @return Underlying PWM handle
246   */
247  public int getHandle() {
248    return m_handle;
249  }
250
251  @Override
252  public void initSendable(SendableBuilder builder) {
253    builder.setSmartDashboardType("PWM");
254    builder.setActuator(true);
255    builder.setSafeState(this::setDisabled);
256    builder.addDoubleProperty(
257        "Value", this::getPulseTimeMicroseconds, value -> setPulseTimeMicroseconds((int) value));
258    builder.addDoubleProperty("Speed", this::getSpeed, this::setSpeed);
259    builder.addDoubleProperty("Position", this::getPosition, this::setPosition);
260  }
261}