Skocz do zawartości
mareq

Arduino- enkoder obrotowy - jakim cudem działa ten kod?

Pomocna odpowiedź

Chodzi mi o kod dostępny jako przykład dla enkodera od WaveShare:

#define PinA 2  
#define PinB 3  

unsigned long time = 0; 
long count = 0; 
long num = 0;


void setup()
{
 Serial.begin(9600);

 pinMode(PinA,INPUT); 
 pinMode(PinB,INPUT); 

 attachInterrupt(0, blinkA, LOW);  
 attachInterrupt(1, blinkB, LOW);  

 time = millis(); 
}

void loop()
{
 while (num != count)
 {
   num = count;
   Serial.println(num);
 }
}

void blinkA()
{
 if ((millis() - time) > 3)
       count ++; 
 time = millis();
}

void blinkB()
{
 if ((millis() - time) > 3)  
       count --;
 time = millis();
}

Ten kod w realu działa, spawdzone.

Po pierwsze: dlaczego przerwanie aktywowane jest stanem a nie zboczem. Toż to te przerwania powinny się wykonywać niemal na okrągło.

Po drugie: n=mamy funkcję "blinkA", która zwiększa licznik oraz "blinkB", która zmniejsza. Przecież kręcąc osią enkodera w jedną stronę stan niski pojawia się sekwencyjnie na obu wejściach a tymczasem licznik zlicza właściwie a na chłopski rozum nie powinien. Gdzie robię błąd w rozumowaniu?

Udostępnij ten post


Link to post
Share on other sites

Zaciekawił mnie ten przypadek, szczególnie że masz rację pisząc - to nie ma szans działać 🙂

Ten kod jest świetnym przykładem, który potwierdza dwie rzeczy: kody dostępne w internecie bywają bardzo różnej jakości (czytaj bardzo kiepskiej). Druga - to że coś działa nie znaczy, że to dobry program.

Zacznijmy od początku. Sprawdziłem - faktycznie program działa, wyniki wyglądają bardzo ładnie, tylko program jakby mniej.

Patrząc na program podejrzewałem, że tajemnica kryje się w 3 ms filtrowaniu. Jeśli impulsy mają mą szerokość <= 3ms to program powinien działać poprawnie. Podłączyłem analizator i wyszło coś takiego:

Od razu widać, że impulsy są szersze niż 3ms, więc jak to działa? Spróbowałem trochę pozmieniać program - na początek wyrzuciłem przerwanie od pinu 3, czyli został tylko blinkA. Program nie wykrywa już kierunku ruchu, ale poza tym działa idelanie... Magia?

Już planowałem poszukiwać księgi czarnej magii, gdy przyszło mi do głowy coś innego - a co będzie jak wyrzucimy ten 3ms filtr? Okazuje się że wtedy faktycznie przychodzi dużo przerwań. Zmieniłem więc trochę program i wyświetlam nie tylko odczyt enkodera, ale również liczbę zgłoszonych przerwań:

#define PinA 2 
#define PinB 3 

unsigned long time = 0;
long count = 0;
long num = 0;
unsigned long repeats = 0;

void setup()
{
 Serial.begin(9600);
 Serial.println("Hello world!");

 pinMode(PinA,INPUT);
 pinMode(PinB,INPUT);

 attachInterrupt(0, blinkA, LOW); 
 //attachInterrupt(1, blinkB, LOW); 

 time = millis();
}

void loop()
{
 while (num != count)
 {
   num = count;
   Serial.print(num);
   Serial.print(" (");
   Serial.print(repeats);
   Serial.println(")");
   repeats = 0;
 }
}

void blinkA()
{
 repeats++;
 if ((millis() - time) > 3)
   count ++;
 time = millis();
}

void blinkB()
{
 if ((millis() - time) > 3) 
   count --;
 time = millis();
}

Rezultaty wyglądają mniej więcej jak:

Hello world!

