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.HAL;
008import edu.wpi.first.hal.PWMJNI;
009import edu.wpi.first.hal.SimDevice;
010import edu.wpi.first.util.sendable.Sendable;
011import edu.wpi.first.util.sendable.SendableBuilder;
012import edu.wpi.first.util.sendable.SendableRegistry;
013
014/**
015 * Class implements the PWM generation in the FPGA.
016 *
017 * <p>The values supplied as arguments for PWM outputs range from -1.0 to 1.0. They are mapped to
018 * the microseconds to keep the pulse high, with a range of 0 (off) to 4096. Changes are immediately
019 * sent to the FPGA, and the update occurs at the next FPGA cycle (5.05ms). There is no delay.
020 */
021public class PWM implements Sendable, AutoCloseable {
022  /** Represents the output period in microseconds. */
023  public enum OutputPeriod {
024    /** Pulse every 5ms. */
025    k5Ms,
026    /** Pulse every 10ms. */
027    k10Ms,
028    /** Pulse every 20ms. */
029    k20Ms
030  }
031
032  private final int m_channel;
033
034  private int m_handle;
035
036  /**
037   * Allocate a PWM given a channel.
038   *
039   * <p>Checks channel value range and allocates the appropriate channel. The allocation is only
040   * done to help users ensure that they don't double assign channels.
041   *
042   * <p>By default, adds itself to SendableRegistry.
043   *
044   * @param channel The PWM channel number. 0-9 are on-board, 10-19 are on the MXP port
045   */
046  public PWM(final int channel) {
047    this(channel, true);
048  }
049
050  /**
051   * Allocate a PWM given a channel.
052   *
053   * @param channel The PWM channel number. 0-9 are on-board, 10-19 are on the MXP port
054   * @param registerSendable If true, adds this instance to SendableRegistry
055   */
056  @SuppressWarnings("this-escape")
057  public PWM(final int channel, final boolean registerSendable) {
058    SensorUtil.checkPWMChannel(channel);
059    m_channel = channel;
060
061    m_handle = PWMJNI.initializePWMPort(channel);
062
063    setDisabled();
064
065    HAL.reportUsage("IO", channel, "PWM");
066    if (registerSendable) {
067      SendableRegistry.add(this, "PWM", channel);
068    }
069  }
070
071  /** Free the resource associated with the PWM channel and set the value to 0. */
072  @Override
073  public void close() {
074    SendableRegistry.remove(this);
075    if (m_handle == 0) {
076      return;
077    }
078    setDisabled();
079    PWMJNI.freePWMPort(m_handle);
080    m_handle = 0;
081  }
082
083  /**
084   * Gets the channel number associated with the PWM Object.
085   *
086   * @return The channel number.
087   */
088  public int getChannel() {
089    return m_channel;
090  }
091
092  /**
093   * Set the PWM value directly to the hardware.
094   *
095   * <p>Write a microsecond pulse value to a PWM channel.
096   *
097   * @param microsecondPulseTime Microsecond pulse PWM value. Range 0 - 4096.
098   */
099  public void setPulseTimeMicroseconds(int microsecondPulseTime) {
100    PWMJNI.setPulseTimeMicroseconds(m_handle, microsecondPulseTime);
101  }
102
103  /**
104   * Get the PWM value directly from the hardware.
105   *
106   * <p>Read a raw value from a PWM channel.
107   *
108   * @return Microsecond pulse PWM control value. Range: 0 - 4096.
109   */
110  public int getPulseTimeMicroseconds() {
111    return PWMJNI.getPulseTimeMicroseconds(m_handle);
112  }
113
114  /** Temporarily disables the PWM output. The next set call will re-enable the output. */
115  public final void setDisabled() {
116    setPulseTimeMicroseconds(0);
117  }
118
119  /**
120   * Sets the PWM output period.
121   *
122   * @param mult The output period to apply to this channel
123   */
124  public void setOutputPeriod(OutputPeriod mult) {
125    int scale =
126        switch (mult) {
127          case k20Ms -> 3;
128          case k10Ms -> 1;
129          case k5Ms -> 0;
130        };
131
132    PWMJNI.setPWMOutputPeriod(m_handle, scale);
133  }
134
135  /**
136   * Get the underlying handle.
137   *
138   * @return Underlying PWM handle
139   */
140  public int getHandle() {
141    return m_handle;
142  }
143
144  /**
145   * Indicates this input is used by a simulated device.
146   *
147   * @param device simulated device handle
148   */
149  public void setSimDevice(SimDevice device) {
150    PWMJNI.setPWMSimDevice(m_handle, device.getNativeHandle());
151  }
152
153  @Override
154  public void initSendable(SendableBuilder builder) {
155    builder.setSmartDashboardType("PWM");
156    builder.setActuator(true);
157    builder.addDoubleProperty(
158        "Value", this::getPulseTimeMicroseconds, value -> setPulseTimeMicroseconds((int) value));
159  }
160}