Skip to Content

Arduino, IR Range Finders, and I2C

I've been getting started with electronics for the past while and felt it was time for me to finally blog about the adventures. In particular, tonight I'm blogging about my experience with I2C based Infra Red Range Finders. (devices that can tell how far away something is...)

To start with, I purchased some range finders from Solarbotics (aka HVW Technologies) some time ago. I got the 4 pack sets of both the analog and digital IR Range Finders. These are based off the Sharp GP2D12 sensors. The digital versions add on an I2C system, but I've ignored this in favor of the analog sensors until tonight. Analog sensors are a little easier to understand, but do require a separate input for each sensor. The I2C devices allow you to hook up many devices on a single input.

Here, you can see the front and back of the digital sensors.

The white portion of the device is a remnant of the analog device and is where Power, Ground, and the Signal line are. These are not used in the digital version. Instead, we use the 4 connections across the bottom of the circuit board. To make life easy for me, I have opted to solder in a 4 pin header row to allow me to do quick and simple hook ups with a breadboard. (If you opt to do this, make sure the long end of the header pins points AWAY from the device - NOT under the white connector!)

And here they are with the header pins soldered on.

Because I2C allows many devices on one input line, we need a way to talk to particular devices. This is accomplished with an address. I2C can support up to 172 devices on a single line, but the range finders have hard-coded half of the 7-bit address range. This leaves us with only 8 possible range finders on an input line. To set the address of our sensors, we use a solder jumper. If you look closely at the right hand sensor above, you'll notice that jumper 1 has been closed with a blob of solder. While the left hand sensor has all three circular solder pads left blank. This means that the sensor on the left will have an address of 0x22, while the one on the right has an address of 0x28. The address can be worked out from the manual which has a nice table showing what jumper combinations are needed for each address.