1 (1065)

2 (4292)

3 (6058)

4 (1057)

5 (1049)

6 (1039)

7 (1069)

8 (1054)

9 (1054)

10 (1763)

11 (496)

12 (654)

13 (1018)

14 (1673)

15 (1668)

16 (1264)

17 (1775)

18 (725)

19 (576)

20 (751)

21 (881)

22 (983)

23 (1703)

24 (1695)

25 (1689)

26 (1691)

27 (1349)

28 (1706)

29 (1694)

30 (1698)

31 (1707)

Teraz już lepiej widać jak działa magia tego programu. Faktycznie przerwanie zgłaszane jest przy stanie niskim. Co więcej zgłaszane jest tysiące razy na każdy impuls. Jak więc działa filtracja?

Otóż procesor nie ma czasu na nic poza obsługą przerwania - czyli wchodzi do procedury, sprawdza czy minęły 3ms, jeśli nie to kończy obsługę. Ale od razu wykrywa że przerwanie oczekuje, więc wraca i jeszcze raz obsługuje przerwanie... I tak w kółko aż stan pinu zmieni się na wysoki.

Ponieważ to przerwanie zajmuje cały czas procesora, nie są obsługiwane inne przerwania, w tym przerwanie timera... Dlatego dla procesora nie mijają nawet 3ms chociaż w rzeczywistości to powiedzmy 20ms.

To chyba wyjaśnia jak działa ten koszmarny program. Należałoby go oprawić w ramki i z daleka pokazywać studentom: Uwaga tak nigdy nie należy robić! A że to działa to już zupełnie inna sprawa....

Udostępnij ten post


Link to post
Share on other sites

Jeden drobiazg: po powrocie z przerwania procesor (w przypadku avr) zawsze wykonuje następną instrukcję przed wejściem w następne przerwanie. I tu się pewnie kryje tajemnica pozornie prawidłowego działania...

Udostępnij ten post


Link to post
Share on other sites

Wykonywanie jednej instrukcji nie tłumaczy dlaczego licznik count daje poprawne wyniki - ale jeśli milis() przestaje działać, mamy gotowe wytłumaczenie "fenomenu" tego programu. Nie jestem ekspertem od przerwań AVR, ale domyślam się, że mamy jakiś prosty układ priorytetów, więc jeśli przerwanie od pinu ma wyższy priorytet niż przerwanie zegara, milis() ma prawo przestać działać. A głowny program możliwe że wykonuje się po jednej instrukcji na przerwanie - wierzę na słowo.

Udostępnij ten post


Link to post
Share on other sites

O ile wiem, to przerwania w AVR są kolejkowane - czyli przerwanie zegarowe kiedyś tam się wykona...

Tak przy okazji - nie rozumiem idei przerwania wywoływanego stanem a nie zdarzeniem... Ale to już inna bajka 🙂

Udostępnij ten post


Link to post
Share on other sites

O ile wiem to nie do końca tak - jeśli więcej niż jedno przerwanie jest zgłoszone w momencie gdy mikrokontroler rozpoczyna obsługę przerwania, zawsze wykona to o najwyższym priorytecie. Nie ma więc kolejkowania, chociaż zgłoszenie przerwania jest niejako pamiętane - więc kiedyś się wykowna, ale dopiero po obsłudze wszystkich z wyższym priorytetem.

Dlatego jeśli przerwanie o wyższym priorytecie jest stale zgłaszane może "zagłodzić" pozostałe przerwania. To prowadzi do różnych błędów, np. złego zliczania czasu... I raczej nie pownno być traktowane jako przykład dobrego programu 😃

Udostępnij ten post


Link to post
Share on other sites

Nie pamiętam już gdzie to czytałem (chyba u Jeleńskiego, ale o piwo się nie założę): przykład otrzymania prawidłowego wyniku mimo błędnego zastosowania wzoru.

Czy to nie to samo? 🙂

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!

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