Używanie millis () i micros () w procedurze przerwań

13

Dokumentacja attachInterrupt()mówi:

... millis()liczy się przerwanie, więc nigdy nie zwiększy się w ISR. Ponieważ delay()wymaga przerwania do pracy, nie zadziała, jeśli zostanie wywołany wewnątrz ISR. micros()działa początkowo, ale zacznie zachowywać się nieregularnie po 1-2 ms. ...

Czym się micros()różni millis()(z wyjątkiem oczywiście ich precyzji)? Czy powyższe ostrzeżenie oznacza, że ​​korzystanie micros()z procedury przerwania jest zawsze złym pomysłem?

Kontekst - Chcę zmierzyć zajętość niskiego impulsu , więc muszę uruchomić moją procedurę, gdy zmieni się mój sygnał wejściowy i zapisać bieżący czas.

Petr Pudlák
źródło

Odpowiedzi:

16

Inne odpowiedzi są bardzo dobre, ale chcę rozwinąć sposób micros()działania. To zawsze odczytuje bieżący timera sprzętowego (ewentualnie TCNT0), który jest stale aktualizowany przez sprzęt (w rzeczywistości, co 4 ms powodu preskalera 64). Następnie dodaje licznik przepełnienia timera 0, który jest aktualizowany przez przerwanie przepełnienia timera (pomnożone przez 256).

Dlatego nawet w ISR możesz polegać na micros()aktualizacji. Jeśli jednak zaczekasz zbyt długo , przegapisz aktualizację przepełnienia, a wtedy zwrócony wynik spadnie (tj. Otrzymasz 253, 254, 255, 0, 1, 2, 3 itd.)

Jest to micros()- nieco uproszczone, aby usunąć definicje dla innych procesorów:

