Przerwanie (ang. interrupt) lub żądanie przerwania (ang. interrupt request, IRQ) – sygnał powodujący zmianę przepływu sterowania, niezależnie od aktualnie wykonywanego programu. Pojawienie się przerwania powoduje wstrzymanie aktualnie wykonywanego programu i wykonanie przez procesor kodu procedury obsługi przerwania (ang. interrupt handler). Procedura ta wykonuje czynności związane z obsługą przerwania i na końcu wydaje instrukcję powrotu z przerwania, która powoduje powrót do programu realizowanego przed przerwaniem. [Źródło: wikipedia.org]
Przerwania w ESP32
W systemach komputerowych występują różne rodzaje przerwań. Możemy je podzielić na programowe, sprzętowe, wewnętrzne i zewnętrzne. W tym artykule opiszę jak skorzystać z przerwania generowanego przez timer układu ESP32.
Często podczas pisania programu musimy wykonać jakąś akcję co określony odcinek czasu. Niezależnie od wykonywanego właściwego programu w równych odstępach czasowych musimy wykonać kilka operacji. W tym przypadku najprostszą realizacją będzie zastosowanie przerwania zegarowego. Programowo ustawimy czas (interwał) pomiędzy wykonywaniem niezbędnych operacji, a resztą zajmie się układ ESP. Obsługa przerwania zegarowego wymaga zastosowania kilku poleceń konfiguracyjnych. Pierwszym z nich jest timerBegin(), który inicjalizuje system przerwań zegarowych. Funkcja ta wymaga trzech parametrów. Pierwszym jest numer zegara, który wykorzystujemy (0-3), drugi parametr to preskaler, trzeci to sposób zliczania (true - w górę, false - w dół).
timer = timerBegin(0, 80, true);
Kilka słów o preskalerze. Standardowo zegar może być taktowany sygnałem bazowym o częstotliwości 80MHz. Częstotliwość ta jest zależna od rodzaju zastosowanego układu ESP. Oznacza to, że zegar może zmieniać swoją wartość 80 milionów razy na sekundę. Preskaler umożliwia podział tej częstotliwości, czyli zmniejszenie taktowania. W przykładzie powyżej sygnał bazowy zostanie podzielony przez 80, co da częstotliwość taktowania zegara równą 1MHz (milion razy na sekundę). Preskaler w układach ESP32 jest 16-to bitowy, czyli możemy zapisać w nim wartość z przedziału 0-65535.
W kolejnym kroku należy powiązać utworzony timer z funkcją, która będzie przez niego wywoływana. Służy do tego polecenie timerAttachInterrupt(). Przyjmuje ono trzy parametry. Pierwszym jest nazwa utworzonego timera, drugim nazwa funkcji wywoływanej w przerwaniu, trzecim sposób wyzwalania przerwania (true - narastające zbocze). Trzeci parametr nie ma w tym wypadku znaczenia (istotny dla osób, które dokładnie wiedzą co chcą uzyskać).
timerAttachInterrupt(timer, &onTimer, true);
Ostatnią funkcją konfigurującą nasze przerwanie jest timerAlarmWrite(). Określa ona wartość licznika przy której zostanie wygenerowane przerwanie. Pierwszym parametrem przekazanym funkcji będzie timer, drugim wartość, przy której wywołane zostanie przerwanie(wpisanie 1 miliona spowoduje odpalenie przerwania co jedną sekundę), a trzeci parametr określa czy timer zostanie przeładowany (true - przeładowanie zegara - przerwanie cykliczne).
timerAlarmWrite(timer, 1000000, true);
Pozostało tylko włączyć obsługę zdefiniowanego przerwania poleceniem timerAlarmEnable(). Polecenie to przyjmuje jeden parametr - nazwę naszego zdefiniowanego timera.
timerAlarmEnable(timer);
Powyżej opisałem niezbędne elementy konfiguracyjne przerwania zegarowego, zapomniałem tylko o małym drobiazgu czyli funkcji obsługującej to przerwanie. Wiadomo, że powinna nazywać się onTimer(). Poniżej struktura typowej funkcji:
void IRAM_ATTR onTimer() {
//instrukcje wykonywane w przerwaniu
}
Funkcja niczego nie zwraca (void), nie przekazujemy też żadnych parametrów. Atrybut IRAM_ATTR jest niezbędny dla kompilatora, żeby umieścił kod obsługi przerwania w odpowiednim miejscu w pamięci ESP32. Poniżej przykładowy kod, który wykorzystuje przerwanie zegarowe do .... zapalania i gaszenia diody co jedną sekundę.
#define LED_PIN 2 //ESP32 Lolin D32 dioda LED
hw_timer_t * timer = NULL; //definicja obiektu zegara na którym pracujemy
//funkcja obsługi przerwania
void IRAM_ATTR onTimer() {
//zmiana stanu pinu na przeciwny
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
timer = timerBegin(0, 80, true);
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmWrite(timer, 1000000, true);
timerAlarmEnable(timer);
}
void loop() {
//główna pętla przesyła co 3 sekundy komunikat
Serial.println("Główna pętla blokowana na 3 sekundy");
delay(3000);
}
Wnioski
Obsługa przerwań zegarowych w układach ESP nie jest czymś skomplikowanym. Wystarczy kilka linii kodu i wszystko działa. Oczywiście zachęcam do wykorzystywania przerwań do czegoś bardziej ambitnego niż zapalanie diody. W odrębnym artykule postaram się opisać przerwania wywoływane zdarzeniami zewnętrznymi (zmiana stanu pinu).