PortentaH7, D100 Lidar by LDRobot and Grayscale OLED

This thread has morphed to the PortentaH7 so probably best to continue it here. Error compiling for board Arduino Mega or Mega 2560 - #48 by jerteach

@cattledog @pert

So I got this Lidar D100 kit from LdRobot and I have connected it to my PortentaH7 with WaveShare Grayscale 128x128 OLED and using a 9 year old library called RPLidarDriver I have some results.

but the windows viewer is really very good this twitter feed here, and what I am getting is not much better than random dots.

My github for reseqarch on the Lidar Sensor is at d100-ldrobot-lidar-ld14 . It is very strange that the library I have works at all, but I have found another library LD06forArduino.ino that seems much closer to what I need and the math looks better.

I am going to keep working on it, but any suggestions would be appreciated. It is very strange that this amazing Lidar sensor does not have a good Arduino library to go with it.

Here is my latest code, but the results are not much good.

/*
 * RoboPeak RPLIDAR Arduino Example
 * This example shows the easy and common way to fetch data from an RPLIDAR
 * 
 * You may freely add your application code based on this template
 *
 * USAGE:
 * ---------------------------------
 * 1. Download this sketch code to your Arduino board
 * 2. Connect the RPLIDAR's serial port (RX/TX/GND) to your Arduino board (Pin 0 and Pin1)
 * 3. Connect the RPLIDAR's motor ctrl pin to the Arduino board pin 3 
 */
 
/* 
 * Copyright (c) 2014, RoboPeak 
 * All rights reserved.
 * RoboPeak.com
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 LDrobot https://www.ldrobot.com/download/en/124
 datasheet at
 https://github.com/ldrobotSensorTeam/DeveloperKit/blob/master/D100Kit.md
 
 D100 red wire to +5V
 Yellow wire to GND
 Green wire to RX1 D13 on Portenta
 black wire strangely to D3 as it is the motor PWM control.
 
 
 
 */
 
// This sketch code is based on the RPLIDAR driver library provided by RoboPeak
#include <RPLidar.h>

// You need to create an driver instance 
RPLidar lidar;

#define RPLIDAR_MOTOR D5 // The PWM pin for control the speed of RPLIDAR's motor.
                        // This pin should connected with the RPLIDAR's MOTOCTRL signal 





#include <Adafruit_SSD1327.h>


// Used for software SPI
#define OLED_CLK D9
#define OLED_MOSI D8

// Used for software or hardware SPI
#define OLED_CS D7
#define OLED_DC D6

// Used for I2C or SPI
#define OLED_RESET -1


// software SPI
//Adafruit_SSD1305 display(128, 64, OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
// hardware SPI
Adafruit_SSD1327 display(128, 128, &SPI, OLED_DC, OLED_RESET, OLED_CS);

// I2C
//Adafruit_SSD1327 display(128, 128, &Wire, OLED_RESET, 1000000);


unsigned long myDelay = 5000;   // non-block delay in milliseconds
unsigned long myStart;

float  myStoreAngle;
                        
