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}