[solved] VL53L0X distance meter does not beep faster/slower in linear fashion

Hej,

using the code below, the buzzer does beep in slower and faster intervals as the distance increases or decreases - but it does so in audibly discrete levels, as if there were "beep speed levels" every few centimetres, while I had intended a continuous acceleration and deceleration of beeps according to the distance measured. Later, the distance shall also be mapped to an LED strip, and that should also happen without the apparent discretisation.

How can I remove the discretisation of beeps?

Many thanks in advance!

#include <Wire.h>
#include <VL53L0X.h> // Pololu library https://github.com/pololu/vl53l0x-arduino

const int buzzerPin = 8;

unsigned long previousMillis = 0; // Store the millisecond value after each time the buzzer was triggered

int maxBeepInterval = 1000; // Longest beep interval
int minBeepInterval = 50; // Shortest beep interval; cannot be shorter than sensing interval?!
int frequency = 440; // Buzzer frequency in Hz

long distance; // Distance from sensor
int triggerDistance = 1500; // Buzzer beeps if the measured distance in millimetres is less

VL53L0X sensor;

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

  sensor.init();
  sensor.setTimeout(0); // Set sensor timeout in milliseconds (0 = no timeout)
  sensor.startContinuous(50); // Sensing interval
}

void loop() {
  long distance = sensor.readRangeContinuousMillimeters();

  if (distance < triggerDistance) { // Check if the distance is within range
    unsigned long currentMillis = millis(); // Store the current time

    // Beep interval will shorten as the measured distance decreases; from 1 beep per second (maxBeepInterval) to 1 beep every 20 milliseconds (minBeepInterval)
    // Map the distance value (which ranges from 30 millimetres to triggerDistance 1500 millimetres) from minBeepInterval to maxBeepInterval

    int val = map(distance, 30, triggerDistance, minBeepInterval, maxBeepInterval);

    if (currentMillis - previousMillis >= val) {
      tone(buzzerPin, frequency, val * 0.5);  //Trigger the buzzer (buzzerPin, frequency, duration); here each beep's duration is set to 50% of the time before it will be triggered again
      Serial.println(distance);
      previousMillis = currentMillis; // Save current time when the buzzer was triggered
    }
  }
}

How can I remove the discretisation of beeps?

How many discrete steps do you hear?

Did you check the sensor output? Does that have the same discrete steps?

Thanks, I adjusted the range for beeps between 30 and 1000 millimetres and, trying to move the target (matt light grey cardboard) as slow as possible, have the impression of 7 discrete "zones" that get larger as the distance increases.

The serial monitor does not show odd jumps, but also seems to display the distance in a ever faster fashion, as the distance decreases, although I've set the sensing interval to 100.

I have removed the serial monitor printing to see if it makes a difference, but it does not.

Lagom:
using the code below, the buzzer does beep in slower and faster intervals as the distance increases or decreases - but it does so in audibly discrete levels, as if there were "beep speed levels" every few centimetres, while I had intended a continuous acceleration and deceleration of beeps according to the distance measured.

Interesting. I encountered the same phenomenon using the similar VL6180X in my project.

dougp:
I encountered the same phenomenon using the similar VL6180X in my project.

Did you manage to rid your project of this "effect" or at least find what may be the reason? I am too much of a beginner to judge whether it's my sloppy coding or something else.

Lagom:
Did you manage to rid your project of this "effect" or at least find what may be the reason?

No, I just decided to live with it. Although, I have a niggling feeling that tone() is somehow to blame.

dougp:
No, I just decided to live with it. Although, I have a niggling feeling that tone() is somehow to blame.

With or without tone, the basic serial monitor output seems smooth to me.

Maybe I save that together with a time stamp and then try to move the target as evenly as possible to then see in Excel, if there are discrete plateaux or jumps.

Although, I have a niggling feeling that tone() is somehow to blame.

Does that mean the discrete levels are just in the length of the tone, not in the pause between the tones?

pylon:
Does that mean the discrete levels are just in the length of the tone, not in the pause between the tones?

I just mean tone is a common feature between OP's issue and my own experience. I did no testing to find the cause. I could be completely off base.

A little bit of progress... even at a fixed distance to an object, the distance measured with the VL53L0X fluctuates rather much, (I would have expected the sensor to be better), introducing a weighted average filter takes some care of the fluctuations.

77 mm measured manually

55 mm shiny aluminium (extrusion)
67 mm coated/lacquered paper (Beginning C for Arduino book cover)
68 mm satin stainless steel (Mitutoyo slide rule)
71 mm anodised aluminium (iPad back)
77 mm near black matt rubber (iPad cover)
79 mm rough plank o’ maple
86 mm human skin

These measurements are repeatable after resetting the Metro Mini or while interchanging the objects.