void setup() {

  Serial.begin(115200);

  myStart = millis();   // set delay

  // bind the RPLIDAR driver to the arduino hardware serial1
  lidar.begin(Serial1);

  //while (! Serial) delay(100);  // blocking, messes up beginners
  // set pin modes
  pinMode(RPLIDAR_MOTOR, OUTPUT);
  //delay(10000); //to conect serial monitor

  //Serial.println("SSD1327 OLED test");
  
  if ( ! display.begin(0x3D) ) {
     Serial.println("Unable to initialize OLED");
     while (1) yield();
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextWrap(false);
  display.setTextColor(SSD1327_WHITE);
  display.setCursor(0,0);
  display.display();

/*

  Serial.println("Stopping Lidar");
  analogWrite(RPLIDAR_MOTOR, 0);  // 255 is max
  delay(5000);

  Serial.println("medium Lidar");
  analogWrite(RPLIDAR_MOTOR, 125);  // 255 is max
  delay(5000);

  Serial.println("fast Lidar");
  analogWrite(RPLIDAR_MOTOR, 255);  // 255 is max

*/

rplidar_response_device_health_t myLidarHealth;  //    _u8   status;   _u16  error_code;

lidar.getHealth(myLidarHealth,2000);
//Serial.println(myLidarHealth);


rplidar_response_device_info_t   myLidarInfo;  
/*
    _u8   model;
    _u16  firmware_version;
    _u8   hardware_version;
    _u8   serialnum[16];
*/

lidar.getDeviceInfo(myLidarInfo, 2000);


}







void loop() {
    if ( (millis() - myStart ) >= myDelay) {       
       myStart = millis();      //  reset the delay time
       display.clearDisplay();
      display.setCursor(0, 0);
      display.println("Rocksetta D100 Lidar");
    }










  if (IS_OK(lidar.waitPoint())) {
    float distance = lidar.getCurrentPoint().distance; // distance value in mm unit
    float angle    = lidar.getCurrentPoint().angle;    // angle value in degree
    bool  startBit = lidar.getCurrentPoint().startBit; // whether this point is belong to a new scan
    byte  quality  = lidar.getCurrentPoint().quality;  // quality of the current measurement








  if (startBit) {

    if (angle > 360 ) {angle = angle - 360;}
      myStoreAngle = angle;  
     Serial.println("----------------------- starting angle: " + String(angle, 3) + " degrees -------------------------");  
  } else {

    myStoreAngle = angle + myStoreAngle;
  }


    if (myStoreAngle > 360 ) {myStoreAngle = myStoreAngle - 360;}
    if (angle > 360 ) {angle = angle - 360;}


 

  Serial.print("d: " + String(distance, 3) + " mm, raw angle: " + String(angle, 3) + " degrees, angle: " + String(myStoreAngle, 3) + " degrees, quality: " + String(quality, 3) +"  " );

 
  
    byte myShow = quality;

/*
    if (myShow > 255) {myShow = 255;}
    if (myShow < 127) {myShow = 127;}
*/


    // ignore3 the above
    myShow = 255;

    ///if (quality < 55) {myShow = 127;} else {myShow = 255;}



    //int myX = (int) (distance * cos(myStoreAngle * 3.1416 / 180.0));
    //int myY = (int) (distance * sin(myStoreAngle * 3.1416 / 180.0));



  // int myX = (int) (distance * cos(angle * 3.1416 / 180.0));
   //int myY = (int) (distance * sin(angle * 3.1416 / 180.0));

   // flip sin cos
   int myX = (int) (distance * sin(angle * 3.1416 / 180.0));
   int myY = (int) (distance * cos(angle * 3.1416 / 180.0));




   //if (angle > 90 && angle < 180){ myX = -1 * myX; myY = -1 * myY; }
   //if (angle > 270 && angle < 360){ myX = -1 * myX; myY = -1 * myY; }

/*
  // flip all values
  int myTemp;
  //if (angle > 90 && angle < 360){ 
    myTemp = myX;
    myX = myY;
    myY =  myTemp;
  //}

  */

  // now check the sign of the values

 // if (angle > 90 && angle <= 180){ myX = -1 * myX; myY = -1 * myY; }  

 // if (angle > 270 && angle <= 360){ myX = -1 * myX; myY = -1 * myY; }  




    int   myX1Map = map((int)myX, 0, 500, 0, 127);  // not sure what to map from possibly 8000 mm = 8 m
   // if (myX1Map < -64){myX1Map = -64;}

    int   myY1Map = map((int)myY, 0, 500, 0, 127);
    //if (myY1Map < -64){myY1Map = 0;}

    int   myMidX = 64;
    int   myMidY = 64;




  Serial.print(" X: ");  
  Serial.print(myX1Map); 
  Serial.print(",  y: ");  
  Serial.print(myY1Map); 
  Serial.println();  
  



 // for the OLED display
  display.drawPixel(myX1Map + myMidX, myY1Map + myMidY, myShow);
  display.display();



    
  } else {
    analogWrite(RPLIDAR_MOTOR, 0); //stop the rplidar motor
    
    // try to detect RPLIDAR... 
    rplidar_response_device_info_t info;
    if (IS_OK(lidar.getDeviceInfo(info, 100))) {
       // detected...
       lidar.startScan();
       
       // start motor rotating at max allowed speed
       analogWrite(RPLIDAR_MOTOR, 255);  // 255 is max
       delay(1000);
    }
  }
}

New plan. Lets look at the data. Make a csv file of this data that should match the viewer

And my random data display. :rofl:

CSV file coming.

This site looks useful

Actually that link is not useful.

The 3 useful sites are:

  1. robopeak RPLIDAR 9 year old library here

  2. Daniel's version of the rpLidar library here

  3. ldRobto's github version here

I assume Robopeak's library kind of works but screws up the distance and angle or doesn't complete the calculations. Weird that it does get anything sort-of correct.

I am going to concentrate on this part of it that extracts the data from the byte stream

          if (recvPos == sizeof(rplidar_response_measurement_node_t)) {
              // store the data ...
              _currentMeasurement.distance = node.distance_q2/4.0f;
              _currentMeasurement.angle = (node.angle_q6_checkbit >> RPLIDAR_RESP_MEASUREMENT_ANGLE_SHIFT)/64.0f;
              _currentMeasurement.quality = (node.sync_quality>>RPLIDAR_RESP_MEASUREMENT_QUALITY_SHIFT);
              _currentMeasurement.startBit = (node.sync_quality & RPLIDAR_RESP_MEASUREMENT_SYNCBIT);
              return RESULT_OK;
          }

Daniels does much more but here is it's byte stream conversion functions possible for 2 different lidar devices

static void convert(const rplidar_response_measurement_node_t& from, rplidar_response_measurement_node_hq_t& to)
{
    to.angle_z_q14 = (((from.angle_q6_checkbit) >> RPLIDAR_RESP_MEASUREMENT_ANGLE_SHIFT) << 8) / 90;  //transfer to q14 Z-angle
    to.dist_mm_q2 = from.distance_q2;
    to.flag = (from.sync_quality & RPLIDAR_RESP_MEASUREMENT_SYNCBIT);  // trasfer syncbit to HQ flag field
    to.quality = (from.sync_quality >> RPLIDAR_RESP_MEASUREMENT_QUALITY_SHIFT) << RPLIDAR_RESP_MEASUREMENT_QUALITY_SHIFT;  //remove the last two bits and then make quality from 0-63 to 0-255
}

static void convert(const rplidar_response_measurement_node_hq_t& from, rplidar_response_measurement_node_t& to)
{
    to.sync_quality = (from.flag & RPLIDAR_RESP_MEASUREMENT_SYNCBIT) | ((from.quality >> RPLIDAR_RESP_MEASUREMENT_QUALITY_SHIFT) << RPLIDAR_RESP_MEASUREMENT_QUALITY_SHIFT);
    to.angle_q6_checkbit = 1 | (((from.angle_z_q14 * 90) >> 8) << RPLIDAR_RESP_MEASUREMENT_ANGLE_SHIFT);
    to.distance_q2 = from.dist_mm_q2 > _u16(-1) ? _u16(0) : _u16(from.dist_mm_q2);
}

And LdRobots sensor team uses this


void LD06forArduino::calc_lidar_data(std::vector<char> &values) {

  start_byte = values[0];
  data_length = 0x1F & values[1];
  Speed = float(values[3] << 8 | values[2]) / 100;
  FSA = float(values[5] << 8 | values[4]) / 100;
  LSA = float(values[values.size() - 4] << 8 | values[values.size() - 5]) / 100;
  time_stamp = int(values[values.size() - 2] << 8 | values[values.size() - 3]);
  CS = int(values[values.size() - 1]);

  if (LSA - FSA > 0) {
    angle_step = (LSA - FSA) / (data_length - 1);
  } else {
    angle_step = (LSA + (360 - FSA)) / (data_length - 1);
  }

  // note: 刻み幅の異常を検知
  if (angle_step > 20) {
    return;
  }

  // note: 過去のデータを初期化
  angles.clear();
  confidences.clear();
  distances.clear();

  for (int i = 0; i < data_length; i++) {
    float raw_deg = FSA + i * angle_step;
    angles.push_back(raw_deg <= 360 ? raw_deg : raw_deg - 360);
    confidences.push_back(values[8 + i * 3]);
    distances.push_back(int(values[8 + i * 3 - 1] << 8 | values[8 + i * 3 - 2]));

  }
}


Obviously there is more than just these functions, but if anyone can see something I could try with the RoboPeak's original library to get it working with the D100 I would appreciate the input.

What happened to your idea of verifying the display by giving the code a known file rather than the output of the calculations from the lidar unit?

Separating display issues from library calculation issues will be helpful.

The csv file is just junk. No patterns or anything useful. The distance measurements are all over the place and mainly really high even though my lidar detector is inside a box. The angles are sensible and high but then only 1 or 2 degrees and then really high again. The startBit seems to work printing the dashes across the screen. I have tried adding the small angles to the original angle but that does not work.

Listing: Angle (in degrees?, Distance in mm?)
I think it is fair to say that the original library is kind of working but it's byte stream analysis is off.



----------------------------------------------
104.0, 66.8
2.0, 16378.5
1.9, 15610.0
164.8, 5438.8
1.8, 14458.5
1.7, 13498.5
1.6, 13050.5
169.3, 7051.0
1.4, 11513.5
1.3, 11001.0
1.3, 10682.0
1.3, 10553.0
1.3, 10426.0
1.2, 14650.0
1.2, 10106.5
1.2, 9978.5
214.4, 5277.8
1.2, 9978.5
168.0, 7179.0
1.2, 10106.5
1.2, 10170.5
1.2, 10298.0
1.2, 10426.0
1.3, 13882.0
1.3, 10681.5
1.3, 10810.0
1.3, 11001.5
1.3, 11258.5
240.3, 5281.8
1.5, 12153.5
1.5, 12410.5
169.5, 7051.0
----------------------------------------------
1.6, 13178.2
----------------------------------------------
165.1, 5404.0
35.0, 14848.0
1.5, 11897.5
1.4, 11513.5
1.4, 11257.5
----------------------------------------------
1.3, 11001.5
1.3, 10681.5
1.3, 10554.0
1.3, 10426.0
----------------------------------------------
165.2, 5387.5
1.2, 10234.0
1.2, 10106.5
----------------------------------------------
165.2, 5393.5
310.0, 14976.0
1.2, 9978.5
1.2, 9978.5
1.2, 10042.5
11.6, 5289.5
1.2, 10106.0
1.2, 10298.0
1.2, 10362.5
1.3, 2489.5
165.3, 5412.0
1.3, 10809.5
1.3, 10937.0
1.4, 11386.0
168.1, 7627.0
1.4, 11770.0
1.5, 12154.5
1.5, 12538.0
----------------------------------------------
66.1, 14848.0
1.4, 11065.5
1.2, 9594.5
1.1, 9274.0
169.0, 6923.0
----------------------------------------------
282.2, 14720.0
1.1, 8761.5
1.0, 8633.5
1.0, 8569.5
1.0, 11705.5
1.0, 8313.5
1.0, 8249.5
1.0, 8185.5
1.0, 8185.5
1.0, 8185.5
1.0, 8185.0
1.0, 8377.5
1.0, 8442.0
1.0, 8569.5
1.0, 8697.5
168.8, 6795.0
1.1, 8889.5
1.1, 9017.0
1.1, 9146.0
1.1, 9466.5
97.8, 5302.2
296.4, 14976.0
1.3, 10617.5
106.4, 5303.5
334.4, 14720.0
1.3, 11257.5
1.4, 11898.0
1.5, 12154.5
----------------------------------------------
165.8, 5427.2
32.4, 14720.0
1.6, 13434.0
1.7, 13946.5
1.8, 15482.5
123.5, 5306.0
110.0, 66.0
----------------------------------------------
45.8, 14656.2
106.0, 66.2
----------------------------------------------
125.8, 14912.2
----------------------------------------------
77.8, 15040.2
----------------------------------------------
104.0, 72.0
104.0, 71.2
104.0, 70.2
100.0, 4501.0
2.2, 1338.0
100.0, 68.5
----------------------------------------------
104.0, 68.2
2.1, 1146.5
----------------------------------------------
108.0, 69.0
149.2, 5309.8
104.0, 68.2
100.0, 68.8
2.1, 1338.0
----------------------------------------------
108.0, 69.2
104.0, 5048.5
46.6, 14848.2
----------------------------------------------
100.0, 70.5
2.2, 1914.0
100.0, 71.8
----------------------------------------------
100.0, 72.5
104.0, 5325.0
204.6, 2837.0
----------------------------------------------
108.0, 76.8
----------------------------------------------
108.0, 76.2
----------------------------------------------
100.0, 5599.8
----------------------------------------------
104.0, 66.8
2.0, 16378.0
1.9, 15674.5
24.7, 13524.8
1.6, 13114.5
1.5, 12218.5
----------------------------------------------
46.6, 2837.0
1.3, 10809.5
1.3, 10617.5
200.6, 5317.5
1.3, 10425.5
1.2, 9978.0
1.2, 9978.0
1.2, 9914.5
308.8, 14976.0
1.2, 9978.5
1.2, 9978.0
1.2, 10170.5
----------------------------------------------
166.3, 5406.5
1.2, 10234.0
1.3, 10554.0
1.3, 7482.0
1.3, 10938.0
1.3, 11066.0
1.4, 11321.5
1.4, 11641.5
1.4, 12154.0
----------------------------------------------


The code PortentaH7 with Grayscale OLED (not needed for the serial print) I used to print the above is at here

Possibly I have done something silly, but with that data, I am not sure how it could be useful.

Daniels replidar here has some promise. It compiles but does not show any data yet. I will work on that.

Pleased that this code prints a massive amount of data.

void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);

}

