Skocz do zawartości

Funkcja Input Capture w Arduino


Pomocna odpowiedź

18 minut temu, ximot napisał:

Jak ten kod, który zamieściłeś powyżej ma się w czasie do obsługi funkcji czujnika SR04?

Rozumiem że chcesz wersję z przechwytem? ... chwileczkę

Link to post
Share on other sites
(edytowany)

najpierw muszę wiedzieć z czy mam doczynienia 🙃 przebieg hc sr 04 napiszę ten pseudokod "po mojemu" później spróbujemy przekonwertować na arduinowy.

Postaram się wszystko wykomentować.

volatile uint8_t start_pomiar = 0;// zmienna pomocnicza
volatile uint16_t wynik;

int main (void){ // tu się znajduje arduinowy setup
	
	PORTB|=(1<<PB0); // to jest wejście impulsu z HC - SR04 włączam wewnętrzny rezystor podciągający
	DDRB|=(1<<PB1);  // to jest wyjście impulsu wyzwalającego czyli pinMode ...output...
	TCCR1B|=(1<<CS11)|(1<<CS12); // tu jest nastawa prescalera czyli wstępnego dzielnika częstotliwości wejściowej UWAGA! ustawienie tegoż
	//od razu go uruchamia!
	TCCR1B|=(1<<ICES1); // będziemy reagowali na zbocze narastające
//	TIMSK|=(1<<TICIE1); // narazie bez tego
	sei();
	
	for(;;){ // tu loop
		if(start_pomiar == 0){
			PORTB|=(1<<PB1); // wysyłka impulsu
			_delay_us(15);
			PORTB&=~(1<<PB1); 
			start_pomiar = 1; // od tego momentu czekamy aż sr zadziała (czyli zbocze narastające)
		}
	if(start_pomiar == 2){
		// tutaj można dokonac obliczeń
	  // a gdy zajdzie potrzeba kolejnego pomiaru zerujesz zmienną start_pomiar	
      // enjoy!
	}
 }
}



ISR(TIMER1_CAPT_vect){ // przerwanie od przechwytu

	if(TCCR1B & (1<<ICES1)){  // jeśli był ustawiony (czyli czekaliśmy na zbocze narastające)to
		TCNT0 = 0; // zerujemy timer
	TCCR1B&=~(1<<ICES1);	// zmieniamy ustawienia tak że następne przerwanie będzie na zboczu opadającym(po impulsie z SR)
		// w tym momencie timer liczy czas impulsu 
      // a mikrokontroler może robić ciekawsze rzeczy... nie wiem... odbierać kody z pilota? ;)
	}
	
	if(!TCCR1B & (1<<ICES1)){ // OK sr skończył obliczac czas :)
		wynik = TCNT0; // no mamy już zmierzoną odległośc
		start_pomiar = 2; // i flagę że pomiar zakończony
		TCCR1B|=(1<<ICES1); // przygotowujemy timer do kolejnej transmisji
	}
}

 

 

Edytowano przez _LM_
  • Lubię! 1
Link to post
Share on other sites

Nie wiem czy się gdzieś nie machnąłem(pewnie tak xD) niestety aby przekodować to do typowego arduinowego kodu trzeba prosić bardziej ogarniętych w temacie kolegów

Link to post
Share on other sites
2 godziny temu, ximot napisał:

Jak ten kod, który zamieściłeś powyżej ma się w czasie do obsługi funkcji czujnika SR04?

Można powiedzieć że czasowo ten programik w ogóle nie obciąża mikrokontrolera, timery mają taką właściwość że pracują w tle, my sprawdzamy tylko flagę start_pomiar. Jak widzisz użyłem tylko jednego delay na czas ustawiania impulsu do SR, reszta programu w loop wykonuje się praktycznie bez spowolnień. 

Link to post
Share on other sites
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

(edytowany)

Jak wstawiłeś komentarze to przynajmniej chociaż trochę wiadomo o co chodzi 😄

Edytowano przez ximot
Link to post
Share on other sites
1 godzinę temu, _LM_ napisał:

Nie wiem czy się gdzieś nie machnąłem(pewnie tak xD) niestety aby przekodować to do typowego arduinowego kodu trzeba prosić bardziej ogarniętych w temacie kolegów

