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 static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
008
009import edu.wpi.first.hal.CounterJNI;
010import edu.wpi.first.hal.FRCNetComm.tResourceType;
011import edu.wpi.first.hal.HAL;
012import edu.wpi.first.util.sendable.Sendable;
013import edu.wpi.first.util.sendable.SendableBuilder;
014import edu.wpi.first.util.sendable.SendableRegistry;
015import edu.wpi.first.wpilibj.AnalogTriggerOutput.AnalogTriggerType;
016import java.nio.ByteBuffer;
017import java.nio.ByteOrder;
018
019/**
020 * Class for counting the number of ticks on a digital input channel.
021 *
022 * <p>This is a general purpose class for counting repetitive events. It can return the number of
023 * counts, the period of the most recent cycle, and detect when the signal being counted has stopped
024 * by supplying a maximum cycle time.
025 *
026 * <p>All counters will immediately start counting - reset() them if you need them to be zeroed
027 * before use.
028 */
029public class Counter implements CounterBase, Sendable, AutoCloseable {
030  /** Mode determines how and what the counter counts. */
031  public enum Mode {
032    /** mode: two pulse. */
033    kTwoPulse(0),
034    /** mode: semi period. */
035    kSemiperiod(1),
036    /** mode: pulse length. */
037    kPulseLength(2),
038    /** mode: external direction. */
039    kExternalDirection(3);
040
041    /** Mode value. */
042    public final int value;
043
044    Mode(int value) {
045      this.value = value;
046    }
047  }
048
049  /** What makes the counter count up. */
050  protected DigitalSource m_upSource;
051
052  /** What makes the counter count down. */
053  protected DigitalSource m_downSource;
054
055  private boolean m_allocatedUpSource;
056  private boolean m_allocatedDownSource;
057
058  /** The FPGA counter object. */
059  int m_counter;
060
061  /** The index of this counter. */
062  private int m_index;
063
064  /** Distance of travel for each tick. */
065  private double m_distancePerPulse = 1;
066
067  /**
068   * Create an instance of a counter with the given mode.
069   *
070   * @param mode The counter mode.
071   */
072  @SuppressWarnings("this-escape")
073  public Counter(final Mode mode) {
074    ByteBuffer index = ByteBuffer.allocateDirect(4);
075    // set the byte order
076    index.order(ByteOrder.LITTLE_ENDIAN);
077    m_counter = CounterJNI.initializeCounter(mode.value, index.asIntBuffer());
078    m_index = index.asIntBuffer().get(0);
079
080    m_allocatedUpSource = false;
081    m_allocatedDownSource = false;
082    m_upSource = null;
083    m_downSource = null;
084
085    setMaxPeriod(0.5);
086
087    HAL.report(tResourceType.kResourceType_Counter, m_index + 1, mode.value + 1);
088    SendableRegistry.addLW(this, "Counter", m_index);
089  }
090
091  /**
092   * Create an instance of a counter where no sources are selected. Then they all must be selected
093   * by calling functions to specify the upsource and the downsource independently.
094   *
095   * <p>The counter will start counting immediately.
096   */
097  public Counter() {
098    this(Mode.kTwoPulse);
099  }
100
101  /**
102   * Create an instance of a counter from a Digital Input. This is used if an existing digital input
103   * is to be shared by multiple other objects such as encoders or if the Digital Source is not a
104   * DIO channel (such as an Analog Trigger)
105   *
106   * <p>The counter will start counting immediately.
107   *
108   * @param source the digital source to count
109   */
110  @SuppressWarnings("this-escape")
111  public Counter(DigitalSource source) {
112    this();
113
114    requireNonNullParam(source, "source", "Counter");
115    setUpSource(source);
116  }
117
118  /**
119   * Create an instance of a Counter object. Create an up-Counter instance given a channel.
120   *
121   * <p>The counter will start counting immediately.
122   *
123   * @param channel the DIO channel to use as the up source. 0-9 are on-board, 10-25 are on the MXP
124   */
125  @SuppressWarnings("this-escape")
126  public Counter(int channel) {
127    this();
128    setUpSource(channel);
129  }
130
131  /**
132   * Create an instance of a Counter object. Create an instance of a simple up-Counter given an
133   * analog trigger. Use the trigger state output from the analog trigger.
134   *
135   * <p>The counter will start counting immediately.
136   *
137   * @param encodingType which edges to count
138   * @param upSource first source to count
139   * @param downSource second source for direction
140   * @param inverted true to invert the count
141   */
142  @SuppressWarnings("this-escape")
143  public Counter(
144      EncodingType encodingType,
145      DigitalSource upSource,
146      DigitalSource downSource,
147      boolean inverted) {
148    this(Mode.kExternalDirection);
149
150    requireNonNullParam(encodingType, "encodingType", "Counter");
151    requireNonNullParam(upSource, "upSource", "Counter");
152    requireNonNullParam(downSource, "downSource", "Counter");
153
154    if (encodingType != EncodingType.k1X && encodingType != EncodingType.k2X) {
155      throw new IllegalArgumentException("Counters only support 1X and 2X quadrature decoding!");
156    }
157
158    setUpSource(upSource);
159    setDownSource(downSource);
160
161    if (encodingType == EncodingType.k1X) {
162      setUpSourceEdge(true, false);
163      CounterJNI.setCounterAverageSize(m_counter, 1);
164    } else {
165      setUpSourceEdge(true, true);
166      CounterJNI.setCounterAverageSize(m_counter, 2);
167    }
168
169    setDownSourceEdge(inverted, true);
170  }
171
172  /**
173   * Create an instance of a Counter object. Create an instance of a simple up-Counter given an
174   * analog trigger. Use the trigger state output from the analog trigger.
175   *
176   * <p>The counter will start counting immediately.
177   *
178   * @param trigger the analog trigger to count
179   */
180  @SuppressWarnings("this-escape")
181  public Counter(AnalogTrigger trigger) {
182    this();
183
184    requireNonNullParam(trigger, "trigger", "Counter");
185
186    setUpSource(trigger.createOutput(AnalogTriggerType.kState));
187  }
188
189  @Override
190  public void close() {
191    SendableRegistry.remove(this);
192
193    setUpdateWhenEmpty(true);
194
195    clearUpSource();
196    clearDownSource();
197
198    CounterJNI.freeCounter(m_counter);
199
200    m_upSource = null;
201    m_downSource = null;
202    m_counter = 0;
203  }
204
205  /**
206   * The counter's FPGA index.
207   *
208   * @return the Counter's FPGA index
209   */
210  public int getFPGAIndex() {
211    return m_index;
212  }
213
214  /**
215   * Set the upsource for the counter as a digital input channel.
216   *
217   * @param channel the DIO channel to count 0-9 are on-board, 10-25 are on the MXP
218   */
219  public final void setUpSource(int channel) {
220    setUpSource(new DigitalInput(channel));
221    m_allocatedUpSource = true;
222    SendableRegistry.addChild(this, m_upSource);
223  }
224
225  /**
226   * Set the source object that causes the counter to count up. Set the up counting DigitalSource.
227   *
228   * @param source the digital source to count
229   */
230  public void setUpSource(DigitalSource source) {
231    if (m_upSource != null && m_allocatedUpSource) {
232      m_upSource.close();
233      m_allocatedUpSource = false;
234    }
235    m_upSource = source;
236    CounterJNI.setCounterUpSource(
237        m_counter, source.getPortHandleForRouting(), source.getAnalogTriggerTypeForRouting());
238  }
239
240  /**
241   * Set the up counting source to be an analog trigger.
242   *
243   * @param analogTrigger The analog trigger object that is used for the Up Source
244   * @param triggerType The analog trigger output that will trigger the counter.
245   */
246  public void setUpSource(AnalogTrigger analogTrigger, AnalogTriggerType triggerType) {
247    requireNonNullParam(analogTrigger, "analogTrigger", "setUpSource");
248    requireNonNullParam(triggerType, "triggerType", "setUpSource");
249
250    setUpSource(analogTrigger.createOutput(triggerType));
251    m_allocatedUpSource = true;
252  }
253
254  /**
255   * Set the edge sensitivity on an up counting source. Set the up source to either detect rising
256   * edges or falling edges.
257   *
258   * @param risingEdge true to count rising edge
259   * @param fallingEdge true to count falling edge
260   */
261  public void setUpSourceEdge(boolean risingEdge, boolean fallingEdge) {
262    if (m_upSource == null) {
263      throw new IllegalStateException("Up Source must be set before setting the edge!");
264    }
265    CounterJNI.setCounterUpSourceEdge(m_counter, risingEdge, fallingEdge);
266  }
267
268  /** Disable the up counting source to the counter. */
269  public void clearUpSource() {
270    if (m_upSource != null && m_allocatedUpSource) {
271      m_upSource.close();
272      m_allocatedUpSource = false;
273    }
274    m_upSource = null;
275
276    CounterJNI.clearCounterUpSource(m_counter);
277  }
278
279  /**
280   * Set the down counting source to be a digital input channel.
281   *
282   * @param channel the DIO channel to count 0-9 are on-board, 10-25 are on the MXP
283   */
284  public void setDownSource(int channel) {
285    setDownSource(new DigitalInput(channel));
286    m_allocatedDownSource = true;
287    SendableRegistry.addChild(this, m_downSource);
288  }
289
290  /**
291   * Set the source object that causes the counter to count down. Set the down counting
292   * DigitalSource.
293   *
294   * @param source the digital source to count
295   */
296  public void setDownSource(DigitalSource source) {
297    requireNonNullParam(source, "source", "setDownSource");
298
299    if (m_downSource != null && m_allocatedDownSource) {
300      m_downSource.close();
301      m_allocatedDownSource = false;
302    }
303    CounterJNI.setCounterDownSource(
304        m_counter, source.getPortHandleForRouting(), source.getAnalogTriggerTypeForRouting());
305    m_downSource = source;
306  }
307
308  /**
309   * Set the down counting source to be an analog trigger.
310   *
311   * @param analogTrigger The analog trigger object that is used for the Down Source
312   * @param triggerType The analog trigger output that will trigger the counter.
313   */
314  public void setDownSource(AnalogTrigger analogTrigger, AnalogTriggerType triggerType) {
315    requireNonNullParam(analogTrigger, "analogTrigger", "setDownSource");
316    requireNonNullParam(triggerType, "analogTrigger", "setDownSource");
317
318    setDownSource(analogTrigger.createOutput(triggerType));
319    m_allocatedDownSource = true;
320  }
321
322  /**
323   * Set the edge sensitivity on a down counting source. Set the down source to either detect rising
324   * edges or falling edges.
325   *
326   * @param risingEdge true to count the rising edge
327   * @param fallingEdge true to count the falling edge
328   */
329  public void setDownSourceEdge(boolean risingEdge, boolean fallingEdge) {
330    if (m_downSource == null) {
331      throw new IllegalStateException("Down Source must be set before setting the edge!");
332    }
333
334    CounterJNI.setCounterDownSourceEdge(m_counter, risingEdge, fallingEdge);
335  }
336
337  /** Disable the down counting source to the counter. */
338  public void clearDownSource() {
339    if (m_downSource != null && m_allocatedDownSource) {
340      m_downSource.close();
341      m_allocatedDownSource = false;
342    }
343    m_downSource = null;
344
345    CounterJNI.clearCounterDownSource(m_counter);
346  }
347
348  /**
349   * Set standard up / down counting mode on this counter. Up and down counts are sourced
350   * independently from two inputs.
351   */
352  public void setUpDownCounterMode() {
353    CounterJNI.setCounterUpDownMode(m_counter);
354  }
355
356  /**
357   * Set external direction mode on this counter. Counts are sourced on the Up counter input. The
358   * Down counter input represents the direction to count.
359   */
360  public void setExternalDirectionMode() {
361    CounterJNI.setCounterExternalDirectionMode(m_counter);
362  }
363
364  /**
365   * Set Semi-period mode on this counter. Counts up on both rising and falling edges.
366   *
367   * @param highSemiPeriod true to count up on both rising and falling
368   */
369  public void setSemiPeriodMode(boolean highSemiPeriod) {
370    CounterJNI.setCounterSemiPeriodMode(m_counter, highSemiPeriod);
371  }
372
373  /**
374   * Configure the counter to count in up or down based on the length of the input pulse. This mode
375   * is most useful for direction sensitive gear tooth sensors.
376   *
377   * @param threshold The pulse length beyond which the counter counts the opposite direction. Units
378   *     are seconds.
379   */
380  public void setPulseLengthMode(double threshold) {
381    CounterJNI.setCounterPulseLengthMode(m_counter, threshold);
382  }
383
384  /**
385   * Read the current counter value. Read the value at this instant. It may still be running, so it
386   * reflects the current value. Next time it is read, it might have a different value.
387   */
388  @Override
389  public int get() {
390    return CounterJNI.getCounter(m_counter);
391  }
392
393  /**
394   * Read the current scaled counter value. Read the value at this instant, scaled by the distance
395   * per pulse (defaults to 1).
396   *
397   * @return The distance since the last reset
398   */
399  public double getDistance() {
400    return get() * m_distancePerPulse;
401  }
402
403  /**
404   * Reset the Counter to zero. Set the counter value to zero. This doesn't affect the running state
405   * of the counter, just sets the current value to zero.
406   */
407  @Override
408  public void reset() {
409    CounterJNI.resetCounter(m_counter);
410  }
411
412  /**
413   * Set the maximum period where the device is still considered "moving". Sets the maximum period
414   * where the device is considered moving. This value is used to determine the "stopped" state of
415   * the counter using the GetStopped method.
416   *
417   * @param maxPeriod The maximum period where the counted device is considered moving in seconds.
418   */
419  @Override
420  public final void setMaxPeriod(double maxPeriod) {
421    CounterJNI.setCounterMaxPeriod(m_counter, maxPeriod);
422  }
423
424  /**
425   * Select whether you want to continue updating the event timer output when there are no samples
426   * captured. The output of the event timer has a buffer of periods that are averaged and posted to
427   * a register on the FPGA. When the timer detects that the event source has stopped (based on the
428   * MaxPeriod) the buffer of samples to be averaged is emptied. If you enable the update when
429   * empty, you will be notified of the stopped source and the event time will report 0 samples. If
430   * you disable update when empty, the most recent average will remain on the output until a new
431   * sample is acquired. You will never see 0 samples output (except when there have been no events
432   * since an FPGA reset) and you will likely not see the stopped bit become true (since it is
433   * updated at the end of an average and there are no samples to average).
434   *
435   * @param enabled true to continue updating
436   */
437  public void setUpdateWhenEmpty(boolean enabled) {
438    CounterJNI.setCounterUpdateWhenEmpty(m_counter, enabled);
439  }
440
441  /**
442   * Determine if the clock is stopped. Determine if the clocked input is stopped based on the
443   * MaxPeriod value set using the SetMaxPeriod method. If the clock exceeds the MaxPeriod, then the
444   * device (and counter) are assumed to be stopped and the method will return true.
445   *
446   * @return true if the most recent counter period exceeds the MaxPeriod value set by SetMaxPeriod.
447   */
448  @Override
449  public boolean getStopped() {
450    return CounterJNI.getCounterStopped(m_counter);
451  }
452
453  /**
454   * The last direction the counter value changed.
455   *
456   * @return The last direction the counter value changed.
457   */
458  @Override
459  public boolean getDirection() {
460    return CounterJNI.getCounterDirection(m_counter);
461  }
462
463  /**
464   * Set the Counter to return reversed sensing on the direction. This allows counters to change the
465   * direction they are counting in the case of 1X and 2X quadrature encoding only. Any other
466   * counter mode isn't supported.
467   *
468   * @param reverseDirection true if the value counted should be negated.
469   */
470  public void setReverseDirection(boolean reverseDirection) {
471    CounterJNI.setCounterReverseDirection(m_counter, reverseDirection);
472  }
473
474  /**
475   * Get the Period of the most recent count. Returns the time interval of the most recent count.
476   * This can be used for velocity calculations to determine shaft speed.
477   *
478   * @return The period of the last two pulses in units of seconds.
479   */
480  @Override
481  public double getPeriod() {
482    return CounterJNI.getCounterPeriod(m_counter);
483  }
484
485  /**
486   * Get the current rate of the Counter. Read the current rate of the counter accounting for the
487   * distance per pulse value. The default value for distance per pulse (1) yields units of pulses
488   * per second.
489   *
490   * @return The rate in units/sec
491   */
492  public double getRate() {
493    return m_distancePerPulse / getPeriod();
494  }
495
496  /**
497   * Set the Samples to Average which specifies the number of samples of the timer to average when
498   * calculating the period. Perform averaging to account for mechanical imperfections or as
499   * oversampling to increase resolution.
500   *
501   * @param samplesToAverage The number of samples to average from 1 to 127.
502   */
503  public void setSamplesToAverage(int samplesToAverage) {
504    CounterJNI.setCounterSamplesToAverage(m_counter, samplesToAverage);
505  }
506
507  /**
508   * Get the Samples to Average which specifies the number of samples of the timer to average when
509   * calculating the period. Perform averaging to account for mechanical imperfections or as
510   * oversampling to increase resolution.
511   *
512   * @return SamplesToAverage The number of samples being averaged (from 1 to 127)
513   */
514  public int getSamplesToAverage() {
515    return CounterJNI.getCounterSamplesToAverage(m_counter);
516  }
517
518  /**
519   * Set the distance per pulse for this counter. This sets the multiplier used to determine the
520   * distance driven based on the count value from the encoder. Set this value based on the Pulses
521   * per Revolution and factor in any gearing reductions. This distance can be in any units you
522   * like, linear or angular.
523   *
524   * @param distancePerPulse The scale factor that will be used to convert pulses to useful units.
525   */
526  public void setDistancePerPulse(double distancePerPulse) {
527    m_distancePerPulse = distancePerPulse;
528  }
529
530  @Override
531  public void initSendable(SendableBuilder builder) {
532    builder.setSmartDashboardType("Counter");
533    builder.addDoubleProperty("Value", this::get, null);
534  }
535}