void loop() {
  while (Serial1.available()){
     Serial.print(Serial1.read());

  }

}

perhaps I should look into the basic data stream defined here

Do I just read the byte stream until I get a "0x54" as the header and then go from there?

.

.

So that gets an output of


Start: ..............................................
Start: ..............................................
Start: ..............................................
Start: ..............................................
Start: ..............................................
Start: ..............................................
Start: ..............................................
Start: ..............................................
Start: ..............................................
Start: ..............................................
Start: ..............................................
Start: ..............................................

which is strange since it should be variable length. I used this code:

void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);

}

void loop() {
  while (Serial1.available()){
    char myIn = Serial1.read();
    if (myIn == 0x54) {
      Serial.println();
      Serial.print("Start: ");
    } else {
      Serial.print(".");
  }

  }

}


Here is the next bit of info that looks like it is a fixed range of bytes

Good job finding the data protocol documentation.
It's not clear to me that there isn't a fixed number of bytes in the transmission.

One other thing to be found in the document is a link to the code on the PC which reads and displays the data correctly. Perhaps there's some reverse engineering to be done.
https://github.com/ldrobotSensorTeam/ld_desktop_tool/releases

@cattledog

Using this code I am getting different lengths of data:


void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);

}

void loop() {
  while (Serial1.available()){
    char myIn = Serial1.read();
    if (myIn == 0x54) {
      Serial.println();
      Serial.print("Start: ");
      Serial.print(myIn, HEX);
    } else {
      Serial.print(" ");
      if (myIn <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn, HEX);
  }

  }

}



