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; // 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  public Counter(final Mode mode) {
062    ByteBuffer index = ByteBuffer.allocateDirect(4);
063    // set the byte order
064    index.order(ByteOrder.LITTLE_ENDIAN);
065    m_counter = CounterJNI.initializeCounter(mode.value, index.asIntBuffer());
066    m_index = index.asIntBuffer().get(0);
067
068    m_allocatedUpSource = false;
069    m_allocatedDownSource = false;
070    m_upSource = null;
071    m_downSource = null;
072
073    setMaxPeriod(0.5);
074
075    HAL.report(tResourceType.kResourceType_Counter, m_index + 1, mode.value + 1);
076    SendableRegistry.addLW(this, "Counter", m_index);
077  }
078
079  /**
080   * Create an instance of a counter where no sources are selected. Then they all must be selected
081   * by calling functions to specify the upsource and the downsource independently.
082   *
083   * <p>The counter will start counting immediately.
084   */
085  public Counter() {
086    this(Mode.kTwoPulse);
087  }
088
089  /**
090   * Create an instance of a counter from a Digital Input. This is used if an existing digital input
091   * is to be shared by multiple other objects such as encoders or if the Digital Source is not a
092   * DIO channel (such as an Analog Trigger)
093   *
094   * <p>The counter will start counting immediately.
095   *
096   * @param source the digital source to count
097   */
098  public Counter(DigitalSource source) {
099    this();
100
101    requireNonNullParam(source, "source", "Counter");
102    setUpSource(source);
103  }
104
105  /**
106   * Create an instance of a Counter object. Create an up-Counter instance given a channel.
107   *
108   * <p>The counter will start counting immediately.
109   *
110   * @param channel the DIO channel to use as the up source. 0-9 are on-board, 10-25 are on the MXP
111   */
112  public Counter(int channel) {
113    this();
114    setUpSource(channel);
115  }
116
117  /**
118   * Create an instance of a Counter object. Create an instance of a simple up-Counter given an
119   * analog trigger. Use the trigger state output from the analog trigger.
120   *
121   * <p>The counter will start counting immediately.
122   *
123   * @param encodingType which edges to count
124   * @param upSource first source to count
125   * @param downSource second source for direction
126   * @param inverted true to invert the count
127   */
128  public Counter(
129      EncodingType encodingType,
130      DigitalSource upSource,
131      DigitalSource downSource,
132      boolean inverted) {
133    this(Mode.kExternalDirection);
134
135    requireNonNullParam(encodingType, "encodingType", "Counter");
136    requireNonNullParam(upSource, "upSource", "Counter");
137    requireNonNullParam(downSource, "downSource", "Counter");
138
139    if (encodingType != EncodingType.k1X && encodingType != EncodingType.k2X) {
140      throw new IllegalArgumentException("Counters only support 1X and 2X quadrature decoding!");
141    }
142
143    setUpSource(upSource);
144    setDownSource(downSource);
145
146    if (encodingType == EncodingType.k1X) {
147      setUpSourceEdge(true, false);
148      CounterJNI.setCounterAverageSize(m_counter, 1);
149    } else {
150      setUpSourceEdge(true, true);
151      CounterJNI.setCounterAverageSize(m_counter, 2);
152    }
153
154    setDownSourceEdge(inverted, true);
155  }
156
157  /**
158   * Create an instance of a Counter object. Create an instance of a simple up-Counter given an
159   * analog trigger. Use the trigger state output from the analog trigger.
160   *
161   * <p>The counter will start counting immediately.
162   *
163   * @param trigger the analog trigger to count
164   */
165  public Counter(AnalogTrigger trigger) {
166    this();
167
168    requireNonNullParam(trigger, "trigger", "Counter");
169
170    setUpSource(trigger.createOutput(AnalogTriggerType.kState));
171  }
172
173  @Override
174  public void close() {
175    SendableRegistry.remove(this);
176
177    setUpdateWhenEmpty(true);
178
179    clearUpSource();
180    clearDownSource();
181
182    CounterJNI.freeCounter(m_counter);
183
184    m_upSource = null;
185    m_downSource = null;
186    m_counter = 0;
187  }
188
189  /**
190   * The counter's FPGA index.
191   *
192   * @return the Counter's FPGA index
193   */
194  public int getFPGAIndex() {
195    return m_index;
196  }
197
198  /**
199   * Set the upsource for the counter as a digital input channel.
200   *
201   * @param channel the DIO channel to count 0-9 are on-board, 10-25 are on the MXP
202   */
203  public void setUpSource(int channel) {
204    setUpSource(new DigitalInput(channel));
205    m_allocatedUpSource = true;
206    SendableRegistry.addChild(this, m_upSource);
207  }
208
209  /**
210   * Set the source object that causes the counter to count up. Set the up counting DigitalSource.
211   *
212   * @param source the digital source to count
213   */
214  public void setUpSource(DigitalSource source) {
215    if (m_upSource != null && m_allocatedUpSource) {
216      m_upSource.close();
217      m_allocatedUpSource = false;
218    }
219    m_upSource = source;
220    CounterJNI.setCounterUpSource(
221        m_counter, source.getPortHandleForRouting(), source.getAnalogTriggerTypeForRouting());
222  }
223
224  /**
225   * Set the up counting source to be an analog trigger.
226   *
227   * @param analogTrigger The analog trigger object that is used for the Up Source
228   * @param triggerType The analog trigger output that will trigger the counter.
229   */
230  public void setUpSource(AnalogTrigger analogTrigger, AnalogTriggerType triggerType) {
231    requireNonNullParam(analogTrigger, "analogTrigger", "setUpSource");
232    requireNonNullParam(triggerType, "triggerType", "setUpSource");
233
234    setUpSource(analogTrigger.createOutput(triggerType));
235    m_allocatedUpSource = true;
236  }
237
238  /**
239   * Set the edge sensitivity on an up counting source. Set the up source to either detect rising
240   * edges or falling edges.
241   *
242   * @param risingEdge true to count rising edge
243   * @param fallingEdge true to count falling edge
244   */
245  public void setUpSourceEdge(boolean risingEdge, boolean fallingEdge) {
246    if (m_upSource == null) {
247      throw new IllegalStateException("Up Source must be set before setting the edge!");
248    }
249    CounterJNI.setCounterUpSourceEdge(m_counter, risingEdge, fallingEdge);
250  }
251
252  /** Disable the up counting source to the counter. */
253  public void clearUpSource() {
254    if (m_upSource != null && m_allocatedUpSource) {
255      m_upSource.close();
256      m_allocatedUpSource = false;
257    }
258    m_upSource = null;
259
260    CounterJNI.clearCounterUpSource(m_counter);
261  }
262
263  /**
264   * Set the down counting source to be a digital input channel.
265   *
266   * @param channel the DIO channel to count 0-9 are on-board, 10-25 are on the MXP
267   */
268  public void setDownSource(int channel) {
269    setDownSource(new DigitalInput(channel));
270    m_allocatedDownSource = true;
271    SendableRegistry.addChild(this, m_downSource);
272  }
273
274  /**
275   * Set the source object that causes the counter to count down. Set the down counting
276   * DigitalSource.
277   *
278   * @param source the digital source to count
279   */
280  public void setDownSource(DigitalSource source) {
281    requireNonNullParam(source, "source", "setDownSource");
282
283    if (m_downSource != null && m_allocatedDownSource) {
284      m_downSource.close();
285      m_allocatedDownSource = false;
286    }
287    CounterJNI.setCounterDownSource(
288        m_counter, source.getPortHandleForRouting(), source.getAnalogTriggerTypeForRouting());
289    m_downSource = source;
290  }
291
292  /**
293   * Set the down counting source to be an analog trigger.
294   *
295   * @param analogTrigger The analog trigger object that is used for the Down Source
296   * @param triggerType The analog trigger output that will trigger the counter.
297   */
298  public void setDownSource(AnalogTrigger analogTrigger, AnalogTriggerType triggerType) {
299    requireNonNullParam(analogTrigger, "analogTrigger", "setDownSource");
300    requireNonNullParam(triggerType, "analogTrigger", "setDownSource");
301
302    setDownSource(analogTrigger.createOutput(triggerType));
303    m_allocatedDownSource = true;
304  }
305
306  /**
307   * Set the edge sensitivity on a down counting source. Set the down source to either detect rising
308   * edges or falling edges.
309   *
310   * @param risingEdge true to count the rising edge
311   * @param fallingEdge true to count the falling edge
312   */
313  public void setDownSourceEdge(boolean risingEdge, boolean fallingEdge) {
314    if (m_downSource == null) {
315      throw new IllegalStateException("Down Source must be set before setting the edge!");
316    }
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}