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 public final int value; 042 043 Mode(int value) { 044 this.value = value; 045 } 046 } 047 048 protected DigitalSource m_upSource; // /< What makes the counter count up. 049 protected DigitalSource m_downSource; // /< What makes the counter count down. 050 private boolean m_allocatedUpSource; 051 private boolean m_allocatedDownSource; 052 int m_counter; // /< The FPGA counter object. 053 private int m_index; // /< The index of this counter. 054 private double m_distancePerPulse = 1; // distance of travel for each tick 055 056 /** 057 * Create an instance of a counter with the given mode. 058 * 059 * @param mode The counter mode. 060 */ 061 @SuppressWarnings("this-escape") 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 @SuppressWarnings("this-escape") 100 public Counter(DigitalSource source) { 101 this(); 102 103 requireNonNullParam(source, "source", "Counter"); 104 setUpSource(source); 105 } 106 107 /** 108 * Create an instance of a Counter object. Create an up-Counter instance given a channel. 109 * 110 * <p>The counter will start counting immediately. 111 * 112 * @param channel the DIO channel to use as the up source. 0-9 are on-board, 10-25 are on the MXP 113 */ 114 @SuppressWarnings("this-escape") 115 public Counter(int channel) { 116 this(); 117 setUpSource(channel); 118 } 119 120 /** 121 * Create an instance of a Counter object. Create an instance of a simple up-Counter given an 122 * analog trigger. Use the trigger state output from the analog trigger. 123 * 124 * <p>The counter will start counting immediately. 125 * 126 * @param encodingType which edges to count 127 * @param upSource first source to count 128 * @param downSource second source for direction 129 * @param inverted true to invert the count 130 */ 131 @SuppressWarnings("this-escape") 132 public Counter( 133 EncodingType encodingType, 134 DigitalSource upSource, 135 DigitalSource downSource, 136 boolean inverted) { 137 this(Mode.kExternalDirection); 138 139 requireNonNullParam(encodingType, "encodingType", "Counter"); 140 requireNonNullParam(upSource, "upSource", "Counter"); 141 requireNonNullParam(downSource, "downSource", "Counter"); 142 143 if (encodingType != EncodingType.k1X && encodingType != EncodingType.k2X) { 144 throw new IllegalArgumentException("Counters only support 1X and 2X quadrature decoding!"); 145 } 146 147 setUpSource(upSource); 148 setDownSource(downSource); 149 150 if (encodingType == EncodingType.k1X) { 151 setUpSourceEdge(true, false); 152 CounterJNI.setCounterAverageSize(m_counter, 1); 153 } else { 154 setUpSourceEdge(true, true); 155 CounterJNI.setCounterAverageSize(m_counter, 2); 156 } 157 158 setDownSourceEdge(inverted, true); 159 } 160 161 /** 162 * Create an instance of a Counter object. Create an instance of a simple up-Counter given an 163 * analog trigger. Use the trigger state output from the analog trigger. 164 * 165 * <p>The counter will start counting immediately. 166 * 167 * @param trigger the analog trigger to count 168 */ 169 @SuppressWarnings("this-escape") 170 public Counter(AnalogTrigger trigger) { 171 this(); 172 173 requireNonNullParam(trigger, "trigger", "Counter"); 174 175 setUpSource(trigger.createOutput(AnalogTriggerType.kState)); 176 } 177 178 @Override 179 public void close() { 180 SendableRegistry.remove(this); 181 182 setUpdateWhenEmpty(true); 183 184 clearUpSource(); 185 clearDownSource(); 186 187 CounterJNI.freeCounter(m_counter); 188 189 m_upSource = null; 190 m_downSource = null; 191 m_counter = 0; 192 } 193 194 /** 195 * The counter's FPGA index. 196 * 197 * @return the Counter's FPGA index 198 */ 199 public int getFPGAIndex() { 200 return m_index; 201 } 202 203 /** 204 * Set the upsource for the counter as a digital input channel. 205 * 206 * @param channel the DIO channel to count 0-9 are on-board, 10-25 are on the MXP 207 */ 208 public final void setUpSource(int channel) { 209 setUpSource(new DigitalInput(channel)); 210 m_allocatedUpSource = true; 211 SendableRegistry.addChild(this, m_upSource); 212 } 213 214 /** 215 * Set the source object that causes the counter to count up. Set the up counting DigitalSource. 216 * 217 * @param source the digital source to count 218 */ 219 public void setUpSource(DigitalSource source) { 220 if (m_upSource != null && m_allocatedUpSource) { 221 m_upSource.close(); 222 m_allocatedUpSource = false; 223 } 224 m_upSource = source; 225 CounterJNI.setCounterUpSource( 226 m_counter, source.getPortHandleForRouting(), source.getAnalogTriggerTypeForRouting()); 227 } 228 229 /** 230 * Set the up counting source to be an analog trigger. 231 * 232 * @param analogTrigger The analog trigger object that is used for the Up Source 233 * @param triggerType The analog trigger output that will trigger the counter. 234 */ 235 public void setUpSource(AnalogTrigger analogTrigger, AnalogTriggerType triggerType) { 236 requireNonNullParam(analogTrigger, "analogTrigger", "setUpSource"); 237 requireNonNullParam(triggerType, "triggerType", "setUpSource"); 238 239 setUpSource(analogTrigger.createOutput(triggerType)); 240 m_allocatedUpSource = true; 241 } 242 243 /** 244 * Set the edge sensitivity on an up counting source. Set the up source to either detect rising 245 * edges or falling edges. 246 * 247 * @param risingEdge true to count rising edge 248 * @param fallingEdge true to count falling edge 249 */ 250 public void setUpSourceEdge(boolean risingEdge, boolean fallingEdge) { 251 if (m_upSource == null) { 252 throw new IllegalStateException("Up Source must be set before setting the edge!"); 253 } 254 CounterJNI.setCounterUpSourceEdge(m_counter, risingEdge, fallingEdge); 255 } 256 257 /** Disable the up counting source to the counter. */ 258 public void clearUpSource() { 259 if (m_upSource != null && m_allocatedUpSource) { 260 m_upSource.close(); 261 m_allocatedUpSource = false; 262 } 263 m_upSource = null; 264 265 CounterJNI.clearCounterUpSource(m_counter); 266 } 267 268 /** 269 * Set the down counting source to be a digital input channel. 270 * 271 * @param channel the DIO channel to count 0-9 are on-board, 10-25 are on the MXP 272 */ 273 public void setDownSource(int channel) { 274 setDownSource(new DigitalInput(channel)); 275 m_allocatedDownSource = true; 276 SendableRegistry.addChild(this, m_downSource); 277 } 278 279 /** 280 * Set the source object that causes the counter to count down. Set the down counting 281 * DigitalSource. 282 * 283 * @param source the digital source to count 284 */ 285 public void setDownSource(DigitalSource source) { 286 requireNonNullParam(source, "source", "setDownSource"); 287 288 if (m_downSource != null && m_allocatedDownSource) { 289 m_downSource.close(); 290 m_allocatedDownSource = false; 291 } 292 CounterJNI.setCounterDownSource( 293 m_counter, source.getPortHandleForRouting(), source.getAnalogTriggerTypeForRouting()); 294 m_downSource = source; 295 } 296 297 /** 298 * Set the down counting source to be an analog trigger. 299 * 300 * @param analogTrigger The analog trigger object that is used for the Down Source 301 * @param triggerType The analog trigger output that will trigger the counter. 302 */ 303 public void setDownSource(AnalogTrigger analogTrigger, AnalogTriggerType triggerType) { 304 requireNonNullParam(analogTrigger, "analogTrigger", "setDownSource"); 305 requireNonNullParam(triggerType, "analogTrigger", "setDownSource"); 306 307 setDownSource(analogTrigger.createOutput(triggerType)); 308 m_allocatedDownSource = true; 309 } 310 311 /** 312 * Set the edge sensitivity on a down counting source. Set the down source to either detect rising 313 * edges or falling edges. 314 * 315 * @param risingEdge true to count the rising edge 316 * @param fallingEdge true to count the falling edge 317 */ 318 public void setDownSourceEdge(boolean risingEdge, boolean fallingEdge) { 319 if (m_downSource == null) { 320 throw new IllegalStateException("Down Source must be set before setting the edge!"); 321 } 322 323 CounterJNI.setCounterDownSourceEdge(m_counter, risingEdge, fallingEdge); 324 } 325 326 /** Disable the down counting source to the counter. */ 327 public void clearDownSource() { 328 if (m_downSource != null && m_allocatedDownSource) { 329 m_downSource.close(); 330 m_allocatedDownSource = false; 331 } 332 m_downSource = null; 333 334 CounterJNI.clearCounterDownSource(m_counter); 335 } 336 337 /** 338 * Set standard up / down counting mode on this counter. Up and down counts are sourced 339 * independently from two inputs. 340 */ 341 public void setUpDownCounterMode() { 342 CounterJNI.setCounterUpDownMode(m_counter); 343 } 344 345 /** 346 * Set external direction mode on this counter. Counts are sourced on the Up counter input. The 347 * Down counter input represents the direction to count. 348 */ 349 public void setExternalDirectionMode() { 350 CounterJNI.setCounterExternalDirectionMode(m_counter); 351 } 352 353 /** 354 * Set Semi-period mode on this counter. Counts up on both rising and falling edges. 355 * 356 * @param highSemiPeriod true to count up on both rising and falling 357 */ 358 public void setSemiPeriodMode(boolean highSemiPeriod) { 359 CounterJNI.setCounterSemiPeriodMode(m_counter, highSemiPeriod); 360 } 361 362 /** 363 * Configure the counter to count in up or down based on the length of the input pulse. This mode 364 * is most useful for direction sensitive gear tooth sensors. 365 * 366 * @param threshold The pulse length beyond which the counter counts the opposite direction. Units 367 * are seconds. 368 */ 369 public void setPulseLengthMode(double threshold) { 370 CounterJNI.setCounterPulseLengthMode(m_counter, threshold); 371 } 372 373 /** 374 * Read the current counter value. Read the value at this instant. It may still be running, so it 375 * reflects the current value. Next time it is read, it might have a different value. 376 */ 377 @Override 378 public int get() { 379 return CounterJNI.getCounter(m_counter); 380 } 381 382 /** 383 * Read the current scaled counter value. Read the value at this instant, scaled by the distance 384 * per pulse (defaults to 1). 385 * 386 * @return The distance since the last reset 387 */ 388 public double getDistance() { 389 return get() * m_distancePerPulse; 390 } 391 392 /** 393 * Reset the Counter to zero. Set the counter value to zero. This doesn't affect the running state 394 * of the counter, just sets the current value to zero. 395 */ 396 @Override 397 public void reset() { 398 CounterJNI.resetCounter(m_counter); 399 } 400 401 /** 402 * Set the maximum period where the device is still considered "moving". Sets the maximum period 403 * where the device is considered moving. This value is used to determine the "stopped" state of 404 * the counter using the GetStopped method. 405 * 406 * @param maxPeriod The maximum period where the counted device is considered moving in seconds. 407 */ 408 @Override 409 public final void setMaxPeriod(double maxPeriod) { 410 CounterJNI.setCounterMaxPeriod(m_counter, maxPeriod); 411 } 412 413 /** 414 * Select whether you want to continue updating the event timer output when there are no samples 415 * captured. The output of the event timer has a buffer of periods that are averaged and posted to 416 * a register on the FPGA. When the timer detects that the event source has stopped (based on the 417 * MaxPeriod) the buffer of samples to be averaged is emptied. If you enable the update when 418 * empty, you will be notified of the stopped source and the event time will report 0 samples. If 419 * you disable update when empty, the most recent average will remain on the output until a new 420 * sample is acquired. You will never see 0 samples output (except when there have been no events 421 * since an FPGA reset) and you will likely not see the stopped bit become true (since it is 422 * updated at the end of an average and there are no samples to average). 423 * 424 * @param enabled true to continue updating 425 */ 426 public void setUpdateWhenEmpty(boolean enabled) { 427 CounterJNI.setCounterUpdateWhenEmpty(m_counter, enabled); 428 } 429 430 /** 431 * Determine if the clock is stopped. Determine if the clocked input is stopped based on the 432 * MaxPeriod value set using the SetMaxPeriod method. If the clock exceeds the MaxPeriod, then the 433 * device (and counter) are assumed to be stopped and the method will return true. 434 * 435 * @return true if the most recent counter period exceeds the MaxPeriod value set by SetMaxPeriod. 436 */ 437 @Override 438 public boolean getStopped() { 439 return CounterJNI.getCounterStopped(m_counter); 440 } 441 442 /** 443 * The last direction the counter value changed. 444 * 445 * @return The last direction the counter value changed. 446 */ 447 @Override 448 public boolean getDirection() { 449 return CounterJNI.getCounterDirection(m_counter); 450 } 451 452 /** 453 * Set the Counter to return reversed sensing on the direction. This allows counters to change the 454 * direction they are counting in the case of 1X and 2X quadrature encoding only. Any other 455 * counter mode isn't supported. 456 * 457 * @param reverseDirection true if the value counted should be negated. 458 */ 459 public void setReverseDirection(boolean reverseDirection) { 460 CounterJNI.setCounterReverseDirection(m_counter, reverseDirection); 461 } 462 463 /** 464 * Get the Period of the most recent count. Returns the time interval of the most recent count. 465 * This can be used for velocity calculations to determine shaft speed. 466 * 467 * @return The period of the last two pulses in units of seconds. 468 */ 469 @Override 470 public double getPeriod() { 471 return CounterJNI.getCounterPeriod(m_counter); 472 } 473 474 /** 475 * Get the current rate of the Counter. Read the current rate of the counter accounting for the 476 * distance per pulse value. The default value for distance per pulse (1) yields units of pulses 477 * per second. 478 * 479 * @return The rate in units/sec 480 */ 481 public double getRate() { 482 return m_distancePerPulse / getPeriod(); 483 } 484 485 /** 486 * Set the Samples to Average which specifies the number of samples of the timer to average when 487 * calculating the period. Perform averaging to account for mechanical imperfections or as 488 * oversampling to increase resolution. 489 * 490 * @param samplesToAverage The number of samples to average from 1 to 127. 491 */ 492 public void setSamplesToAverage(int samplesToAverage) { 493 CounterJNI.setCounterSamplesToAverage(m_counter, samplesToAverage); 494 } 495 496 /** 497 * Get 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 * @return SamplesToAverage The number of samples being averaged (from 1 to 127) 502 */ 503 public int getSamplesToAverage() { 504 return CounterJNI.getCounterSamplesToAverage(m_counter); 505 } 506 507 /** 508 * Set the distance per pulse for this counter. This sets the multiplier used to determine the 509 * distance driven based on the count value from the encoder. Set this value based on the Pulses 510 * per Revolution and factor in any gearing reductions. This distance can be in any units you 511 * like, linear or angular. 512 * 513 * @param distancePerPulse The scale factor that will be used to convert pulses to useful units. 514 */ 515 public void setDistancePerPulse(double distancePerPulse) { 516 m_distancePerPulse = distancePerPulse; 517 } 518 519 @Override 520 public void initSendable(SendableBuilder builder) { 521 builder.setSmartDashboardType("Counter"); 522 builder.addDoubleProperty("Value", this::get, null); 523 } 524}