Start: 54 2C 69  8 B8 72 93  0 E6 93  0 E6 92  0 E6 92  0 E8 91  0 E8 91  0 E4 91  0 E6 90  0 E8 90  0 90  0 90  0 8F  0 A3 76 9D 6A
Start: 54 2C 69  8  5 77 8F  0 E6 8F  0 E6 8F  0 E8 8F  0 E6 8F  0 E6 8F  0 E4 8F  0 E8 8F  0 E8 8F  0 E6 90  0 EA  0 E4 90  0 F3 7A A2 DB
Start: 54 2C 69  8 4E 7B 90  0 E6 91  0 E4 91  0 EA 92  0 E8 92  0 E4 93  0 E6 93  0 E8 94  0 E6 94  0 E4 95  0 E6 96  0 E6 97 E4 3F A7 14 55
Start: 54 2C 6D  8 9A 7F 98  0 E4 98  0 E4 99  0 E4 9A  0 E6 9B  0 E4 9C  0 E4 9E  0 E4 9E  0 E4 A0  0 E4  0 E4 A2  0 E2 E4 8B 83 14 16
Start: 54 2C 6D  8 E8 83 A5  0 E2 A6  0 E2 A7  0 E2 A9  0 E0 AB  0 E2 AD  0 E0 AE  0 B0  0 E2  0 E4 B4 E2 B6 E2 B8  0 E4 D2 B1 14 3B
Start: 54 2C 68  8 2D 88 BA  0 E2 BD  0 E2 BF  0 E2 C1  0 E0 C4  0 E2 C7  0 E0 CA  0 E2 CC  0 E0 CF  0 E0  0 E0  0 E4 D9  0 E2 8C B6 A9
Start: 54 2C 72  8 7C 8C DC  0 E0 E0  0 E0 E4  0 E0 E9  0 E2 EC  0 E0 F1  0 E0 F5  0 E0 FA  0 E0 FF  0 E2  4  1  A  1 E4  7  1 CF  3 BB 29
Start: 54 2C 72  8 2B  4  5  1 E4  2  1 E4 FF  0 E4 FD  0 E6 FB  0 E8 F9  0 E6 F6  0 E6 F4  0 E6  0 E6  0 E6 F0 E8 EF  0 E4  8 C0 14
Start: 54 2C 71  8 7E  8 ED  0 E6 EC  0 E4 EB  0 E4 EA  0 E8 E9  0 EA E8  0 E8 E7  0 E6 E5  0 E8 E5  0 EA E5  0 E4 E4  0 E3  0 E8 70  C 14 C0
Start: 54 2C 70  8 CB  C E2  0 E8 E2  0 E8 E2  0 E8 E1  0 E8 E1  0 E8 E1  0 EA E1  0 E8 E1  0 E8 E1  0 EA E1 E6 E0  0 E8 E0 EA BB CB 14 32
Start: 54 2C 70  8 15 11 E1  0 EA E1  0 E8 E2  0 E8 E1  0 E8 E2  0 E8 E3  0 EA E3  0 E6 E3  0 EA E5  0 E6  0 E8 E6 EA E7  0  C

