001/*----------------------------------------------------------------------------*/ 002/* Copyright (c) FIRST 2008-2017. 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.livewindow.LiveWindowSendable; 018import edu.wpi.first.wpilibj.tables.ITable; 019import edu.wpi.first.wpilibj.util.BoundaryException; 020 021/** 022 * Class for counting the number of ticks on a digital input channel. This is a general purpose 023 * class for counting repetitive events. It can return the number of counts, the period of the most 024 * recent cycle, and detect when the signal being counted has stopped by supplying a maximum cycle 025 * 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 extends SensorBase implements CounterBase, LiveWindowSendable, PIDSource { 031 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 private 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 } 089 090 /** 091 * Create an instance of a counter where no sources are selected. Then they all must be selected 092 * by calling functions to specify the upsource and the downsource independently. 093 * 094 * <p>The counter will start counting immediately. 095 */ 096 public Counter() { 097 this(Mode.kTwoPulse); 098 } 099 100 /** 101 * Create an instance of a counter from a Digital Input. This is used if an existing digital input 102 * is to be shared by multiple other objects such as encoders or if the Digital Source is not a 103 * DIO channel (such as an Analog Trigger) 104 * 105 * <p>The counter will start counting immediately. 106 * 107 * @param source the digital source to count 108 */ 109 public Counter(DigitalSource source) { 110 this(); 111 if (source == null) { 112 throw new NullPointerException("Digital Source given was null"); 113 } 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 if (encodingType == null) { 144 throw new NullPointerException("Encoding type given was null"); 145 } 146 if (encodingType != EncodingType.k1X && encodingType != EncodingType.k2X) { 147 throw new RuntimeException("Counters only support 1X and 2X quadreature decoding!"); 148 } 149 if (upSource == null) { 150 throw new NullPointerException("Up Source given was null"); 151 } 152 setUpSource(upSource); 153 if (downSource == null) { 154 throw new NullPointerException("Down Source given was null"); 155 } 156 setDownSource(downSource); 157 158 if (encodingType == EncodingType.k1X) { 159 setUpSourceEdge(true, false); 160 CounterJNI.setCounterAverageSize(m_counter, 1); 161 } else { 162 setUpSourceEdge(true, true); 163 CounterJNI.setCounterAverageSize(m_counter, 2); 164 } 165 166 setDownSourceEdge(inverted, true); 167 } 168 169 /** 170 * Create an instance of a Counter object. Create an instance of a simple up-Counter given an 171 * analog trigger. Use the trigger state output from the analog trigger. 172 * 173 * <p>The counter will start counting immediately. 174 * 175 * @param trigger the analog trigger to count 176 */ 177 public Counter(AnalogTrigger trigger) { 178 this(); 179 if (trigger == null) { 180 throw new NullPointerException("The Analog Trigger given was null"); 181 } 182 setUpSource(trigger.createOutput(AnalogTriggerType.kState)); 183 } 184 185 @Override 186 public void free() { 187 setUpdateWhenEmpty(true); 188 189 clearUpSource(); 190 clearDownSource(); 191 192 CounterJNI.freeCounter(m_counter); 193 194 m_upSource = null; 195 m_downSource = null; 196 m_counter = 0; 197 } 198 199 /** 200 * The counter's FPGA index. 201 * 202 * @return the Counter's FPGA index 203 */ 204 @SuppressWarnings("AbbreviationAsWordInName") 205 public int getFPGAIndex() { 206 return m_index; 207 } 208 209 /** 210 * Set the upsource for the counter as a digital input channel. 211 * 212 * @param channel the DIO channel to count 0-9 are on-board, 10-25 are on the MXP 213 */ 214 public void setUpSource(int channel) { 215 setUpSource(new DigitalInput(channel)); 216 m_allocatedUpSource = true; 217 } 218 219 /** 220 * Set the source object that causes the counter to count up. Set the up counting DigitalSource. 221 * 222 * @param source the digital source to count 223 */ 224 public void setUpSource(DigitalSource source) { 225 if (m_upSource != null && m_allocatedUpSource) { 226 m_upSource.free(); 227 m_allocatedUpSource = false; 228 } 229 m_upSource = source; 230 CounterJNI.setCounterUpSource(m_counter, source.getPortHandleForRouting(), 231 source.getAnalogTriggerTypeForRouting()); 232 } 233 234 /** 235 * Set the up counting source to be an analog trigger. 236 * 237 * @param analogTrigger The analog trigger object that is used for the Up Source 238 * @param triggerType The analog trigger output that will trigger the counter. 239 */ 240 public void setUpSource(AnalogTrigger analogTrigger, AnalogTriggerType triggerType) { 241 if (analogTrigger == null) { 242 throw new NullPointerException("Analog Trigger given was null"); 243 } 244 if (triggerType == null) { 245 throw new NullPointerException("Analog Trigger Type given was null"); 246 } 247 setUpSource(analogTrigger.createOutput(triggerType)); 248 m_allocatedUpSource = true; 249 } 250 251 /** 252 * Set the edge sensitivity on an up counting source. Set the up source to either detect rising 253 * edges or falling edges. 254 * 255 * @param risingEdge true to count rising edge 256 * @param fallingEdge true to count falling edge 257 */ 258 public void setUpSourceEdge(boolean risingEdge, boolean fallingEdge) { 259 if (m_upSource == null) { 260 throw new RuntimeException("Up Source must be set before setting the edge!"); 261 } 262 CounterJNI.setCounterUpSourceEdge(m_counter, risingEdge, fallingEdge); 263 } 264 265 /** 266 * Disable the up counting source to the counter. 267 */ 268 public void clearUpSource() { 269 if (m_upSource != null && m_allocatedUpSource) { 270 m_upSource.free(); 271 m_allocatedUpSource = false; 272 } 273 m_upSource = null; 274 275 CounterJNI.clearCounterUpSource(m_counter); 276 } 277 278 /** 279 * Set the down counting source to be a digital input channel. 280 * 281 * @param channel the DIO channel to count 0-9 are on-board, 10-25 are on the MXP 282 */ 283 public void setDownSource(int channel) { 284 setDownSource(new DigitalInput(channel)); 285 m_allocatedDownSource = true; 286 } 287 288 /** 289 * Set the source object that causes the counter to count down. Set the down counting 290 * DigitalSource. 291 * 292 * @param source the digital source to count 293 */ 294 public void setDownSource(DigitalSource source) { 295 if (source == null) { 296 throw new NullPointerException("The Digital Source given was null"); 297 } 298 299 if (m_downSource != null && m_allocatedDownSource) { 300 m_downSource.free(); 301 m_allocatedDownSource = false; 302 } 303 CounterJNI.setCounterDownSource(m_counter, source.getPortHandleForRouting(), 304 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 if (analogTrigger == null) { 316 throw new NullPointerException("Analog Trigger given was null"); 317 } 318 if (triggerType == null) { 319 throw new NullPointerException("Analog Trigger Type given was null"); 320 } 321 322 setDownSource(analogTrigger.createOutput(triggerType)); 323 m_allocatedDownSource = true; 324 } 325 326 /** 327 * Set the edge sensitivity on a down counting source. Set the down source to either detect rising 328 * edges or falling edges. 329 * 330 * @param risingEdge true to count the rising edge 331 * @param fallingEdge true to count the falling edge 332 */ 333 public void setDownSourceEdge(boolean risingEdge, boolean fallingEdge) { 334 if (m_downSource == null) { 335 throw new RuntimeException(" Down Source must be set before setting the edge!"); 336 } 337 CounterJNI.setCounterDownSourceEdge(m_counter, risingEdge, fallingEdge); 338 } 339 340 /** 341 * Disable the down counting source to the counter. 342 */ 343 public void clearDownSource() { 344 if (m_downSource != null && m_allocatedDownSource) { 345 m_downSource.free(); 346 m_allocatedDownSource = false; 347 } 348 m_downSource = null; 349 350 CounterJNI.clearCounterDownSource(m_counter); 351 } 352 353 /** 354 * Set standard up / down counting mode on this counter. Up and down counts are sourced 355 * independently from two inputs. 356 */ 357 public void setUpDownCounterMode() { 358 CounterJNI.setCounterUpDownMode(m_counter); 359 } 360 361 /** 362 * Set external direction mode on this counter. Counts are sourced on the Up counter input. The 363 * Down counter input represents the direction to count. 364 */ 365 public void setExternalDirectionMode() { 366 CounterJNI.setCounterExternalDirectionMode(m_counter); 367 } 368 369 /** 370 * Set Semi-period mode on this counter. Counts up on both rising and falling edges. 371 * 372 * @param highSemiPeriod true to count up on both rising and falling 373 */ 374 public void setSemiPeriodMode(boolean highSemiPeriod) { 375 CounterJNI.setCounterSemiPeriodMode(m_counter, highSemiPeriod); 376 } 377 378 /** 379 * Configure the counter to count in up or down based on the length of the input pulse. This mode 380 * is most useful for direction sensitive gear tooth sensors. 381 * 382 * @param threshold The pulse length beyond which the counter counts the opposite direction. Units 383 * are seconds. 384 */ 385 public void setPulseLengthMode(double threshold) { 386 CounterJNI.setCounterPulseLengthMode(m_counter, threshold); 387 } 388 389 /** 390 * Read the current counter value. Read the value at this instant. It may still be running, so it 391 * reflects the current value. Next time it is read, it might have a different value. 392 */ 393 @Override 394 public int get() { 395 return CounterJNI.getCounter(m_counter); 396 } 397 398 /** 399 * Read the current scaled counter value. Read the value at this instant, scaled by the distance 400 * per pulse (defaults to 1). 401 * 402 * @return The distance since the last reset 403 */ 404 public double getDistance() { 405 return get() * m_distancePerPulse; 406 } 407 408 /** 409 * Reset the Counter to zero. Set the counter value to zero. This doesn't effect the running state 410 * of the counter, just sets the current value to zero. 411 */ 412 @Override 413 public void reset() { 414 CounterJNI.resetCounter(m_counter); 415 } 416 417 /** 418 * Set the maximum period where the device is still considered "moving". Sets the maximum period 419 * where the device is considered moving. This value is used to determine the "stopped" state of 420 * the counter using the GetStopped method. 421 * 422 * @param maxPeriod The maximum period where the counted device is considered moving in seconds. 423 */ 424 @Override 425 public void setMaxPeriod(double maxPeriod) { 426 CounterJNI.setCounterMaxPeriod(m_counter, maxPeriod); 427 } 428 429 /** 430 * Select whether you want to continue updating the event timer output when there are no samples 431 * captured. The output of the event timer has a buffer of periods that are averaged and posted to 432 * a register on the FPGA. When the timer detects that the event source has stopped (based on the 433 * MaxPeriod) the buffer of samples to be averaged is emptied. If you enable the update when 434 * empty, you will be notified of the stopped source and the event time will report 0 samples. If 435 * you disable update when empty, the most recent average will remain on the output until a new 436 * sample is acquired. You will never see 0 samples output (except when there have been no events 437 * since an FPGA reset) and you will likely not see the stopped bit become true (since it is 438 * updated at the end of an average and there are no samples to average). 439 * 440 * @param enabled true to continue updating 441 */ 442 public void setUpdateWhenEmpty(boolean enabled) { 443 CounterJNI.setCounterUpdateWhenEmpty(m_counter, enabled); 444 } 445 446 /** 447 * Determine if the clock is stopped. Determine if the clocked input is stopped based on the 448 * MaxPeriod value set using the SetMaxPeriod method. If the clock exceeds the MaxPeriod, then the 449 * device (and counter) are assumed to be stopped and it returns true. 450 * 451 * @return true if the most recent counter period exceeds the MaxPeriod value set by SetMaxPeriod. 452 */ 453 @Override 454 public boolean getStopped() { 455 return CounterJNI.getCounterStopped(m_counter); 456 } 457 458 /** 459 * The last direction the counter value changed. 460 * 461 * @return The last direction the counter value changed. 462 */ 463 @Override 464 public boolean getDirection() { 465 return CounterJNI.getCounterDirection(m_counter); 466 } 467 468 /** 469 * Set the Counter to return reversed sensing on the direction. This allows counters to change the 470 * direction they are counting in the case of 1X and 2X quadrature encoding only. Any other 471 * counter mode isn't supported. 472 * 473 * @param reverseDirection true if the value counted should be negated. 474 */ 475 public void setReverseDirection(boolean reverseDirection) { 476 CounterJNI.setCounterReverseDirection(m_counter, reverseDirection); 477 } 478 479 /** 480 * Get the Period of the most recent count. Returns the time interval of the most recent count. 481 * This can be used for velocity calculations to determine shaft speed. 482 * 483 * @return The period of the last two pulses in units of seconds. 484 */ 485 @Override 486 public double getPeriod() { 487 return CounterJNI.getCounterPeriod(m_counter); 488 } 489 490 /** 491 * Get the current rate of the Counter. Read the current rate of the counter accounting for the 492 * distance per pulse value. The default value for distance per pulse (1) yields units of pulses 493 * per second. 494 * 495 * @return The rate in units/sec 496 */ 497 public double getRate() { 498 return m_distancePerPulse / getPeriod(); 499 } 500 501 /** 502 * Set the Samples to Average which specifies the number of samples of the timer to average when 503 * calculating the period. Perform averaging to account for mechanical imperfections or as 504 * oversampling to increase resolution. 505 * 506 * @param samplesToAverage The number of samples to average from 1 to 127. 507 */ 508 public void setSamplesToAverage(int samplesToAverage) { 509 CounterJNI.setCounterSamplesToAverage(m_counter, samplesToAverage); 510 } 511 512 /** 513 * Get the Samples to Average which specifies the number of samples of the timer to average when 514 * calculating the period. Perform averaging to account for mechanical imperfections or as 515 * oversampling to increase resolution. 516 * 517 * @return SamplesToAverage The number of samples being averaged (from 1 to 127) 518 */ 519 public int getSamplesToAverage() { 520 return CounterJNI.getCounterSamplesToAverage(m_counter); 521 } 522 523 /** 524 * Set the distance per pulse for this counter. This sets the multiplier used to determine the 525 * distance driven based on the count value from the encoder. Set this value based on the Pulses 526 * per Revolution and factor in any gearing reductions. This distance can be in any units you 527 * like, linear or angular. 528 * 529 * @param distancePerPulse The scale factor that will be used to convert pulses to useful units. 530 */ 531 public void setDistancePerPulse(double distancePerPulse) { 532 m_distancePerPulse = distancePerPulse; 533 } 534 535 /** 536 * Set which parameter of the encoder you are using as a process control variable. The counter 537 * class supports the rate and distance parameters. 538 * 539 * @param pidSource An enum to select the parameter. 540 */ 541 public void setPIDSourceType(PIDSourceType pidSource) { 542 if (pidSource == null) { 543 throw new NullPointerException("PID Source Parameter given was null"); 544 } 545 BoundaryException.assertWithinBounds(pidSource.value, 0, 1); 546 m_pidSource = pidSource; 547 } 548 549 public PIDSourceType getPIDSourceType() { 550 return m_pidSource; 551 } 552 553 @Override 554 public double pidGet() { 555 switch (m_pidSource) { 556 case kDisplacement: 557 return getDistance(); 558 case kRate: 559 return getRate(); 560 default: 561 return 0.0; 562 } 563 } 564 565 @Override 566 public String getSmartDashboardType() { 567 return "Counter"; 568 } 569 570 private ITable m_table; 571 572 @Override 573 public void initTable(ITable subtable) { 574 m_table = subtable; 575 updateTable(); 576 } 577 578 @Override 579 public ITable getTable() { 580 return m_table; 581 } 582 583 @Override 584 public void updateTable() { 585 if (m_table != null) { 586 m_table.putNumber("Value", get()); 587 } 588 } 589 590 @Override 591 public void startLiveWindowMode() { 592 } 593 594 @Override 595 public void stopLiveWindowMode() { 596 } 597}