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