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}