VL53L0X Time-of-Flight (ToF) Laser Abstandssensor - [Teil 2]

Einführung

Im ersten Teil dieser Reihe haben wir den ToF-Sensor an einen ESP8266 NodeMCU D1 Mini angeschlossen und einen akustischen Abstandswarner gebaut. In diesem Beitrag möchte ich einen ESP32 NodeMCU sowie einen Arduino Nano verwenden. Ich möchte herausfinden, welche Funktionalität noch zur Verfügung steht und ob wir die Pins GPIO1 und XSHUT verwenden können. Los geht's.

Benötigte Hardware

Anzahl Bauteil Notiz
1 VL53L0X Time-of-Flight (ToF) Laser Abstandssensor
1 ESP8266 Node MCU D1 Mini
1 ESP32 NodeMCU Module oder ESP32 Dev Kit
1 Arduino Nano
1 KY-006 Passives Piezo Buzzer Alarm Modul für Arduino
Verbindungskabel
Computer mit Arduino IDE und Internetverbindung


Pins verbinden

Zuerst verbinden wir den Sensor mit einem ESP8266 NodeMCU D1 Mini wie in Teil 1 nach folgendem Schema:

VL53L0X Pins D1 Mini GPIO
VIN 3.3V
GND GND
SCL SCL (D1 / GPIO5)
SDA SDA (D2 / GPIO4)
GPIO1 -
XSHUT -
KY-006 Buzzer D1 Mini GPIO
- (Minus) GND
S (Signal) D7 (GPIO 13)

 


Die Pins GPIO1 und XSHUT lassen wir frei.

Zur Erinnerung: Für die I²C-Pins SDA und SCL ist es ratsam, einen Blick in die Übersicht der Pinouts zu werfen. Am ESP8266 sind diese Pins GPIO4 und GPIO5, die aber auf dem Board als D2 und D1 beschriftet sind. Das ist von Mikrocontroller zu Mikrocontroller unterschiedlich. Die Pinnummer 13 muss in der Arduino IDE verwendet werden. Beschriftet ist der Pin mit D7.

Im ersten Teil haben wir folgende Ergänzungen und Änderungen am Beispielcode vl53l0x vorgenommen:

#define BUZZER 13
#define FREQ 800
#define DURATION 300

Eine Variable für die Zeitmessung:

unsigned long zeitAlt = 0;

Im setup() den Buzzerpin als Ausgang initialisieren:

pinMode(BUZZER, OUTPUT);

Augabe der Zeitmessung in der loop():

if (measure.RangeStatus != 4) {  // phase failures have incorrect data
  Serial.print("Distance (mm): "); Serial.println(measure.RangeMilliMeter);  
} else {
  Serial.println(" out of range ");
}

Buzzer hinzufügen:

if (measure.RangeStatus != 4) {  // phase failures have incorrect data
  Serial.print("Distance (mm): ");
  Serial.println(measure.RangeMilliMeter);

  if (millis() - zeitAlt >= measure.RangeMilliMeter * 3) {
    zeitAlt = millis();
    tone(BUZZER, FREQ, DURATION);
  }   
} else {
  Serial.println(" out of range ");
}

Laden wir das Programm auf den Mikrocontroller, haben wir den Stand vom letzten Mal und können nun fortfahren.

Kompletter Quellcode: ToFBuzzerESP8266_Adafruit.ino

ESP32

Wir wechseln nun zum ESP32 NodeMCU Module. Dafür schließen wir den Sensor und den Buzzer wie folgt an:

VL53L0X Pins ESP32
VIN 3.3V oder 5V
GND GND
SCL GPIO22
SDA GPIO21
GPIO1 -
XSHUT -
KY-006 Buzzer ESP32
- (Minus) GND
S (Signal) GPIO13


Je nach Ausführung des Entwicklerboards kann die Anordnung der Pins variieren.


