LiDAR in combination with GPS logging

I'm working on a setup to record GPS data and a distance recording from a laser sensor. I mounted an Adafruit Ultimate GPS+Logging Shield on an Arduino Uno. The laser scanner (LiDAR Lite v3) is connected via the SCA and SCL pins to the UNO.

I'm not really experienced with Arduino programming, so I grabbed examples of both the GPS logging and the LiDARLite and merged them together.

Without the LiDAR, I'm able to let this setup log NMEA strings on the SD card, but when I load the LiDARLite library, it won't make a new textfile to write the data to. The distance measurement from the LiDAR is received though (checked via serial monitor).

Could there be some conflict between the GPS shield and LiDAR that the SCA can't be used in combination with GPS logging?

This is the script I'm currently running.

#include <SPI.h>
#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>
#include <SD.h>
#include <avr/sleep.h>
#include <LIDARLite.h>
// Ladyada's logger modified by Bill Greiman to use the SdFat library
//
// This code shows how to listen to the GPS module in an interrupt
// which allows the program to have more 'freedom' - just parse
// when a new NMEA sentence is available! Then access data when
// desired.
//
// Tested and works great with the Adafruit Ultimate GPS Shield
// using MTK33x9 chipset
//    ------> http://www.adafruit.com/products/
// Pick one up today at the Adafruit electronics shop
// and help support open source hardware & software! -ada
// Fllybob added 10 sec logging option

SoftwareSerial mySerial(7, 8);
Adafruit_GPS GPS(&mySerial);

LIDARLite lidarLite; // assign variable to LIDARLite module
int cal_cnt = 0;    // variable that keeps track of the amount of loops (used by LiDARLite)

// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences
#define GPSECHO  true
/* set to true to only log to SD when GPS has a fix, for debugging, keep it false */
#define LOG_FIXONLY false

// this keeps track of whether we're using the interrupt
// off by default!
#ifndef ESP8266 // Sadly not on ESP8266
boolean usingInterrupt = false;
#endif


// Set the pins used
#define chipSelect 10
#define ledPin 13

File logfile;

// read a Hex value and return the decimal equivalent
uint8_t parseHex(char c) {
  if (c < '0')
    return 0;
  if (c <= '9')
    return c - '0';
  if (c < 'A')
    return 0;
  if (c <= 'F')
    return (c - 'A')+10;
}

// blink out an error code
void error(uint8_t errno) {
  /*
  if (SD.errorCode()) {
   putstring("SD error: ");
   Serial.print(card.errorCode(), HEX);
   Serial.print(',');
   Serial.println(card.errorData(), HEX);
   }
   */
  while(1) {
    uint8_t i;
    for (i=0; i<errno; i++) {
      digitalWrite(ledPin, HIGH);
      delay(100);
      digitalWrite(ledPin, LOW);
      delay(100);
    }
    for (i=errno; i<10; i++) {
      delay(200);
    }
  }
}

void setup() {
  // for Leonardos, if you want to debug SD issues, uncomment this line
  // to see serial output
  //while (!Serial);

  // connect at 115200 so we can read the GPS fast enough and echo without dropping chars
  // also spit it out
  Serial.begin(115200);
  Serial.println("\r\nUltimate GPSlogger Shield");
  pinMode(ledPin, OUTPUT);

  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(10, OUTPUT);

  if (!SD.begin(chipSelect)) {
    Serial.println("Card init. failed!");
    error(2);
  }

  char filename[15];
  strcpy(filename, "GPSLOG00.TXT");
  for (uint8_t i = 0; i < 100; i++) {
    filename[6] = '0' + i/10;
    filename[7] = '0' + i%10;
    // create if does not exist, do not open existing, write, sync after write
    if (! SD.exists(filename)) {
      break;
    }
  }

  logfile = SD.open(filename, FILE_WRITE);
  if( ! logfile ) {
    Serial.print("Couldnt create ");
    Serial.println(filename);
    error(3);
  }
  Serial.print("Writing to ");
  Serial.println(filename);

  // connect to the GPS at the desired rate
  GPS.begin(9600);
    
  lidarLite.begin(0, true); // Set configuration to default and I2C to 400 kHz
  lidarLite.configure(0); // Change this number to try out alternate configurations
  
  // uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
//  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  // uncomment this line to turn on only the "minimum recommended" data
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // For logging data, we don't suggest using anything but either RMC only or RMC+GGA
  // to keep the log files at a reasonable size
  // Set the update rate
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_5HZ);   // 100 millihertz (once every 10 seconds), 1Hz or 5Hz update rate

  // Turn off updates on antenna status, if the firmware permits it
  GPS.sendCommand(PGCMD_NOANTENNA);

  // the nice thing about this code is you can have a timer0 interrupt go off
  // every 1 millisecond, and read data from the GPS for you. that makes the
  // loop code a heck of a lot easier!
