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 org.wpilib.hardware.discrete;
006
007import org.wpilib.hardware.hal.HAL;
008import org.wpilib.hardware.hal.PWMJNI;
009import org.wpilib.hardware.hal.SimDevice;
010import org.wpilib.system.SensorUtil;
011import org.wpilib.util.sendable.Sendable;
012import org.wpilib.util.sendable.SendableBuilder;
013import org.wpilib.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 output period in microseconds. */
024  public enum OutputPeriod {
025    /** Pulse every 5ms. */
026    k5Ms,
027    /** Pulse every 10ms. */
028    k10Ms,
029    /** Pulse every 20ms. */
030    k20Ms
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.
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
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(channel);
063
064    setDisabled();
065
066    HAL.reportUsage("IO", channel, "PWM");
067    if (registerSendable) {
068      SendableRegistry.add(this, "PWM", channel);
069    }
070  }
071
072  /** Free the resource associated with the PWM channel and set the value to 0. */
073  @Override
074  public void close() {
075    SendableRegistry.remove(this);
076    if (m_handle == 0) {
077      return;
078    }
079    setDisabled();
080    PWMJNI.freePWMPort(m_handle);
081    m_handle = 0;
082  }
083
084  /**
085   * Gets the channel number associated with the PWM Object.
086   *
087   * @return The channel number.
088   */
089  public int getChannel() {
090    return m_channel;
091  }
092
093  /**
094   * Set the PWM value directly to the hardware.
095   *
096   * <p>Write a microsecond pulse value to a PWM channel.
097   *
098   * @param microsecondPulseTime Microsecond pulse PWM value. Range 0 - 4096.
099   */
100  public void setPulseTimeMicroseconds(int microsecondPulseTime) {
101    PWMJNI.setPulseTimeMicroseconds(m_handle, microsecondPulseTime);
102  }
103
104  /**
105   * Get the PWM value directly from the hardware.
106   *
107   * <p>Read a raw value from a PWM channel.
108   *
109   * @return Microsecond pulse PWM control value. Range: 0 - 4096.
110   */
111  public int getPulseTimeMicroseconds() {
112    return PWMJNI.getPulseTimeMicroseconds(m_handle);
113  }
114
115  /** Temporarily disables the PWM output. The next set call will re-enable the output. */
116  public final void setDisabled() {
117    setPulseTimeMicroseconds(0);
118  }
119
120  /**
121   * Sets the PWM output period.
122   *
123   * @param mult The output period to apply to this channel
124   */
125  public void setOutputPeriod(OutputPeriod mult) {
126    int scale =
127        switch (mult) {
128          case k20Ms -> 3;
129          case k10Ms -> 1;
130          case k5Ms -> 0;
131        };
132
133    PWMJNI.setPWMOutputPeriod(m_handle, scale);
134  }
135
136  /**
137   * Get the underlying handle.
138   *
139   * @return Underlying PWM handle
140   */
141  public int getHandle() {
142    return m_handle;
143  }
144
145  /**
146   * Indicates this input is used by a simulated device.
147   *
148   * @param device simulated device handle
149   */
150  public void setSimDevice(SimDevice device) {
151    PWMJNI.setPWMSimDevice(m_handle, device.getNativeHandle());
152  }
153
154  @Override
155  public void initSendable(SendableBuilder builder) {
156    builder.setSmartDashboardType("PWM");
157    builder.setActuator(true);
158    builder.addDoubleProperty(
159        "Value", this::getPulseTimeMicroseconds, value -> setPulseTimeMicroseconds((int) value));
160  }
161}