HC SR04 and ATMega 1284P problem.

Go To Last Post
14 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello to all!

As a title, i'm working about a driver to interface the HC SR04 sensor sonar to my ATMega 1284P.
Following the HC SR04 datasheet i wrote a driver with this logic:

 

i send a pulse of about 10us (~13[us] in my code) to trigger the sensor and after i wait the echo pulse.
I listening the echo pulse using the INT0 external interrupt. When the rising edge of the echo pulse is detected, I starting the timer and I setting the INT0 to trigger when the falling edge of echo pulse is incoming. To measure the duration of the echo, i use the timer0 in CTC mode.
When the falling edge is detected i stop the timer0, I read the counter value, I set the INT0 to detect the rising edge and I turn off the INT0 interrupt.
In the main program, firstly I send a pulse, i wait for 40[ms], i read the value of the counter and i sent this value via usart, and in the end i wait for another 20[ms] to finish the loop and starting a new.
In this way i think that my acquisition is about 60[ms], according to the HC SR04 datasheet. 
To do this, i have a STK500 board with a crystal of 16MHz. I using this configuration for the fuse bit: LOW=0xF7, HIGH=0xD9, EXTENDED=0xFF.

 

The problem is that the number of count that I measure doesn't match with my math! Following the HC SR04 datasheet, at 10[cm] i should obtain a pulse width of 580[us] from the sensor, but my driver give me a different number that is great than the value that i aspect.

In particular I set the timer0 to fire every 29[us] (OCR0A = 0x1C), so in this test, i aspect a value of 20[count] (20*29 = 580) but i read a value that jump between 31 and 32.

I have make some test before ask your help, but the result confusing me a lot! In particular i have simulated the echo pulse using the portC of my micro.
I send a pulse of 0.58[ms] on pin 3 to the port C and using the INT0 pin, i try to measure the width of this pulse. In this case, i aspect a value of 20[count] but 
the value that i read is between 53 and 54. 

Anyone have idea where i'm wrong?

 

This is my code

// HC SR04 driver
volatile unsigned long tot_counter          = 0;
volatile unsigned long sonar_counter        = 0;
volatile unsigned char sonar_flag           = 0;

void init_HC_SR04(void)
{
	/***** init IO *****/
	DDRD &= ~(1<<2);                 // pin D.2 is output (INT0)
	PORTD |= (1<<2);                 // pull-up on  on D.2

	DDRC |= (1<<3);                  // il pin C.3 è l'uscita per l'impulso
	PORTC &= ~(1<<3);                // pull-up off on C.3
	/*******************/

	/***** init External Interrupt *****/
	EICRA = ( (1<<ISC01) | (1<<ISC00) ); // rising edge
	/***********************************/
	
	/***** init timer counter *****/
	TCCR0A |= (1<<WGM01);   // set timer0 in CTC mode
	OCR0A = 0x1c;           // set timer0 resolution @ 29[us]
	TIMSK0 |= (1<<OCIE0A);  // enable il timer0 compare match interrupt
	/******************************/
}

void sendPulse(void)
{
	sonar_counter         = 0;
	sonar_flag            = 1;
	EIMSK |= (1<<INT0);                  // enable int0
	
	// invia un impulso di ~13.6us (> 10us) sul pin C3
	PORTC |= (1<<3);
	
	_delay_ms(0.58);
	
	PORTC &= ~(1<<3);
		
}

float getSonarMeasure(void)
{
	return (float)(tot_counter*1.0); 
}

ISR(INT0_vect)
{
	if(sonar_flag) // the echo is arrived!
	{
		sonar_flag = 0;
		TCCR0B |=  (1<<CS01); // start timer-counter0 @FCPU/8
		EICRA = (1<<ISC01);   // falling edge NB: scrivo tutto il registro
	}
	else // the echo is finish
	{
		TCCR0B &=  ~(1<<CS01);    // stop timer-counter0
		tot_counter = (unsigned long)( sonar_counter ); // durata impulso ricevuto [count]
		
		EIMSK  &=  ~(1<<INT0);    // disable int0
		EICRA = ( (1<<ISC01) | (1<<ISC00) ); // rising edge
	}
}

ISR(TIMER0_COMPA_vect) // resolution of 29[us]
{
	sonar_counter++; // increase the counter
}


