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