Chyba nie rozumiem. Czyli cały programik, który napisałeś trzeba przekodować na język Arduino?

Link to post
Share on other sites

Chodzi o to że możesz zamiast tych "dziwnych" napisów typu PORTB|=(1<<PB0) zastąpić instrukcją digitalWrite(); choć uważam że warto znać co w procku piszczy a operatory bitowe to jest taki must hew każdego orogramisty

Link to post
Share on other sites

Wgl spróbuj to skopiować do jakiegoś swojego projektu i włącz kompilację. Najlepiej Utwórz sobie jakiś inny oddzielny do tego celu. 

Link to post
Share on other sites

Ok, serdeczne dzięki za pomoc i rozjaśnienie tematu. Muszę sobie to na spokojnie przemyśleć i trochę się z tym pobawić. 

Link to post
Share on other sites

Aha i najpewniej pomyliłem się w ustawieniu pinu od icp to raczej jest PB6 sprawdź to proszę

Link to post
Share on other sites

Jasne, jeszcze raz dzięki, doceniam Twoją pomoc. Odezwę się jak będę miał więcej czasu, żeby z tym tematem pokombinować. 

Link to post
Share on other sites

Zacząłem trochę analizować Twój kod i staram się go w jakiś sposób zrozumieć i zamienić na swój. Czy mógłbyś odpowiedzieć na pytania w komentarzach i ewentualnie zwrócić uwagę na to co zrobiłem tutaj źle?

 

void setup() { // tu się znajduje arduinowy setup

  pinMode (BZR, OUTPUT);                                    // ustawienie buzzera na wyjscie
  pinMode (TRIG, OUTPUT);                                   // ustawienie wyjscia sr04  
  pinMode (ECHO, INPUT);                                    // ustawienie wejscia sr04

  TCCR1B|=(1<<CS11)|(1<<CS12);                              // tu jest nastawa prescalera czyli wstępnego dzielnika częstotliwości wejściowej UWAGA! ustawienie tegoż od razu go uruchamia!
  TCCR1B|=(1<<ICES1);                                       // co oznacza ta linijka? co to jest TCCR1B, a co oznacza (1<<ICES1)
//  TIMSK|=(1<<TICIE1);                                    // co oznacza ta linijka?
}
 
void loop() { 
  
  if(start_pomiar == 0){                                 // czy w tym miejscu dobrze zamieniłem Twój kod na swój arduinowy?
      digitalWrite (TRIG, LOW);    
      delayMicroseconds(5);
      digitalWrite (TRIG, HIGH);  
      start_pomiar = 1; 								// od tego momentu czekamy aż sr zadziała (czyli zbocze narastające)
    }
    
  if(start_pomiar == 2){
    
    odleglosc = wynik / 58 ;        // zamiana zmierzonego czasu przez sr04 na odleglosc
    
        if (odleglosc < 15){        // jesli odlegosc mniejsza niz 15cm to alarm
    digitalWrite (BZR, HIGH);
    }
        if (odleglosc > 15){      // jesli odlegosc większaa niz 15cm to wyłacz alarm
    digitalWrite (BZR, LOW);
    }

    start_pomiar = 0;
  }
 }



ISR(TIMER1_CAPT_vect){ // przerwanie od przechwytu

  if(TCCR1B & (1<<ICES1)){  						// co dokładnie oznaczają te warunki?
    TCNT0 = 0; // zerujemy timer
  TCCR1B&=~(1<<ICES1);  // zmieniamy ustawienia tak że następne przerwanie będzie na zboczu opadającym(po impulsie z SR)
    // w tym momencie timer liczy czas impulsu 
  }
  
  if(!TCCR1B & (1<<ICES1)){ 						// co dokładnie oznaczają te warunki?
    wynik = TCNT0; // napisałeś, że mamy zmierzoną odległość, ale rozumiem, że chodziło Ci o czas
    start_pomiar = 2; // i flagę że pomiar zakończony
    TCCR1B|=(1<<ICES1); 							// co oznacza ta linijka? 
  }
}

 

 

Link to post
Share on other sites
(edytowany)

TCCR1B to jest rejestr licznika nr1 robiąc tę operacje:

TCCR1B|=(1<<ICES1);

tak naprawdę dzieje się coś takiego:

