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.wpilibj.util.ErrorMessages.requireNonNullParam; 008import static java.util.Objects.requireNonNull; 009 010import edu.wpi.first.hal.CounterJNI; 011import edu.wpi.first.hal.FRCNetComm.tResourceType; 012import edu.wpi.first.hal.HAL; 013import edu.wpi.first.util.sendable.Sendable; 014import edu.wpi.first.util.sendable.SendableBuilder; 015import edu.wpi.first.util.sendable.SendableRegistry; 016import edu.wpi.first.wpilibj.AnalogTriggerOutput.AnalogTriggerType; 017import java.nio.ByteBuffer; 018import java.nio.ByteOrder; 019 020/** 021 * Class for counting the number of ticks on a digital input channel. 022 * 023 * <p>This is a general purpose class for counting repetitive events. It can return the number of 024 * counts, the period of the most recent cycle, and detect when the signal being counted has stopped 025 * by supplying a maximum cycle 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 implements CounterBase, Sendable, AutoCloseable { 031 /** Mode determines how and what the counter counts. */ 032 public enum Mode { 033 /** mode: two pulse. */ 034 kTwoPulse(0), 035 /** mode: semi period. */ 036 kSemiperiod(1), 037 /** mode: pulse length. */ 038 kPulseLength(2), 039 /** mode: external direction. */ 040 kExternalDirection(3); 041 042 public final int value; 043 044 Mode(int value) { 045 this.value = value; 046 } 047 } 048 049 protected DigitalSource m_upSource; // /< What makes the counter count up. 050 protected DigitalSource m_downSource; // /< What makes the counter count down. 051 private boolean m_allocatedUpSource; 052 private boolean m_allocatedDownSource; 053 int m_counter; // /< The FPGA counter object. 054 private int m_index; // /< The index of this counter. 055 private double m_distancePerPulse; // distance of travel for each tick 056 057 /** 058 * Create an instance of a counter with the given mode. 059 * 060 * @param mode The counter mode. 061 */ 062 public Counter(final Mode mode) { 063 ByteBuffer index = ByteBuffer.allocateDirect(4); 064 // set the byte order 065 index.order(ByteOrder.LITTLE_ENDIAN); 066 m_counter = CounterJNI.initializeCounter(mode.value, index.asIntBuffer()); 067 m_index = index.asIntBuffer().get(0); 068 069 m_allocatedUpSource = false; 070 m_allocatedDownSource = false; 071 m_upSource = null; 072 m_downSource = null; 073 074 setMaxPeriod(0.5); 075 076 HAL.report(tResourceType.kResourceType_Counter, m_index + 1, mode.value + 1); 077 SendableRegistry.addLW(this, "Counter", m_index); 078 } 079 080 /** 081 * Create an instance of a counter where no sources are selected. Then they all must be selected 082 * by calling functions to specify the upsource and the downsource independently. 083 * 084 * <p>The counter will start counting immediately. 085 */ 086 public Counter() { 087 this(Mode.kTwoPulse); 088 } 089 090 /** 091 * Create an instance of a counter from a Digital Input. This is used if an existing digital input 092 * is to be shared by multiple other objects such as encoders or if the Digital Source is not a 093 * DIO channel (such as an Analog Trigger) 094 * 095 * <p>The counter will start counting immediately. 096 * 097 * @param source the digital source to count 098 */ 099 public Counter(DigitalSource source) { 100 this(); 101 102 requireNonNullParam(source, "source", "Counter"); 103 setUpSource(source); 104 } 105 106 /** 107 * Create an instance of a Counter object. Create an up-Counter instance given a channel. 108 * 109 * <p>The counter will start counting immediately. 110 * 111 * @param channel the DIO channel to use as the up source. 0-9 are on-board, 10-25 are on the MXP 112 */ 113 public Counter(int channel) { 114 this(); 115 setUpSource(channel); 116 } 117 118 /** 119 * Create an instance of a Counter object. Create an instance of a simple up-Counter given an 120 * analog trigger. Use the trigger state output from the analog trigger. 121 * 122 * <p>The counter will start counting immediately. 123 * 124 * @param encodingType which edges to count 125 * @param upSource first source to count 126 * @param downSource second source for direction 127 * @param inverted true to invert the count 128 */ 129 public Counter( 130 EncodingType encodingType, 131 DigitalSource upSource, 132 DigitalSource downSource, 133 boolean inverted) { 134 this(Mode.kExternalDirection); 135 136 requireNonNullParam(encodingType, "encodingType", "Counter"); 137 requireNonNullParam(upSource, "upSource", "Counter"); 138 requireNonNullParam(downSource, "downSource", "Counter"); 139 140 if (encodingType != EncodingType.k1X && encodingType != EncodingType.k2X) { 141 throw new IllegalArgumentException("Counters only support 1X and 2X quadrature decoding!"); 142 } 143 144 setUpSource(upSource); 145 setDownSource(downSource); 146 147 if (encodingType == EncodingType.k1X) { 148 setUpSourceEdge(true, false); 149 CounterJNI.setCounterAverageSize(m_counter, 1); 150 } else { 151 setUpSourceEdge(true, true); 152 CounterJNI.setCounterAverageSize(m_counter, 2); 153 } 154 155 setDownSourceEdge(inverted, true); 156 } 157 158 /** 159 * Create an instance of a Counter object. Create an instance of a simple up-Counter given an 160 * analog trigger. Use the trigger state output from the analog trigger. 161 * 162 * <p>The counter will start counting immediately. 163 * 164 * @param trigger the analog trigger to count 165 */ 166 public Counter(AnalogTrigger trigger) { 167 this(); 168 169 requireNonNullParam(trigger, "trigger", "Counter"); 170 171 setUpSource(trigger.createOutput(AnalogTriggerType.kState)); 172 } 173 174 @Override 175 public void close() { 176 SendableRegistry.remove(this); 177 178 setUpdateWhenEmpty(true); 179 180 clearUpSource(); 181 clearDownSource(); 182 183 CounterJNI.freeCounter(m_counter); 184 185 m_upSource = null; 186 m_downSource = null; 187 m_counter = 0; 188 } 189 190 /** 191 * The counter's FPGA index. 192 * 193 * @return the Counter's FPGA index 194 */ 195 @SuppressWarnings("AbbreviationAsWordInName") 196 public int getFPGAIndex() { 197 return m_index; 198 } 199 200 /** 201 * Set the upsource for the counter as a digital input channel. 202 * 203 * @param channel the DIO channel to count 0-9 are on-board, 10-25 are on the MXP 204 */ 205 public void setUpSource(int channel) { 206 setUpSource(new DigitalInput(channel)); 207 m_allocatedUpSource = true; 208 SendableRegistry.addChild(this, m_upSource); 209 } 210 211 /** 212 * Set the source object that causes the counter to count up. Set the up counting DigitalSource. 213 * 214 * @param source the digital source to count 215 */ 216 public void setUpSource(DigitalSource source) { 217 if (m_upSource != null && m_allocatedUpSource) { 218 m_upSource.close(); 219 m_allocatedUpSource = false; 220 } 221 m_upSource = source; 222 CounterJNI.setCounterUpSource( 223 m_counter, source.getPortHandleForRouting(), source.getAnalogTriggerTypeForRouting()); 224 } 225 226 /** 227 * Set the up counting source to be an analog trigger. 228 * 229 * @param analogTrigger The analog trigger object that is used for the Up Source 230 * @param triggerType The analog trigger output that will trigger the counter. 231 */ 232 public void setUpSource(AnalogTrigger analogTrigger, AnalogTriggerType triggerType) { 233 requireNonNullParam(analogTrigger, "analogTrigger", "setUpSource"); 234 requireNonNullParam(triggerType, "triggerType", "setUpSource"); 235 236 setUpSource(analogTrigger.createOutput(triggerType)); 237 m_allocatedUpSource = true; 238 } 239 240 /** 241 * Set the edge sensitivity on an up counting source. Set the up source to either detect rising 242 * edges or falling edges. 243 * 244 * @param risingEdge true to count rising edge 245 * @param fallingEdge true to count falling edge 246 */ 247 public void setUpSourceEdge(boolean risingEdge, boolean fallingEdge) { 248 if (m_upSource == null) { 249 throw new IllegalStateException("Up Source must be set before setting the edge!"); 250 } 251 CounterJNI.setCounterUpSourceEdge(m_counter, risingEdge, fallingEdge); 252 } 253 254 /** Disable the up counting source to the counter. */ 255 public void clearUpSource() { 256 if (m_upSource != null && m_allocatedUpSource) { 257 m_upSource.close(); 258 m_allocatedUpSource = false; 259 } 260 m_upSource = null; 261 262 CounterJNI.clearCounterUpSource(m_counter); 263 } 264 265 /** 266 * Set the down counting source to be a digital input channel. 267 * 268 * @param channel the DIO channel to count 0-9 are on-board, 10-25 are on the MXP 269 */ 270 public void setDownSource(int channel) { 271 setDownSource(new DigitalInput(channel)); 272 m_allocatedDownSource = true; 273 SendableRegistry.addChild(this, m_downSource); 274 } 275 276 /** 277 * Set the source object that causes the counter to count down. Set the down counting 278 * DigitalSource. 279 * 280 * @param source the digital source to count 281 */ 282 public void setDownSource(DigitalSource source) { 283 requireNonNull(source, "The Digital Source given was null"); 284 285 if (m_downSource != null && m_allocatedDownSource) { 286 m_downSource.close(); 287 m_allocatedDownSource = false; 288 } 289 CounterJNI.setCounterDownSource( 290 m_counter, source.getPortHandleForRouting(), source.getAnalogTriggerTypeForRouting()); 291 m_downSource = source; 292 } 293 294 /** 295 * Set the down counting source to be an analog trigger. 296 * 297 * @param analogTrigger The analog trigger object that is used for the Down Source 298 * @param triggerType The analog trigger output that will trigger the counter. 299 */ 300 public void setDownSource(AnalogTrigger analogTrigger, AnalogTriggerType triggerType) { 301 requireNonNullParam(analogTrigger, "analogTrigger", "setDownSource"); 302 requireNonNullParam(triggerType, "analogTrigger", "setDownSource"); 303 304 setDownSource(analogTrigger.createOutput(triggerType)); 305 m_allocatedDownSource = true; 306 } 307 308 /** 309 * Set the edge sensitivity on a down counting source. Set the down source to either detect rising 310 * edges or falling edges. 311 * 312 * @param risingEdge true to count the rising edge 313 * @param fallingEdge true to count the falling edge 314 */ 315 public void setDownSourceEdge(boolean risingEdge, boolean fallingEdge) { 316 requireNonNull(m_downSource, "Down Source must be set before setting the edge!"); 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}