rounding to 3mm

Hi,

Using HC-04 ultrasonic sensor and this library to measure millimeters https://github.com/dimircea/Arduino/tree/master/libraries/HCSR04

I think the best resolution the HC-04 can get is 3mm?

So, I would like to round things so that the output is rounded to 3mm. So, if the output is 91, 93, 92, 92, 93, 91 etc, then the output should be 92, or something like that. Basically, i want to use the maximum resolution. I have used round() which is good as it removes the decimal places from 91.04, 92.45 etc. I then tried averaging but that still produced incorrect results.

#include "HCSR04.h"
#define TRIGGER_PIN 10
#define ECHO_PIN 13

HCSR04 hcsr04(TRIGGER_PIN, ECHO_PIN);

void setup() {
  // Start serial communication, used to show
  // sensor data in the Arduino serial monitor.
  Serial.begin(115200);
  // Wait for the HCSR04 sensor to settle.
  // This usually needs about one second...
  delay(1000);
}

void loop() {
  float distance = hcsr04.read(HCSR04::MetricsEL::mm);  
    distance = round(distance);

  
  if (distance > 0) {
    Serial.println(distance);
  } else {
    Serial.println("Error reading the sensor!");
  }
  // continuously read the sensor, ~10 times/s
  // Note: may be much less than ~10 times/s because reading
  // the sensor and checking the timeout may take up to 50ms
  delay(100);

I think the best resolution the HC-04 can get is 3mm?

How are we supposed to know what you think, or why you think that?

then the output should be 92

92 isn't a multiple of 3.

I don’t think you can get the average of multiple readings with just a comment and a delay(). Please post what you tried and the results that you think were incorrect.

Steve

PaulS: How are we supposed to know what you think, or why you think that? 92 isn't a multiple of 3.

You know what I think Paul, because I have told you what I think.

PaulS: How are we supposed to know what you think, or why you think that? 92 isn't a multiple of 3.

Gosh Paul, are you having a bad day?

You know what I think, because I told you what I think. I think it is 0.3mm, and it doesn't matter what it is anyway. The question is how to round or limit such a thing so that the output remains between certain boundaries.

"92 isn't a multiple of 3", obviously, and I did say "or something like that". Again it doesn't matter anyway, as it is not relevant to the answer.

A bit picky I think mate.

slipstick: I don't think you can get the average of multiple readings with just a comment and a delay(). Please post what you tried and the results that you think were incorrect.

Steve

It was such a minor thing, I didn't bother posting the code.

This works. Limits it to 5mm increments.

#include "HCSR04.h"
#define TRIGGER_PIN 10
#define ECHO_PIN 13

HCSR04 hcsr04(TRIGGER_PIN, ECHO_PIN);

int temp = 0; int prevDistance = 0;

int average = 0;
void setup() {
  // Start serial communication, used to show
  // sensor data in the Arduino serial monitor.
  Serial.begin(115200);
  // Wait for the HCSR04 sensor to settle.
  // This usually needs about one second...
  delay(1000);
}

void loop() {
  float distance = hcsr04.read(HCSR04::MetricsEL::mm);  
    distance = round(distance);
   temp = (int)distance % 5;
  

  
  if (distance > 0 && temp == 0) {
    Serial.print(distance); Serial.print("  "); Serial.println(temp);
    prevDistance = distance;
  } else {
    Serial.println(prevDistance);
  }
  
  delay(100);
}

If you want something rounded to increments of n (and you can afford the division) simply divide by n and multiply by n. You can add an adjustment to get a mid point.

Maybe there's a simpler way, but the code below might work:

int rounded = (int) round(distance);
int below = rounded - 1;
int above = rounded + 1;

if (rounded % 3 == 0) {
  //use this
} else if (below % 3 == 0) {
  //use this
} else if (above % 3 == 0) {
  //use this
}

I can't seem to understand why you want to do this, though, since % is expensive to use.

Edit: Simpler code here:

int rounded = (int) round(distance);
int r = rounded % 3;
if (r == 2) {
  rounded++;
} else if (r == 1) {
  rounded--;
}

That’s still too much.

int roundedToThree = (whateverNumber / 3) * 3;

Let integer math do it for you.

Delta_G:

int roundedToThree = (whateverNumber / 3) * 3;

Let integer math do it for you.

Or, if whateverNumber is a float

int roundedToThree = (int)(whateverNumber / 3) * 3;

pizzapie: I can't seem to understand why you want to do this, though, since % is expensive to use.

There is a 100mS delay in the program loop, So it wouldn't matter, would it?

Adding quantization into the reading will reduce the accuracy of the result, and inject noise...

Averaging successive values would be a much better thing to do, you might increase the resolution noticeably, you'll reduce noise.

Do you mean a moving average?

tim77777:
There is a 100mS delay in the program loop, So it wouldn’t matter, would it?

If you ever wanted it to be faster, it would. Not only for speed purposes, but I also don’t see how this would give you more accurate data, personally.

pizzapie: but I also don't see how this would give you more accurate data, personally.

Im not sure why he is doing what he is doing but t cannot be for accuracy sake. Think of the math here, he is taking a value with an error margin of 3 and turning it into an error margin of 6. He is cutting his accuracy in half here. So it can't have anything to do with accuracy.

tim77777:
Do you mean a moving average?

For most places where people want to use a moving average, a software low pass filter does the job. Moving averages involve keeping a buffer of N samples so that when you take a new sample, you can remove the old one. A low pass filter just needs one persistent variable.

BTW: note that for any moving average to be meaningful, samples must be taken at known intervals. The delay(100) is sufficient for most cases.

double filteredDistance = 0;

void loop() {
  float distance = hcsr04.read(HCSR04::MetricsEL::mm);

  if (distance > 0) {
    filteredDistance = .75 * average + .25 * distance;
  } else {
    Serial.println("Error reading the sensor!");
  }

  delay(100);
}

the .75/.25 split controls how responsive the filtered average is to changes. .9/.1 will reduce noise at the cost of making the filter slower and less responsive.

If your code is meant to switch between ‘in range’ and ‘not in range’, then you will want to add hysteresis .

double filteredDistance = 999;
boolean isInRange = false;

void loop() {
  float distance = hcsr04.read(HCSR04::MetricsEL::mm);

  if (distance > 0) {
    filteredDistance = .75 * average + .25 * distance;

    if(isInRange && filteredDistance > 20) {
      Serial.println("Now out of range");
      isInRange = false;
    }
    else if (!isInRange && filteredDistance < 17) {
      Serial.println("Now in range");
      isInRange = true;
    }

  } else {
    Serial.println("Error reading the sensor!");
  }

  delay(100);
}

Note what happens when the range is between 17 and 20 - there’s a little bit of wiggle room where the distance is allowed to drift. This stops the system rapidly switching when the range is just on the switchover point.

The actual values you use for the filter and the hysteresis are a matter of what your data actually looks like.

(edit) Oh, BTW: if you use one filter that’s sensitive and also another one that’s less sensitive, the difference between the two can tell you if the value you are tracking is moving quickly. This can be useful if you don’t care what the distance is, but you want to react if it changes suddenly.