TCCR1B = TCCR1B| 1<<ICES1 czyli na wybranym bicie tegoż rejestru ustawisz jedynkę można to zapisać jako 

TCCR1B|= (1<<6);

czyli ustaw jedynkę na szóstym bicie. Ogólnie aby poznać te zawiłości należy sięgnąć do noty katalogowej mikrokontrolera. Dalej...

//  TIMSK|=(1<<TICIE1);                                    // co oznacza ta linijka?

tu znów odnosimy się do rejestru przerwań (Timer/Counter Interrupt Mask Register) i  znów ustawiamy bit TICIE1 który jest odpowiedzialny za przerwania od przechwytu. Oczywiście powinien być odkomentowany ponieważ popełniłem tam błąd.

 

1 godzinę temu, ximot napisał:

if(start_pomiar == 0){ // czy w tym miejscu dobrze zamieniłem Twój kod na swój arduinowy? digitalWrite (TRIG, LOW); delayMicroseconds(5); digitalWrite (TRIG, HIGH); start_pomiar = 1; // od tego momentu czekamy aż sr zadziała (czyli zbocze narastające) }

 

 

if(start_pomiar == 0){ // czy w tym miejscu dobrze zamieniłem Twój kod na swój arduinowy? digitalWrite (TRIG, LOW); delayMicroseconds(5); digitalWrite (TRIG, HIGH); start_pomiar = 1; // od tego momentu czekamy aż sr zadziała (czyli zbocze narastające) }

tak

if(TCCR1B & (1<<ICES1)){  // co dokładnie oznaczają te warunki?

tym razem sprawdzam w jakim stanie jest bit ICES1 to już wykonuje się w przerwaniu więc należy sprawdzić na jakim zboczu impulsu wejściowego to przerwanie nastąpiło stąd ten warunek.

if(!TCCR1B & (1<<ICES1)){// co dokładnie oznaczają te warunki?

to samo co wyżej z tym że ten warunek spełni się gdy ICES1 jest wyzerowany czy wtedy gdy przerwanie nastąpiło na zboczu opadającym

wynik = TCNT0; // napisałeś, że mamy zmierzoną odległość, ale rozumiem, że chodziło Ci o czas

tak co więcej proste przeliczenie tego tak jak to robisz w pętli głównej nie wystarczy ale o tym za chwilę bo UWAGA tu jest błąd wynik powinno się odczytywać w ten sposób

wynik = ICR1; // ICR1 jest rejestrem do którego automatycznie przepisywana jest wartość timera podczas przerwania na ICP
TCCR1B|=(1<<ICES1); // co oznacza ta linijka? 

tym razem nastawiamy z powrotem ICP aby reagował na zbocze narastające impulsu. No to tak z grubsza niestety dzisiaj już nie dam rady więcej napisać jak coś to jutro.

Edytowano przez _LM_
  • Lubię! 1
Link to post
Share on other sites

Ok dzisiaj mam nieco więcej czasu więc napisałem na szybko programik obsługujący HCSR za pomocą ICP. Postaram się jak najdokładniej wszystko opisać. Tak samo będę wrzucał to co uważam żeby miało działać z bibliotekami od Arduino. Zakładam że masz stdandardowego klona z kwarcem 16Mhz.

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#include "MK_MULTI_UART/mk_multi_uart.h"

volatile uint8_t start_pomiar = 0; // zmienna pomocnicza
volatile uint16_t wynik;

int main(void) {
	uart_init();   // czyli Serial.begin()
	DDRC |= (1 << PC2);
	PORTC &= (1 << PC2);
	DDRC |= (1 << PC1);
	PORTD |= (1 << PD6); // icp
	TCCR1B |= (1 << CS11); // f/8 = 2Mhz = 1imp = 500nS
	TIMSK |= (1 << TICIE1);
	sei();
	uart_puts(0, "START"); // Serial.prinln("START");
	for (;;) {

		if (!start_pomiar) {
			PORTC |= (1 << PC2);
			_delay_us(15);
			PORTC &= ~(1 << PC2);

			TIFR &= ~(1 << ICF1); // kasowanie flagi przerwania na wypadek gdyby sie "uchowalo"
			TIMSK |= (1 << TICIE1); // przerwania od przechwytu
			TCNT1 = 0;          // zerowanie timera

			TCCR1B |= (1 << ICES1);   // przerwanie na zboczu narastajacym
			start_pomiar = 1;
		}

		if (start_pomiar == 2) { // jest wynik
			uart_puts(0, "Wynik:");

			uart_putint(0, wynik, 10); // surowa wartosc
			uart_puts(0, "\r\n");
			uart_putint(0, wynik / 116, 10); // przeliczenie na cm
			uart_puts(0, "cm");
			uart_puts(0, "\r\n");

			_delay_ms(500); // co 0.5s pomiar

			start_pomiar = 0;
		}
		if (start_pomiar == 3) {
			uart_puts(0, "POZA ZAKRESEM!"); // gdy przerwanie od przepelnienia
			start_pomiar = 0;
		}
	}
}

ISR(TIMER1_CAPT_vect) { // przechwyt

	if (TCCR1B & (1 << ICES1)) {
		TCNT1 = 0;
		TCCR1B &= ~(1 << ICES1); // nastepne przerwanie na zbocze opadajace 
		TIMSK |= (1 << TOIE1);    // odblokowanie przerwania od przepelnienia
	} else {
		TIMSK &= ~(1 << TOIE1); // blokuj przerwania przepelnienia
		TIMSK &= ~(1 << TICIE1); // blokuj przerwanie icp
		TCCR1B |= (1 << ICES1); // nast przerwanie na zbocze narastajace
		wynik = ICR1;
		start_pomiar = 2;
	}
}

ISR(TIMER1_OVF_vect) { // przepelnienie
	PORTC ^= (1 << PC1); // sygnalizacja przepelnienia
	start_pomiar = 3;
	TIMSK &= ~(1 << TOIE1); // blokowanie tego przerwania
}

To co widzisz jest programem napisanym w C z dołączonymi bibliotekami.

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "MK_MULTI_UART/mk_multi_uart.h"

U ciebie najpewniej wystarczy tylko dołączyć #include <Arduino.h>

volatile uint8_t start_pomiar = 0; // zmienna pomocnicza
volatile uint16_t wynik;

prefiks volatile jest potrzebny ponieważ zmienna jest obsługiwana w przerwaniu

int main(void) {
	uart_init();   // czyli Serial.begin()
	DDRC |= (1 << PC2);
	PORTC &= (1 << PC2);
	DDRC |= (1 << PC1);
	PORTD |= (1 << PD6); // icp
	TCCR1B |= (1 << CS11); // f/8 = 2Mhz = 1imp = 500nS
	TIMSK |= (1 << TICIE1);
	sei();
	uart_puts(0, "START"); // Serial.prinln("START");

U ciebie będzie to setup(); ustawienia portów omówiłem we wcześniejszym poście, myślę że nie będzie problemu. Jednak linijka

TCCR1B |= (1 << CS11); // f/8 = 2Mhz = 1imp = 500nS

może rodzić pytania. Otóż tutaj ustawiamy preskaler czyli wstępny dzielnik częstotliwości tutaj = 8 reszta w komentarzu. Jest to w miarę wygodna częstotliwość dla naszego projektu dlatego że gdy timer zliczy 2 impulsy (2*500nS = 1µS) mija jedna mikrosekunda.

OK wskakujemy w pętlę nieskończoną loop();

		if (!start_pomiar) {
			PORTC |= (1 << PC2);
			_delay_us(15);
			PORTC &= ~(1 << PC2);

			TIFR &= ~(1 << ICF1); // kasowanie flagi przerwania na wypadek gdyby sie "uchowalo"
			TIMSK |= (1 << TICIE1); // przerwania od przechwytu
			TCNT1 = 0;          // zerowanie timera

			TCCR1B |= (1 << ICES1);   // przerwanie na zboczu narastajacym
			start_pomiar = 1;
		}

Jeśli start_pomiar ==0 to wysyłka impusu do HC... następnie 

TIFR &= ~(1 << ICF1); // kasowanie flagi przerwania na wypadek gdyby sie "uchowalo"

tak to może się zdarzyć więc kasujemy bit Interrupt Capture Flag w rejestrze Timer Interrupt Mask Register

Następne linijki mam nadzieję że są zrozumiałe. Wskakujemy w warunek kiedy mamy pomiar

			uart_puts(0, "Wynik:");

			uart_putint(0, wynik, 10); // surowa wartosc
			uart_puts(0, "\r\n");
			uart_putint(0, wynik / 116, 10); // przeliczenie na cm
			uart_puts(0, "cm");
			uart_puts(0, "\r\n");

			_delay_ms(500); // co 0.5s pomiar

			start_pomiar = 0;

wiadomo uart_puts zastąpisz serial.begin(). Obliczanie wyniku w cm:

uart_putint(0, wynik / 116, 10); // przeliczenie na cm

Dzielisz przez 116 gdyż czas impulsu nie jest liczony co jedną us a co 500nS stąd taka wartość (58*2).

Po wyświetleniu wyniku warto nieco odczekać zrobiłem to delay-em nie jest to rozwiązanie eleganckie no ale niech będzie. Opóźnienie jest konieczne gdyż HC... ma ograniczoną ilość pomiarów w sekundzie różne źródła różnie mówią czytałem gdzieś o max 20 pomiarów/sek.

Ostatni warunek w loop

		if (start_pomiar == 3) {
			uart_puts(0, "POZA ZAKRESEM!"); // gdy przerwanie od przepelnienia
			start_pomiar = 0;
		}

   Może się zdarzyć że licznik się "przekręci" czyli doliczy do swojego MAX(65535) i zacznie liczyć od zera. Czasami zdarza się że hc potrafi się zawiesić przy większych odległościach wtedy można to pośrednio wykryć dzięki temu przerwaniu.

Wisienka na torcie: przerwania

ISR(TIMER1_CAPT_vect) { // przechwyt

	if (TCCR1B & (1 << ICES1)) { // sprawdzam na jakie zbocze zareagoalo przerwanie
		TCNT1 = 0;
		TCCR1B &= ~(1 << ICES1); // nastepne przerwanie na zbocze opadajace 
		TIMSK |= (1 << TOIE1);    // odblokowanie przerwania od przepelnienia
	} else {
		TIMSK &= ~(1 << TOIE1); // blokuj przerwania przepelnienia
		TIMSK &= ~(1 << TICIE1); // blokuj przerwanie icp
		TCCR1B |= (1 << ICES1); // nast przerwanie na zbocze narastajace
		wynik = ICR1;
		start_pomiar = 2;
	}
}

TCCR1B wg notykatalogowej  Timer/Counter1 Control Register B czyli rejestr kontrolny "B" timera1

if (TCCR1B & (1 << ICES1)) { // sprawdzam na jakie zbocze zareagoalo przerwanie

Jeśli w rejestrze ICES1 (Input Capture Edge Select)była jedynka to znaczy że zbocze było narastające i czas rozpocząć pomiar. 

Zeruję timer (Timer/Counter1)

TCNT1 = 0;

i liczymy czas....... a gdy

	} else {
		TIMSK &= ~(1 << TOIE1); // blokuj przerwania przepelnienia
		TIMSK &= ~(1 << TICIE1); // blokuj przerwanie icp
		TCCR1B |= (1 << ICES1); // nast przerwanie na zbocze narastajace
		wynik = ICR1;
		start_pomiar = 2;

wcześniejszy warunek dał w odpowiedzi 0 wykonuje się ten fragment. 

Przerwanie od przepełnienia

ISR(TIMER1_OVF_vect) { // przepelnienie
	PORTC ^= (1 << PC1); // sygnalizacja przepelnienia
	start_pomiar = 3;
	TIMSK &= ~(1 << TOIE1); // blokowanie tego przerwania
}

jeśli do niego doszło to dioda zmieni swój stan na przeciwny, blokuję też to przerwanie przez wyzerowanie bitu TOIE1 po to aby przy zablokowanym HC program nie skakał do tej funkcji.

No mam nadzieję że teraz to jaśniej opisałem a przede wszystkim zaprezentowany program na pewno działa. Spróbuj go przerobić stosownie do bibliotek używanych przez Arduino a potem można działać ze swoją wersją. Zachęcam też do przejrzenia noty katalogowej Atmegi(w uno jest 328?) W AVR jest to fajne że jak poznasz jeden mikrokontroler to w innych nazwy rejestrów bywają te same lub bardzo podobne do siebie.   

  • Lubię! 1
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!

Anonim
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...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.