So things are getting more interesting.



byte myIn8bitA, myIn8bitB;
short myIn16bit;
int myHeader, myVerlen, mySpeed, myStartAngle, myEndAngle, myTimeStamp, myCRC;

// should be an array but this is easier
int myData1,myQuality1, myData2,myQuality2, myData3,myQuality3, myData4,myQuality4, myData5,myQuality5, myData6,myQuality6;

void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);

}

void loop() {
  while (Serial1.available()){

    myIn8bitA = Serial1.read();
    if (myIn8bitA == 0x54) {
      //first byte: header
      myHeader = (int) myIn8bitA;  // who cares.
      Serial.println();
      Serial.print("Start: ");
      Serial.print(myIn8bitA, HEX);
      
      // 2nd byte length
      myIn8bitA = Serial1.read();
      myVerlen = (int) myIn8bitA;  // who cares
      Serial.print(" ");
      if (myIn8bitA <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitA, HEX); 

      // 3rd and 4th bytes angular speed iof lidar
      myIn8bitA = Serial1.read();
      myIn8bitB = Serial1.read();
      mySpeed = myIn8bitB *256 + myIn8bitA;   // check the order of this
      Serial.print(" ");
      if (myIn8bitA <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitA, HEX); 
      Serial.print(" ");
      if (myIn8bitB <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitB, HEX); 

      // 5th and 6th bytes start angle
      myIn8bitA = Serial1.read();
      myIn8bitB = Serial1.read();
      myStartAngle = myIn8bitB *256 + myIn8bitA;   // check the order of this
      Serial.print(" ");
      if (myIn8bitA <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitA, HEX); 
      Serial.print(" ");
      if (myIn8bitB <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitB, HEX); 

      Serial.print(" ---"); 

      // 7th and 8th bytes Data1
      myIn8bitA = Serial1.read();
      myIn8bitB = Serial1.read();
      myData1 = myIn8bitB *256 + myIn8bitA;   // check the order of this
      Serial.print(" ");
      if (myIn8bitA <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitA, HEX); 
      Serial.print(" ");
      if (myIn8bitB <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitB, HEX); 

      // 9th byte Quality for data 1
      myIn8bitA = Serial1.read();
      myQuality1 = (int) myIn8bitA;  // who cares
      Serial.print(" ");
      if (myIn8bitA <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitA, HEX); 

      Serial.print(" |"); 

      // 10th and 11th bytes Data1
      myIn8bitA = Serial1.read();
      myIn8bitB = Serial1.read();
      myData2 = myIn8bitB *256 + myIn8bitA;   // check the order of this
      Serial.print(" ");
      if (myIn8bitA <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitA, HEX); 
      Serial.print(" ");
      if (myIn8bitB <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitB, HEX); 

      // 12th byte Quality for data 1
      myIn8bitA = Serial1.read();
      myQuality2 = (int) myIn8bitA;  // who cares
      Serial.print(" ");
      if (myIn8bitA <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitA, HEX); 

      Serial.print(" |"); 

      // 13th and 14th bytes Data1
      myIn8bitA = Serial1.read();
      myIn8bitB = Serial1.read();
      myData3 = myIn8bitB *256 + myIn8bitA;   // check the order of this
      Serial.print(" ");
      if (myIn8bitA <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitA, HEX); 
      Serial.print(" ");
      if (myIn8bitB <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitB, HEX); 

      // 15th byte Quality for data 1
      myIn8bitA = Serial1.read();
      myQuality3 = (int) myIn8bitA;  // who cares
      Serial.print(" ");
      if (myIn8bitA <= 15 ){Serial.print(" ");} // add a space for single digit hex values
      Serial.print(myIn8bitA, HEX); 


      Serial.print(" |"); 








      // ignore the rest for now.
      // summary of what we have so far
      Serial.println();
      Serial.println("header:"+String(myHeader)+ ", VerLen:"+String(myVerlen)+ ", Speed:"+String(mySpeed)+ " degrees per second, Start Angle:"+String(myStartAngle) +" 100th of a degree, "   ); 
      Serial.println("Data1: "+String(myData1)+ " mm, Quality1: "+String(myQuality1) );   
      Serial.println("Data2: "+String(myData2)+ " mm, Quality2: "+String(myQuality2) );   
      Serial.println("Data3: "+String(myData3)+ " mm, Quality3: "+String(myQuality3) );   

      Serial.println();
    }  // end if startbit

  }  // end while

}  // end loop
Start: 54 2C 72 FF  8 B8 --- 10 89  1 | EA 89  1 | EC 8A  1 |
header:84, VerLen:44, Speed:65394 degrees per second, Start Angle:47112 100th of a degree, 
Data1: 35088 mm, Quality1: 1
Data2: 35306 mm, Quality2: 1
Data3: 35564 mm, Quality3: 1