Next we need to hook up the sensors and then write some code to make use of them. For this I have used a Freeduino (which is a clone of the Open Source Arduino). The only real difference between a stock Arduino/Freeduino and mine is that I replaced my ATMega168 chip with an ATMega328 I had spare after messing up on an Ardweeny and soldered the chip in backwards. So I have mounted the now useless Ardweeny onto my Freeduino board. (Luckily the Ardweeny's are only $10 and not a huge loss.) Here is a picture of the completed circuit, but we'll go over it in some detail below.

That picture has both sensors running and reporting the distance to the nearest object. You can see the purplish glow on one of the sensors, which is the IR signal - you can't see it directly, but my camera picked it up.

Here is the same circuit in a cleaner layout.

For this excercise, I just used the new header pins on each sensor and plugged them directly into the breadboard and then used jumper wires to make the connections.

We start by connecting the Gnd and +5V lines on the Arduino to the first sensor. The Gnd and Vcc lines are the two outside pins (don't get them reversed or things won't work). Then we take the SDA line from the sensor, which is our signal line, and plug it into pin 4 on the Arduino. We have no choice on the Arduino pins used - this is what the Wire.h library is expecting. Lastly we connect the SCL line from the sensor, which is the clock line, to pin 5 on the Arduino. The second sensor is hooked up by simply connecting matching pins to sensor 1. (i.e. Vcc to Vcc, SCL to SCL, etc.)

With that, we are done with the physical part of the project. On to the software part.

Arduino's have their own programming environment that can be downloaded from http://arduino.cc/en/Main/Software. When you get your Arduino, spend some time (if you haven't yet) getting some of the sample applications running, like "Blink". This will not only give you immediate gratification and faith that your hardware is not defective, but it will also make sure you have your environment set up right. Use the Getting Started guide to work through this part. A few hints I have found to help:

  1. read the guide carefully - it really does handle almost all situations, though you may not realize it until you have a better understanding of the terminology.
  2. Don't make a habit of unplugging the USB cable (from either end) and plugging it in again. This tends to make the detected USB port a little sporadic, and you'll have to reset which port you are talking to before things work as expected.
  3. Make sure you select the correct device under Tools -> Board (standard Freeduino uses the 168 chip so should select the "Arduino . . . w/ATmega168", unless you've mounted a 328 chip, of course...).
  4. Make sure you have selected the right port/device under Tools -> Serial Port.

Messing up one of the last two steps is a sure way to see the infamous errors "programmer not responding", or "sync error".

Now that you have a functional environment for programming your Arduino, we can move on. The code shown below was adapted from the sample code RobotShop has posted on their Useful Links section for the Range Finder product. (which is the same as Solarbotics.) That code can be found in the zip file at http://www.robotshop.ca/ZIP/I2C_IT_Sketch.zip. (HUGE thanks to whoever wrote this!!) The only changes I made to this code was to add the second device and "too close" section that would turn on the LED for one second.

Also, there is some good information for understanding I2C on the Arduino, and the Wire.h library at these links:

With that background out of the way, here is the code I came up with:

#include <Wire.h>

int ledPin = 13;                // LED connected to digital pin 13
int dist = 0;
int dist2 = 0;

void setup()                    // run once, when the sketch starts
{
  Wire.begin();
  Serial.begin(9600);

  pinMode(ledPin, OUTPUT);      // sets the digital pin as output
}

void loop(){
  dist = i2c_eeprom_read_byte( 0x20 );   // read input value
  dist2 = i2c_eeprom_read_byte( 0x28 );   // read input value
  
  Serial.print("0x20: ");
  Serial.print(dist, DEC);
  Serial.print(",  0x28: ");
  Serial.println(dist2, DEC);
 delay(150);
}


byte i2c_eeprom_read_byte( int deviceaddress ) {
  byte rdata = 0;
  Wire.beginTransmission(deviceaddress);
  Wire.send(1); 
  Wire.endTransmission();
  Wire.requestFrom(deviceaddress,1);
  if (Wire.available()) rdata = Wire.receive();
  Wire.endTransmission();
  return rdata;
}

First, we include the Wire.h library. Then we set up some values we'll use throughout the application.

Next we enter the setup() function and get things ready to go. Tell the Wire library that we do really want to be using it, then setup the Serial communications to 9600 baud (the original sample code used 19200, but I was getting garbage spit out until I dropped to 9600 - which is what all my other successful uses of the Serial communications used...). And finally we indicate that the LED pin will be used for output. We don't really need this though as I've pulled the code that used the LED in any way.

Next we enter the loop() method. We get two values - dist and dist2. We call the i2c_eeprom_read_byte() method for both. The only change to note is that we specify a different I2C address for each call. The remainder of the code in the loop() dumps the values to the Serial line for us and waits 150 milliseconds before looping.

The i2c_eeprom_read_byte() function is where all the magic happens. And I'm sure I don't understand it all yet. May I suggest you read through the details at http://www.neufeld.newton.ks.us/electronics/?p=241 and http://arduino.cc/en/Reference/Wire to get a better understanding yourself. But I'll take a crack at this.

First we declare the rdata variable and give it an initial value of zero. Then we tell our I2C device we want to talk to it, send it a value of one, and end the transmission. I believe that sending the value of one tells our device we want to talk to port 1. Next we request one byte of data from our device. If we find there is data available, we read that data (all of one byte) into the rdata variable. Then we tell the device we are done talking to it, and return the data from the function.

When we compile the code (fixing any errors that may come up), and send it to the Freeduino, we get output like this on the Serial Monitor:

0x20: 22,  0x28: 12
0x20: 42,  0x28: 11
0x20: 34,  0x28: 10
0x20: 16,  0x28: 12
0x20: 33,  0x28: 12
0x20: 10,  0x28: 12
0x20: 27,  0x28: 11
0x20: 20,  0x28: 12
0x20: 34,  0x28: 17
0x20: 20,  0x28: 12
0x20: 34,  0x28: 11
0x20: 32,  0x28: 12
0x20: 11,  0x28: 12
0x20: 27,  0x28: 17

I was waving my hand above sensor 2 for this snippet, and sensor 1 caught some of it (they are kinda close together).

What does all this mean? Each value returned is the number of inches to the detected object (I actually measured with a ruler to confirm this, and found the accuracy to be within an inch - accounting for fat fingers). This means that if we were to position the sensors on the front and back of a moving platform, like a robot perhaps, we can get some idea if the way ahead/behind is clear or blocked. This information can then be used to feed an obstacle avoidance algorithm. Or maybe we hook up a "screamer" to our Halloween pumpkins set to go off if someone gets too close. So while the project as is may not be very much fun, it makes more complex projects possible.

Let me know if I need to change anything in my write up.