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