Lecture valeurs LIDAR LDS02RR avec arduino et affichage avec processing
Description :
Ce projet consiste à acquérir et visualiser les données d’un capteur LIDAR LDS02RR à l’aide d’une carte Arduino et du logiciel Processing.
Le capteur LIDAR LDS02RR permet de mesurer des distances précises grâce à un faisceau laser tournant, offrant une cartographie 2D de l’environnement.
L’Arduino est utilisé pour :
- Lire les données série transmises par le LIDAR (angles, distances, intensités).
 - Traiter et formater ces informations pour les envoyer via le port série USB vers l’ordinateur.
 
Processing sert à :
Afficher en temps réel une visualisation graphique des mesures :
chaque point représente un obstacle ou une surface détectée à une distance donnée selon son angle.
Recevoir et interpréter les données envoyées par l’Arduino.
Prérequis :
- 1 x Carte Arduino Nano
 - 1 x Module IRF520
 - 1 x Capteur laser LiDAR LDS02RR
 - 1 x Breadboard
 
Version IDE :
- Arduino IDE 2.3.5
 
Vidéo de démonstration :
Schéma de câblage :


Code :
Code Arduino :
#include <SoftwareSerial.h>
#define RX_PIN 10            // Broche RX du radar
#define MOTOR_PIN 5          // Numéro de broche du contrôleur moteur sur Arduino Uno
#define VitesseMOTOR 230     // Réglage de la vitesse moteur 0-255
#define NB_DATA 4            // Mesure 1, Mesure 2, Mesure 3, Mesure 4
#define DATA_SIZE 5          // angle, speed, distance, invalid, strengh
int Framedata[NB_DATA][DATA_SIZE];
#define PACKET_SIZE 22
uint8_t Trame[PACKET_SIZE];  // Buffer trame (22 octets)
unsigned int packetIndex = 0;// Packet index
bool waitPacket = true;       
bool debug = 0;              //Active donnée pour la console si debug = 1
int ActivErreurlecture = 0;  //Filtre les données erronées si ActivErreurlecture = 0
int Erreurlecture;
int ErreurlectureDist1;
int ErreurlectureDist2;
int ErreurlectureDist3;
int ErreurlectureDist4;
SoftwareSerial lidarSerial = SoftwareSerial(RX_PIN, 3);
void setup() {
  lidarSerial.begin(115200);
  Serial.begin(115200);
  pinMode(RX_PIN, INPUT);
  analogWrite(MOTOR_PIN, 255);  
  // Initialiser le tampon de paquets
  for (int i = 0; i < PACKET_SIZE; i++) Trame[i] = 0;  
}
void loop() {
  analogWrite(MOTOR_PIN, VitesseMOTOR);  // Envoyer un signal PWM au contrôleur de moteur
  if (lidarSerial.available() > 0) {
    uint8_t BufferByte = lidarSerial.read();
    if (waitPacket) {  
      if (BufferByte == 0xFA) {
        Erreurlecture = 0;
        ErreurlectureDist1 = 0;
        ErreurlectureDist2 = 0;
        ErreurlectureDist3 = 0;
        ErreurlectureDist4 = 0;
        packetIndex = 0;  
        waitPacket = false;
        Trame[packetIndex++] = BufferByte;
      }
    } else {                                
      if (Trame[0] == 0xFA) { 
        Trame[packetIndex++] = BufferByte;  
        if (packetIndex >= PACKET_SIZE) {   
          uint16_t calc = Checksum(Trame);
          uint16_t recv = Trame[20] | (Trame[21] << 8);
          if (calc != recv) {
            //Serial.println("checksum invalide");
            Erreurlecture = 1;
          } else {
            //Serial.print("valide");
          }
          waitPacket = true;                 
          decodePacket(Trame, PACKET_SIZE); 
          if (debug == 0 && Erreurlecture == 0) {
            if (ErreurlectureDist1 == 0) sendData1();
            if (ErreurlectureDist2 == 0) sendData2();
            if (ErreurlectureDist3 == 0) sendData3();
            if (ErreurlectureDist4 == 0) sendData4();
          }
          if (debug == 1) DebugData(Trame, PACKET_SIZE);
        }
      }
    }
  }
}
void decodePacket(uint8_t packet[], int packetSize) {
  //Remise à zero des Frame datas
  for (int i = 0; i < NB_DATA; i++) {
    for (int b = 0; b < DATA_SIZE; b++) {
      Framedata[i][b] = 0;
    }
  }
 
  for (int i = 0; i < packetSize; i++) {
    int test = i;
    switch (test) {
      case 1:
        //Angle
        Framedata[0][0] = (Trame[1] - 0xA0) * 4;        // Convertion de la valeur 0 ~ 360°
        Framedata[1][0] = ((Trame[1] - 0xA0) * 4) + 1;  // Convertion de la valeur 0 ~ 360°
        Framedata[2][0] = ((Trame[1] - 0xA0) * 4) + 2;  // Convertion de la valeur 0 ~ 360°
        Framedata[3][0] = ((Trame[1] - 0xA0) * 4) + 3;  // Convertion de la valeur 0 ~ 360°
        //Filtre Angle non valide
        if (ActivErreurlecture == 0 && (Framedata[0][0] > 360 || Framedata[0][0] < 0)) {
          Erreurlecture = 1;
        }
        break;
      case 2:
        //Vitesse
        Framedata[0][1] = ((Trame[3] << 8) | Trame[2]) / 64.f;
        Framedata[1][1] = ((Trame[3] << 8) | Trame[2]) / 64.f;
        Framedata[2][1] = ((Trame[3] << 8) | Trame[2]) / 64.f;
        Framedata[3][1] = ((Trame[3] << 8) | Trame[2]) / 64.f;
        //Filtre Vitesse non valide
        if (ActivErreurlecture == 0 && (Framedata[0][1] < 50)) {
          Erreurlecture = 1;
        }
        break;
      case 4:
        //Distance 1
        Framedata[0][2] |= ((Trame[4 + 1] & 0x3F) << 8) | Trame[4];
        //Filtre Distance non valide
        if (ActivErreurlecture == 0 && (Framedata[0][2] < 150 || Framedata[0][2] > 3000)) {
          ErreurlectureDist1 = 1;
        }
        break;
      case 5:
        //Ce champ indique si la mesure est invalide(invalid)
        //Si ce bit est 1 → mesure invalide (le capteur n’a pas pu renvoyer de distance correcte).
        //Si 0 → mesure valide (distance correcte en mm).
        Framedata[0][3] = (Trame[4 + 1] & 0x80) != 0;
        //Filtre mesure invalide
        if (ActivErreurlecture == 0 && (Framedata[0][3] == 1)) {
          ErreurlectureDist1 = 1;
        }
        break;
      case 6:
        //Force du signal réfléchi (strengh)
        //Faible valeur (≈0–100) → retour faible → réflexion mauvaise ou distance longue.
        //Grande valeur (>1000) → retour fort → bonne surface réfléchissante, distance fiable.
        Framedata[0][4] = (Trame[4 + 2]) | ((Trame[4 + 3]) << 8);
        //Filtre Force du signal
        if (ActivErreurlecture == 0 && (Framedata[0][4] < 100)) {
          ErreurlectureDist1 = 1;
        }
        break;
      case 8:
        //Distance 2
        Framedata[1][2] |= ((Trame[8 + 1] & 0x3F) << 8) | Trame[8];
        if (ActivErreurlecture == 0 && (Framedata[1][2] < 150 || Framedata[1][2] > 3000)) {
          ErreurlectureDist2 = 1;
        }
        break;
      case 9:
        //invalid
        Framedata[1][3] = (Trame[8 + 1] & 0x80) != 0;
        if (ActivErreurlecture == 0 && (Framedata[1][3] == 1)) {
          ErreurlectureDist2 = 1;
        }
        break;
      case 10:
        //strengh
        Framedata[1][4] = (Trame[8 + 2]) | ((Trame[8 + 3]) << 8);
        if (ActivErreurlecture == 0 && (Framedata[1][4] < 100)) {
          ErreurlectureDist2 = 1;
        }
        break;
      case 12:
        //Distance 3
        Framedata[2][2] |= ((Trame[12 + 1] & 0x3F) << 8) | Trame[12];
        if (ActivErreurlecture == 0 && (Framedata[2][2] < 150 || Framedata[2][2] > 3000)) {
          ErreurlectureDist3 = 1;
        }
        break;
      case 13:
        //invalid
        Framedata[2][3] = (Trame[12 + 1] & 0x80) != 0;
        if (ActivErreurlecture == 0 && (Framedata[2][3] == 1)) {
          ErreurlectureDist3 = 1;
        }
        break;
      case 14:
        //strengh
        Framedata[2][4] = (Trame[12 + 2]) | ((Trame[12 + 3]) << 8);
        if (ActivErreurlecture == 0 && (Framedata[2][4] < 100)) {
          ErreurlectureDist3 = 1;
        }
        break;
      case 16:
        //Distance 4
        Framedata[3][2] |= ((Trame[16 + 1] & 0x3F) << 8) | Trame[16];
        if (ActivErreurlecture == 0 && (Framedata[3][2] < 150 || Framedata[3][2] > 3000)) {
          ErreurlectureDist4 = 1;
        }
        break;
      case 17:
        //invalid
        Framedata[3][3] = (Trame[16 + 1] & 0x80) != 0;
        if (ActivErreurlecture == 0 && (Framedata[3][3] == 1)) {
          ErreurlectureDist4 = 1;
        }
        break;
      case 18:
        //strengh
        Framedata[3][4] = (Trame[16 + 2]) | ((Trame[16 + 3]) << 8);
        if (ActivErreurlecture == 0 && (Framedata[3][4] < 100)) {
          ErreurlectureDist4 = 1;
        }
        break;
      default:
        break;
    }
  }
}
int sendData1() {
  //[angle1, distance 1,vitesse 1, invalid1, strengh1]
  Serial.print(Framedata[0][0]);
  Serial.print(",");
  Serial.print(Framedata[0][2]);
  Serial.print(",");
  Serial.println(Framedata[0][1]);
}
int sendData2() {
  //[angle2, distance 2,vitesse 2, invalid2, strengh2]
  Serial.print(Framedata[1][0]);
  Serial.print(",");
  Serial.print(Framedata[1][2]);
  Serial.print(",");
  Serial.println(Framedata[1][1]);
}
int sendData3() {
  //[angle3, distance 3,vitesse 3, invalid3, strengh3]
  Serial.print(Framedata[2][0]);
  Serial.print(",");
  Serial.print(Framedata[2][2]);
  Serial.print(",");
  Serial.println(Framedata[2][1]);
}
int sendData4() {
  //[angle4, distance 4,vitesse 4, invalid4, strengh4]
  Serial.print(Framedata[3][0]);
  Serial.print(",");
  Serial.print(Framedata[3][2]);
  Serial.print(",");
  Serial.println(Framedata[3][1]);
}
int DebugData(uint8_t packet[], int packetSize) {
  for (int i = 0; i < packetSize; i++) {
    Serial.print("0x");
    Serial.print(packet[i]);
    Serial.print('\t');
  }
  Serial.println("");
  for (int i = 0; i < packetSize; i++) {
    Serial.print(packet[i], HEX);
    Serial.print('\t');
  }
  Serial.println("");
  for (int i = 0; i < NB_DATA; i++) {
    Serial.print("Angle ");
    Serial.print(i + 1);
    Serial.print(" : ");
    Serial.print(Framedata[i][0]);
    Serial.print('\t');
    Serial.print("Dist ");
    Serial.print(i + 1);
    Serial.print(" : ");
    Serial.print(Framedata[i][2]);
    Serial.print('\t');
    Serial.print("RPM ");
    Serial.print(i + 1);
    Serial.print(" : ");
    Serial.print(Framedata[i][1]);
    Serial.print('\t');
    Serial.print("Invalid ");
    Serial.print(i + 1);
    Serial.print(" : ");
    Serial.print(Framedata[i][3]);
    Serial.print('\t');
    Serial.print("Strength ");
    Serial.print(i + 1);
    Serial.print(" : ");
    Serial.print(Framedata[i][4]);
    Serial.println('\t');
  }
}
uint16_t Checksum(const uint8_t *data20) {
  uint16_t words[10];
  for (int i = 0; i < 10; i++) {
    words[i] = data20[2 * i] | (data20[2 * i + 1] << 8);
  }
  uint32_t chk32 = 0;
  for (int i = 0; i < 10; i++) {
    chk32 = (chk32 << 1) + words[i];
    chk32 &= 0xFFFFFFFF;
  }
  uint16_t checksum = ((chk32 & 0x7FFF) + (chk32 >> 15)) & 0x7FFF;
  return checksum;
}
Code Processing :
import processing.serial.*;
import java.util.ArrayList;
// Classe pour stocker l'angle, la distance et le temps de vie de chaque point
class ScanPoint {
  float angle;
  float distance;
  int lifetime; // Temps de vie restant (en frames)
  ScanPoint(float a, float d, int lt) {
    angle = a;
    distance = d;
    lifetime = lt;
  }
}
// L'objet Serial pour la communication
Serial myPort;
String data = "";
// Variables pour stocker les données lues
float currentAngle = 0;
float currentDistance = 0;
float currentSpeed = 0; 
// Constantes pour les limites de distance (en mm)
final float MIN_DIST = 10; // 10 mm (Distance minimale mesurable)
final float MAX_DIST = 2000; // 1000 mm (Distance maximale mesurable)
// Rayon maximal pour l'affichage (en pixels)
final float MAX_DRAW_RADIUS = 300; 
// Centre du radar
float centerX, centerY;
// Liste pour stocker tous les points scannés avec leur temps de vie
ArrayList<ScanPoint> scanHistory = new ArrayList<ScanPoint>();
// Les points vivent pendant 100 frames (pour un effacement plus long)
final int POINT_LIFETIME = 100; 
// Opacité du fondu d'arrière-plan (plus petit = plus de rémanence)
final int FADE_OPACITY = 10; 
void setup() {
  size(1050, 700); 
  smooth(); 
  frameRate(60); 
  
  // Le centre est décalé vers la gauche
  // Ajustement de centerX pour laisser de la place aux étiquettes d'angle
  centerX = (height - 50) / 2 + 30; 
  centerY = height / 2;
  println(Serial.list());
  
  // --- CONFIGURATION DU PORT SÉRIE ---
  try {
    myPort = new Serial(this, "COM4", 115200); 
    myPort.bufferUntil('\n'); 
  } catch (Exception e) {
    println("Erreur d'ouverture du port série: " + e.getMessage());
    println("Vérifiez le nom du port et s'il est déjà utilisé.");
  }
  background(0); // Fond noir initial
}
void draw() {
  // 1. Mise à jour de l'arrière-plan (effet de rémanence)
  fill(0, FADE_OPACITY); 
  rect(0, 0, width, height);
  pushMatrix();
  translate(centerX, centerY);
  // 2. Dessin de l'échelle du radar (lignes de grille)
  noFill();
  stroke(50, 200, 50, 150); // Vert foncé pour la grille
  strokeWeight(1);
  
  // Cercles de distance
  ellipse(0, 0, MAX_DRAW_RADIUS * 2, MAX_DRAW_RADIUS * 2); 
  float halfRadius = map(500, MIN_DIST, MAX_DIST, 0, MAX_DRAW_RADIUS);
  ellipse(0, 0, halfRadius * 2, halfRadius * 2);
  float minRadius = map(MIN_DIST, MIN_DIST, MAX_DIST, 0, MAX_DRAW_RADIUS);
  ellipse(0, 0, minRadius * 2, minRadius * 2); 
  
  // Étiquettes de distance
  fill(50, 200, 50);
  textSize(12);
  textAlign(CENTER);
  text("1000 mm", MAX_DRAW_RADIUS - 30, -5);
  text("500 mm", halfRadius - 20, -5);
  text("10 mm", minRadius - 15, -5);
  
  // Lignes d'angle (tous les 45 degrés)
  stroke(50, 200, 50, 100);
  for (int a = 0; a < 360; a += 45) {
    float x_line = MAX_DRAW_RADIUS * cos(radians(a));
    float y_line = MAX_DRAW_RADIUS * sin(radians(a));
    line(0, 0, x_line, y_line);
  }
  
  // MISE À JOUR : AFFICHAGE DE L'ÉCHELLE DES ANGLES (de 0° à 360° complet)
  float angleTextRadius = MAX_DRAW_RADIUS + 25; // Rayon légèrement plus grand pour le texte
  textSize(14);
  fill(0, 200, 0); // Vert pour les étiquettes d'angle
  // Boucle de 0 à 359 par pas de 30 degrés. 
  // Remarque : 360° est la même position que 0°, on affiche donc 0° une seule fois.
  for (int a = 0; a < 360; a += 30) { 
    // Utiliser 0° pour l'affichage à la place de 360° pour la lisibilité
    int displayAngle = a == 360 ? 0 : a;
    
    // Calculer la position (x, y) pour le texte sur le cercle extérieur
    float x_text = angleTextRadius * cos(radians(a));
    float y_text = angleTextRadius * sin(radians(a));
    
    // Ajuster l'alignement pour que le texte soit centré sur le point
    if (a == 0) textAlign(LEFT); // 0°/360° est à droite, on décale le texte à gauche du point
    else if (a == 180) textAlign(RIGHT); // 180° est à gauche, on décale le texte à droite du point
    else textAlign(CENTER); // Ailleurs : centrer
    
    // Afficher l'angle
    text(displayAngle + "°", x_text, y_text);
  }
  // 3. --- DESSIN ET MISE À JOUR DE L'HISTORIQUE DES POINTS ---
  
  ArrayList<ScanPoint> updatedHistory = new ArrayList<ScanPoint>();
  for (int i = 0; i < scanHistory.size(); i++) {
    ScanPoint p = scanHistory.get(i);
    
    // Calculer la position et la transparence
    float drawRadius = map(p.distance, MIN_DIST, MAX_DIST, 0, MAX_DRAW_RADIUS);
    float angleInRadians = radians(p.angle);
    float x = drawRadius * cos(angleInRadians);
    float y = drawRadius * sin(angleInRadians);
    
    // Transparence basée sur le temps de vie (alpha max 255)
    float alpha = map(p.lifetime, 0, POINT_LIFETIME, 0, 255);
    
    // Dessiner le point
    stroke(255, 0, 0, alpha); // Rouge, avec transparence
    strokeWeight(3);
    point(x, y);
    // Mettre à jour le temps de vie
    p.lifetime--;
    
    // Garder le point s'il a encore du temps de vie
    if (p.lifetime > 0) {
      updatedHistory.add(p);
    }
  }
  
  scanHistory = updatedHistory; // Mettre à jour l'historique
  // 4. Dessin de la ligne de balayage actuelle (vert vif)
  float currentDrawRadius = map(currentDistance, MIN_DIST, MAX_DIST, 0, MAX_DRAW_RADIUS);
  float currentAngleInRadians = radians(currentAngle); 
  float currentX = currentDrawRadius * cos(currentAngleInRadians);
  float currentY = currentDrawRadius * sin(currentAngleInRadians);
  
  stroke(0, 255, 0, 255); // Vert vif (totalement opaque)
  strokeWeight(2);
  line(0, 0, currentX, currentY);
  popMatrix();
  
  // 5. --- AFFICHAGE DE LA LÉGENDE ET DES VALEURS ---
  
  float legendX = 2 * centerX + 10; 
  float legendY = 50;
  
  fill(255);
  textSize(24);
  textAlign(LEFT);
  text("LÉGENDE ET DONNÉES", legendX, legendY);
  
  // Section des données actuelles
  textSize(16);
  text("Données Actuelles:", legendX, legendY + 40);
  fill(0, 255, 0); // Vert pour les données en direct
  text("Angle (θ): " + nf(currentAngle, 0, 2) + "°", legendX, legendY + 65);
  text("Distance (D): " + nf(currentDistance, 0, 2) + " mm", legendX, legendY + 90);
  text("Vitesse (V): " + nf(currentSpeed, 0, 2) + " m/s", legendX, legendY + 115);
  
  // Séparateur 
  stroke(255);
  line(legendX, legendY + 135, legendX + 250, legendY + 135);
  
  // Section de la Légende Graphique 
  fill(255);
  text("Légende Graphique:", legendX, legendY + 165);
  
  // Ligne de balayage actuelle
  stroke(0, 255, 0, 255);
  strokeWeight(2);
  line(legendX, legendY + 190, legendX + 20, legendY + 190);
  fill(255);
  text("Balayage/Mesure Actuelle", legendX + 30, legendY + 195);
  
  // Point historique
  stroke(255, 0, 0, 255);
  strokeWeight(3);
  point(legendX + 10, legendY + 225);
  fill(255);
  text("Point Historique (s'estompe)", legendX + 30, legendY + 230);
  // Échelle de distance
  stroke(50, 200, 50, 150);
  strokeWeight(1);
  noFill();
  ellipse(legendX + 10, legendY + 260, 20, 20);
  fill(255);
  text("Grille de Distance (10-1000 mm)", legendX + 30, legendY + 265);
}
void serialEvent(Serial p) {
  // Lecture et parsing des données 
  try {
    data = p.readStringUntil('\n'); 
  } catch (Exception e) {
    return;
  }
  
  if (data != null) {
    data = trim(data); 
    if (data.contains(",")) {
      String[] values = split(data, ',');
      
      if (values.length == 3) {
        try {
          currentAngle = float(values[0]);
          currentDistance = float(values[1]);
          currentSpeed = float(values[2]); 
          
          currentAngle = currentAngle % 360;
          if (currentAngle < 0) {
            currentAngle += 360;
          }
          currentDistance = constrain(currentDistance, MIN_DIST, MAX_DIST);
          
          // Ajouter le point à l'historique
          ScanPoint newPoint = new ScanPoint(currentAngle, currentDistance, POINT_LIFETIME);
          scanHistory.add(newPoint);
          
        } catch (NumberFormatException e) {
          println("Erreur de format de nombre: " + data);
        }
      } 
    }
  }
}
