Skocz do zawartości
bmajkut

[C] HC-SR04 mierzenie odległości, skalowanie

Pomocna odpowiedź

Witam,
napisałem kod do obsługi tego czujnika. Wysyłam wartość zmierzonego czasu przez usart do terminala.

Jeżeli zbliżę przeszkodę na odległość 2 cm to otrzymuję w odpowiedzi wartość "40" co po podzieleniu przez 20 da mi 2 cm. Wyczytałem tutaj na forum w jakimś starym artykule, że to należy tak skalować. Chciałbym się jednak dowiedzieć dlaczego akurat dzielimy przez "20".

W nocie katalogowej czujnika mamy wzór:

odl = (czas stanu wysokiego * 340[m/s] ) / 2

Z tego wzoru nie wynika, że mamy stan wysoki dzielić przez "20" chyba że nie potrafię sobie tego przekształcić. Czas wysoki mierzę Timer2 w mikrosekundach, więc po przekształceniu wzoru powinienem czas stanu wysokiego mnożyć razy "0,017".

Dodatkowo zauważyłem, że czujnik kiedy jest skierowany w stronę sufitu to maksymalnie daje mi wartość 1800, co po podzieleniu przez "20" daje "90", a do sufitu z blatu biurka mam zdecydowanie więcej.

Kod programu:

#define F_CPU 8000000ul
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>
#include "uart/uart.h"


volatile int odl0 = 0;
volatile int licznik = 0;
char flaga = 0;
char bufor[20];

void sonar_init()
{
DDRC = 0x02;
MCUCR |=(1<<ISC00)|(1<<ISC01);
GICR |= (1<<INT0);
TCNT2 |= 0x00;
TCCR2 |= (1<<WGM21)|(1<<CS20);

OCR2 = 0x08;
sei();
}

ISR(TIMER2_COMP_vect)
{
  licznik++;
}

ISR(INT0_vect)
{
if(flaga==0)
    {
        TIMSK = (1<<OCIE2);               

        GICR &= ~(1<<INT0);

        MCUCR = 0x00;                    
        MCUCR = (1<<ISC01);                

        GICR = (1<<INT0);

        flaga=1;
    }

    else if(flaga==1)
    {
        TIMSK &= ~(1<<OCIE2);                

        GICR &= ~(1<<INT0);

        MCUCR = 0x00;                      
        MCUCR = (1<<ISC01)|(1<<ISC00);    

        GICR = (1<<INT0);

        odl0=licznik;                    
        //odl0/=20;                       

        licznik=0;                       
        flaga=0;

    }

}

int l_pomiar(void)
{
//odl0=0;
   GICR |= (1<<INT0);
  	PORTC |= (1<<PINC1);
   _delay_us(15);
   PORTC &= ~(1<<PINC1);

   _delay_ms(8);
   GICR &= ~(1<<INT0);
   return odl0;
}

int main(void)
{
sonar_init();
USART_Init();
uart_puts(" start ");
while(1)
{
		_delay_ms(1000);

		sprintf(bufor, "D: %d",l_pomiar());

		uart_puts(bufor);

}
}

Udostępnij ten post


Link to post
Share on other sites

Gdybyś jeszcze napisał jaki masz procesor i jak podłączyłeś czujnik to łatwiej byłoby zagłębić się w kod.

Generalnie przedstawiony program to jakaś pomyłka. Timer 2 ustawiony jest na zgłaszanie przerwań co 1us. Żaden procesor z zegarem 8MHz nie jest w stanie obsłużyć tak częstych przerwań. Autor pewnie tego nie wiedział, licznik jakoś mu się inkrementował bo przerwania przecież były obsługiwane tylko wcale nie tak często jak chciał timer więc i wyniki były bzdurne. Wtedy zobaczył ile dostaje, dobrał sobie jakiś współczynnik i mniej więcej mu działało. Żałosne.

Do zliczania interwałów 1us zaprogramuj po prostu któryś timer na liczenie z podzielnikiem 1/8. Puszczaj go od wartości 0 gdy wykryjesz stan wysoki na sygnale z sonaru a zatrzymuj po zakończeniu impulsu wyjściowego. Wynik będzie na pewno w mikrosekundach. Dla pewności możesz też obsługiwać przerwanie od przepełnienia tego timera w którym ustawisz flagę "overrange".

Najbardziej elegancko byłoby oczywiście mierzyć długość impulsu przy pomocy wejścia InputCapture. Do tego właśnie zostało stworzone.

Zamiast mnożyć przez 0.017 lub dzielić przez 58 możesz pomnożyć liczbę mikrosekund przez 1130 a z wyniku wziąć tylko 16 najstarszych bitów. Tak chyba będzie najszybciej.

Udostępnij ten post


Link to post
Share on other sites

Kod napisany na Atmega8 z wewnętrznym oscylatorem 8Mhz.

A jaka jest różnica pomiędzy tym czy mam Timer z preskalerem 1/8 i liczę do 8 i wtedy mam jedną mikrosekundę a tym co ty zasugerowałeś?

Udostępnij ten post


Link to post
Share on other sites

Różnica jest następująca:

