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
otherwise we keep on halving the interval. The procedure is repeated until |ai-bi|=1ms.
(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