Measuring Distance with a Laser
2018-10-03 | By John LeDuc
License: None Programmers
How do you measure distance with light?
The idea of measuring distance is done via using a laser, by pulsing the laser and timing the return of the light back to a light detector. A high speed processor then calculates the distance between the laser and the object it was pointing at based upon the measured return time. A commonly known module for this is called “LIDAR” which stands for Light Detector and Ranging. It can be thought of as radar that uses light to detect where objects are located.
Continuing from my earlier blog titled “Laser Distance Measurements using Garmin’s LIDAR-LITE v3HP”, here is how I put this project together:
Parts needed
- (1) 1568-1871-ND- Measuring Distance with a Laser JLs4
- (1) 1568-1867-ND – Garmin LIDAR-Lite v3HP
- (1) 1528-5306-ND – Big mechanical key switch
- (3) 1568-1912-ND – 6.5-inch Seven Segment Display
- (4) 1568-1913-ND – Large digit drivers for 7 segment displays
- (1) flexible/movable camera/light holder with table clamp
- (1) Cardboard box – I used a Digi-Key shipping box (not sold by Digi-Key)
The project idea was to show distance on large 7-segment displays (6.5 Inch) (1568-1912-ND) using a LIDAR module with an Arduino UNO. The 7-segment displays require a driver board (1568-1913-ND) that you can solder to the back of each display. Make sure to cover up the exposed land patterns on the back of the driver with tape as shown below.
To display 3 digits, you need to daisy chain a cable between drivers (shown below).
I made my own with 6 pin single row female sockets and ribbon cable however, you can use our Flat Flex cables (a 4 inch length = Digi-Key Part no. A9BBG-0604F-ND)
Next we’ll look at wiring the Display (1568-1912-ND) to the SparkFun RedBoard Edge (1568-1871-ND – the edge being a panel mountable Red Board UNO if you will)
You have to use 12 Volts to power the large digits. I used a transformer 237-1453-ND wall mount power supply.
Strangely, IO 7 was not brought out to use like it is on the original REDBOARD 1568-1768-ND.
So, I soldered a wire to the Chip (MEGA328P) Pin 11 and attached the other end to pin SER of the driver.
Next we’ll look at the wiring the LIDAR (1568-1867-ND) to the RedBoard Edge (1568-1871-ND)
For the LIDAR, it is recommended to use a 1000 µF CAP (493-1065-ND) across the power rails (5 V & GND).
Below are the pinouts for the LIDAR-LITE v3HP (1568-1867-ND) and a picture showing where to make the connections.
Note that Yellow and Orange are left open when wiring to the REDBOARD Edge.
I basically combined the data output from the LIDAR and relayed to the 7-segments display information routine, all based off a switch input to make a read of the distance at that moment the Large Switch (1528-5306-ND) was pushed. Below is the schematic drawing for connecting the switch.
Here are two links to SparkFun’s projects that show how to hook up the large 7-segment displays to count up and seperately the LIDAR serial output of the distance showing on a computer monitor (the fantastic work of Shawn Hymel and Nathan Seidle respectively).
I basically combined their code but slightly modified it to check for a switch input and display the results of the distance measured once the big switch was pressed.
The code: This example shows methods for running the LIDAR-Lite v3 HP in various modes of operation. To exercise the examples open a serial terminal program (or the Serial Monitor in the Arduino IDE) and send ASCII characters to trigger the commands. See "loop" function for details.
Connections:
- LIDAR-Lite 5 Vdc (red) to Arduino 5v
- LIDAR-Lite I2C SCL (green) to Arduino SCL
- LIDAR-Lite I2C SDA (blue) to Arduino SDA
- LIDAR-Lite Ground (black) to Arduino GND
(Capacitor recommended to mitigate inrush current when device is enabled)
- 680 µF capacitor (+) to Arduino 5v
- 680 µF capacitor (-) to Arduino GND
See the Operation Manual for wiring diagrams and more information: http://static.garmin.com/pumac/LIDAR_Lite_v3HP_Operation_Manual_and_Technical_Specifications.pdf
------------------------------------------------------------------------------*/
#include <stdint.h>
#include <Wire.h>
#include <LIDARLite_v3HP.h>
LIDARLite_v3HP myLidarLite;
int pin9 = 9;
int pin3 = 3;
byte segmentClock = 6;
byte segmentLatch = 5;
byte segmentData = 7;
#define FAST_I2C
enum rangeType_T
{
RANGE_NONE,
RANGE_SINGLE,
RANGE_CONTINUOUS,
RANGE_TIMER
};
void setup()
{
uint8_t dataByte;
// Initialize Arduino serial port (for display of ASCII output to PC)
Serial.begin(9600);
// Initialize Arduino I2C (for communication to LidarLite)
Wire.begin();
#ifdef FAST_I2C
#if ARDUINO >= 157
Wire.setClock(400000UL); // Set I2C frequency to 400kHz (for Arduino Due)
#else
TWBR = ((F_CPU / 400000UL) - 16) / 2; // Set I2C frequency to 400kHz
#endif
#endif
// Configure the LidarLite internal parameters so as to lend itself to
// various modes of operation by altering 'configure' input integer to
// anything in the range of 0 to 5. See LIDARLite_v3HP.cpp for details.
myLidarLite.configure(0);
pinMode (pin9,INPUT);
pinMode (pin3, OUTPUT);
pinMode(segmentClock, OUTPUT);
pinMode(segmentData, OUTPUT);
pinMode(segmentLatch, OUTPUT);
digitalWrite(segmentClock, LOW);
digitalWrite(segmentData, LOW);
digitalWrite(segmentLatch, LOW);
}
void loop()
{
int x;
uint16_t distance;
uint8_t newDistance = 0;
uint8_t c;
rangeType_T rangeMode = RANGE_NONE;
// Continuous loop
while (1)
{
// Each time through the loop, look for a serial input character
delay (200);
x = digitalRead(pin9);
if (x == LOW)
{
// read input character ...
// c = (uint8_t) Serial.read();
// ... and parse
switch ('s')
{
case 'S':
case 's':
rangeMode = RANGE_SINGLE;
break;
case 'C':
case 'c':
rangeMode = RANGE_CONTINUOUS;
break;
case 'T':
case 't':
rangeMode = RANGE_TIMER;
break;
case '.':
rangeMode = RANGE_NONE;
break;
case 'D':
case 'd':
rangeMode = RANGE_NONE;
dumpCorrelationRecord();
break;
case 0x0D:
case 0x0A:
break;
default:
Serial.println("=====================================");
Serial.println("== Type a single character command ==");
Serial.println("=====================================");
Serial.println(" S - Single Measurement");
Serial.println(" C - Continuous Measurement");
Serial.println(" T - Timed Measurement");
Serial.println(" . - Stop Measurement");
Serial.println(" D - Dump Correlation Record");
break;
}
}
switch (rangeMode)
{
case RANGE_NONE:
newDistance = 0;
break;
case RANGE_SINGLE:
newDistance = distanceSingle(&distance);
break;
case RANGE_CONTINUOUS:
newDistance = distanceContinuous(&distance);
break;
case RANGE_TIMER:
delay(250); // 4 Hz
newDistance = distanceFast(&distance);
break;
default:
newDistance = 0;
break;
}
// When there is new distance data, print it to the serial port
if (newDistance)
{
Serial.println(distance);
showNumber(distance);
}
// Single measurements print once and then stop
if (rangeMode == RANGE_SINGLE)
{
rangeMode = RANGE_NONE;
}
}
}
//---------------------------------------------------------------------
// Read Single Distance Measurement
//
// This is the simplest form of taking a measurement. This is a
// blocking function as it will not return until a range has been
// taken and a new distance measurement can be read.
//---------------------------------------------------------------------
uint8_t distanceSingle(uint16_t * distance)
{
// 1. Wait for busyFlag to indicate device is idle. This must be
// done before triggering a range measurement.
myLidarLite.waitForBusy();
// 2. Trigger range measurement.
myLidarLite.takeRange();
// 3. Wait for busyFlag to indicate device is idle. This should be
// done before reading the distance data that was triggered above.
myLidarLite.waitForBusy();
// 4. Read new distance data from device registers
*distance = myLidarLite.readDistance();
return 1;
}
//---------------------------------------------------------------------
// Read Continuous Distance Measurements
//
// The most recent distance measurement can always be read from
// device registers. Polling for the BUSY flag in the STATUS
// register can alert the user that the distance measurement is new
// and that the next measurement can be initiated. If the device is
// BUSY this function does nothing and returns 0. If the device is
// NOT BUSY this function triggers the next measurement, reads the
// distance data from the previous measurement, and returns 1.
//---------------------------------------------------------------------
uint8_t distanceContinuous(uint16_t * distance)
{
uint8_t newDistance = 0;
// Check on busyFlag to indicate if device is idle
// (meaning = it finished the previously triggered measurement)
if (myLidarLite.getBusyFlag() == 0)
{
// Trigger the next range measurement
myLidarLite.takeRange();
// Read new distance data from device registers
*distance = myLidarLite.readDistance();
// Report to calling function that we have new data
newDistance = 1;
}
return newDistance;
}
//---------------------------------------------------------------------
// Read Distance Measurement, Quickly
//
// Read distance. The approach is to poll the status register until the device goes
// idle after finishing a measurement, send a new measurement command, then read the
// previous distance data while it is performing the new command.
//---------------------------------------------------------------------
uint8_t distanceFast(uint16_t * distance)
{
// 1. Wait for busyFlag to indicate device is idle. This must be
// done before triggering a range measurement.
myLidarLite.waitForBusy();
// 2. Trigger range measurement.
myLidarLite.takeRange();
// 3. Read previous distance data from device registers.
// After starting a measurement we can immediately read previous
// distance measurement while the current range acquisition is
// ongoing. This distance data is valid until the next
// measurement finishes. The I2C transaction finishes before new
// distance measurement data is acquired.
*distance = myLidarLite.readDistance();
return 1;
}
//---------------------------------------------------------------------
// Print the correlation record for analysis
//---------------------------------------------------------------------
void dumpCorrelationRecord()
{
myLidarLite.correlationRecordToSerial(256);
}
void showNumber(float value)
{
int number = abs(value); //Remove negative signs and any decimals
//Serial.print("number: ");
//Serial.println(number);
for (byte x = 0 ; x < 3 ; x++)
{
int remainder = number % 10;
postNumber(remainder, false);
number /= 10;
}
//Latch the current segment data
digitalWrite(segmentLatch, LOW);
digitalWrite(segmentLatch, HIGH); //Register moves storage register on the rising edge of RCK
}
//Given a number, or '-', shifts it out to the display
void postNumber(byte number, boolean decimal)
{
// - A
// / / F/B
// - G
// / / E/C
// -. D/DP
#define a 1<<0
#define b 1<<6
#define c 1<<5
#define d 1<<4
#define e 1<<3
#define f 1<<1
#define g 1<<2
#define dp 1<<7
byte segments;
switch (number)
{
case 1: segments = b | c; break;
case 2: segments = a | b | d | e | g; break;
case 3: segments = a | b | c | d | g; break;
case 4: segments = f | g | b | c; break;
case 5: segments = a | f | g | c | d; break;
case 6: segments = a | f | g | e | c | d; break;
case 7: segments = a | b | c; break;
case 8: segments = a | b | c | d | e | f | g; break;
case 9: segments = a | b | c | d | f | g; break;
case 0: segments = a | b | c | d | e | f; break;
case ' ': segments = 0; break;
case 'c': segments = g | e | d; break;
case '-': segments = g; break;
}
if (decimal) segments |= dp;
//Clock these bits out to the drivers
for (byte x = 0 ; x < 8 ; x++)
{
digitalWrite(segmentClock, LOW);
digitalWrite(segmentData, segments & 1 << (7 - x));
digitalWrite(segmentClock, HIGH); //Data transfers to the register on the rising edge of SRCK
}
}
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.
Visit TechForum