Is this to be expected? Are there some settings in the Pololu library or other tricks how one can get something far more predictable? In STMicroelectronics PDF from 2017 on page 4 it says that reflectance of object is not an issue and everything is just so wonderful : (

Back on topic, I meanwhile read that tone() uses its own timer and should not interfere with the rest of the code. Well, now back to the plateauing buzz issue then...

/******* VL53L0X distance meter (with OLED display and piezoelectronic buzzer) *******/

/******* LIBRARIES *******/

#include <Wire.h>
#include <VL53L0X.h> // Pololu library https://github.com/pololu/vl53l0x-arduino
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

/******* FUNCTION PROTOTYPES *******/

int ReadSensorVL53L0X();
float WeightedAverageFilter(float incomingValue, float lastValue);
void WriteToDisplaySSD1306 (int distance);
void EmitTimedBuzz (int distance);

/******* SENSOR *******/

VL53L0X sensor;

#define HIGH_ACCURACY
#define RANGEMIN 30
#define RANGEMAX 1200
#define VL53L0X_CORR 0 // Correction factor of 15 mm from table surface
#define WAF_WEIGHT 0.2 // Factor for the weighted average filter; higher = less smoothing

float lastDistance = 0;
float currentRange = RANGEMAX - RANGEMIN;

/******* DISPLAY *******/

#define OLED_RESET 4

Adafruit_SSD1306 display(OLED_RESET);

/******* BUZZER *******/

#define BUZZER_PIN 8 // Buzzer pin on Adafruit Metro Mini
#define BUZZER_FREQ 784 // Buzzer frequency in Hz (g''/G5) https://en.wikipedia.org/wiki/Piano_key_frequencies
#define BUZZMAX 1000 // Maximum duration of tone()

unsigned long buzzTimer;

/******* FUNCTIONS *******/

void setup() {
  Wire.begin();

  Serial.begin(9600);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3D); // Default I2C address (for 0x3C connect SA0 pin to GND)
  display.setRotation(2); // Rotates display 180°
  display.clearDisplay();

  sensor.init();
  sensor.setTimeout(0); // Set sensor timeout in milliseconds (0 = no timeout)
  sensor.startContinuous(100); // Sensing interval four times per second
  sensor.setMeasurementTimingBudget(100000); // For HIGH_ACCURACY mode (default = 33)

  buzzTimer = millis(); // Start buzz timer
}

void loop() {
  int distance = ReadSensorVL53L0X();
  float currentDistance = WeightedAverageFilter(distance, lastDistance);
  lastDistance = currentDistance;
  if (currentDistance > RANGEMIN && currentDistance < RANGEMAX) {
    WriteToDisplaySSD1306 (currentDistance);
    EmitTimedBuzz (currentDistance);
  }
  else {
    Serial.println("Out of range!");
    display.display(); // Tell display to display nothing
    display.clearDisplay();
  }
}

void EmitTimedBuzz (int distance) {
  float distancePercentage = distance / currentRange * 100;
  float buzzLength = BUZZMAX / 100 * distancePercentage;

  if ((millis() - buzzTimer) > buzzLength) {
    buzzTimer += buzzLength; // Reset timer by moving it along to the next interval
    tone(BUZZER_PIN, BUZZER_FREQ, 100);
  }
}

int ReadSensorVL53L0X() {
  int reading = sensor.readRangeContinuousMillimeters();
  reading = reading + VL53L0X_CORR;
  return reading;
}

float WeightedAverageFilter(float incomingValue, float lastValue) { // See https://www.tigoe.com/pcomp/code/arduinowiring/37/
  float result = WAF_WEIGHT * incomingValue + (1.0 - WAF_WEIGHT) * lastValue;
  return result;
}

void WriteToDisplaySSD1306 (int distance) {
  display.setCursor(8, 20); // x,y co-ordinates
  display.setTextSize(3);
  display.setTextColor(WHITE); // Monochrome, BLACK would erase
  display.println(distance); // Reading from sensor
  display.setCursor(86, 20);
  display.setTextSize(3);
  display.println("mm");
  display.display(); // Tell display to display everything
  display.clearDisplay(); // Clears the display
}

According to the Pololu product description below I would have expected about that behavior.

The sensor can report distances of up to 2 m (6.6 ft) with 1 mm resolution, but its effective range and accuracy (noise) depend heavily on ambient conditions and target characteristics like reflectance and size, as well as the sensor configuration. (The sensor’s accuracy is specified to range from ±3% at best to over ±10% in less optimal conditions.)

If you have ever shined a flashlight through your hand you will know that skin is translucent and therefore some of the photons reflected back to the sensor will be from several mm below the surface of the skin. Perhaps it errs on the side of longest travel time rather than shortest.

