Here we have it – an affordable Open Source Laser RangeFinder – OSLRF-01 from www.lightware.co.za. You can order it fully assembled and working or just PCB and optics (all other components have to find by Yourself).

So next step is connect it somehow to something. At that moment I found just one sucsess story shared online – Arduino Scanning LIDAR by Michael Ball http://arduino-pi.blogspot.com/2014/03/prototype-of-oslrf01-arduino-scanning.html. That was a very nice place to start connecting it to my Arduino UNO. Michael done it with Arduino Fio, which is 3.3V logic level board, not 5V like UNO’s. Don’t know real reasons why (maybe different voltage levels(different ADC’s), maybe different register settings for different boards), but straight out of the box I was receiving strange data. It was like added ~400cm to all my readings. OSLRF-01 was reacting to distance changes, but was very far from desired(real) values.

First I was little angry, then little depressed, but now I’m happy 🙂 because it made me dive a little bit deeper into this sensor. And I will share some findings with You.

I will not repeat info about sensor working principles, they are nicely covered by creators. I’ll try to concentrate on getting something out of it.

1. Wires

There is an illustration of simplest way to connect OSLRF-01 to Arduino UNO:





Fig 1. OSLRF-01 connected to Arduino UNO

Laser rangefinder must be powered by stabilized 12V source ( “Vin” ).

“Zero” pin to A1,

“Return” to A3 and

“Sync” to Arduino’s digital 2

Arduino board has to be powered separately by USB cable or external power source (not displayed)

Thats all 🙂

2. Signals

First is worth to mention, that without using “Control” measurements are regularly made on about 37 Hz frequency. “About” is very good description, because frequency is drifting a little all the time. Just after power-on it drifts more:





Fig. 2. Y-axis is a “Sync” period in microseconds, and X – is samples over time.

After few minutes it stabilizes, but I think it is good practice constantly checking its exact value.

Second, illustration from official manual:





Fig. 3. Signal diagrams from official manual

Everything works in this way:

1) “Sync” signal goes low and at this moment starts measurement process.

2) Laser fires and electronics generates “Zero” impulse.

3) Light returns from target and electronics generates “Return” impulse.

Everything repeats on next cycle.

Our task is to measure time between “Zero” and “Return” and convert this difference into meaningful distance value.

From above diagram I assumed that there is plenty of time between laser fire (Zero) and Return signals. In reality I had a lot of trouble finding rising edge of “Return” signal after I found falling edge of “Zero”. Thats because if You don’t use “Control” pin on OSLRF-01 (to adjust frequency of laser fires) then “Return” signal overlaps “Zero” signal when target moves closer than 400-500cm.

Here is my real-world measurements:





Fig. 4. OSLRF-01 signals (one complete period)

On each distance two measurements were made. One with white target (higher signal amplitude), other with black. I used fabric (T-Shirts), as I’m more interested in people detection. 500cm is my room maximum value, not sensors 😉 Strange (at least for me) but color of the target doesn’t change much. Except when object comes really close to the sensor.

3. Figuring out something

As I mentioned in part 2 – from Zero and Return signals You have somehow get distance value. User manual offers us magic formula d = ((Rt – Zt) / Sp) * 18.33 , where d – distance to target (in meters), Rt – time from falling “Sync” edge to “Return” signal, Zt – time from falling “Sync” edge to “Zero” signal, Sp – “Sync” period.

Seems easy, until You start to think what is “time from signal to signal”. Is it time from front of one signal to front of other ? Short answer – No. Long answer No, if you need to measure distances closer than 1 meter. Look at Fig.4 – 100cm 50cm and 25 cm fronts are almost the same. And it is possible to choose wrong threshold when 50cm and especially 25 cm will appear farther than 100cm. User manual recommends to measure signals “centers”, or midways between rising and falling edges. This really helps to deal with close distances. But leaves open question about choosing right threshold.

Lets look closer. We can choose “secure” (high) threshold above all possible noise levels:





Fig.5. High threshold

But this way we loose close and dark targets (25cm black). Other way is to choose lowest possible threshold.

Low threshold illustration:





Fig.6. Low threshold

And as You can see from Fig.5 and Fig.6 measuring midways doesn’t return linear response (above b/w ruler). So, we will need to make some adjustments before final value is returned (will cover that in next chapter).