Start: 54 2C 76 FF  8  B --- 15  0  0 | FE 2A  1 | E6 56  1 |
header:84, VerLen:44, Speed:65398 degrees per second, Start Angle:2824 100th of a degree, 
Data1: 21 mm, Quality1: 0
Data2: 11006 mm, Quality2: 1
Data3: 22246 mm, Quality3: 1


Start: 54 2C 76  8 5D 19 --- A8  1 EA | AB  1 EC | 93  0 E2 |
header:84, VerLen:44, Speed:2166 degrees per second, Start Angle:6493 100th of a degree, 
Data1: 424 mm, Quality1: 234
Data2: 427 mm, Quality2: 236
Data3: 147 mm, Quality3: 226

These numbers look a bit better. I don't need all the data but I do need to figure out how to get the end-angle so I can estimate the individual angles. I need to think about that a bit.

@cattledog Also can someone check how I have added the two bytes into a single 16 bit digit. I am not sure if I am getting the order correct, however the numbers look good.

One real concern @cattledog is that the second byte for which 5 of the bits describe the length of the byte stream is always one of 2 amounts. That seems very strange when the old RPLidar code does get different lengths of data.

I agree that looking at the working PC code might be the only way to get this Lidar device working.

myIn8bitA = Serial1.read();
myIn8bitB = Serial1.read();
mySpeed = myIn8bitB *256 + myIn8bitA;   // check the order of this

