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.EncoderJNI; 010import edu.wpi.first.hal.HAL; 011import edu.wpi.first.hal.SimDevice; 012import edu.wpi.first.util.sendable.Sendable; 013import edu.wpi.first.util.sendable.SendableBuilder; 014import edu.wpi.first.util.sendable.SendableRegistry; 015 016/** 017 * Class to read quadrature encoders. 018 * 019 * <p>Quadrature encoders are devices that count shaft rotation and can sense direction. The output 020 * of the Encoder class is an integer that can count either up or down, and can go negative for 021 * reverse direction counting. When creating Encoders, a direction can be supplied that inverts the 022 * sense of the output to make code more readable if the encoder is mounted such that forward 023 * movement generates negative values. Quadrature encoders have two digital outputs, an A Channel 024 * and a B Channel, that are out of phase with each other for direction sensing. 025 * 026 * <p>All encoders will immediately start counting - reset() them if you need them to be zeroed 027 * before use. 028 */ 029public class Encoder implements CounterBase, Sendable, AutoCloseable { 030 private final EncodingType m_encodingType; 031 032 int m_encoder; // the HAL encoder object 033 034 /** 035 * Common initialization code for Encoders. This code allocates resources for Encoders and is 036 * common to all constructors. 037 * 038 * <p>The encoder will start counting immediately. 039 * 040 * @param aChannel The a channel. 041 * @param bChannel The b channel. 042 * @param reverseDirection If true, counts down instead of up (this is all relative) 043 */ 044 private void initEncoder( 045 int aChannel, int bChannel, boolean reverseDirection, final EncodingType type) { 046 m_encoder = EncoderJNI.initializeEncoder(aChannel, bChannel, reverseDirection, type.value); 047 048 String typeStr = 049 switch (type) { 050 case k1X -> "Encoder:1x"; 051 case k2X -> "Encoder:2x"; 052 case k4X -> "Encoder:4x"; 053 default -> "Encoder"; 054 }; 055 HAL.reportUsage("IO[" + aChannel + "," + bChannel + "]", typeStr); 056 057 int fpgaIndex = getFPGAIndex(); 058 SendableRegistry.add(this, "Encoder", fpgaIndex); 059 } 060 061 /** 062 * Encoder constructor. Construct a Encoder given a and b channels. 063 * 064 * <p>The encoder will start counting immediately. 065 * 066 * @param channelA The 'a' channel DIO channel. 0-9 are on-board, 10-25 are on the MXP port 067 * @param channelB The 'b' channel DIO channel. 0-9 are on-board, 10-25 are on the MXP port 068 * @param reverseDirection represents the orientation of the encoder and inverts the output values 069 * if necessary so forward represents positive values. 070 */ 071 public Encoder(final int channelA, final int channelB, boolean reverseDirection) { 072 this(channelA, channelB, reverseDirection, EncodingType.k4X); 073 } 074 075 /** 076 * Encoder constructor. Construct an Encoder given a and b channels. 077 * 078 * <p>The encoder will start counting immediately. 079 * 080 * @param channelA The a channel digital input channel. 081 * @param channelB The b channel digital input channel. 082 */ 083 public Encoder(final int channelA, final int channelB) { 084 this(channelA, channelB, false); 085 } 086 087 /** 088 * Encoder constructor. Construct an Encoder given a and b channels. 089 * 090 * <p>The encoder will start counting immediately. 091 * 092 * @param channelA The a channel digital input channel. 093 * @param channelB The b channel digital input channel. 094 * @param reverseDirection represents the orientation of the encoder and inverts the output values 095 * if necessary so forward represents positive values. 096 * @param encodingType either k1X, k2X, or k4X to indicate 1X, 2X or 4X decoding. If 4X is 097 * selected, then an encoder FPGA object is used and the returned counts will be 4x the 098 * encoder spec'd value since all rising and falling edges are counted. If 1X or 2X are 099 * selected, then a counter object will be used and the returned value will either exactly 100 * match the spec'd count or be double (2x) the spec'd count. 101 */ 102 @SuppressWarnings("this-escape") 103 public Encoder( 104 final int channelA, 105 final int channelB, 106 boolean reverseDirection, 107 final EncodingType encodingType) { 108 requireNonNullParam(encodingType, "encodingType", "Encoder"); 109 110 m_encodingType = encodingType; 111 // SendableRegistry.addChild(this, m_aSource); 112 // SendableRegistry.addChild(this, m_bSource); 113 initEncoder(channelA, channelB, reverseDirection, encodingType); 114 } 115 116 /** 117 * Get the FPGA index of the encoder. 118 * 119 * @return The Encoder's FPGA index. 120 */ 121 public int getFPGAIndex() { 122 return EncoderJNI.getEncoderFPGAIndex(m_encoder); 123 } 124 125 /** 126 * Used to divide raw edge counts down to spec'd counts. 127 * 128 * @return The encoding scale factor 1x, 2x, or 4x, per the requested encoding type. 129 */ 130 public int getEncodingScale() { 131 return EncoderJNI.getEncoderEncodingScale(m_encoder); 132 } 133 134 @Override 135 public void close() { 136 SendableRegistry.remove(this); 137 // if (m_aSource != null && m_allocatedA) { 138 // m_aSource.close(); 139 // m_allocatedA = false; 140 // } 141 // if (m_bSource != null && m_allocatedB) { 142 // m_bSource.close(); 143 // m_allocatedB = false; 144 // } 145 // if (m_indexSource != null && m_allocatedI) { 146 // m_indexSource.close(); 147 // m_allocatedI = false; 148 // } 149 150 // m_aSource = null; 151 // m_bSource = null; 152 // m_indexSource = null; 153 EncoderJNI.freeEncoder(m_encoder); 154 m_encoder = 0; 155 } 156 157 /** 158 * Gets the raw value from the encoder. The raw value is the actual count unscaled by the 1x, 2x, 159 * or 4x scale factor. 160 * 161 * @return Current raw count from the encoder 162 */ 163 public int getRaw() { 164 return EncoderJNI.getEncoderRaw(m_encoder); 165 } 166 167 /** 168 * Gets the current count. Returns the current count on the Encoder. This method compensates for 169 * the decoding type. 170 * 171 * @return Current count from the Encoder adjusted for the 1x, 2x, or 4x scale factor. 172 */ 173 @Override 174 public int get() { 175 return EncoderJNI.getEncoder(m_encoder); 176 } 177 178 /** Reset the Encoder distance to zero. Resets the current count to zero on the encoder. */ 179 @Override 180 public void reset() { 181 EncoderJNI.resetEncoder(m_encoder); 182 } 183 184 /** 185 * Returns the period of the most recent pulse. Returns the period of the most recent Encoder 186 * pulse in seconds. This method compensates for the decoding type. 187 * 188 * <p><b>Warning:</b> This returns unscaled periods. Use getRate() for rates that are scaled using 189 * the value from setDistancePerPulse(). 190 * 191 * @return Period in seconds of the most recent pulse. 192 * @deprecated Use getRate() in favor of this method. 193 */ 194 @Override 195 @Deprecated 196 public double getPeriod() { 197 return EncoderJNI.getEncoderPeriod(m_encoder); 198 } 199 200 /** 201 * Sets the maximum period for stopped detection. Sets the value that represents the maximum 202 * period of the Encoder before it will assume that the attached device is stopped. This timeout 203 * allows users to determine if the wheels or other shaft has stopped rotating. This method 204 * compensates for the decoding type. 205 * 206 * @param maxPeriod The maximum time between rising and falling edges before the FPGA will report 207 * the device stopped. This is expressed in seconds. 208 * @deprecated Use setMinRate() in favor of this method. This takes unscaled periods and 209 * setMinRate() scales using value from setDistancePerPulse(). 210 */ 211 @Override 212 @Deprecated 213 public void setMaxPeriod(double maxPeriod) { 214 EncoderJNI.setEncoderMaxPeriod(m_encoder, maxPeriod); 215 } 216 217 /** 218 * Determine if the encoder is stopped. Using the MaxPeriod value, a boolean is returned that is 219 * true if the encoder is considered stopped and false if it is still moving. A stopped encoder is 220 * one where the most recent pulse width exceeds the MaxPeriod. 221 * 222 * @return True if the encoder is considered stopped. 223 */ 224 @Override 225 public boolean getStopped() { 226 return EncoderJNI.getEncoderStopped(m_encoder); 227 } 228 229 /** 230 * The last direction the encoder value changed. 231 * 232 * @return The last direction the encoder value changed. 233 */ 234 @Override 235 public boolean getDirection() { 236 return EncoderJNI.getEncoderDirection(m_encoder); 237 } 238 239 /** 240 * Get the distance the robot has driven since the last reset as scaled by the value from {@link 241 * #setDistancePerPulse(double)}. 242 * 243 * @return The distance driven since the last reset 244 */ 245 public double getDistance() { 246 return EncoderJNI.getEncoderDistance(m_encoder); 247 } 248 249 /** 250 * Get the current rate of the encoder. Units are distance per second as scaled by the value from 251 * setDistancePerPulse(). 252 * 253 * @return The current rate of the encoder. 254 */ 255 public double getRate() { 256 return EncoderJNI.getEncoderRate(m_encoder); 257 } 258 259 /** 260 * Set the minimum rate of the device before the hardware reports it stopped. 261 * 262 * @param minRate The minimum rate. The units are in distance per second as scaled by the value 263 * from setDistancePerPulse(). 264 */ 265 public void setMinRate(double minRate) { 266 EncoderJNI.setEncoderMinRate(m_encoder, minRate); 267 } 268 269 /** 270 * Set the distance per pulse for this encoder. This sets the multiplier used to determine the 271 * distance driven based on the count value from the encoder. Do not include the decoding type in 272 * this scale. The library already compensates for the decoding type. Set this value based on the 273 * encoder's rated Pulses per Revolution and factor in gearing reductions following the encoder 274 * shaft. This distance can be in any units you like, linear or angular. 275 * 276 * @param distancePerPulse The scale factor that will be used to convert pulses to useful units. 277 */ 278 public void setDistancePerPulse(double distancePerPulse) { 279 EncoderJNI.setEncoderDistancePerPulse(m_encoder, distancePerPulse); 280 } 281 282 /** 283 * Get the distance per pulse for this encoder. 284 * 285 * @return The scale factor that will be used to convert pulses to useful units. 286 */ 287 public double getDistancePerPulse() { 288 return EncoderJNI.getEncoderDistancePerPulse(m_encoder); 289 } 290 291 /** 292 * Set the direction sensing for this encoder. This sets the direction sensing on the encoder so 293 * that it could count in the correct software direction regardless of the mounting. 294 * 295 * @param reverseDirection true if the encoder direction should be reversed 296 */ 297 public void setReverseDirection(boolean reverseDirection) { 298 EncoderJNI.setEncoderReverseDirection(m_encoder, reverseDirection); 299 } 300 301 /** 302 * Set the Samples to Average which specifies the number of samples of the timer to average when 303 * calculating the period. Perform averaging to account for mechanical imperfections or as 304 * oversampling to increase resolution. 305 * 306 * @param samplesToAverage The number of samples to average from 1 to 127. 307 */ 308 public void setSamplesToAverage(int samplesToAverage) { 309 EncoderJNI.setEncoderSamplesToAverage(m_encoder, samplesToAverage); 310 } 311 312 /** 313 * Get the Samples to Average which specifies the number of samples of the timer to average when 314 * calculating the period. Perform averaging to account for mechanical imperfections or as 315 * oversampling to increase resolution. 316 * 317 * @return SamplesToAverage The number of samples being averaged (from 1 to 127) 318 */ 319 public int getSamplesToAverage() { 320 return EncoderJNI.getEncoderSamplesToAverage(m_encoder); 321 } 322 323 /** 324 * Indicates this input is used by a simulated device. 325 * 326 * @param device simulated device handle 327 */ 328 public void setSimDevice(SimDevice device) { 329 EncoderJNI.setEncoderSimDevice(m_encoder, device.getNativeHandle()); 330 } 331 332 /** 333 * Gets the decoding scale factor for scaling raw values to full counts. 334 * 335 * @return decoding scale factor 336 */ 337 public double getDecodingScaleFactor() { 338 return switch (m_encodingType) { 339 case k1X -> 1.0; 340 case k2X -> 0.5; 341 case k4X -> 0.25; 342 }; 343 } 344 345 @Override 346 public void initSendable(SendableBuilder builder) { 347 if (EncoderJNI.getEncoderEncodingType(m_encoder) == EncodingType.k4X.value) { 348 builder.setSmartDashboardType("Quadrature Encoder"); 349 } else { 350 builder.setSmartDashboardType("Encoder"); 351 } 352 353 builder.addDoubleProperty("Speed", this::getRate, null); 354 builder.addDoubleProperty("Distance", this::getDistance, null); 355 builder.addDoubleProperty("Distance per Tick", this::getDistancePerPulse, null); 356 } 357}