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  public PWM(final int channel, final boolean registerSendable) {
058    SensorUtil.checkPWMChannel(channel);
059    m_channel = channel;
060
061    m_handle = PWMJNI.initializePWMPort(HAL.getPort((byte) channel));
062
063    setDisabled();
064
065    PWMJNI.setPWMEliminateDeadband(m_handle, false);
066
067    HAL.report(tResourceType.kResourceType_PWM, channel + 1);
068    if (registerSendable) {
069      SendableRegistry.addLW(this, "PWM", channel);
070    }
071  }
072
073  /** Free the resource associated with the PWM channel and set the value to 0. */
074  @Override
075  public void close() {
076    SendableRegistry.remove(this);
077    if (m_handle == 0) {
078      return;
079    }
080    setDisabled();
081    PWMJNI.freePWMPort(m_handle);
082    m_handle = 0;
083  }
084
085  /**
086   * Optionally eliminate the deadband from a motor controller.
087   *
088   * @param eliminateDeadband If true, set the motor curve for the motor controller to eliminate the
089   *     deadband in the middle of the range. Otherwise, keep the full range without modifying any
090   *     values.
091   */
092  public void enableDeadbandElimination(boolean eliminateDeadband) {
093    PWMJNI.setPWMEliminateDeadband(m_handle, eliminateDeadband);
094  }
095
096  /**
097   * Set the bounds on the PWM pulse widths. This sets the bounds on the PWM values for a particular
098   * type of controller. The values determine the upper and lower speeds as well as the deadband
099   * bracket.
100   *
101   * @param max The max PWM pulse width in us
102   * @param deadbandMax The high end of the deadband range pulse width in us
103   * @param center The center (off) pulse width in us
104   * @param deadbandMin The low end of the deadband pulse width in us
105   * @param min The minimum pulse width in us
106   */
107  public void setBoundsMicroseconds(
108      int max, int deadbandMax, int center, int deadbandMin, int min) {
109    PWMJNI.setPWMConfigMicroseconds(m_handle, max, deadbandMax, center, deadbandMin, min);
110  }
111
112  /**
113   * Gets the bounds on the PWM pulse widths. This gets the bounds on the PWM values for a
114   * particular type of controller. The values determine the upper and lower speeds as well as the
115   * deadband bracket.
116   *
117   * @return The bounds on the PWM pulse widths.
118   */
119  public PWMConfigDataResult getBoundsMicroseconds() {
120    return PWMJNI.getPWMConfigMicroseconds(m_handle);
121  }
122
123  /**
124   * Gets the channel number associated with the PWM Object.
125   *
126   * @return The channel number.
127   */
128  public int getChannel() {
129    return m_channel;
130  }
131
132  /**
133   * Set the PWM value based on a position.
134   *
135   * <p>This is intended to be used by servos.
136   *
137   * @param pos The position to set the servo between 0.0 and 1.0.
138   * @pre setBoundsMicroseconds() called.
139   */
140  public void setPosition(double pos) {
141    PWMJNI.setPWMPosition(m_handle, pos);
142  }
143
144  /**
145   * Get the PWM value in terms of a position.
146   *
147   * <p>This is intended to be used by servos.
148   *
149   * @return The position the servo is set to between 0.0 and 1.0.
150   * @pre setBoundsMicroseconds() called.
151   */
152  public double getPosition() {
153    return PWMJNI.getPWMPosition(m_handle);
154  }
155
156  /**
157   * Set the PWM value based on a speed.
158   *
159   * <p>This is intended to be used by motor controllers.
160   *
161   * @param speed The speed to set the motor controller between -1.0 and 1.0.
162   * @pre setBoundsMicroseconds() called.
163   */
164  public void setSpeed(double speed) {
165    PWMJNI.setPWMSpeed(m_handle, speed);
166  }
167
168  /**
169   * Get the PWM value in terms of speed.
170   *
171   * <p>This is intended to be used by motor controllers.
172   *
173   * @return The most recently set speed between -1.0 and 1.0.
174   * @pre setBoundsMicroseconds() called.
175   */
176  public double getSpeed() {
177    return PWMJNI.getPWMSpeed(m_handle);
178  }
179
180  /**
181   * Set the PWM value directly to the hardware.
182   *
183   * <p>Write a microsecond pulse value to a PWM channel.
184   *
185   * @param microsecondPulseTime Microsecond pulse PWM value. Range 0 - 4096.
186   */
187  public void setPulseTimeMicroseconds(int microsecondPulseTime) {
188    PWMJNI.setPulseTimeMicroseconds(m_handle, microsecondPulseTime);
189  }
190
191  /**
192   * Get the PWM value directly from the hardware.
193   *
194   * <p>Read a raw value from a PWM channel.
195   *
196   * @return Microsecond pulse PWM control value. Range: 0 - 4096.
197   */
198  public int getPulseTimeMicroseconds() {
199    return PWMJNI.getPulseTimeMicroseconds(m_handle);
200  }
201
202  /** Temporarily disables the PWM output. The next set call will re-enable the output. */
203  public void setDisabled() {
204    PWMJNI.setPWMDisabled(m_handle);
205  }
206
207  /**
208   * Slow down the PWM signal for old devices.
209   *
210   * @param mult The period multiplier to apply to this channel
211   */
212  public void setPeriodMultiplier(PeriodMultiplier mult) {
213    switch (mult) {
214      case k4X:
215        // Squelch 3 out of 4 outputs
216        PWMJNI.setPWMPeriodScale(m_handle, 3);
217        break;
218      case k2X:
219        // Squelch 1 out of 2 outputs
220        PWMJNI.setPWMPeriodScale(m_handle, 1);
221        break;
222      case k1X:
223        // Don't squelch any outputs
224        PWMJNI.setPWMPeriodScale(m_handle, 0);
225        break;
226      default:
227        // Cannot hit this, limited by PeriodMultiplier enum
228    }
229  }
230
231  public void setZeroLatch() {
232    PWMJNI.latchPWMZero(m_handle);
233  }
234
235  /** Sets the PWM output to be a continous high signal while enabled. */
236  public void setAlwaysHighMode() {
237    PWMJNI.setAlwaysHighMode(m_handle);
238  }
239
240  /**
241   * Get the underlying handle.
242   *
243   * @return Underlying PWM handle
244   */
245  public int getHandle() {
246    return m_handle;
247  }
248
249  @Override
250  public void initSendable(SendableBuilder builder) {
251    builder.setSmartDashboardType("PWM");
252    builder.setActuator(true);
253    builder.setSafeState(this::setDisabled);
254    builder.addDoubleProperty(
255        "Value", this::getPulseTimeMicroseconds, value -> setPulseTimeMicroseconds((int) value));
256    builder.addDoubleProperty("Speed", this::getSpeed, this::setSpeed);
257    builder.addDoubleProperty("Position", this::getPosition, this::setPosition);
258  }
259}