Range finder (Part 2)

Part 1, 2

As in most arduinoos posts, the examples given here differ from what is usually seen and read in terms of  code efficiency  and ruggedness of applications. Early tests performed on the HC-SR04 module proved its easiness of setup and interesting results obtained from scratch. However things are changing when accuracy is concerned and headaches begin when timeout management is needed. This post should help you in solving these issues.

In theory, the minimal code as originally proposed by David A. Mellis:

/* HC-SR04 Sensor
   https://www.dealextreme.com/p/hc-sr04-ultrasonic-sensor-distance-measuring-module-133696
  
   This sketch reads a HC-SR04 ultrasonic rangefinder and returns the
   distance to the closest object in range. To do this, it sends a pulse
   to the sensor to initiate a reading, then listens for a pulse 
   to return.  The length of the returning pulse is proportional to 
   the distance of the object from the sensor.
     
   The circuit:
	* VCC connection of the sensor attached to +5V
	* GND connection of the sensor attached to ground
	* TRIG connection of the sensor attached to digital pin 2
	* ECHO connection of the sensor attached to digital pin 4
 
 
   Original code for Ping))) example was created by David A. Mellis
   Adapted for HC-SR04 by Tautvidas Sipavicius
 
   This example code is in the public domain.
 */
 
 
const int trigPin = 2;
const int echoPin = 4;
 
void setup() {
  // initialize serial communication:
  Serial.begin(9600);
}
 
void loop()
{
  // establish variables for duration of the ping, 
  // and the distance result in inches and centimeters:
  long duration, inches, cm;
 
  // The sensor is triggered by a HIGH pulse of 10 or more microseconds.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  pinMode(trigPin, OUTPUT);
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  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);
  duration = pulseIn(echoPin, HIGH);
 
  // convert the time into a distance
  inches = microsecondsToInches(duration);
  cm = microsecondsToCentimeters(duration);
  
  Serial.print(inches);
  Serial.print("in, ");
  Serial.print(cm);
  Serial.print("cm");
  Serial.println();
  
  delay(100);
}
 
long microsecondsToInches(long microseconds)
{
  // According to Parallax's datasheet for the PING))), there are
  // 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
  // second).  This gives the distance travelled by the ping, outbound
  // and return, so we divide by 2 to get the distance of the obstacle.
  // See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
  return microseconds / 74 / 2;
}
 
long microsecondsToCentimeters(long microseconds)
{
  // The speed of sound is 340 m/s or 29 microseconds per centimeter.
  // The ping travels out and back, so to find the distance of the
  // object we take half of the distance travelled.
  return microseconds / 29 / 2;
}

This code can be translated to something more compact which looks like:

const uint8_t _echoPinMask = (1 << PINB3);
const uint8_t _triggerPinMask = (1 << PINB4);
const uint8_t _ledPinMask = (1 << PINB5);
const uint16_t _timeout = 38000; /* in us */
const float _speedOfSound = 343.0; /* in m/s */

void setup() 
{          
	Serial.begin(115200);
	/* Set pins */
	DDRB |= (_triggerPinMask | _ledPinMask);
	DDRB &= ~_echoPinMask;
	/* Ready */
	Blink(3);
}


void loop() 
{
	/* Mark beginning of measurement */
	PORTB |= _ledPinMask;
	/* Measure distance */
	float distance = Distance();
	/* Mark end of measurement */
	PORTB &= ~_ledPinMask;
	/* Print distance */
	Serial.print(distance, 2);
	Serial.println();
	delay(500);
}

float Distance(void)
{
	/* Send one positive pulses at least 10 us long */
	PORTB |= _triggerPinMask;
	delayMicroseconds(10);
	PORTB &= ~_triggerPinMask;
	/* Wait rising edge of echo */
	while ((~PINB & _echoPinMask) == _echoPinMask);
	/* Record start time of echo pulse */
	int32_t tip = micros();
	/* Wait falling  edge of echo or timeout */
	while (((PINB & _echoPinMask) == _echoPinMask) && (micros() < _timeout));
	/* Record stop time of echo pulse */
	int32_t top = micros();
	/* Compute distance: travel time / 2 converted to seconds multiplied by speed of sound */
	float distance =  (((top - tip) >> 1) / 1000000) * _speedOfSound;
	/* Return computed distance */
	return(distance);
}


void Blink(uint8_t blinks) 
{
	for (uint8_t i = 0; i < (blinks << 1); i++) {
		PORTB ^= _ledPinMask;
		delay(200);
	}
	PORTB &= _ledPinMask;
}

What is different in this code is the use of DDRx, PORTx and PINx native instructions for sake of speed. Also, the waiting loops for detecting the rising and falling edges of the echo signal do not make use of the pulseIn() function. What the code does is:

  • Set constants in the header part of the sketch
  • Set ports as outputs (trig line and control led) or input (echo line), set the serial port for printing results
  • idle loop for measuring and printing distance from module

This code works like a charm, as long as the target stays within the 4 meters range. If not, the control led (PNB5, alias digital pin 13) will stay on for ever. The same problem occurs when the ultrasonic beam hit an irregular surface, so that the sound slips on the surface (e.g. a ball) and fails to be echoed to the module.

Too bad :-[

Next post will try to explain why and how to turn around this issue 🙂

 

Leave a Reply

You must be logged in to post a comment.