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