Measuring Distance with an HC-SR04

The HC-SR04 is a cheap (in terms of money)  module that can measure distances using ultrasound. The module uses a 40 KHz tone, which is of course way above what any human can hear, to measure the distance. It works as follows.

  • It expects a 10µs TTL, i.e. 0-5V, pulse on its ‘Trigger’ input
  • It will then send out a sound
  • Waits until the sound comes back and calculate the distance
  • Create a pulse on the ‘Echo’ output. The length of the pulse will correspond to the distance measured

I used a ATMega328p MCU to control the HC-SR04.

HCSR04

The yellow channel shows the outgoing pulse to the ‘Trigger’ input of the HCSR04. The blue channel shows the signal coming back on the ‘Echo’ output the length of it being proportional to the distance measure. The OLED display shows the values measured. The first few values are for debugging the last shows the measured and calculated distance in meters. In the top right the HCSR04 is visible sitting on my laser distance meter. Compare the value of that 0.156 to the one on the OLED display 0.150. They are pretty close.

The main parts of the program consist of:

  • an ISR (Interrupt Service Routine) to be called when the echo pin goes to high
  • a timer to measure the length of the pulse sent by the HC-SR04

One thing to keep in mind that it is not the time between starting the HC-SR04 and the start of the echo pulse that should be measured but the length of the echo pulse. That is to say it is not really the echo the HC-SR04 just sends you back the measure distance and the simplest way is to vary the length of the signal.

My ATMega328p is clocked at 8MHz. The supply voltate is 3.3V to stay compatible with my RPi. The HCSR04 however needs to be connected to a 5V supply. Obviously you need to connect the grounds of both power supplies to make it work. The ‘Trigger’ input was connected directly to the ATMega328p since it produces a 3.3V high and that is well within the limits of what is considered ‘high’ in TTL. However the ‘Echo’ pin I connected through a two resistor voltage divider (i.e. two resistors in series, one connected to the ‘Echo’ ouput and the other to ground while the middle is the ‘Echo’ that goes to the ATMega. The one connected to the ground divided by both, times the 5V should be somewhat below 3.3V.

Note that the code should be optimized, obviously, for any real application. Note also that the following is not complete (since I already deleted the code but I’ll recreate it some time and update this) just showing the idea. I used the OLED from a previous post for the output.

volatile uint16_t MeasureCount;
ISR (INT0_vect)
{
	// Reset timer
	TCNT1H = 0;
	TCNT1L = 0;
	// Wait until pind goes back to zero
	
	while( PIND & EchoPin );
	
	uint16_t LSB = TCNT1L;
	uint16_t MSB = TCNT1H;
	uint16_t Pulses = ( MSB << 8 ) | LSB;
	//Pulses = 65535;
	float Distance = ( (float) Pulses * 343.2f ) / ( 2.0f * 8000000.0f ) + 0.0005f;
	uint16_t Whole = (int)Distance;
	uint16_t Partial = (int)( ( Distance  - Whole ) * 1000.0f );
	++MeasureCount;
	sprintf( Message0, "M %06u H = %u L = %u %u", MeasureCount, MSB, LSB, Pulses );
	sprintf( Message4, "%u.%03u Meters", Whole, Partial );
}

void Init()
{
	cli();
	strcpy( (char*)Message4, "Started Hello World");
	DDRB = 0;  // All pins input
	DDRB |= 1;
	DDRB  |= BlinkPin; // Blinkping output
	PORTB &= ~BlinkPin; // Make pins low to start

	// turns on pin change interrupts
	PCICR |= 1;
	//
	DDRB |= PulsePin; // Pulse pin output
	PORTB  |= EchoPin;    // Enable pull up
	PCMSK0 |= EchoPin;    // turn on interrupts on pins
	
	sei();
	
}
u8g_t u8g;

void u8g_setup(void)
{
	u8g_InitI2C(&u8g, &u8g_dev_ssd1306_128x64_i2c, U8G_I2C_OPT_NONE);
	u8g_Begin( &u8g );
}
void StartMeasure()
{
	cli();
	PORTB |= PulsePin;
	_delay_us( 11 );
	PORTB & = ~PulsePin;
        // I used a small delay here, sometimes it seemed to immediately start measuring, maybe it picked up the pulse I sent
        // So wait just a little bit
	_delay_us( 2 );
	sei();
}
void draw()
{
   u8g_SetFont(&u8g, u8g_font_6x10);

   u8g_DrawStr(&u8g, 0, 10, Message0 );
   u8g_DrawStr(&u8g, 0, 21, Message1 );
   u8g_DrawStr(&u8g, 0, 32, Message2 );
   u8g_DrawStr(&u8g, 0, 43, Message3 );
   u8g_DrawStr(&u8g, 0, 54, Message4 );
}

int main(void)
{
	Init();
	u8g_setup();
	
	strcpy( Message4, "Hello world" );
		
        StartMeasure();
	u8g_FirstPage(&u8g);
        while(1)
        {
	  do
	  {

	    draw();
	  } 
          while ( u8g_NextPage( &u8g ) );
     }
  }
	
}

 

Leave a Reply