Location of a robot to a precision of small distances (i.e. <.1 inches) is valuable for navigation of small robots. The goal of this project was to create an affordable "GPS" system that could locate a robot within .1 inches and be easy to construct from off the shelf parts and easy to setup in the field.

General Theory of Operation

I cannot take credit for this idea but it is beautifully simple. Use a radio transmitter/receiver pair on two different Arduinos. One to transmit the request for a "ping" from a beacon, the other a beacon to receive the request and then "ping". When the beacon gets the request for a ping over radio it "pings" by oscillating an acoustic transducer for a short period of time. The requesting Arduino simply measures the amount of time between the request and the ping. This is very highly correlated with the distance between the requesting Arduino and the beacon Arduino. To multiplex this if the request is for a particular Beacon to ping then the distance to N Beacons can be measured by one requesting Arduino. I will refer to the requesting Arduino as the GPS unit because it becomes an analogy for a "real" GPS unit after this point.


For each Beacon you need

  1. Arduino
  2. Battery
  3. Power Switch
  4. A radio receiver similar to MO-RX3400 (cheap will be fine)
  5. An LED
  6. Some Jumpers
  7. A high frequency transducer (although lower might work also) like the ones from an ultra-sonic measuring device.

For the requesting Arduino (GPS Unit)

  1. Arduino
  2. Battery
  3. Power Switch
  4. A radio transmitter the compliment of the MO-RX3400 in the receiver
  5. An LED
  6. Some Jumpers
  7. A high frequency transducer (although lower might work also) like the ones from an ultra-sonic measuring device.
  8. One LM324 and some capacitors and resistors (details later)

Block Diagram

The block diagram is basically to get a feel for the two devices.

In the Beacon the Transducer is connected across two digital lines, so a 10 -> 01 pattern on those lines causes a 2 X 5V movement of the transducer. Where if a single line was used only a 5V deflection would occur. The current limited on the Arduino is relied upon so the transducer is directly connected across the two lines. The LED is used to debug/diagnose problems. In general it flashes when a ping request for it is received. The output of the reciever is tied to the hardware serial input line. Not shown are three (or more) pins used to set the "address" of the beacon. If these pins are labeled X, Y, and Z then no short is Address 0. A short between X and Y is Address 1 and a short between Y and Z is Address 2. The LED is nice when three beacons are used because you can see the requests cycling through the beacons. It is kinda fun to see as well.

The GPS uses the same transducer but it is connected through a LM324 Operation Amplifier Circuit that essentially is tuned to the frequency of the transducer and creates levels that are Digital Level (0-5V) with distances of many feet away. The circuit is very much like the first two stages of EFYMag February 2006 Ultrasonic Proximity Detector or Maxim App-notes on Proximity Detector. The detector part d1 to the rest of the left is not needed that is done with software. Any ultrasonic range finder amplifier will do. I used one that only needed capacitors and resistors and used a single ended power supply for the Op Amp (the LM324 my favorite Op Amp). The transmitter is tied to pins that are a software serial port. This leaves the hardware serial port for communication with another device. The LED blinks when a "ping" from at least one Beacon is heard through the amplifier.