Now lets look how Arduino actually “sees” those signals after ADC conversion. I recorded “Return” signal into big array and plotted graphs:

Fig.7, 8 “Return” signals after Arduino ADC conversion

As You see if we take lowest possible threshold from 25cm black target graph ~20 units (or 100mV) value, this won’t be suitable for 200cm white targets rising/falling edges detection. So I choose to use adaptive threshold. After some experimentation I got simple empiric formula that worked well for all distances:

Adaptive_Return_Thresh = 0.13 * Amplitude + 10 (For 3.3V boards You have to scale it)

Of course this slows-down response a little bit (if object/sensor moves very fast, you need some time to adapt to new distance). And maybe this approach not suitable for an applications, where You don’t have luxury firing multiple times at the same target. But for me it worked very well. It even produced less noise in results:





Fig.9 Adaptive threshold





Fig.8. Constant threshold

Later I added “security” test in code – to prevent errors, when program was sometime losing herself in noise, that’s the cost of walking on the edge.

Also I took some assumptions – “Zero” is always the same shape, same amplitude, same width, and maybe the same latency from falling “Sync” edge, but to avoid drifting problems and to be sure I still measure rising edge at constant threshold. Then adding constant half/width to detected front of “Zero”.

4. Smooth the rough edges off

But even when targets were completely still, I got some random noise on distance readings (1-2 cm order). So I went easiest way – averaged “Sync” period readings and calculated distance values. I achieved desired stability using 40 values (~1 second) of running average on “Sync” period and continuously averaging last 20 values of final distance. This maybe too slow for very fast changing environment like plane/copter or fast robot, but for natural targets like myself that was OK 🙂

Also I calibrated distance values. For example

Calibration table Nr.1 No. Real distance (cm) Measured distance (cm) 1 100 220 2 200 310 3 300 397 4 400 496 5 500 591 Calibration table Nr.2 No. Real distance (cm) Measured distance (cm) 1 100 220 2 90 203 3 80 187 4 70 174 5 60 158 6 50 140 7 40 130 8 30 118 9 20 108 10 10 96

As You see calculated values are pretty far away from what we expect. BUT making graphs out of those “errors” clearly shows easy way to make corrections. So I made simple(linear) empiric correction formulas and coded them into program. One formula for 100-500cm range, and other 0-100cm segment. I expect that from 500cm to 1000cm I would have to do another one, but I need more space for measurements 🙂

5. The results

Yeah, Jesse, we share the same feelings 🙂 Here is an example of me walking away from sensor:





Fig.10 Walk test

I was walking away, stopped at 300 cm mark, then walked to 500 cm mark, turned around, walked back to 200 cm, made a little stop, then few more steps closer and moved aside – out of laser sight.

5. Code