Wood, paper and many plastics are translucent to some degree with light bouncing around inside the material before re-emerging at the surface and returning to the sensor. Fluorescent pigments are added to paper to make it appear super bright white under office lighting. This may be confusing the sensor as fluorescence isn't instantaneous.

The transmitted 'beam' is actually a 30 deg cone. The acceptance angle of the sensor is 25 deg, therefore when measuring a flat surface at a short distance the geometry means that photons at the periphery of the cone will be travelling further to reach the target than those directly on axis.

The off-axis reflectivity characteristics may also influence the way the distance is calculated within the device. For something highly polished the vast majority of the received photons will be directly on axis. For something like paper you would expect photons from all angles and therefore a range of distances.

Thanks m &p; the STM PDF on slide 4 made it sound very different though, which was behind the decision not to use an IR or ultrasonic sensor.

203 (77) mm measured manually, same ambient light conditions as before, room temperature also

176 174 (55) mm shiny aluminium (extrusion)
197 192 (67) mm coated/lacquered paper (Beginning C for Arduino book cover)
193 189 (68) mm satin stainless steel (Mitutoyo slide rule)
205 203 (71) mm anodised aluminium (iPad back)
214 211 (77) mm near black matt rubber (iPad cover)
215 213 (79) mm rough plank o' maple
220 219 (86) mm human skin

First row numbers are with the weighted averaging disabled, they fluctuate much and are averaged by eye; looks like that ole iPad is the winner ; )

But it really means this sensor is no good with such a huge delta. What breakout-board would you suggest as an alternative, even if it costs twice or thrice the price?

Lagom:
Thanks m &p; the STM PDF on slide 4 made it sound very different though, which was behind the decision not to use an IR or ultrasonic sensor.

You known in those tv commercials when they make something sound really good, then right at the end someone speaks really fast and says a bunch of stuff that basically says that "your milage may vary, conditions apply", well it's like that.

That's why companies sell evaluation boards so that people can see for themselves how a product behaves under real world conditions before committing to using it in a product.

What breakout-board would you suggest as an alternative, even if it costs twice or thrice the price?

To be able to suggest anything we have to know your specs or at least what it would be used for (so we might create the specs ourselves). Do you want a metering device that's exact to the millimeter or a car parking lot beeper?

mikb55:
That's why companies sell evaluation boards so that people can see for themselves how a product behaves under real world conditions before committing to using it in a product.

The eval board costs moar than a breakout-board ; )

But it's ok. It's more about the parking aid functionality, the electronic tennis ball, so to speak. Putting things together, I got side-tracked and thought, well, if STM is only 50% faithful in their marketing-blurb, I can also output distance to a display with 2-3% error and lotsa materials/surfaces. This thing is no Balluff or Garmin sensor, that I know.

I think, with that weighted average filter, the reading is quite stable to now focus back on the original topic, getting that smoothly decelerating/accelerating beep without discrete banding/plateaux.

pylon:
Do you want a metering device that's exact to the millimeter or a car parking lot beeper?

This little project began as an electronic tennis ball, a parking lot beeper, and then I got intrigued into seeing what this thing measures and how accurately, see my previous post. So, I better get back on the original track and leave metrology to the pros and high quality gear.

So, my layman's metrology attempts out of the way, I need to improve my code for the variable buzzing.

I have a sensor that is set to sense every 100ms with a timing budget of 100ms for the high accuracy mode, as per the latest code above.

A weighted average smoothes fluctuations out and introduces some catch-up delay, when the sensed obstacle is moved back or forth.

I define the buzz interval with the currently measured distance as a percentage of the sensing range, the length of the buzz always being 100ms.

When the sensed object moves back or forth, the interval must change; because of the weighted average catch-up delay, I was hoping for a sort-of gradual deceleration/acceleration of buzzes.

That, at the moment, is not working; there are plateaux of beep lengths and the closer one gets, the ever faster beeps interfere with each other.

Maybe having the buzz interval as a percentage of the sensing range is the wrong idea.

This was not the case with the very first code example in the original post, although that one had audible discreetness/plateaux, even if the obstacle's distance was changed very gradually, it also had no weighted average applied.

Just saw from some online car bumper collision warning discussions that (I own an old banger with no sensors) apparently automakers define 3-5 sub-ranges, hence 3-5 fixed buzz intervals exist, according to which bracket the sensed distance is in.

Are all cars like that? Is it actually a futile idea to have a continuous spectrum of buzz intervals, meaning that only bracketed approaches can work reliably?

If several discrete brackets are the only reliable way, that would then be a switch/case state machine for the whole shebang?

Are all cars like that? Is it actually a futile idea to have a continuous spectrum of buzz intervals, meaning that only bracketed approaches can work reliably?

No, I think continuous intervals would work but the user experience is better if you have them bracketed. That way the user gets a feeling for the distance which it won't get with the continuous approach (at least the average user, a musician might be an exception).