#ifndef ESP8266 // Not on ESP8266
  useInterrupt(false);
#endif

  Serial.println("Ready!");
}


// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
#ifndef ESP8266 // Not on ESP8266
ISR(TIMER0_COMPA_vect) {
  char c = GPS.read();
  // if you want to debug, this is a good time to do it!
  #ifdef UDR0
      if (GPSECHO)
        if (c) UDR0 = c;
      // writing direct to UDR0 is much much faster than Serial.print
      // but only one character can be written at a time.
  #endif
}

void useInterrupt(boolean v) {
  if (v) {
    // Timer0 is already used for millis() - we'll just interrupt somewhere
    // in the middle and call the "Compare A" function above
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  }
  else {
    // do not call the interrupt function COMPA anymore
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}
#endif // ESP8266

void loop() {
  int dist;
  // At the beginning of every 100 readings,
  // take a measurement with receiver bias correction
  if ( cal_cnt == 0 ) {
    dist = lidarLite.distance();      // With bias correction
  } else {
    dist = lidarLite.distance(false); // Without bias correction
  }
  // Increment reading counter
  cal_cnt++;
  cal_cnt = cal_cnt % 100;
  
  Serial.print(lidarLite.distance());
  if (! usingInterrupt) {
    // read data from the GPS in the 'main loop'
    char c = GPS.read();
    // if you want to debug, this is a good time to do it!
    if (GPSECHO)
      if (c) Serial.print(c);
      Serial.print(dist);
  }

  // if a sentence is received, we can check the checksum, parse it...
  if (GPS.newNMEAreceived()) {
    // a tricky thing here is if we print the NMEA sentence, or data
    // we end up not listening and catching other sentences!
    // so be very wary if using OUTPUT_ALLDATA and trying to print out data

    // Don't call lastNMEA more than once between parse calls!  Calling lastNMEA
    // will clear the received flag and can cause very subtle race conditions if
    // new data comes in before parse is called again.
    char *stringptr = GPS.lastNMEA();

    if (!GPS.parse(stringptr))   // this also sets the newNMEAreceived() flag to false
      return;  // we can fail to parse a sentence in which case we should just wait for another

    // Sentence parsed!
    Serial.println("OK");
    if (LOG_FIXONLY && !GPS.fix) {
      Serial.print("No Fix");
      return;
    }

    // Rad. lets log it!
    Serial.println("Log");

    uint8_t stringsize = strlen(stringptr);
    if (stringsize != logfile.write((uint8_t *)stringptr, stringsize))    //write the string to the SD file
        error(4);
    if (strstr(stringptr, "RMC") || strstr(stringptr, "GGA"))   logfile.flush();
    Serial.println();
  }
}

Welcome to the forum, and thanks for posting code using code tags! Note that this is not a "script", it is a C/C++ program.

it won't make a new textfile to write the data to.

Explain, and post any error messages as text.

Please post a hand drawn wiring diagram of the connections, with all pins labeled and identify your power supply. The combination of GPS, SD card and Lidar Lite is probably exceeding the maximum current draw of the on-board voltage regulator.

If that is the case, you will need to provide external power for the Lidar Lite. Don't forget to connect all the grounds.

Hi, Thanks for the feedback. I'm coming from a Python "script" background, so it's a bit confusing, but really fun to learn.

My setup right now is pretty simple. I have the RedBoard Uno. On top of that I attached the Adafruit GPS Data Logger Shield. I connected the LiDARLite v3 to the header pins (5v, GRD, SDA and SCL) of the GPS shield using wires. The wiring is similar to this schame (from the lidarlite guide), but not including the capacitor.

If it is a power issue, I could get a capacitor to try. Currently I'm powering this setup via the USB port via my laptop.

The error I'm receiving comes from this piece of code:

logfile = SD.open(filename, FILE_WRITE);
  if( ! logfile ) {
    Serial.print("Couldnt create ");
    Serial.println(filename);
    error(3);

Is there a way to see a more detailed description of this error?

You would have to look in the SD library code to see why the open command failed. Post a link to where you downloaded the SD library (there are several with the same name).

If the open works without the Lidar connected, it is VERY likely that you are exceeding the current capability of the 5V output. Try powering the Lidar Lite separately, but connect the grounds. A 5V phone charger should work.

I didn't download a SD library, it came installed with the Arduino software. The strange thing is, it does write the textfile, but if( ! logfile ) still returns true so nothing is written to the file. I'm supplying it now with power from a 12v wall wart (1.5A), which should be plenty for this setup, but still no writing to the file.

I tried to modify the working GPS logging program that was provided with the Adafruit GPS shield step by step to figure out where the program stopped working. (I didn't change anything to the hardware, GPS and LiDAR are connected). By adding this line of code, the error pops up and there is no GPS data writting to the file on the SD card anymore:

lidarLite.begin(0, true); // Set configuration to default and I2C to 400 kHz

So I think there is some kind of conflict between these libraries. Do you have an idea where the conflict could be?

I'm supplying it now with power from a 12v wall wart (1.5A), which should be plenty for this setup, but still no writing to the file.

The 12V "wall wart" is NOT the issue.

The 12V has to be reduced to 5V by the on board voltage regulator, which has power dissipation limits and current limits. You can look up those limits.

All of the power hungry 5V devices you have connected together are almost certainly exceeding those limits.

Please do the experiment that I suggested, which is to disconnect the Lidar and see if the file is opened correctly.

I tried the code without the LiDAR Lite connected to the board and with externally powering the LiDAR, but still the line

lidarLite.begin(0, true); // Set configuration to default and I2C to 400 kHz

results in not writing NMEA strings to the txt file.

Does it maybe have something to do with SoftwareSerial? I'm not so familiar with it, but I read this on the arduino website: If using multiple software serial ports, only one can receive data at a time.

Did the file open correctly? If so, then the 5V power is the problem. Use a separate 5V power supply for the Lidar and connect the grounds.

If the Lidar is disconnected, why would you expect the .begin() method to work?

I connected the LiDAR to an external power supply (usb cable into usb adapter), but this did not resolve the problem. The following line does create the textfile on the SD card

logfile = SD.open(filename, FILE_WRITE);

But the check afterwards says that the file is not created. I also tried to add a delay after opening the file to give the system time before checking if the file exists, but still the same result.

So the power seems not to be the issue, right?

Do you think the memory use could be the problem?

Sketch uses 20230 bytes (62%) of program storage space. Maximum is 32256 bytes.
Global variables use 1770 bytes (86%) of dynamic memory, leaving 278 bytes for local variables. Maximum is 2048 bytes.

The GPS uses pin 7 and 8 for SoftwareSerial, these are the Tx and Rx pins. I think the LiDARLite also uses Tx and Rx, so there might be the conflict?
How can I assign both to other pins?

It is working now!

I now use PWM to connect the LiDAR instead of I2C, so I think the conflict between the two serial data streams (GPS and LiDAR) was somehow the problem. I don't think power was the issue, because I got it working now with only providing power via the usb port.

Thanks for the help

I don't think power was the issue

You can't conclude that.

When powering from the USB port, the on board regulator is not used, so you learn nothing about possible issues when using that regulator.