Tuesday, December 24, 2013

HC-SR04: avoiding oversampling errors




We have seen in previous posts (here and here) that if the time between two measurements is too short then the sensor outputs a 0 to signal a measurement error. We call these kind of errors ‘oversampling’, i.e. sampling too frequently beyond the capacity of the sensor.

From data collected in this post, we have seen that a delay of 26ms is enough to solve the problem. This note tries to answer the following question:

        can we minimize this time?

The idea

We have conceived a bisection procedure which will try to determine the minimal delay to use in order to avoid oversampling. The idea is to start with delay D=26ms. Therefore the search interval will be

I0=(a0,b0)=[0,D]

Then, if there are no oversampling errors, I_0 is halved and a new sample batch is run. If at some stage i oversampling errors are more than a certain threshold ε, we search in the interval

(ai+1,bi+1)=[b,(b-ai)/2]   for i>1

otherwise we keep on halving the interval. The procedure is repeated until |ai-bi|=1ms.

At this point, a second procedure will search for the right number of us. According to Arduino specifications (see here), this second procedure is stopped if the length of the interval is less than 3us (i.e. |ai-bi|<=3us).

The program
The program which performs the experiment is given at the end of this post. Let us comment its parts. First of all, the constants

#define SAMPLESIZE 1000
#define NUMTESTS 5

are to indicate that we are going to perform 5 (NUMTESTS) batches and that each batch will consist of 1000 (SAMPLESIZE) tests. A test fails if the percentage of errors (zeroes) which are measured in a batch is greater than a TOLERANCE. Here the tolerance is fixed at 1% (see below).

#define TOLERANCE .01

Finally the following two constants define the initial length of the search interval for milliseconds and for microseconds.

#define INITDELAYMS 26
#define INITDELAYUS 1000

As we have already said, the value for INITDELAYMS is fixed to 26ms because of previous experiences. INITDELAYUS is naturally fixed to 1000us since it is the amount of micros in a millisecond.
The following portion of code is taken directly for here and just performs one read of the sensor. Remark that echo pin is connected to Arduino pin 2 and trigger pin is connected to Arduino pin 4.



const uint8_t trigPin = 4; // trigger pin of HC-SR04
const uint8_t echoPin = 2; // echo pin of HC-SR04

uint16_t readoutSensor() {
  // The sensor is triggered by a HIGH pulse of 10us or more.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  pinMode(trigPin, OUTPUT);
  digitalWrite(trigPin, LOW);
  delayMicroseconds(3);

  // Start trigger signal

  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
 
  // Read the signal from the sensor: a HIGH pulse whose
  // duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.

  pinMode(echoPin, INPUT);
 
  return pulseIn(echoPin, HIGH);
}

The following procedure is the core of the sketch and checks if a given value of delay returns errors. The returned value is the mean (rounded up to integers) of the number of errors found per batch. Remark that this procedure is used both to test millis and micros. In the case of micros one should provide the additional value of millis. 

uint16_t TestDelay(uint16_t curdelay, uint16_t mdelay, void (*delayfun)(long unsigned int) ) {
  
  uint16_t numerr;
  uint16_t i,j;
  uint32_t sumErr;

  for(j=0,sumErr=0;j<NUMTESTS;j++,sumErr+=numerr) {
    for(i=0,numerr=0;i<SAMPLESIZE;i++) {
        if(mdelay)
          delay(mdelay);
        delayfun(curdelay);
        if(!readoutSensor())
          numerr += 1;
    }
  }

  return (uint16_t)(sumErr/NUMTESTS); // return the mean (rounded up)
}


The bisection procedure is implemented in TestMillis and TestMicros with the obvious meaning. The returned value is "significant" value for millis and micros. 


/ return the the least significant value for millis
uint16_t TestMillis() {
  uint16_t right,left; // left and right bounds of the search interval
  uint16_t mid; // midpoint of the interval
  uint16_t epsilon=TOLERANCE*SAMPLESIZE;
  
  for(right=INITDELAYMS,left=0,mid=(right-left)>>1;right>1+left;mid=left+((right-left)>>1))
  {  
    if(TestDelay(mid,0,delay)>epsilon)
      left = mid;
    else
      right = mid;
  }
  return right;
}