This code was written with certain choices for connecting the LED, Jumper for setting Address, and Transducer. The Beacon software is

  1. #define SigPin 7
  2. #define Node1Pin 6
  3. #define Node2Pin 8
  4. #define Transducer1 13
  5. #define Transducer2 9
  6. #define PacketPin 2
  7. #define LEDC 3
  8. #define LEDA 4
  10. int beaconNumber;
  12. void findBeaconNumber() {
  13.   pinMode(SigPin,OUTPUT);
  14.   pinMode(Node1Pin,INPUT);
  15.   pinMode(Node2Pin,INPUT);
  16.   digitalWrite(Node1Pin,HIGH); // activate pullup
  17.   digitalWrite(Node2Pin,HIGH); // activate pullup
  18.   digitalWrite(SigPin,LOW);
  20.   // need to check code below
  21.   if (digitalRead(Node1Pin)==LOW) beaconNumber=1; // must be connected to SigPin
  22.   else if (digitalRead(Node2Pin)==LOW) beaconNumber=2; // must be connected to SigPin
  23.   else beaconNumber=0;
  24. }
  26. void setup() {
  27.   pinMode(LEDC,OUTPUT);
  28.   pinMode(LEDA,OUTPUT);
  29.   digitalWrite(LEDC,LOW);
  30.   digitalWrite(LEDA,LOW);
  31.   pinMode(Transducer1,OUTPUT);
  32.   pinMode(Transducer2,OUTPUT);
  33.   digitalWrite(Transducer1,LOW);
  34.   digitalWrite(Transducer2,LOW);  
  35.   findBeaconNumber();
  36.   Serial.begin(2400);
  37. }
  39. void PulseTransducer() {  // 42Khz pulsing
  40.     for (int i=0;i<13;i++) {
  41.       digitalWrite(Transducer1,HIGH);
  42.       delayMicroseconds(5);
  43.       digitalWrite(Transducer2,HIGH);
  44.       digitalWrite(Transducer1,LOW);
  45.       delayMicroseconds(5);
  46.       digitalWrite(Transducer2,LOW);
  47.     }
  48. }
  50. void loop() {
  51.   if (Serial.available()) {
  52.     char;
  53. //    Serial.write('|');
  54.     while (!Serial.available());
  55.     if (frame==0x5a) {
  56.       while (!Serial.available()); // Wait for second byte
  57.       Serial.write('-');
  58.       char;
  59.       if (request==beaconNumber) {
  60.         PulseTransducer();
  61.         digitalWrite(LEDA,HIGH);
  62.       } else {
  63.         digitalWrite(LEDA,LOW);
  64.       }
  65.     }
  66.   }
  67. }

The GPS Software is

  1. #include <SoftwareSerial.h>
  3. #define ADDetector 5
  4. #define rxPin 3
  5. #define txPin 4
  6. #define LEDC 7
  7. #define LEDA 8
  9. SoftwareSerial mySerial(rxPin,txPin);
  11. void setup() {
  12.   Serial.begin(9600);
  13.   pinMode(rxPin,INPUT);
  14.   pinMode(txPin,OUTPUT);
  15.   pinMode(LEDC,OUTPUT);
  16.   pinMode(LEDA,OUTPUT);
  17.   digitalWrite(LEDC,LOW);
  18.   digitalWrite(LEDA,LOW);
  19.   mySerial.begin(2400);
  20. }
  22. long minDetectorNoise() {
  23.   long val=analogRead(ADDetector);
  24.   for (int i=0;i<500;i++) {
  25.     long tmp=analogRead(ADDetector);
  26.     if (tmp<val) val=tmp;
  27.   }
  28.   return val;
  29. }
  31. void loop() {
  32.   long start;
  33.   long dist[3]; // in roughly tenths of a foot
  34.   boolean any=false;
  36.   for (int i=0;i<3;i++) {
  37.     long ADMin=(minDetectorNoise()*95)/100; // Below 95 percent of min of 500 samples is a signal
  38.     dist[i]=0;
  39.     mySerial.print((char)0xff);
  40.     mySerial.print((char)0x00);
  41.     mySerial.print((char)0x5a);
  42.     mySerial.print((char)i);
  43.     start=millis();
  44. //    delayMicroseconds(400);  // let detector settle from radio noise
  45.     do {
  46.       dist[i]++;
  47.     }while (dist[i]<1000 && analogRead(ADDetector)>ADMin);
  48.     if (dist[i]<1000) {
  49.       delay(100-dist[i]/10);
  50.       any=true;
  51.     }
  52.   }
  53.   if (any) digitalWrite(LEDA,HIGH);
  54.   else     digitalWrite(LEDA,LOW);
  55.   for(int i=0;i<3;i++) {
  56.     Serial.print(i);
  57.     Serial.print("|");
  58.     Serial.print(dist[i]/10);
  59.     Serial.print('.');
  60.     Serial.print(dist[i]%10);
  61.     Serial.print('|');
  62.   }
  63.   Serial.println();
  64. }

I have a version of the GPS receiver that performs the Trilateralization that takes the three distances and converts to an X and Y in a plane.

Next Steps

We have reproduced another set of this GPS system and experimented with cheaper radios. The cheaper radios required and extra byte be transmitted to give the radio receivers a more data before actual data is transmitted.

The different Arduino had some time synchronization issues. Most importantly is that you "tune" the delay in lines 42 and 45 of the beacon to get the most signal from the transducer on the receiver. We did this by connecting a spare transducer to an oscilloscope and changing the loop function to just repeatedly call PulseTransducer. We then changed the time delay to get the maximum peak on the oscilloscope.