OLSRF-01 example for Arduino UNO // // OLSRF-01 for Arduino UNO code // (c) Ignas Gramba, 2014 // www.berryjam.eu/2014/06/oslrf-01 // #define ZERO_PIN A1 // Arduino pin tied to Zero pin on the OSLRF. #define RETURN_PIN A3 // Arduino pin tied to Return pin on the OSLRF. #define SYNC_PIN 2 // Arduino pin tied to Sync pin on the OSLRF. int zero_val = 0; int return_val = 0; int sync_val_1 = 0; int sync_val_2 = 0; int amp = 0; int zero_thresh = 40; int return_thresh = 50; float raw_distance = 0.0; float distance = 0.0; unsigned long zero_time; unsigned long zero_time1; unsigned long zero_time2; unsigned long echo_time; unsigned long echo_time1; unsigned long echo_time2; unsigned long sync_time_1; unsigned long sync_time_2; unsigned long sync_period; // Distance average variables const int numReadings = 20; int readings[numReadings]; // the readings from the analog input int index = 0; // the index of the current reading int total = 0; // the running total int avgDist = 0; // the average // Sync period average variables const int numReadings2 = 40; unsigned long readings2[numReadings2]; // the readings from the analog input int index2 = 0; // the index of the current reading unsigned long total2 = 0; // the running total unsigned long avgSync = 0; // the average void setup() { pinMode(SYNC_PIN, INPUT); Serial.begin(57600); // Sync averaging itialize routine for (int thisReading = 0; thisReading < numReadings; thisReading++) readings[thisReading] = 0; // Distance averaging itialize routine for (int thisReading2 = 0; thisReading2 < numReadings2; thisReading2++) readings2[thisReading2] = 0; } void loop() { getSyncPeriod(); // This "Sync" period update can be done not on every loop cycle, AverageSyncPeriod(); // as it takes time and looses some (3) distance measurements // Detect Zero signal rising edge while ((zero_val = analogRead(ZERO_PIN)) < zero_thresh) zero_time1 = micros(); // Detect Return signal rising edge based on previous measurement amplitude while ((return_val = analogRead(RETURN_PIN)) < return_thresh) echo_time1 = micros(); // Get maximum height of Return pulse... amp = 0; while (amp <= return_val ) { amp = return_val; return_val = analogRead(RETURN_PIN); } // Detect Return signal falling edge while ((return_val = analogRead(RETURN_PIN)) > return_thresh) echo_time2 = micros(); // New Return signal threshold for next measurement, based on the new amplitude return_thresh = 0.13 * (float)amp + 10; // Pure empiric, based on observations. if (return_thresh < 18) return_thresh = 18; // Make sure that threshold is over the noise zero_time = zero_time1 + 3500; // Midpoint of Zero. Full Zero signal width - 7000us, when threshold 40 echo_time = echo_time1 + ((float)echo_time2 - (float)echo_time1)/3.0; raw_distance = (float)(echo_time - zero_time)/(float)avgSync * 1833.0; if (raw_distance > 1000) {} // Just ignore this reading else{ if (raw_distance < 220){ // RAW measure corrections if distance less than 100 cm distance = 0.725 * raw_distance - 56.208; } else distance = 1.078 * raw_distance - 134.05; // Empiric correction for 100cm and up } AverageDistanceReadings(); Serial.println(avgDist); } //loop void getSyncPeriod(){ // sync_period = 2*pulseIn(SYNC_PIN, LOW); // was too big about 80us, because duty cycle not perfect 50% // Need to optimize, as it takes full two clocks unsigned long sync_period1 = pulseIn(SYNC_PIN, LOW); unsigned long sync_period2 = pulseIn(SYNC_PIN, HIGH); sync_period = sync_period1 + sync_period2; } void AverageSyncPeriod(){ total2 = total2 - readings2[index2]; // subtract the last reading readings2[index2] = sync_period; // Get last measure total2 = total2 + readings2[index2]; // add the reading to the total index2 = index2 + 1; // advance to the next position in the array if (index2 >= numReadings2) // if we're at the end of the array... index2 = 0; // ...wrap around to the beginning // avgSync = 1; avgSync = total2 / numReadings2; // calculate the average } void AverageDistanceReadings(){ total = total - readings[index]; // subtract the last reading readings[index] = distance; // Get last measure total = total + readings[index]; // add the reading to the total index = index + 1; // advance to the next position in the array if (index >= numReadings) // if we're at the end of the array... index = 0; // ...wrap around to the beginning avgDist = total / numReadings; // calculate the average } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 // // OLSRF-01 for Arduino UNO code // (c) Ignas Gramba, 2014 // www.berryjam.eu/2014/06/oslrf-01 // #define ZERO_PIN A1 // Arduino pin tied to Zero pin on the OSLRF. #define RETURN_PIN A3 // Arduino pin tied to Return pin on the OSLRF. #define SYNC_PIN 2 // Arduino pin tied to Sync pin on the OSLRF. int zero_val = 0 ; int return_val = 0 ; int sync_val_1 = 0 ; int sync_val_2 = 0 ; int amp = 0 ; int zero_thresh = 40 ; int return_thresh = 50 ; float raw_distance = 0.0 ; float distance = 0.0 ; unsigned long zero_time ; unsigned long zero_time1 ; unsigned long zero_time2 ; unsigned long echo_time ; unsigned long echo_time1 ; unsigned long echo_time2 ; unsigned long sync_time_1 ; unsigned long sync_time_2 ; unsigned long sync_period ; // Distance average variables const int numReadings = 20 ; int readings [ numReadings ] ; // the readings from the analog input int index = 0 ; // the index of the current reading int total = 0 ; // the running total int avgDist = 0 ; // the average // Sync period average variables const int numReadings2 = 40 ; unsigned long readings2 [ numReadings2 ] ; // the readings from the analog input int index2 = 0 ; // the index of the current reading unsigned long total2 = 0 ; // the running total unsigned long avgSync = 0 ; // the average void setup ( ) { pinMode ( SYNC_PIN , INPUT ) ; Serial . begin ( 57600 ) ; // Sync averaging itialize routine for ( int thisReading = 0 ; thisReading < numReadings ; thisReading ++ ) readings [ thisReading ] = 0 ; // Distance averaging itialize routine for ( int thisReading2 = 0 ; thisReading2 < numReadings2 ; thisReading2 ++ ) readings2 [ thisReading2 ] = 0 ; } void loop ( ) { getSyncPeriod ( ) ; // This "Sync" period update can be done not on every loop cycle, AverageSyncPeriod ( ) ; // as it takes time and looses some (3) distance measurements // Detect Zero signal rising edge while ( ( zero_val = analogRead ( ZERO_PIN ) ) < zero_thresh ) zero_time1 = micros ( ) ; // Detect Return signal rising edge based on previous measurement amplitude while ( ( return_val = analogRead ( RETURN_PIN ) ) < return_thresh ) echo_time1 = micros ( ) ; // Get maximum height of Return pulse... amp = 0 ; while ( amp <= return _ val ) { amp = return_val ; return_val = analogRead ( RETURN_PIN ) ; } // Detect Return signal falling edge while ( ( return_val = analogRead ( RETURN_PIN ) ) > return_thresh ) echo_time2 = micros ( ) ; // New Return signal threshold for next measurement, based on the new amplitude return_thresh = 0.13 * ( float ) amp + 10 ; // Pure empiric, based on observations. if ( return_thresh < 18 ) return_thresh = 18 ; // Make sure that threshold is over the noise zero_time = zero_time1 + 3500 ; // Midpoint of Zero. Full Zero signal width - 7000us, when threshold 40 echo_time = echo_time1 + ( ( float ) echo_time2 - ( float ) echo_time1 ) / 3.0 ; raw_distance = ( float ) ( echo_time - zero_time ) / ( float ) avgSync * 1833.0 ; if ( raw_distance > 1000 ) { } // Just ignore this reading else { if ( raw_distance < 220 ) { // RAW measure corrections if distance less than 100 cm distance = 0.725 * raw_distance - 56.208 ; } else distance = 1.078 * raw_distance - 134.05 ; // Empiric correction for 100cm and up } AverageDistanceReadings ( ) ; Serial . println ( avgDist ) ; } //loop void getSyncPeriod ( ) { // sync_period = 2*pulseIn(SYNC_PIN, LOW); // was too big about 80us, because duty cycle not perfect 50% // Need to optimize, as it takes full two clocks unsigned long sync_period1 = pulseIn ( SYNC_PIN , LOW ) ; unsigned long sync_period2 = pulseIn ( SYNC_PIN , HIGH ) ; sync_period = sync_period1 + sync_period2 ; } void AverageSyncPeriod ( ) { total2 = total2 - readings2 [ index2 ] ; // subtract the last reading readings2 [ index2 ] = sync_period ; // Get last measure total2 = total2 + readings2 [ index2 ] ; // add the reading to the total index2 = index2 + 1 ; // advance to the next position in the array if ( index2 >= numReadings2 ) // if we're at the end of the array... index2 = 0 ; // ...wrap around to the beginning // avgSync = 1; avgSync = total2 / numReadings2 ; // calculate the average } void AverageDistanceReadings ( ) { total = total - readings [ index ] ; // subtract the last reading readings [ index ] = distance ; // Get last measure total = total + readings [ index ] ; // add the reading to the total index = index + 1 ; // advance to the next position in the array if ( index >= numReadings ) // if we're at the end of the array... index = 0 ; // ...wrap around to the beginning avgDist = total / numReadings ; // calculate the average }

Of course its not optimized, just step-by-step implemented basic ideas, keeping everything easy to read for myself. Any improvements welcome.

6. Final thoughts

When I purchased OSLRF-01 it was priced 100$, now its price went up to 150$ and I think it’s still pretty good for this kind of device. For me it is very hard to imagine all those conversions/ detections and other magic happening at the speed of light 🙂 Do you imagine how much time light travels 50cm distance ? About 1.67 nano seconds, nano – thats one billionth part of second. Amazing. Isn’t it ?

For my next project I found (SF-02) would be more suited. If I helped You, please help me and I’ll prepare similar presentation of next laser rangefinder sensor. Thank You!