Page 6 shows the transmission order from the lidar unit is lsb msb so you are doing combination this correctly. Multiplying by 256 is the same as bit shifting 8.

I'm not clear about the Portenta and the size of integers, but you may want to make the code explicit by using

uint8_t (byte)
uint16_t or int16_t (unsigned or signed 16 bit integer  instead of short)

If you are not rotating, but just looking a a fixed echo, do the data make sense for the distance?

This code may work with your LIDAR. Worth a look:

1 Like

Looks the same:

1 Like

This is my working routine for the LD06, which looks to be producing the same data stream as your D100 Lidar:

bool LIDAR_LD06::calc_lidar_data(std::vector<char> &values) {

  //--check CRC
  CS_Received = int(values[values.size() - 1]);
  CS_Calculated = CalCRC8(&*values.begin(), values.size() - 1); // uint8_t CalCRC8(uint8_t *p, uint8_t len){
  //CS_Calculated = CalCRC8(&*values.begin(), data_length - 1); // uint8_t CalCRC8(uint8_t *p, uint8_t len){
  //--20230204 testing CS_Calculated = CalCRC8(&values.front(), data_length - 1); // uint8_t CalCRC8(uint8_t *p, uint8_t len){
  if (CS_Received == CS_Calculated)
  {
    
  
    start_byte = values[0];
    data_length = 0x1F & values[1];
    Speed = float(values[3] << 8 | values[2]) / 100.0f;
  
    // MSB = values[3];
    // LSB = values[2];
    
    FSA = float(values[5] << 8 | values[4]) / 100.0f;
    LSA = float(values[values.size() - 4] << 8 | values[values.size() - 5]) / 100.0f;
    time_stamp = int(values[values.size() - 2] << 8 | values[values.size() - 3]);
    //--20230204 moved to above. Check first so we don't waste CPU. CS_Received = int(values[values.size() - 1]);

    if (LSA - FSA > 0) {
      angle_step = (LSA - FSA) / (data_length - 1);
    } else {
      angle_step = (LSA + (360 - FSA)) / (data_length - 1);
    }

    // note: Detect abnormal step size
    if (angle_step > 20) {
      return false;
    }

    // note: Initialize past data
    angles.clear();
    confidences.clear();
    distances.clear();

    for (int i = 0; i < data_length; i++) {
      float raw_deg = FSA + i * angle_step;
      angles.push_back(raw_deg <= 360 ? raw_deg : raw_deg - 360);
      confidences.push_back(values[8 + i * 3]);
      distances.push_back(int(values[8 + i * 3 - 1] << 8 | values[8 + i * 3 - 2]));
    }
    return true;
  }
  return false;
}
1 Like

@jhaytera thank you so much. I had seen that code before but could not get it compiling with Serial2. With your information that it should process the data properly I tried again. Not much luck yet with the Portenta, but with my new $15 USD XIAO esp32s3 (camera, mic, wifi, sd card, BLE) I got some data coming in.

this is also useful to see

    // note: 12 data *3 Byte
    const int DATA_BYTE = 36;
    // note: 1(Start)+2(Datalen)+2(Speed)+2(SAngle)+36(DataByte)+2(EAngle)+2(TimeStamp)+1(CRC)
    const int TOTAL_DATA_BYTE = 48;

So it is a good start.

@jhaytera
I remember the Portenta Serial1 only did 9600 bauds. When I switch the XIAO esp32s3 to 9600 bauds the data does not flow. Does anyone know if any UART on the Portenta can handle 115200. I have the breakout board and my own HD board. I know SerialUSB can handle 115200, but for debugging I really like the Serial monitor, I guess I could use the OLED for debugging but that might be a pain.