// main program
#include <avr/io.h>
#define F_CPU 16000000L
#include <util/delay.h>
#include <avr/interrupt.h>
#include "USART_polling.h"
#include "HC_SR04_2.h"

int main(void)
{
	InitUART( BAUD_115200_2x_16MHZ, EN_2X );
	init_HC_SR04();
	
	sei();
	
    while(1)
    {
        sendPulse();
        
        _delay_ms(40);
        
        float m = (float)(getSonarMeasure());		
        Transmit_float(m); 
        
        _delay_ms(20);
    
    }
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What toolchain and version are you using?  What optimization level?

 

Your post looks OK to me.  Are you sure that the STK500 is >>really<< delivering a 16MHz crystal signal to your AVR?  How have you proven that?

 

Once you get this going, we'll discuss "input capture".  Most of us would use timer1 and the input capture feature with the signal to the ICP1 pin, and let the timer help with the measuring.  (Your model also has timer3/ICP3)  The use of 32-bit values and float operations seems like overkill, but that would probably only skew your results a little bit, and not wildly as you're seeing.

 

Everything running at 5V?

 

It would be best if you actually measure the input signal with a 'scope. ;)

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi teusch, thank you so much for the reply!

I'm using the AtmelStudio 6 (ver. 6.2.1153) and the optimization level is "O1".

 

Are you sure that the STK500 is >>really<< delivering a 16MHz crystal signal to your AVR?  How have you proven that?

No! I setting the jumper OSCEL on the STK500 to "on board xtail oscillator", but i haven't measured any! How i measure this?

If it's util, with this setting i using with success the I2C protocol and the usart protocols communication, and the frequency that they work it's based on the FCPU at 16MHz, both work well.

I looked at "input capture" method in this forum, but i use both the timer1 and the timer3 to drive 4 esc (i'm working on a quadcopter control system) so, i can't use the input capture function.

The 32-bit variable are "residual" of the original drive. I need to measure the altitude, so i need some float variable. In this test code i forget to substitute this big variable, i'm sorry!

 

Everything running at 5V?

I think yes, but i'm not sure! I plug the echo and trigger pins of the sensor on the STK500, also the sensor is supply using the STK500. It should be 5V!

 

It would be best if you actually measure the input signal with a 'scope. ;)

 I think so, but at this moment i haven't an oscilloscope. In your opinion i can measure this signals using the adc of my micro?

 

 Ps

I have looked at the arduino library (NewPing v1.5) and at line 107 of NewPing.ccp file there is write:

Timer interrupt ping methods (won't work with ATmega8 and ATmega128)

Is possible that the micro doen't work proprerly?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I looked at "input capture" method in this forum, but i use both the timer1 and the timer3 to drive 4 esc (i'm working on a quadcopter control system) so, i can't use the input capture function.

Now, this is a side note not directly related to your problem, but as long as either time1 or timer3 is free-running then you can use input capture, even if doing PWM or such.  Admittedly, if ICRn is used as TOP then there would appear to be a conflict.

 

If you comms are working cleanly, I guess that would be an indication of 16MHz operation.  Are you sure the UBRR value ends up to be 16?  I note from the datasheet table that you are pushing the envelope a bit with 16MHz, 115200bps -- 2.1% error with U2X and 3.5% without.

 

If you in fact posted your entire test program, then the |= when setting up timer(s) should be OK.  But why not just = ?

 

If you vary your pulse width and/or target distance, do the returned values change linearly?

 

TCNT0 isn't cleared between test runs, but that would be a "little error" and not a "big error" as you are seeing.

 

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm sorry, but now i'm bit tired, so i can't answer to your question 3, I not checked this fact. Tomorrow i do some work about your questions in these two post and i give you some appropriate answer!

 

If you comms are working cleanly, I guess that would be an indication of 16MHz operation. Are you sure the UBRR value ends up to be 16?

Yes, for answer you, i used the comms to indicate the correctness of the oscillator,  I'm sure that the UBRR value is 16! However tomorrow i check this and i will answer you with more security!

If you in fact posted your entire test program, then the |= when setting up timer(s) should be OK.  But why not just = ?

I know the difference between this two operand, but i'm newbe on programming the microcontroller so i use these two operand without an apparent distinction..

Now i realize that this is wrong style to programming, thanks a lot! ;)

TCNT0 isn't cleared between test runs, but that would be a "little error" and not a "big error" as you are seeing.

You are rigth! I will fix this error.

 

Now, this is a side note not directly related to your problem, but as long as either time1 or timer3 is free-running then you can use input capture, even if doing PWM or such.  Admittedly, if ICRn is used as TOP then there would appear to be a conflict.

I'm a newbe in this field, so for now i would follow the "simplicity" when i write my programs, so that it can understand how a microcontroller work. But if you like to speak about this argument i listen to you gladly! ;)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have done some experiment using the test program about the returned value. This value it's like linear, but really it isn't. I have done a graph..
The voltage on the pin is about 5v (4.95) and the UBRR value is 16. I can't measure the input frequency.
It's possible that the int0 doesn't work properly? Some days ago, i had used the mode "any change" for int0. 
Using this mode the returned value was the same for all experiment that i had done (about 8[mm]). 
With the changing the mode of operation, now i obtain this strange behavior.
Any advice on some new test? 

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This value it's like linear, but really it isn't. I have done a graph..

It isn't?!?  It looks linear to me...

 

I wonder if there is a false trip somewhere.  Before "

	EIMSK |= (1<<INT0);                  // enable int0

do an EIFR = (1 << INT0); ?

 

Also, do TCCR0A = instead of |=, and a TCCR0B = 0; and TCNT0 = 0; before a sequence.

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For 29us from a 2MHz clock (16MHz/8 prescaler) you need

	OCR0A = (29*2)-1;           // set timer0 resolution @ 29[us]

/Lars

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It isn't?!?  It looks linear to me...

It's linear because this is an approximation. In reality in this experiment the returned value jumps between two value. In the first part of the experiment this jump take 2-3 values, in the other part this jump is reduced to only two values. So for do the graph i have used a mean of the values for each experiment. Maybe i'm a bit scrupulous within the definitions! :)
Also, I noticed that the above chart is wrong. The red graph isn't the measured value, but it's the difference between the measured value and the expected value, i'm sorry for this mistake. I attached a new graph (experiment) that represent  the measured values and the expected values for this experiment.

 

