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 edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType; 011import edu.wpi.first.wpilibj.hal.HAL; 012import edu.wpi.first.wpilibj.livewindow.LiveWindow; 013import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable; 014import edu.wpi.first.wpilibj.tables.ITable; 015 016/** 017 * Ultrasonic rangefinder class. The Ultrasonic rangefinder measures absolute distance based on the 018 * round-trip time of a ping generated by the controller. These sensors use two transducers, a 019 * speaker and a microphone both tuned to the ultrasonic range. A common ultrasonic sensor, the 020 * Daventech SRF04 requires a short pulse to be generated on a digital channel. This causes the 021 * chirp to be emmitted. A second line becomes high as the ping is transmitted and goes low when the 022 * echo is received. The time that the line is high determines the round trip distance (time of 023 * flight). 024 */ 025public class Ultrasonic extends SensorBase implements PIDSource, LiveWindowSendable { 026 027 /** 028 * The units to return when PIDGet is called. 029 */ 030 public enum Unit { 031 /** 032 * Use inches for PIDGet. 033 */ 034 kInches, 035 /** 036 * Use millimeters for PIDGet. 037 */ 038 kMillimeters 039 } 040 041 // Time (sec) for the ping trigger pulse. 042 private static final double kPingTime = 10 * 1e-6; 043 private static final double kSpeedOfSoundInchesPerSec = 1130.0 * 12.0; 044 // head of the ultrasonic sensor list 045 private static Ultrasonic m_firstSensor = null; 046 // automatic round robin mode 047 private static boolean m_automaticEnabled = false; 048 private DigitalInput m_echoChannel; 049 private DigitalOutput m_pingChannel = null; 050 private boolean m_allocatedChannels; 051 private boolean m_enabled = false; 052 private Counter m_counter = null; 053 private Ultrasonic m_nextSensor = null; 054 // task doing the round-robin automatic sensing 055 private static Thread m_task = null; 056 private Unit m_units; 057 private static int m_instances = 0; 058 protected PIDSourceType m_pidSource = PIDSourceType.kDisplacement; 059 060 /** 061 * Background task that goes through the list of ultrasonic sensors and pings each one in turn. 062 * The counter is configured to read the timing of the returned echo pulse. 063 * 064 * <p><b>DANGER WILL ROBINSON, DANGER WILL ROBINSON:</b> This code runs as a task and assumes that 065 * none of the ultrasonic sensors will change while it's running. If one does, then this will 066 * certainly break. Make sure to disable automatic mode before changing anything with the 067 * sensors!! 068 */ 069 private class UltrasonicChecker extends Thread { 070 071 @Override 072 public synchronized void run() { 073 Ultrasonic ultrasonic = null; 074 while (m_automaticEnabled) { 075 if (ultrasonic == null) { 076 ultrasonic = m_firstSensor; 077 } 078 if (ultrasonic == null) { 079 return; 080 } 081 if (ultrasonic.isEnabled()) { 082 // Do the ping 083 ultrasonic.m_pingChannel.pulse(kPingTime); 084 } 085 ultrasonic = ultrasonic.m_nextSensor; 086 Timer.delay(.1); // wait for ping to return 087 } 088 } 089 } 090 091 /** 092 * Initialize the Ultrasonic Sensor. This is the common code that initializes the ultrasonic 093 * sensor given that there are two digital I/O channels allocated. If the system was running in 094 * automatic mode (round robin) when the new sensor is added, it is stopped, the sensor is added, 095 * then automatic mode is restored. 096 */ 097 private synchronized void initialize() { 098 if (m_task == null) { 099 m_task = new UltrasonicChecker(); 100 } 101 final boolean originalMode = m_automaticEnabled; 102 setAutomaticMode(false); // kill task when adding a new sensor 103 m_nextSensor = m_firstSensor; 104 m_firstSensor = this; 105 106 m_counter = new Counter(m_echoChannel); // set up counter for this 107 // sensor 108 m_counter.setMaxPeriod(1.0); 109 m_counter.setSemiPeriodMode(true); 110 m_counter.reset(); 111 m_enabled = true; // make it available for round robin scheduling 112 setAutomaticMode(originalMode); 113 114 m_instances++; 115 HAL.report(tResourceType.kResourceType_Ultrasonic, m_instances); 116 LiveWindow.addSensor("Ultrasonic", m_echoChannel.getChannel(), this); 117 } 118 119 /** 120 * Create an instance of the Ultrasonic Sensor. This is designed to supchannel the Daventech SRF04 121 * and Vex ultrasonic sensors. 122 * 123 * @param pingChannel The digital output channel that sends the pulse to initiate the sensor 124 * sending the ping. 125 * @param echoChannel The digital input channel that receives the echo. The length of time that 126 * the echo is high represents the round trip time of the ping, and the 127 * distance. 128 * @param units The units returned in either kInches or kMilliMeters 129 */ 130 public Ultrasonic(final int pingChannel, final int echoChannel, Unit units) { 131 m_pingChannel = new DigitalOutput(pingChannel); 132 m_echoChannel = new DigitalInput(echoChannel); 133 m_allocatedChannels = true; 134 m_units = units; 135 initialize(); 136 } 137 138 /** 139 * Create an instance of the Ultrasonic Sensor. This is designed to supchannel the Daventech SRF04 140 * and Vex ultrasonic sensors. Default unit is inches. 141 * 142 * @param pingChannel The digital output channel that sends the pulse to initiate the sensor 143 * sending the ping. 144 * @param echoChannel The digital input channel that receives the echo. The length of time that 145 * the echo is high represents the round trip time of the ping, and the 146 * distance. 147 */ 148 public Ultrasonic(final int pingChannel, final int echoChannel) { 149 this(pingChannel, echoChannel, Unit.kInches); 150 } 151 152 /** 153 * Create an instance of an Ultrasonic Sensor from a DigitalInput for the echo channel and a 154 * DigitalOutput for the ping channel. 155 * 156 * @param pingChannel The digital output object that starts the sensor doing a ping. Requires a 157 * 10uS pulse to start. 158 * @param echoChannel The digital input object that times the return pulse to determine the 159 * range. 160 * @param units The units returned in either kInches or kMilliMeters 161 */ 162 public Ultrasonic(DigitalOutput pingChannel, DigitalInput echoChannel, Unit units) { 163 if (pingChannel == null || echoChannel == null) { 164 throw new NullPointerException("Null Channel Provided"); 165 } 166 m_allocatedChannels = false; 167 m_pingChannel = pingChannel; 168 m_echoChannel = echoChannel; 169 m_units = units; 170 initialize(); 171 } 172 173 /** 174 * Create an instance of an Ultrasonic Sensor from a DigitalInput for the echo channel and a 175 * DigitalOutput for the ping channel. Default unit is inches. 176 * 177 * @param pingChannel The digital output object that starts the sensor doing a ping. Requires a 178 * 10uS pulse to start. 179 * @param echoChannel The digital input object that times the return pulse to determine the 180 * range. 181 */ 182 public Ultrasonic(DigitalOutput pingChannel, DigitalInput echoChannel) { 183 this(pingChannel, echoChannel, Unit.kInches); 184 } 185 186 /** 187 * Destructor for the ultrasonic sensor. Delete the instance of the ultrasonic sensor by freeing 188 * the allocated digital channels. If the system was in automatic mode (round robin), then it is 189 * stopped, then started again after this sensor is removed (provided this wasn't the last 190 * sensor). 191 */ 192 @Override 193 public synchronized void free() { 194 final boolean wasAutomaticMode = m_automaticEnabled; 195 setAutomaticMode(false); 196 if (m_allocatedChannels) { 197 if (m_pingChannel != null) { 198 m_pingChannel.free(); 199 } 200 if (m_echoChannel != null) { 201 m_echoChannel.free(); 202 } 203 } 204 205 if (m_counter != null) { 206 m_counter.free(); 207 m_counter = null; 208 } 209 210 m_pingChannel = null; 211 m_echoChannel = null; 212 213 if (this == m_firstSensor) { 214 m_firstSensor = m_nextSensor; 215 if (m_firstSensor == null) { 216 setAutomaticMode(false); 217 } 218 } else { 219 for (Ultrasonic s = m_firstSensor; s != null; s = s.m_nextSensor) { 220 if (this == s.m_nextSensor) { 221 s.m_nextSensor = s.m_nextSensor.m_nextSensor; 222 break; 223 } 224 } 225 } 226 if (m_firstSensor != null && wasAutomaticMode) { 227 setAutomaticMode(true); 228 } 229 } 230 231 /** 232 * Turn Automatic mode on/off. When in Automatic mode, all sensors will fire in round robin, 233 * waiting a set time between each sensor. 234 * 235 * @param enabling Set to true if round robin scheduling should start for all the ultrasonic 236 * sensors. This scheduling method assures that the sensors are non-interfering 237 * because no two sensors fire at the same time. If another scheduling algorithm 238 * is preffered, it can be implemented by pinging the sensors manually and waiting 239 * for the results to come back. 240 */ 241 public void setAutomaticMode(boolean enabling) { 242 if (enabling == m_automaticEnabled) { 243 return; // ignore the case of no change 244 } 245 m_automaticEnabled = enabling; 246 247 if (enabling) { 248 /* Clear all the counters so no data is valid. No synchronization is 249 * needed because the background task is stopped. 250 */ 251 for (Ultrasonic u = m_firstSensor; u != null; u = u.m_nextSensor) { 252 u.m_counter.reset(); 253 } 254 255 // Start round robin task 256 m_task.start(); 257 } else { 258 // Wait for background task to stop running 259 try { 260 m_task.join(); 261 } catch (InterruptedException ex) { 262 Thread.currentThread().interrupt(); 263 ex.printStackTrace(); 264 } 265 266 /* Clear all the counters (data now invalid) since automatic mode is 267 * disabled. No synchronization is needed because the background task is 268 * stopped. 269 */ 270 for (Ultrasonic u = m_firstSensor; u != null; u = u.m_nextSensor) { 271 u.m_counter.reset(); 272 } 273 } 274 } 275 276 /** 277 * Single ping to ultrasonic sensor. Send out a single ping to the ultrasonic sensor. This only 278 * works if automatic (round robin) mode is disabled. A single ping is sent out, and the counter 279 * should count the semi-period when it comes in. The counter is reset to make the current value 280 * invalid. 281 */ 282 public void ping() { 283 setAutomaticMode(false); // turn off automatic round robin if pinging 284 // single sensor 285 m_counter.reset(); // reset the counter to zero (invalid data now) 286 // do the ping to start getting a single range 287 m_pingChannel.pulse(kPingTime); 288 } 289 290 /** 291 * Check if there is a valid range measurement. The ranges are accumulated in a counter that will 292 * increment on each edge of the echo (return) signal. If the count is not at least 2, then the 293 * range has not yet been measured, and is invalid. 294 * 295 * @return true if the range is valid 296 */ 297 public boolean isRangeValid() { 298 return m_counter.get() > 1; 299 } 300 301 /** 302 * Get the range in inches from the ultrasonic sensor. If there is no valid value yet, i.e. at 303 * least one measurement hasn't completed, then return 0. 304 * 305 * @return double Range in inches of the target returned from the ultrasonic sensor. 306 */ 307 public double getRangeInches() { 308 if (isRangeValid()) { 309 return m_counter.getPeriod() * kSpeedOfSoundInchesPerSec / 2.0; 310 } else { 311 return 0; 312 } 313 } 314 315 /** 316 * Get the range in millimeters from the ultrasonic sensor. If there is no valid value yet, i.e. 317 * at least one measurement hasn't completed, then return 0. 318 * 319 * @return double Range in millimeters of the target returned by the ultrasonic sensor. 320 */ 321 public double getRangeMM() { 322 return getRangeInches() * 25.4; 323 } 324 325 @Override 326 public void setPIDSourceType(PIDSourceType pidSource) { 327 if (!pidSource.equals(PIDSourceType.kDisplacement)) { 328 throw new IllegalArgumentException("Only displacement PID is allowed for ultrasonics."); 329 } 330 m_pidSource = pidSource; 331 } 332 333 @Override 334 public PIDSourceType getPIDSourceType() { 335 return m_pidSource; 336 } 337 338 /** 339 * Get the range in the current DistanceUnit for the PIDSource base object. 340 * 341 * @return The range in DistanceUnit 342 */ 343 @Override 344 public double pidGet() { 345 switch (m_units) { 346 case kInches: 347 return getRangeInches(); 348 case kMillimeters: 349 return getRangeMM(); 350 default: 351 return 0.0; 352 } 353 } 354 355 /** 356 * Set the current DistanceUnit that should be used for the PIDSource base object. 357 * 358 * @param units The DistanceUnit that should be used. 359 */ 360 public void setDistanceUnits(Unit units) { 361 m_units = units; 362 } 363 364 /** 365 * Get the current DistanceUnit that is used for the PIDSource base object. 366 * 367 * @return The type of DistanceUnit that is being used. 368 */ 369 public Unit getDistanceUnits() { 370 return m_units; 371 } 372 373 /** 374 * Is the ultrasonic enabled. 375 * 376 * @return true if the ultrasonic is enabled 377 */ 378 public boolean isEnabled() { 379 return m_enabled; 380 } 381 382 /** 383 * Set if the ultrasonic is enabled. 384 * 385 * @param enable set to true to enable the ultrasonic 386 */ 387 public void setEnabled(boolean enable) { 388 m_enabled = enable; 389 } 390 391 /** 392 * Live Window code, only does anything if live window is activated. 393 */ 394 @Override 395 public String getSmartDashboardType() { 396 return "Ultrasonic"; 397 } 398 399 private ITable m_table; 400 401 @Override 402 public void initTable(ITable subtable) { 403 m_table = subtable; 404 updateTable(); 405 } 406 407 @Override 408 public ITable getTable() { 409 return m_table; 410 } 411 412 @Override 413 public void updateTable() { 414 if (m_table != null) { 415 m_table.putNumber("Value", getRangeInches()); 416 } 417 } 418 419 @Override 420 public void startLiveWindowMode() { 421 } 422 423 @Override 424 public void stopLiveWindowMode() { 425 } 426}