5 * Abstraction for a NXT Ultrasonic Sensor.
8 public class UltrasonicSensor extends I2CSensor
10 /* Device control locations */
11 private static final byte MODE = 0x41;
12 private static final byte DISTANCE = 0x42;
13 private static final byte FACTORY_DATA = 0x11;
14 private static final byte UNITS = 0x14;
15 private static final byte CALIBRATION = 0x4a;
16 private static final byte PING_INTERVAL = 0x40;
18 private static final byte MODE_OFF = 0x0;
19 private static final byte MODE_SINGLE = 0x1;
20 private static final byte MODE_CONTINUOUS = 0x2;
21 private static final byte MODE_CAPTURE = 0x3;
22 private static final byte MODE_RESET = 0x4;
24 private static final int DELAY_CMD = 0x5;
25 private static final int DELAY_AVAILABLE = 0xf;
27 private byte[] buf = new byte[1];
28 private byte[] inBuf = new byte[8];
29 private String units = " ";
30 private char [] unitsChars = StringUtils.getCharacters(units);
31 private int nextCmdTime;
32 private int dataAvailableTime;
33 private int currentDistance;
37 * Return the current time in milliseconds
41 return (int)System.currentTimeMillis();
45 * Wait until the specified time
48 private void wait(int when)
50 int delay = when - now();
61 * Over-ride standard get function to ensure correct inter-command timing
62 * when using the ultrasonic sensor. The Lego Ultrasonic sensor uses a
63 * "bit-banged" i2c interface and seems to require a minimum delay between
64 * commands otherwise the commands fail.
66 public int getData(int register, byte [] buf, int len)
69 int ret = super.getData(register, buf, len);
70 nextCmdTime = now() + DELAY_CMD;
75 * Over-ride the standard send function to ensure the correct inter-command
76 * timing for the ultrasonic sensor.
79 public int sendData(int register, byte [] buf, int len)
82 int ret = super.sendData(register, buf, len);
83 nextCmdTime = now() + DELAY_CMD;
87 public UltrasonicSensor(SensorPort port)
90 // Set correct sensor type, default is TYPE_LOWSPEED
91 port.setType(TYPE_LOWSPEED_9V);
92 // Default mode is continuous
93 mode = MODE_CONTINUOUS;
94 // Set initial inter-command delays
95 nextCmdTime = now() + DELAY_CMD;
96 dataAvailableTime = now() + DELAY_AVAILABLE;
97 currentDistance = 255;
101 * Return distance to an object. To ensure that the data returned is valid
102 * this method may have to wait a short while for the distance data to
105 * @return distance or 255 if no object in range
107 public int getDistance()
109 // If we are in continuous mode and new data will not yet be available
110 // simply return the current reading (since this is what the sensor
112 if (mode == MODE_CONTINUOUS && now() < dataAvailableTime)
113 return currentDistance;
114 wait(dataAvailableTime);
115 int ret = getData(DISTANCE, buf, 1);
116 currentDistance = (ret == 0 ? (buf[0] & 0xff) : 255);
117 // Make a note of when new data should be available.
118 if (mode == MODE_CONTINUOUS)
119 dataAvailableTime = now() + DELAY_AVAILABLE;
120 return currentDistance;
124 * Return an array of 8 echo distances. These are generated when using ping
125 * mode. A value of 255 indicates that no echo was obtained. The array must
126 * contain at least 8 elements, if not -1 is returned. If the distnace data
127 * is not yet available the method will wait until it is.
129 * @return 0 if ok <> 0 otherwise
131 public int getDistances(int dist[])
133 if (dist.length < inBuf.length || mode != MODE_SINGLE) return -1;
134 wait(dataAvailableTime);
135 int ret = getData(DISTANCE, inBuf, inBuf.length);
136 for(int i = 0; i < inBuf.length; i++)
137 dist[i] = (int)inBuf[i] & 0xff;
142 * Set the sensor into the specified mode. Keep track of which mode we are
143 * operating in. Make a note of when any distance data will become available
146 private int setMode(byte mode)
149 int ret = sendData(MODE, buf, 1);
150 // Make a note of when the data will be available
151 dataAvailableTime = now() + DELAY_AVAILABLE;
152 if (ret == 0) this.mode = mode;
157 * Send a single ping.
158 * The sensor operates in two modes, continuous and ping. When in continuous
159 * mode the sensor sends out pings as often as it can and the most recently
160 * obtained result is available via a call to getDistance. When in ping mode
161 * a ping is only transmitted when a call is made to ping. This sends a
162 * single ping and up to 8 echoes are captured. These may be read by making
163 * a call to getDistance and passing a suitable array. A delay of
164 * approximately 20ms is required between the call to ping and getDistance.
165 * This delay is not included in the method. Calls to getDistance before
166 * this period may result in an error or no data being returned. The normal
167 * getDistance call may also be used with ping, returning information for
168 * the first echo. Calling this method will disable teh default continuous
169 * mode, to switch back to continuous mode call continuous.
171 * @return 0 if ok <> 0 otherwise
176 return setMode(MODE_SINGLE);
180 * Switch to continuous ping mode.
181 * This method enables continuous ping and capture mode. This is the default
182 * operating mode of the sensor. Please the notes for ping for more details.
184 * @return 0 if ok <> 0 otherwise
187 public int continuous()
189 return setMode(MODE_CONTINUOUS);
192 * Turn off the sensor.
193 * This call disables the sensor. No pings will be issued after this call,
194 * until either ping, continuous or reset is called.
196 * @return 0 if ok <> 0 otherwise
201 return setMode(MODE_OFF);
206 * Set the sensor into capture mode. The Lego documentation states:
207 * "Within this mode the sensor will measure whether any other ultrasonic
208 * sensors are within the vicinity. With this information a program can
209 * evaluate when it is best to make a new measurement which will not
210 * conflict with other ultrasonic sensors."
211 * I have no way of testing this. Perhaps someone with a second NXT could
214 * @return 0 if ok <> 0 otherwise
219 return setMode(MODE_CAPTURE);
224 * Performs a "soft reset" of the device. Restores things to the default
225 * state. Following this call the sensor will be operating in continuous
228 * @return 0 if ok <> 0 otherwise
233 int ret = setMode(MODE_RESET);
234 // In continuous mode after a reset;
235 if (ret == 0) mode = MODE_CONTINUOUS;
239 private int getMultiBytes(int reg, byte data[], int len)
242 * For some locations that are adjacent in address it is not possible
243 * to read the locations in a single read, instead we must read them
244 * using a series of individual reads. No idea why this should be, but
248 for(int i = 0; i < len; i++)
250 ret = getData(reg+i, buf, 1);
251 if (ret != 0) return ret;
257 private int setMultiBytes(int reg, byte data[], int len)
260 * For some locations that are adjacent in address it is not possible
261 * to read the locations in a single write, instead we must write them
262 * using a series of individual writes. No idea why this should be, but
266 for(int i = 0; i < len; i++)
269 ret = sendData(reg+i, buf, 1);
270 if (ret != 0) return ret;
276 * Return 10 bytes of factory calibration data. The bytes are as follows
277 * data[0] : Factory zero (cal1)
278 * data[1] : Factory scale factor (cal2)
279 * data[2] : Factory scale divisor.
281 * @return 0 if ok <> 0 otherwise
283 public int getFactoryData(byte data[])
285 if (data.length < 3) return -1;
286 return getMultiBytes(FACTORY_DATA, data, 3);
289 * Return a string indicating the type of units in use by the unit.
290 * The default response is 10E-2m indicating centimetres in use.
292 * @return 7 byte string
294 public String getUnits()
296 int ret = getData(UNITS, inBuf, 7);
297 for(int i = 0; i < 7; i++)
298 unitsChars[i] = (ret == 0 ? (char)inBuf[i] : ' ');
303 * Return 3 bytes of calibration data. The bytes are as follows
304 * data[0] : zero (cal1)
305 * data[1] : scale factor (cal2)
306 * data[2] : scale divisor.
308 * @return 0 if ok <> 0 otherwise
310 public int getCalibrationData(byte data[])
312 /* Note the lego documentation says this is at loacation 0x50, however
313 * it looks to me like this is a hex v decimal thing and it should be
314 * location 0x49 + 1 which is 0x4a not 0x50! There certainly seems to be
315 * valid data at 0x4a...
317 if (data.length < 3) return -1;
318 return getMultiBytes(CALIBRATION, data, 3);
322 * Set 3 bytes of calibration data. The bytes are as follows
323 * data[0] : zero (cal1)
324 * data[1] : scale factor (cal2)
325 * data[2] : scale divisor.
327 * This does not currently seem to work.
329 * @return 0 if ok <> 0 otherwise
331 public int setCalibrationData(byte data[])
333 if (data.length < 3) return -1;
334 return setMultiBytes(CALIBRATION, data, 3);
338 * Return the interval used in continuous mode.
339 * This seems to be in the range 1-15. It can be read and set. However tests
340 * seem to show it has no effect. Others have reported that this does vary
341 * the ping interval (when used in other implementations). Please report
344 * @return -1 if error otherwise the interval
346 public byte getContinuousInterval()
348 int ret = getData(PING_INTERVAL, buf,1);
349 return (ret == 0 ? buf[0] : -1);
353 * Set the ping inetrval used when in continuous mode.
354 * See getContinuousInterval for more details.
356 * @return 0 if 0k <> 0 otherwise.
358 public int setContinuousInterval(byte interval)
361 int ret = sendData(PING_INTERVAL, buf, 1);
366 * Returns the current operating mode of the sensor.
368 * 1 : Single shot ping mode
369 * 2 : continuous ping mode (default)
370 * 3 : Event capture mode
372 * @return -1 if error otherwise the operating mode
374 public byte getMode()
376 int ret = getData(MODE, buf,1);
377 return (ret == 0 ? buf[0] : -1);