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