I have my Portenta H7 mounted on the Portenta Breakout Board, and have UART1/Serial1 running at 230400 baud for my LD06 Lidar.

UART SerialLIDAR_UART1(PA_9,  PA_10, NC, NC);

void LIDAR_LD06::Init(const int pin) {
  //--20230406 new below SerialLIDAR_UART0.begin(230400, SERIAL_8N1); //--20230113, SERIAL_8N1, pin);
  SerialLIDAR_UART1.begin(230400, SERIAL_8N1); //--20230113, SERIAL_8N1, pin);
  SerialLIDAR_UART1.setTimeout(10);
}

This is my messy routine, I modified from the library, to read the Lidar data. I call it every 1ms to 3ms.

bool LIDAR_LD06::read_lidar_data() {
  
  // char tmpInt;
  // Actual_Data_Bytes = 0;
  // while(SerialLIDAR.available()) {
  //   tmpInt = SerialLIDAR.read();
  //   if (tmpInt > -1){
  //     Actual_Data_Bytes++;
  //   }
  // }
  //--20230115 take out above and put back below------------------------

  //--20230406 new below if (!SerialLIDAR_UART0.available()) {
  if (!SerialLIDAR_UART1.available()) {
    return false;
  }
  Actual_Data_Bytes = 0;
  bool loopFlag = true;
  int16_t tmpInt; //--20230416 was --> char tmpInt;
  unsigned long KickOutMS = millis();

  while (loopFlag) { // && SerialLIDAR.available()) { // && iKickOut++ < 47 ) {

    //--kick out if taking too long
    if (millis() - KickOutMS > 10) { //--20230502 was 10 //--20230419 was 2
      // loopFlag = false;
      // Actual_Data_Bytes = 0;
      // tmpChars.clear();
      return false;
    }

    tmpInt = SerialLIDAR_UART1.read();
    if (tmpInt > -1){
      Actual_Data_Bytes++;
      if (tmpInt == 0x54 && tmpChars.empty()) {
        tmpChars.push_back(tmpInt);
        continue;
      } else if (tmpChars.size() >= TOTAL_DATA_BYTE ) { //--should only be '==' and never '>' but you never know.
      //--20230115 took out '- 1' } else if (tmpChars.size() >= TOTAL_DATA_BYTE - 1 ) { //--should only be '=' and never '>' but you never know.

        loopFlag = false;
        if (!calc_lidar_data(tmpChars)){
          tmpChars.clear();
          return false;
        }
        tmpChars.clear();
        //continue;
        return true;
        
      } else if (tmpChars.size() > 0) {
        if (tmpChars[0] == 0x54) { //  received 0x54, which is not the first. fine.
          tmpChars.push_back(tmpInt);
        }
        if (tmpChars.size() > 1) { // tmpChars[1] must = 0x2C 
          if (tmpChars[1] != 0x2C) {
            tmpChars.clear();
          }
        }
      }
    }else{
      //tmpChars.clear();
      return false;
    }
  }
  return false; //--20230116 was true
}

This is my version of the LIDAR_LD06.h, think I just added a couple of variables. Don't remember. Check out my code comment on the total data bytes (TOTAL_DATA_BYTE = 47;):

// #ifndef LIDAR_HEADER
// #define LIDAR_HEADER

#ifndef LIDAR_LD06_DEF
#define LIDAR_LD06_DEF

#include <Arduino.h>
#include <vector>

//#include "SRAM_Communication_Vars.h"
//InterComm local_InterComm;

class LIDAR_LD06
{
  private:
    // note: 12 data *3 Byte
    const int DATA_BYTE = 36;
    // note: 1(Start)+2(Datalen)+2(Speed)+2(SAngle)+36(DataByte)+2(EAngle)+2(TimeStamp)+1(CRC)
    const unsigned int TOTAL_DATA_BYTE = 47; //-- .pdf says (Datalen) is 1 byte, but 

    bool calc_lidar_data(std::vector<char> &values);
    void Init(const int pin);
    bool read_lidar_data();

    int internal_BPS;

  public:
    std::vector<float> angles;
    std::vector<float> distances;
    std::vector<int> confidences;

    byte start_byte;
    byte data_length;
    float Speed;
    float FSA;
    float LSA;
    int time_stamp;
    int CS_Received;
    int CS_Calculated;
    float angle_step;
    
    int Actual_Data_Bytes; //-- .pdf says (Datalen) is 1 byte, but 
    int BPS;
   

    bool setup_LIDAR_LD06();
    bool check_LIDAR_LD06(unsigned long current_MS);

};


#endif

//#endif // LIDAR_HEADER