// returns the least significant value for micros
uint16_t TestMicros(uint16_t msdelay) {
  uint16_t right,left; // left and right bounds of the search interval
  uint16_t mid; // midpoint of the interval
  uint16_t epsilon=TOLERANCE*SAMPLESIZE;

  for(right=INITDELAYUS,left=0,mid=(right-left)>>1;right>3+left;mid=left+((right-left)>>1))
  {
    if(TestDelay(mid,msdelay,(void(*)(long unsigned int))delayMicroseconds)>epsilon)
      left = mid;
    else
      right = mid;
   }
   return right;
}

Conclusions
Running the program several time we got the following response: 21ms and 3us.
Remark that 3us is the minimal "granted" value to which the function delayMicroseconds is sensitive (see here). Therefore we can assume that 21ms is our target value.

The complete source

/*****************************************************************
OVERSAMPLING:

Run batteries of tests over the HC-SR04 to determine the precise 
value of the minimal delay which allows to avoid oversampling
errors.

Author: Enrico Formenti
Date: 18/12/2013
Revision: none

LICENCE:

The MIT License (MIT)

Copyright (c) 2014 Edson Hilios

Permission is hereby granted, free of charge, to any person 
obtaining a copy of this software and associated documentation 
files (the "Software"), to deal in the Software without 
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be 
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
DEALINGS IN THE SOFTWARE.
******************************************************************/


#include <Serial.h>

#define SAMPLESIZE 1000
#define NUMTESTS 5
#define TOLERANCE .01
#define INITDELAYMS 26
#define INITDELAYUS 1000

const uint8_t trigPin = 4; // trigger pin of HC-SR04
const uint8_t echoPin = 2; // echo pin of HC-SR04

uint16_t readoutSensor() {
  // The sensor is triggered by a HIGH pulse of 10us or more.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  pinMode(trigPin, OUTPUT);
  digitalWrite(trigPin, LOW);
  delayMicroseconds(3);

  // Start trigger signal

  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
 
  // Read the signal from the sensor: a HIGH pulse whose
  // duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.

  pinMode(echoPin, INPUT);
 
  return pulseIn(echoPin, HIGH);
}

uint16_t TestDelay(uint16_t curdelay, uint16_t mdelay, void (*delayfun)(long unsigned int) ) {
  
  uint16_t numerr;
  uint16_t i,j;
  uint32_t sumErr;

  for(j=0,sumErr=0;j<NUMTESTS;j++,sumErr+=numerr) {
    for(i=0,numerr=0;i<SAMPLESIZE;i++) {
        if(mdelay)
          delay(mdelay);
        delayfun(curdelay);
        if(!readoutSensor())
          numerr += 1;
    }
  }

  return (uint16_t)(sumErr/NUMTESTS); // return the mean (rounded up)
}

// return the the least significant value for millis
uint16_t TestMillis() {
  uint16_t right,left; // left and right bounds of the search interval
  uint16_t mid; // midpoint of the interval
  uint16_t epsilon=TOLERANCE*SAMPLESIZE;
  
  for(right=INITDELAYMS,left=0,mid=(right-left)>>1;right>1+left;mid=left+((right-left)>>1))
  {  
    if(TestDelay(mid,0,delay)>epsilon)
      left = mid;
    else
      right = mid;
  }
  return right;
}


// returns the least significant value for micros
uint16_t TestMicros(uint16_t msdelay) {
  uint16_t right,left; // left and right bounds of the search interval
  uint16_t mid; // midpoint of the interval
  uint16_t epsilon=TOLERANCE*SAMPLESIZE;

  for(right=INITDELAYUS,left=0,mid=(right-left)>>1;right>3+left;mid=left+((right-left)>>1))
  {
    if(TestDelay(mid,msdelay,(void(*)(long unsigned int))delayMicroseconds)>epsilon)
      left = mid;
    else
      right = mid;
   }
   return right;
}

void setup() {
  uint16_t ms,us;
  
  Serial.begin(9600);
  while(!Serial);  // this is only needed for Leonardo
  
  Serial.print("Test for millis...");
  ms=TestMillis();
  Serial.println("done.");
  Serial.print("Test for micros...");
  us=TestMicros(ms);
  Serial.println("done.");
  Serial.print("Optimal delay: ");
  Serial.print(ms);
  Serial.print(" millis and ");
  Serial.print(us);
  Serial.println(" micros");
}

void loop() {
     // nothing happens after setup
}

No comments :

Post a Comment