unsigned long micros() {
    unsigned long m;
    uint8_t oldSREG = SREG, t;
    cli();
    m = timer0_overflow_count;
    t = TCNT0;
    if ((TIFR0 & _BV(TOV0)) && (t < 255))
        m++;
    SREG = oldSREG;
    return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

Powyższy kod dopuszcza przepełnienie (sprawdza bit TOV0), aby mógł poradzić sobie z przepełnieniem, gdy przerwania są wyłączone, ale tylko raz - nie ma możliwości obsługi dwóch przepełnień.


TLDR;

  • Nie rób opóźnień w ISR
  • Jeśli musisz to zrobić, możesz czas, micros()ale nie millis(). Również delayMicroseconds()jest taka możliwość.
  • Nie opóźniaj więcej niż 500 µs, bo przegapisz przepełnienie timera.
  • Nawet krótkie opóźnienia mogą spowodować, że przegapisz przychodzące dane szeregowe (przy 115200 bodów dostaniesz nowy znak co 87 µs).
Nick Gammon
źródło
Nie rozumiem podanego poniżej oświadczenia używanego w programie micros (). Czy możesz prosić o opracowanie? Ponieważ ISR jest zapisany, flaga TOV0 zostanie wyczyszczona, gdy tylko ISR zostanie wprowadzony, a zatem poniższe warunki mogą nie być spełnione! if ((TIFR0 i _BV (TOV0)) && (t <255)) m ++;
Rajesh
micros()nie jest ISR. Jest to normalna funkcja. Flaga TOV0 pozwala przetestować sprzęt, aby sprawdzić, czy nastąpiło przepełnienie timera (ale jeszcze nie zostało przetworzone).
Nick Gammon
Ponieważ test jest wykonywany przy wyłączonych przerwach, wiesz, że flaga nie zmieni się podczas testu.
Nick Gammon
Chcesz powiedzieć, że po funkcji cli () wewnątrz funkcji micros (), jeśli nastąpi przepełnienie, to musisz to sprawdzić? To ma sens. W przeciwnym razie nawet jeśli micros nie jest funkcją, TIMER0_OVF_vect ISR wyczyści TOV0 w TIFR0, co jest moim wątpliwością.
Rajesh
Także dlaczego TCNT0 jest porównywany z 255, aby sprawdzić, czy jest mniejszy niż 255? Co się stanie po cli (), jeśli TCNT0 osiągnie 255?
Rajesh
8

Nie jest niewłaściwe używanie millis()lub micros()wykonywanie procedury przerwania.

To jest źle wykorzystać je nieprawidłowo.

Najważniejsze jest to, że podczas rutynowego przerywania „zegar nie tyka”. millis()i micros()nie zmieni się (no cóż, micros()początkowo będzie, ale kiedy minie ten magiczny milisekundowy punkt, w którym wymagany jest milisekundowy haczyk, wszystko się rozpada).

Możesz więc z pewnością zadzwonić millis()lub micros()sprawdzić aktualny czas w swoim ISR, ale nie oczekuj, że ten czas się zmieni.

To właśnie ten brak zmiany w czasie jest ostrzegany w cytowanym przez ciebie cytacie. delay()polega na millis()zmianie, aby wiedzieć, ile czasu minęło. Ponieważ to się nie zmienia, delay()nigdy się nie skończy.

Zasadniczo millis()i micros()poda czas, w którym został wywołany Twój ISR, bez względu na to, kiedy go używasz.

Majenko
źródło
3
Nie, micros()aktualizacje. Zawsze odczytuje rejestr timera sprzętowego.
Nick Gammon
4

Cytowane wyrażenie nie jest ostrzeżeniem, jest jedynie stwierdzeniem o tym, jak rzeczy działają.

Nie ma nic z natury niewłaściwego w użyciu millis()lub micros()w ramach prawidłowo napisanej procedury przerwania.

Z drugiej strony, robienie czegokolwiek w niewłaściwie napisanej procedurze przerwań jest z definicji błędne.

Procedura przerwania, której wykonanie zajmuje więcej niż kilka mikrosekund, jest prawdopodobnie napisana nieprawidłowo.

W skrócie: Prawidłowo napisana procedura przerwania nie spowoduje ani nie napotka problemów z millis()lub micros().

Edycja: Jeśli chodzi o „dlaczego micros ()„ zaczyna zachowywać się nieprawidłowo ””, jak wyjaśniono na stronie internetowej „ badanie funkcji mikr Arduino ”, micros()kod na zwykłym Uno jest funkcjonalnie równoważny z

unsigned long micros() {
  return((timer0_overflow_count << 8) + TCNT0)*(64/16);
}

Zwraca to czterobajtowy niepodpisany długi składający się z trzech najniższych bajtów z timer0_overflow_counti jednego bajtu z rejestru licznika timera-0.

Jest timer0_overflow_counton zwiększany mniej więcej raz na milisekundę przez modułTIMER0_OVF_vect obsługi przerwań, jak wyjaśniono w badaniu strony internetowej funkcji arduino millis .

Przed rozpoczęciem obsługi przerwań sprzęt AVR wyłącza przerwania. Jeśli (na przykład) moduł obsługi przerwań miałby działać przez pięć milisekund z przerwaniami wciąż wyłączonymi, co najmniej cztery przepełnienia timera 0 zostałyby pominięte. [Przerwania napisane w kodzie C w systemie Arduino nie są ponownie wysyłane (zdolne do prawidłowej obsługi wielu nakładających się wykonań w ramach tego samego modułu obsługi), ale można napisać moduł obsługi języka asemblera ponownego wprowadzania, który ponownie włącza przerwania, zanim rozpocznie się czasochłonny proces.]

Innymi słowy, przepełnienia timera nie „gromadzą się”; za każdym razem, gdy nastąpi przepełnienie, zanim zostanie obsłużone przerwanie z poprzedniego przepełnienia, millis()licznik traci milisekundę, a rozbieżność timer0_overflow_countz kolei również micros()znika przez milisekundę.

Jeśli chodzi o „krótszy niż 500 μs” jako górny limit czasowy przetwarzania przerwań, „aby zapobiec zbyt długiemu blokowaniu przerwania timera”, możesz wzrosnąć do nieco poniżej 1024 μs (np. 1020 μs) i millis()nadal działałby, większość czas. Uważam jednak, że program obsługi przerwań, który przyjmuje więcej niż 5 μs jako opieszałość, więcej niż 10 μs jako lenistwo, ponad 20 μs jak ślimak.

James Waldby - jwpat7
źródło
Czy mógłbyś wyjaśnić „jak działają rzeczy”, w szczególności dlaczego micros()„zacząć zachowywać się nieregularnie”? A co rozumiesz przez „poprawnie napisaną procedurę przerwania”? Zakładam, że oznacza to „krótszy niż 500us” (aby zapobiec zbyt długiemu blokowaniu przerwania timera), „używanie zmiennych komunikacyjnych do komunikacji” i „niewywoływanie kodu biblioteki” w jak największym stopniu, czy jest coś jeszcze?
Petr Pudlák
@ PetrPudlák, patrz edycja
James Waldby - jwpat7