In der Arduino IDE wechseln wir unter Werkzeuge das Board auf ESP32 und laden das gleiche Programm auf den Mikrocontroller. Es wird uns eine Fehlermeldung ausgegeben, dass die Funktion tone() nicht bekannt ist. Diese ist im ESP32 Core nicht implementiert. In der Bibliotheksverwaltung gibt es einige Derivate. Ich habe bei Github die Bibliothek Tone32 gefunden. Sie funktioniert ähnlich, wie die Arduino Tone Funktion. Es gibt lediglich einen weiteren Parameter namens "Channel", den wir auf 0 Setzen. Man lädt sich den Quellcode als ZIP herunter. Anschließend wählt man in der Arduino IDE im Menü Sketch->Bibliothek einbinden->.ZIP-Bibliothek hinzufügen.

Im Quellcode unseres Programms fügen wir folgende Zeilen hinzu:

#include <Tone32.h>

#define BUZZER_CHANNEL 0

Dann ändern wir die Zeile:

tone(BUZZER, FREQ, DURATION);

in

tone(BUZZER, FREQ, DURATION, BUZZER_CHANNEL);

Damit sollte nun der Upload möglich sein und alles wieder so funktionieren, wie mit dem D1 Mini. Mir ist allerdings aufgefallen, dass die Ausgabe des Tones für Zeitverzögerungen sorgt. Da ich die Bibliothek nicht kenne, kann ich im Moment nicht genau sagen, woran es liegt. Scheinbar funktioniert die Ausgabe des Tones nicht ohne Blockieren. Es wird mit den verwendeten Timern und der erzeugten PWM zu tun haben. Es gibt noch eine Bibliothek namens ESP32Servo, die man in der Verwaltung finden kann. Dort ist ebenfalls die tone()-Funktion implementiert. Allerdings zeigt sich damit das gleiche Verhalten. Falls Sie das testen möchten, installieren Sie diese Bibliothek und ändern den Quellcode folgendermaßen:

#include <ESP32Servo.h>

Im setup():

 ESP32PWM::allocateTimer(0);
  ESP32PWM::allocateTimer(1);
  ESP32PWM::allocateTimer(2);
  ESP32PWM::allocateTimer(3);

und in der loop():

tone(BUZZER, FREQ, DURATION);

So habe ich es aus dem mitgelieferten Beispiel übernommen. Ich denke allerdings, dass diese Bibliothek etwas schwergewichtiger ist, da sie auch andere Funktionalität bietet.

Es gibt die Möglichkeit, die Tonerzeugung händisch mit PWM zu programmieren. Darauf möchte ich nicht weiter eingehen, da es in diesem Beitrag hauptsächlich um den ToF-Sensor geht.

Kompletter Quellcode: ToFBuzzerESP32_Adafruit.ino

Arduino NANO

Für einen akustischen Abstandswarner ist WLAN nicht unbedingt notwendig (das ist sonst einer der wichtigsten Gründe, den ESP zu verwenden). Daher möchte ich nun die Schaltung noch einmal mit dem Arduino NANO aufbauen.

VL53L0X Pins Arduino NANO
VIN 5V oder 3.3V
GND GND
SCL A5
SDA A4
GPIO1 -
XSHUT -
KY-006 Buzzer Arduino NANO
- (Minus) GND
S (Signal) D13

 

 


Für die Pins SDA und SCL müssen wir wieder in die Pinout-Übersicht schauen. Beim Arduino Nano (und auch UNO) sind das die Pins A4 und A5. Da wir wieder Pin 13 für den Buzzer nutzen, der auch gleichzeitig die interne LED des Nanos ansteuert, haben wir zum akustischen gleichzeitig ein optisches Signal.

Wir öffnen nun das Programm, das wir zu Beginn auf den D1 Mini geladen hatten. Das können wir genauso wiederverwenden. Wir ändern jetzt in der Arduino IDE im Menü Werkzeuge erneut das Board und wählen den Arduino Nano aus. Eventuell ist es notwendig, unter "Prozessor" auf den alten Bootloader umzustellen. In meinem Fall ist das so. Sollte es eine Fehlermeldung beim Upload geben, versuchen Sie diese Einstellung.