W zaprezentowanym programie timer liczy z pełną częstotliwością zegara, co 1us zgłasza przerwanie i zeruje się (tryb CTC) zaczynając cykl od nowa. To program jest odpowiedzialny za to, by reagować na przerwania i co 1us inkrementować zmienną "licznik". To jest oczywiście niemożliwe, bo samo wejście i wyjście z obsługi przerwania zajmuje więcej niż 1us więc program zaczyna się spoźniać. Przerwania przychodzą co 1us a licznik jest inkrementowany rzadziej np. raz na dwa lub trzy przerwania. W rezultacie mimo iż procesor był zajęty na 100%, pod koniec masz w zmiennej "licznik" jakiś wynik, ale spokojnie możesz go wyrzucić do kosza. Nie ma wiele wspólnego z faktyczną liczbą mikrosekund jakie upłynęły w czasie liczenia czasu. Takie metody można stosować gdy masz liczyć np. z rozdzielczością 1ms ale nie 1us. Ktoś zupełnie nie rozumiał co robi, przykro mi.

Gdy używasz timera jako licznika czasu, puszczasz go od zera z zegarem np. 1us i nic więcej nie robisz. Czas się liczy sam, timer niczego oprócz zegara nie potrzebuje, sam się inkrementuje a program może robić coś innego np. sprawdzać czy impuls już się zakończył. Wtedy zatrzymujesz timer przez wyłączenie mu zegara i spokojnie, już przy zatrzymanym zliczaniu odczytujesz odpowiedni rejestr TCNTx.

Dostrzegasz?

Tak jak napisałem istnieje jeszcze bardziej elegancka metoda pomiaru długości czasu trwania impulsu korzystająca ze specjalnej funkcji timera, tzw. Input Capture. Tam to już nawet timera nie trzeba zatrzymywać, a wystarczy tylko z odpowiedniego rejestru odczytać momenty początku, końca impulsu zapamiętane sprzętowo a potem je od siebie odjąć. Tak właśnie powinna być zrobiona obsługa sonaru.

Udostępnij ten post


Link to post
Share on other sites

Czyli jeśli dobrze zrozumiałem:

-skonfigurować Timer tak, by co 1us zwiększał zawartość rejestru TCNT1

-gdy na wyjściu echo pojawi się stan wysoki to zapisać aktualny stan rejestru TCNT1

-sprawdzać stan wyjścia echo i jeśli pojawi się niski to zapisać końcowy stan TCNT1

-różnica tych dwóch wartości to czas w mikrosekundach

Od razu widać że muszę użyć licznika 16-bitowego. W funkcji wyzwalania powinienem zerować rejestr TCNT1, aby się licznik nie przekręcił w trakcie zliczania czasu trwania echa?

Udostępnij ten post


Link to post
Share on other sites

To juz zależy od Ciebie.

Jeśli licznik wyzerujesz a dopiero potem wystartujesz, to wynik masz na talerzu - po zatrzymaniu odczytujesz i już. Co więcej możesz wtedy programowo podglądać stan odpowiedniej flagi TOV i jeśli się ona ustawi to będzie znaczyło, że minęło ponad 65ms i na echo nie ma chyba już sensu czekać dłużej. Możesz też sprawdzać ja na końcu, już po zatrzymaniu licznika i odczytaniu jego zawartości w celu upewnienia się, że podczas liczenia TCNT nie przekręcił się ani razu przez 0xFFFF. Oczywiście używając TOV powinieneś ją zerować przed każdym startem zliczania. Za darmo masz sprzętowe wykrywanie przepełnień.

Niezerowanie licznika kosztuje tylko jedno odejmowanie więcej. Niewielki koszt, ale tracisz możliwość korzystania z TOV - moim zdaniem cenną.

EDIT:

Metoda InputCapture jest jeszcze czymś innym. Tam korzystasz ze sprzętowej możliwości zatrzaskiwania stanu pracujacego licznika w specjalnym rejestrze. To blok timera - odpowiednio zaprogramowany na zbocze narastające - wykrywa pojawienie się tego zdarzenia na wejściu ICx, zapamiętuje stan licznika i zgłasza przerwanie. Ty na spokojnie odczytujesz ten rejestr, przeprogramowujesz na wykrywanie tym razem opadającego zbocza i dalej spokojnie czekasz. Gdy się ono pojawi, znowu dostaniesz przerwanie, odczytasz ten sam rejestr, odejmiesz obie wartości i już. Oczywiście nie musisz korzystać z przerwań - flagę gotowości nowych danych możesz sprawdzać programowo. Zauważ, że w tej metodzie nie ma żadnych opóźnień związanych z pracą programu. Jeśli chcesz łapać mikrosekundy to zaczyna być ważny czas jaki upływa od momentu wykrycia przez program odpowiedniego stanu na wejściu a wyzerowaniem i puszczeniem timera. To samo przy końcu: też trzeba wykryć stan niski i zatrzymać timer. Ten czas nie zawsze jest taki sam (program kręci się w pętli a zbocze może go "złapać" w przypadkowym momencie tej pętli) więc pomiar będzie zawsze obarczony pewnym błędem. Co gorsza, jeśli program w tym czasie obsługuje jakieś inne przerwania, ich czas obsługi musi się dodać do błędu zliczania czasu.

ATmega8 ma mechanizm InputCapture wbudowany w Timer1, więc nic nie stoi na przeszkodzie by go wykorzystać. Jedynym wymaganiem jest to, by mierzony impuls doprowadzić do wejścia IC1.

Udostępnij ten post


Link to post
Share on other sites

Należy zaznaczyć, że istnieją dwa sposoby obliczania odległości od przeszkody:

1. Długość stanu wysokiego na nóżce Echo jest proporcjonalna do odległości ze współczynnikiem proporcjonalności 58 dla centymetrów

2. Czas pomiędzy impulsem Trigger a stanem wysokim na nóżce Echo można obliczyć ze wzoru:

d = t *340[m/s] / 2

Udostępnij ten post


Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Gość
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.


×
×
  • Utwórz nowe...