However i repeated the experiment following your advice. Now all the values are stable! But the graph (experiment_1) does not change substantially.

Note that i used this code for the new experiment:

 

void init_HC_SR04(void)
{
	/***** init IO *****/
	DDRD &= ~(1<<2);                 // pin D.2 is output (INT0)
	PORTD = (1<<2);                 // pull-up on  on D.2

	DDRC = (1<<3);                  // il pin C.3 è l'uscita per l'impulso
	PORTC &= ~(1<<3);                // pull-up off on C.3
	/*******************/

	/***** init External Interrupt *****/
	EICRA = ( (1<<ISC01) | (1<<ISC00) ); // rising edge
	/***********************************/
	
	/***** init timer counter *****/
	TCCR0A = (1<<WGM01);   // set timer0 in CTC mode
	OCR0A = 0x1c;           // set timer0 resolution @ 29[us]
	TIMSK0 = (1<<OCIE0A);  // abilito il timer0 compare match interrupt
	/******************************/
}

void sendPulse(void)
{
	sonar_counter         = 0;
	sonar_flag            = 1;
	
	EIFR = (1<<INT0);
	EIMSK = (1<<INT0);                  // enable int0
	
	TCCR0B = 0x0; 
	TCNT0 = 0x0;
	
	// invia un impulso di ~13.6us (> 10us) sul pin C3
	PORTC |= (1<<3);
	
	//_delay_ms(0.58); // 10 cm
	//_delay_ms(1.16); // 20 cm
	//_delay_ms(1.74); // 30 cm
	//_delay_ms(2.32); // 40 cm
	//_delay_ms(2.90); // 50 cm
	//_delay_ms(3.48); // 60 cm
	//_delay_ms(4.06); // 70 cm
	//_delay_ms(4.64); // 80 cm
	//_delay_ms(5.22); // 90 cm
	_delay_ms(5.80); // 100 cm
	
	PORTC &= ~(1<<3);
	
}

float getSonarMeasure(void)
{
	return (float)(tot_counter*1.0); //[m]
}