Führen wir das Programm aus, funktioniert der Abstandswarner wieder wie auf dem D1 Mini. Der Ton klingt etwas anders. Das liegt an der geringeren Taktfrequenz. Es fällt auf, dass es keine Verzögerung bei der Tonerzeugung gibt. Das liegt an der tone()-Funktion aus der Arduino Core Bibliothek, die wir jetzt wieder mit dem Nano verwenden können.

Wir haben nun den Sensor an allen drei Mikrocontrollern getestet. Die Adafruit-Bibliothek kann also, je nach Anwendungsfall, mit einem der Geräte verwendet werden. Sollten Sie die Daten in ein Netzwerk übertragen wollen, kommt der Arduino Nano vorläufig nicht in Frage. Ist die Verzögerung der Tonerzeugung hinderlich, lassen Sie den ESP32 außen vor.

Sie können an dieser Stelle noch selbständig die Pololu-Bibliothek mit den anderen beiden Mikrocontrollern testen. Verwenden Sie dazu den Quellcode aus Teil 1.

Kompletter Quellcode: ToFBuzzerESP8266_Adafruit.ino

Genauigkeit vs. Geschwindigkeit vs. Reichweite

In der Beschreibung des ToF-Sensors (siehe Teil 1) steht geschrieben, dass man die "Ranging profiles" umstellen kann. Die verfügbaren Modi sind High accuracy, High speed, Long range und Default. Um diese umzustellen, musste ich etwas tiefer im Quelltext der Adafruit-Bibliothek graben, da dafür kein Beispiel mitgeliefert wurde.

Dort sind die Modi in einem enum als neuer Datentyp definiert. Diese heißen wie folgt:

  • VL53L0XSENSEDEFAULT
  • VL53L0XSENSELONG_RANGE
  • VL53L0XSENSEHIGH_SPEED
  • VL53L0XSENSEHIGH_ACCURACY

In der Adafruit_VL53L0X-Klasse gibt es eine Funktion namens configSensor(), der der gewünschte Modus übergeben wird. Wir schreiben in die setup()-Funktion nach der Initialisierung des Sensors folgende Zeile:

lox.configSensor(Adafruit_VL53L0X::VL53L0X_SENSE_HIGH_ACCURACY);

Damit wird der Sensor auf den Modus "Hohe Genauigkeit" eingestellt. Lässt man diese Zeile weg, wird automatisch VL53L0XSENSEDEFAULT benutzt. Ersetzt man VL53L0XSENSEHIGHACCURACY durch VL53L0XSENSELONGRANGE, kann man Hindernisse bis zu 2 m Entfernung erkennen. Dadurch sinkt aber die Genauigkeit. Hinter diesen Modi steckt in der Bibliothek eine switch-case-Anweisung, die die Werte für Timingbudget, Pulsperiode und Signalrate einstellt.

Kompletter Quellcode: ToFBuzzerESP8266AdafruitTeil2.ino

Um die gleiche Änderung mit der Pololu-Bibliothek durchzuführen, öffnen wir den Beispiel-Sketch "Single". Dort ist die Möglichkeit implementiert, den Modus umzustellen. Wir müssen lediglich eine der Zeilen einkommentieren (ich wähle hier den Long Range Mode):

#define LONG_RANGE
//#define HIGH_SPEED
//#define HIGH_ACCURACY

Damit wir das in unseren akustischen Abstandswarner einfließen lassen können, fügen wir in das Beispiel den dazugehörigen Quelltext aus dem Continuous-Beispiel aus Teil 1 hinzu, das wir verändert hatten.

Konstanten:

#define BUZZER 13
#define FREQ 800
#define DURATION 300

Variable für die Zeitmessung: 

unsigned long zeitAlt = 0;

Im setup() diese Zeilen einfügen oder anpassen:

Serial.begin(115200);
pinMode(BUZZER, OUTPUT);
...
      Serial.println(F("Failed to detect and initialize sensor!"));
...

In der loop() die Zeitmessung einfügen:

void loop()
{
  if (sensor.timeoutOccurred()) { Serial.print(" TIMEOUT"); }
  if (sensor.readRangeSingleMillimeters() < 4000) {
    Serial.println(sensor.readRangeSingleMillimeters());
    if (millis() - zeitAlt >= sensor.readRangeSingleMillimeters() * 3) {
      zeitAlt = millis();
      tone(BUZZER, FREQ, DURATION);
    }
  }
  else {
    Serial.println(" out of range ");
  } 
}

Ich benutze an dieser Stelle wieder den ESP8266 D1 Mini und lade dort das Programm hoch. Wir können nun auch mit dieser Bibliothek Hindernisse mit einem Abstand von bis zu 2 m erkennen.

Kompletter Quellcode: ToFBuzzerESP8266PololuSingle_LongRange.ino

XSHUT Pin und mehrere Sensoren

Der Pin mit der Bezeichnung XSHUT ist dafür da, den Sensor in einen Hardware Standby Modus zu bringen. So kann man z.B. einzelne Sensoren ein- und ausschalten. So stören sie sich z.B. nicht gegenseitig (laut Hersteller werden allerdings keine Störungen auftreten, solange die Sensoren nicht direkt aufeinander gerichtet sind). Für die Zusammenarbeit der Sensoren gibt es unterschiedliche Möglichkeiten. Entweder wird das Auslesen der Messwerte synchronisiert, oder es geschieht asynchron. Außerdem kann der GPIO1 genutzt werden. So wird jeder Sensor nur dann ausgelesen, wenn er auch brauchbare Werte liefern kann.

Wichtig bei der Nutzung mehrere Sensoren ist das Einstellen der I²C-Adresse. In der Adafruit-Bibliothek werden dafür Beispiele mitgeliefert. In dem Beispiel-Sketch "vl53l0x_dual" wird gezeigt, wie man zwei Sensoren gleichzeitig verwendet. Da jeder Sensor die gleiche Standard-Adresse hat (0x29), müssen die Adressen neu vergeben werden. Diese bleiben leider nicht permanent erhalten, sondern müssen bei jedem Neustart des Mikrocontrollers neu eingestellt werden. Die Sensoren müssen mittels XSHUT nacheinander zugeschaltet und dann die jeweilige Adressen vergeben werden. Adafruit gibt dafür eine kurze Anleitung.

Für die XSHUT-Pins der Sensoren werden weitere digitale Pins am Mikrocontroller benötigt.

Ich werde an dieser Stelle nur einen Sensor per XSHUT schlafen legen. Wir erweitern unsere Schaltung am D1 Mini durch die Verbindung vom XSHUT-Pin zu Pin D6 (GPIO12).

 

Nun ergänzen wir im Programmcode für unseren akustischen Abstandswarner die Pinzuordnung und das Aufwecken. Ich nutze hier die Adafruit-Bibliothek:

Eine Konstante für die Pinnummer:

#define XSHUT 12

Variablen für Zeitmessungen und Zustand des XSHUT-Pins:

unsigned long sleepTime = 2000;
unsigned long sleepTimeAlt = 0;
bool XSHUT_STATE = HIGH;

Den Pin im setup() als Ausgang initialisieren und auf HIGH setzen:

pinMode(XSHUT, OUTPUT);
pinMode(XSHUT, OUTPUT);
digitalWrite(XSHUT, XSHUT_STATE);

Wird der Sensor gleich zu Beginn per XSHUT ausgeschaltet, wird er auch in der darauf folgenden Initialisierung nicht mehr erkannt. Geben wir dem Pin keinen eindeutigen Zustand, kann es sein, dass er sich noch im LOW-Zustand befindet, wodurch der Sensor folglich auch nicht erkannt wird.

An den Anfang der loop()-Funktion fügen wir folgende Zeilen ein:

if (millis() - sleepTimeAlt >= sleepTime) {
  sleepTimeAlt = millis();
  XSHUT_STATE = !XSHUT_STATE;
  digitalWrite(XSHUT, XSHUT_STATE);
  
  if (XSHUT_STATE == HIGH) {
    Serial.println("Sensor On");
    if (!lox.begin()) {
      Serial.println(F("Failed to boot VL53L0X"));
      while(1);
    }   
  }
  else {
    Serial.println("Sensor in Standby");
  }
}