ISR(INT0_vect)
{
	if(sonar_flag) // the echo is arrived!
	{
		sonar_flag = 0;
		TCCR0B =  (1<<CS01); // start timer-counter0 @FCPU/8
		
		EICRA = (1<<ISC01); // falling edge NB: scrivo tutto il registro
	}
	else // the echo is finish
	{
		TCCR0B =  0x0;    // stop timer-counter0
		tot_counter = (unsigned long)( sonar_counter ); // durata impulso ricevuto [count]
		
		EIMSK  =  0x0;    // disable int0
		EICRA = ( (1<<ISC01) | (1<<ISC00) ); // rising edge
	}
}

ISR(TIMER0_COMPA_vect) // risoluzione di 29[us]
{
	sonar_counter++; // incrementa il counter di 29[us]
}

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lajon wrote:

For 29us from a 2MHz clock (16MHz/8 prescaler) you need

	OCR0A = (29*2)-1;           // set timer0 resolution @ 29[us]

/Lars

 

Hi Lars!

I have tryed this setting now and the error that i obtain is very little respect to the old value.

For 100[cm] i expect a 200[count] value ( (200*29)/58 = 100 ), but the code above return me a value of 229 ( (229*29)/58 = 114.5 ).

I doesn't need to much precision. My quadcopter is an robot and my work is only to stabilize it around an equilibrium point. So this error is acceptable to me, but if i improve it is better!

 

But now where does that number? To calculate the frequency of the timer I used the formula that is found on page 99 on the ATMega1284P datasheet.

I have used this value:

f_clk = 16000000, N=8, f_osc = 34482.75, so --->  OCR = 16000000/(2*8*34482.75)-1 = 28 = 0x1C.

This formula is incorrect or i have a mistake?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Excuse me if you disorder still, but following the Lars advice i have reduced the error between the expected value and the measured value. But using this value, according with the formula a pg 99 of the ATMega1284P datasheet the code gives me an interrupt every 58[us] instead of 29[us].
Is the formula that is wrong or am i wrong?

In addition to, plotting the expected values vs measured values it seems that, to each measurement the counter increases by a factor of 3 compared to the expected value, so the first measure gives me a count value of 22 instead that 20, the second measure gives me a value of 45 instead that 40 (i think 45=22 (old value measured)+20(real increment value)+3(mysterious factor)), the third measure gives me a value of 68 (68 = 45(old value measured) + 20(real increment value) + 3(mysterious factor)) and so on.. In attached there is this graph.

Please, anyone help me to understand where is the error or how can I figure out where I wrong?
What kind of testing can I do to find the error?
Thanks a lot in anticipation

Attachment(s): 

Last Edited: Thu. Jan 15, 2015 - 10:29 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,

 

Please excuse me if I'm wrong, but I thought that to calculate the distance from a sonar sensor, you'd have to measure the time of flight of the pulse, i.e. the interval between the trigger pulse and the echo pulse, not the length of the echo pulse itself.

Is this correct ?

 

Edit: Yes my theory is correct, but the sensor already do some kind of conversion...So Luca's program looks correct. Sorry !

Have a nice day,
Kraal

Last Edited: Thu. Jan 15, 2015 - 11:10 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Kraal! Thanks you so much for your interest! :)

I have read some datasheet of the sensor sonar. The most reliable and clear I think it is this http://www.cypress.com/?docID=43639 (look at pg 3) 

but also the HC SR04 datasheet says the same thing http://www.micropik.com/PDF/HCSR04.pdf.

 

Unless I misunderstood the datasheets, the distance measured by the sonar is proportional to the length of the pulse echo received.

Can anyone confirm this?

 

Ps

If my program is correct, then where is the error?? I'm going crazy to find him!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

..the formula a pg 99 of the ATMega1284P datasheet the code gives me an interrupt every 58[us] instead of 29[us].
Is the formula that is wrong or am i wrong?

The formula is correct, the mistake is mine! I'm sorry!
If i want an interrupt every 29[us], the resultant signal have a period of 29*2 = 58[us], so the frequency is: 1/58[us] = 17241.37 Hz.
Using this frequency and the above values in the formula a pg 99 of the ATMega1284P datasheet one gets:

f_clk = 16000000, N=8, f_osc = 17241.37, so --->  OCR = 16000000/(2*8*17241.37)-1 = 57 = 0x39.

Thanks a lot Lars! ;)

 

Now i try to find other error...