Wenn die Zeit abgelaufen ist, wird der Zustand umgeschaltet und auf den digitalen Pin für den XSHUT ausgegeben. Wenn der Sensor aufgeweckt wird, ist es notwendig, ihn mit lox.begin() neu zu initialisieren. Laden wir das Programm auf den D1 Mini, sollte die Abstandsmessung im angegebenen Intervall ein- und ausgeschaltet werden. Der Nachteil ist in diesem Fall, dass der Sensor weiterhin den letzten Messwert liefert, nachdem er in den Standby gebracht wurde. Um es etwas sauberer darzustellen und wirklich nur dann zu messen, wenn XSHUT auf HIGH steht, passen wir den Quellcode noch einmal an und setzen die Messung des Sensors in eine if-Anweisung:

if (XSHUT_STATE) {
  Serial.print("Reading a measurement... ");
  lox.rangingTest(&measure, false); // pass in 'true' to get debug data printout!
 
  if (measure.RangeStatus != 4) {  // phase failures have incorrect data
    Serial.print("Distance (mm): ");
    Serial.println(measure.RangeMilliMeter);
 
    if (millis() - zeitAlt >= measure.RangeMilliMeter * 2) {
      zeitAlt = millis();
      tone(BUZZER, FREQ, DURATION);
    }   
  } else {
    Serial.println(" out of range ");
  }
}

Durch diese Erweiterung wird der Sensor auch nur dann verwendet, wenn der Zustand des XSHUT-Pins auf HIGH steht. Laden Sie das Programm auf den Mikrocontroller und probieren Sie es aus.

Wir haben nun den Sensor zeitabhängig ein- und ausgeschaltet. Würden wir den Abstandsensor in einem Fahrzeug verwenden, könnten wir den XSHUT-Pin über eine elektronische Schaltung z.B. mit dem Rückwärtsgang verbinden. Meistens ist dort ein Anschluss vorhanden, der dafür sorgt, dass das Rückfahrlicht eingeschaltet wird. Damit wäre es möglich, den akustischen Abstandswarner nur dann zu benutzen, wenn man rückwärts fährt.

Würde man einen Roboter konstruieren, könnte man ihn mit mehreren dieser Sensoren bestücken und abhängig von den Anforderungen nur einen Sensor, der in eine bestimmte Richtung zeigt, einschalten.

Kompletter Quellcode: ToFBuzzerESP8266AdafruitTeil2_XSHUT.ino

Möchten Sie statt der Adafruit-Bibliothek die Pololu-Bibliothek verwenden, sieht das Programm sehr ähnlich aus. Wir hatten unseren akustischen Abstandswarner aus dem Beispiel "Single" entwickelt. Das ergänzen wir durch die gleichen Variablen und Einstellungen. Der einzige Unterschied sind die Namen der Funktionsaufrufe. Um den Sensor wieder aufzuwecken, wird dann statt lox.begin() die Funktion sensor.init() aufgerufen, genau wie in der setup()-Funktion.

Kompletter Quellcode: ToFBuzzerESP8266PololuSingleLongRangeXSHUT.ino

GPIO1 und Interrupts

Zum Schluss möchte ich noch auf den GPIO1-Pin an dem ToF-Sensor eingehen. Dieser digitale Pin wird dann geschaltet, wenn sich ein Hindernis innerhalb einer festgelegten Distanz zum Sensor befindet. So die Theorie. Leider ist das nicht gut dokumentiert, auch wenn es von STM in der API implementiert worden ist. Für die Arduino IDE wurde das bisher nicht ausführlich umgesetzt. Wir wollen zuerst einmal die Schaltung erweitern und den Pin verbinden:

 

 

GPIO1 des ToF-Sensors verbinden wir mit D5 (In der Arduino IDE also Pin 14). Ab diesem Punkt habe ich wirklich lange und intensiv recherchiert, wie man den GPIO1 in Verbindung mit einem Interrupt benutzen kann. Kris Winer hat die Pololu-Bibliothek verwendet und bei Github für den ESP32 ein Arduino-Programm geschrieben. Das habe ich etwas abgeändert, damit das mit dem ESP8266 läuft:

Der LED-Pin geändert von 5 auf 2:

int myLed = 2;

Die I²C-Initialisierung ohne Parameter:

Wire.begin();

Die Variable in der Interrupt Service Routine muss volatile deklariert sein:

volatile bool newData = false;

Die Interrupt Service Routine muss auf dem ESP8266 in den IRAM geladen werden. Dafür ergänzen wir folgendes:

ICACHE_RAM_ATTR void myinthandler()
{
  newData = true; // set the new data ready flag to true on interrupt
}

Laden wir das Programm auf den D1 Mini, sollte im Seriellen Monitor die Abstandsmessung zu sehen sein. Ziehen wir den Pin D5 ab, wird die Messung unterbrochen. Der Interrupt funktioniert also. Leider ist keine Möglichkeit implementiert, die Schwellwerte einzutragen, ab welcher Distanz der Interrupt ausgelöst werden soll. Somit werden immer Messdaten erkannt und folglich auch immer der Interrupt ausgelöst. Dadurch funktioniert es eigentlich genauso wie das "Continuous"-Beispiel ohne Interruptfunktionalität. Wirklich brauchbar ist das also nicht.

Kompletter Quellcode: ToFESP8266Interrupt_Pololu.ino

Mit der Adafruit-Bibliothek (Stand Version 1.1.0) wird ein Beispiel namens vl53l0xmultiextended mitgeliefert, in dem ein asynchroner Modus implementiert ist, der den GPIO1 nutzt. Die Funktion heißt timedasyncread_gpio(). Daraus kann man ein ähnliches Programm herleiten, wie das vorangegangene mit der Pololu-Bibliothek. Die Funktionalität ist die gleiche. Leider fehlt auch dort die Möglichkeit, Schwellwerte einzutragen.

Auf Github gibt es eine alte Version der Adafruit-Bibliothek, die auch ein Interrupt-Beispiel enthält. In dem Beispiel werden Funktionen aufgerufen, die es in der aktuellen Version nicht gibt. Daher habe ich diese alte Version installiert und dann das Beispiel auf den D1 Mini geladen. Bereits während des Kompilierens treten viele Warnings auf, was kein gutes Zeichen ist. Das Programm selbst läuft dann auch nicht auf dem Mikrocontroller. Es wäre eventuell möglich, die aktuelle Version der Bibliothek umzuschreiben und die Funktionen, die in der alten Version vorhanden sind, dorthin zu übertragen. Das ist dann aber nicht mehr trivial. Daher beende ich meine Tests an dieser Stelle.

Fazit

Ich habe sehr viel recherchiert und getestet. Adafruit und Pololu liefern Bibliotheken, um den Sensor auf Basis der API von STMicroelectronics in der Arduino IDE verwenden zu können. Interrupts sind auf einem Arduino normalerweise nicht zwingend notwendig. Da die ESPs aber einen Tiefschlaf unterstützen, wäre ein Interrupt sinnvoll, der den Mikrocontroller aus einem Deep Sleep aufwecken könnte.

Die verschiedenen Mikrocontroller mit den beiden Bibliotheken so zu verwenden, hat funktioniert. Wir konnten auch die Modi für die "Ranging profiles" einstellen.

Aufgrund der möglichen gegenseitigen Störungen der akustischen Abstandssensoren ist der VL53L0X Time of Flight Sensor auch eine sehr gute Alternative für die Hinderniserkennung unserer Robot Cars.

 

Andreas Wolter

für AZ-Delivery Blog

 

Esp-32Esp-8266Für arduinoProduktvorstellungenProjekte für anfängerSensoren

1 Kommentar

Walter

Walter

Hallo Andreas,
vielen Dank für deinen Bericht und deine Mühe.
Konstet Du ermitteln welchen Öffnungswinkel der Sensor besitzt?
Also z.b. bei einer Entfernung von 100 cm muss die Refektionsfläche (bei 90 grad) im minimum …? mm² betragen und was ist wenn sich der Winkel ändert?

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert