Pisanie procedur obsługi przerwań¶
Na odpowiednim sprzęcie MicroPython oferuje możliwość pisania procedur obsługi przerwań w języku Python. Procedury obsługi przerwań - znane również jako procedury obsługi przerwań (ISR, interrupt service routines) - są definiowane jako funkcje wywołania zwrotnego. Są one wykonywane w odpowiedzi na zdarzenie, takie jak wyzwolenie licznika czasu (timer) lub zmiana napięcia na pinie. Takie zdarzenia mogą wystąpić w dowolnym momencie wykonywania kodu programu. Niesie to ze sobą istotne konsekwencje, z których część jest specyficzna dla języka MicroPython. Inne są wspólne dla wszystkich systemów zdolnych do reagowania na zdarzenia czasu rzeczywistego. Niniejszy dokument omawia najpierw zagadnienia specyficzne dla języka, a następnie zawiera krótkie wprowadzenie do programowania czasu rzeczywistego dla osób, które dopiero się z nim zapoznają.
To wprowadzenie używa nieprecyzyjnych określeń, takich jak „powolny” czy „tak szybko, jak to możliwe”. Jest to celowe, ponieważ szybkości zależą od aplikacji. Dopuszczalne czasy trwania ISR zależą od częstotliwości występowania przerwań, charakteru programu głównego oraz obecności innych współbieżnych zdarzeń.
Wskazówki i zalecane praktyki¶
Poniżej podsumowano szczegółowo omówione punkty i wymieniono główne zalecenia dotyczące kodu procedur obsługi przerwań.
Utrzymuj kod tak krótki i prosty, jak to możliwe.
Unikaj alokacji pamięci: bez dołączania do list ani wstawiania do słowników, bez liczb zmiennoprzecinkowych.
Rozważ użycie
micropython.schedule, aby obejść powyższe ograniczenie.Gdy ISR zwraca wiele bajtów, użyj wstępnie zaalokowanego
bytearray. Jeśli wiele liczb całkowitych ma być współdzielonych między ISR a programem głównym, rozważ tablicę (array.array).Gdy dane są współdzielone między programem głównym a ISR, rozważ wyłączenie przerwań przed uzyskaniem dostępu do danych w programie głównym i ich ponowne włączenie natychmiast po tym (zobacz Sekcje krytyczne).
Zaalokuj awaryjny bufor wyjątków (zobacz poniżej).
Zagadnienia związane z MicroPython¶
Awaryjny bufor wyjątków¶
Jeśli w ISR wystąpi błąd, MicroPython nie jest w stanie wygenerować raportu o błędzie, chyba że w tym celu utworzono specjalny bufor. Debugowanie jest uproszczone, jeśli następujący kod zostanie dołączony do dowolnego programu używającego przerwań.
import micropython
micropython.alloc_emergency_exception_buf(100)
Awaryjny bufor wyjątków może przechowywać tylko jeden ślad stosu wyjątku. Oznacza to, że jeśli drugi wyjątek zostanie zgłoszony podczas obsługi wyjątku, gdy sterta jest zablokowana, ślad stosu tego drugiego wyjątku zastąpi oryginalny - nawet jeśli drugi wyjątek zostanie czysto obsłużony. Może to prowadzić do mylących komunikatów o wyjątkach, jeśli bufor zostanie później wydrukowany.
Prostota¶
Z różnych powodów ważne jest, aby kod ISR był tak krótki i prosty, jak to możliwe. Powinien on robić tylko to, co musi zostać zrobione natychmiast po zdarzeniu, które go wywołało: operacje, które można odłożyć, powinny zostać przekazane do głównej pętli programu. Zazwyczaj ISR zajmuje się urządzeniem sprzętowym, które spowodowało przerwanie, przygotowując je na wystąpienie kolejnego przerwania. Komunikuje się z główną pętlą poprzez aktualizację współdzielonych danych, aby zasygnalizować, że przerwanie wystąpiło, i powraca. ISR powinien zwrócić sterowanie do głównej pętli tak szybko, jak to możliwe. Nie jest to zagadnienie specyficzne dla MicroPython, więc jest omówione bardziej szczegółowo poniżej.
Komunikacja między ISR a programem głównym¶
Zwykle ISR musi komunikować się z programem głównym. Najprostszym sposobem osiągnięcia tego jest użycie jednego lub większej liczby współdzielonych obiektów danych, zadeklarowanych jako globalne lub współdzielonych za pośrednictwem klasy (zobacz poniżej). Wiążą się z tym różne ograniczenia i zagrożenia, które są omówione bardziej szczegółowo poniżej. W tym celu powszechnie używa się liczb całkowitych, obiektów bytes i bytearray wraz z tablicami (z modułu array), które mogą przechowywać różne typy danych.
Użycie metod obiektów jako wywołań zwrotnych¶
MicroPython obsługuje tę potężną technikę, która umożliwia ISR współdzielenie zmiennych instancji z kodem bazowym. Umożliwia ona również klasie implementującej sterownik urządzenia obsługę wielu instancji urządzeń. Poniższy przykład powoduje, że dwie diody LED migają z różnymi częstotliwościami.
import machine
import micropython
micropython.alloc_emergency_exception_buf(100)
class Foo(object):
def __init__(self, freq, led):
self.led = led
self.timer = machine.Timer(-1, freq=freq, callback=self.cb, hard=True)
def cb(self, tim):
self.led.toggle()
red = Foo(1, machine.LED("LED_RED"))
green = Foo(0.8, machine.LED("LED_GREEN"))
W tym przykładzie instancja red steruje czerwoną diodą LED z wirtualnego licznika czasu (timer) o częstotliwości 1 Hz: za każdym razem, gdy licznik czasu wyzwala, wywoływana jest funkcja red.cb(), przełączająca czerwoną diodę LED. Instancja green działa podobnie, z licznikiem czasu o częstotliwości 0,8 Hz przełączającym zieloną diodę LED. Użycie metod instancji daje dwie korzyści. Po pierwsze, pojedyncza klasa umożliwia współdzielenie kodu między wieloma instancjami sprzętowymi. Po drugie, jako metoda związana, pierwszym argumentem funkcji wywołania zwrotnego jest self. Umożliwia to wywołaniu zwrotnemu dostęp do danych instancji i zapisywanie stanu między kolejnymi wywołaniami. Na przykład, gdyby powyższa klasa miała zmienną self.count ustawioną na zero w konstruktorze, cb() mogłoby zwiększać licznik. Instancje red i green utrzymywałyby wówczas niezależne liczniki tego, ile razy każda dioda LED zmieniła stan.
Tworzenie obiektów Pythona¶
ISR nie mogą tworzyć instancji obiektów Pythona. Dzieje się tak, ponieważ MicroPython musi zaalokować pamięć dla obiektu z magazynu wolnych bloków pamięci zwanego heap. Nie jest to dozwolone w procedurze obsługi przerwań, ponieważ alokacja sterty nie jest reentrantna. Innymi słowy, przerwanie może wystąpić, gdy program główny jest w połowie wykonywania alokacji - aby zachować integralność sterty, interpreter nie zezwala na alokacje pamięci w kodzie ISR.
Konsekwencją tego jest to, że ISR nie mogą używać arytmetyki zmiennoprzecinkowej; dzieje się tak, ponieważ liczby zmiennoprzecinkowe są obiektami Pythona. Podobnie ISR nie może dołączyć elementu do listy. W praktyce może być trudno dokładnie ustalić, które konstrukcje kodu spróbują wykonać alokację pamięci i wywołać komunikat o błędzie: to kolejny powód, aby kod ISR był krótki i prosty.
Jednym ze sposobów uniknięcia tego problemu jest użycie przez ISR wstępnie zaalokowanych buforów. Na przykład konstruktor klasy tworzy instancję bytearray oraz flagę logiczną. Metoda ISR przypisuje dane do lokalizacji w buforze i ustawia flagę. Alokacja pamięci następuje w kodzie programu głównego podczas tworzenia instancji obiektu, a nie w ISR.
Metody wejścia/wyjścia biblioteki MicroPython zwykle zapewniają opcję użycia wstępnie zaalokowanego bufora. Na przykład machine.I2C.readfrom_into() odczytuje dane do dostarczonego przez wywołującego modyfikowalnego bufora: umożliwia to jego użycie w ISR.
Sposób utworzenia obiektu bez użycia klasy lub zmiennych globalnych jest następujący:
def set_volume(t, buf=bytearray(3)):
buf[0] = 0xa5
buf[1] = t >> 4
buf[2] = 0x5a
return buf
Kompilator tworzy instancję domyślnego argumentu buf, gdy funkcja jest ładowana po raz pierwszy (zwykle podczas importowania modułu, w którym się znajduje).
Instancja tworzenia obiektu występuje podczas tworzenia odwołania do metody związanej. Oznacza to, że ISR nie może przekazać metody związanej do funkcji. Jednym z rozwiązań jest utworzenie odwołania do metody związanej w konstruktorze klasy i przekazanie tego odwołania w ISR. Na przykład:
class Foo():
def __init__(self):
self.bar_ref = self.bar # Allocation occurs here
self.x = 0.1
self.tim = machine.Timer(-1, freq=2, callback=self.cb, hard=True)
def bar(self, _):
self.x *= 1.2
print(self.x)
def cb(self, t):
# Passing self.bar would cause allocation.
micropython.schedule(self.bar_ref, 0)
Inne techniki polegają na zdefiniowaniu i utworzeniu instancji metody w konstruktorze lub na przekazaniu Foo.bar() z argumentem self.
Użycie obiektów Pythona¶
Kolejne ograniczenie dotyczące obiektów wynika ze sposobu działania Pythona. Gdy wykonywana jest instrukcja import, kod Pythona jest kompilowany do bytecode, przy czym jedna linia kodu zazwyczaj odpowiada wielu kodom bajtowym. Gdy kod jest uruchamiany, interpreter odczytuje każdy kod bajtowy i wykonuje go jako serię instrukcji kodu maszynowego. Biorąc pod uwagę, że przerwanie może wystąpić w dowolnym momencie między instrukcjami kodu maszynowego, oryginalna linia kodu Pythona może być wykonana tylko częściowo. W konsekwencji obiekt Pythona, taki jak zbiór, lista lub słownik, modyfikowany w głównej pętli, może nie zachowywać wewnętrznej spójności w momencie wystąpienia przerwania.
Typowy rezultat jest następujący. W rzadkich przypadkach ISR uruchomi się dokładnie w momencie, gdy obiekt jest częściowo zaktualizowany. Gdy ISR próbuje odczytać obiekt, dochodzi do awarii. Ponieważ takie problemy zazwyczaj występują rzadko i losowo, mogą być trudne do zdiagnozowania. Istnieją sposoby obejścia tego problemu, opisane w sekcji Sekcje krytyczne poniżej.
Ważne jest, aby jasno określić, co stanowi modyfikację obiektu. Zmiana zawartości tablicy lub bytearray jest bezpieczna. Dzieje się tak, ponieważ bajty lub słowa są zapisywane jako pojedyncza instrukcja kodu maszynowego, która nie jest przerywalna: w terminologii programowania czasu rzeczywistego zapis jest atomowy. To samo dotyczy aktualizacji elementu słownika, ponieważ elementy są słowami maszynowymi, będąc liczbami całkowitymi lub wskaźnikami do obiektów. Obiekt zdefiniowany przez użytkownika może utworzyć instancję tablicy lub bytearray. Zarówno główna pętla, jak i ISR mogą prawidłowo zmieniać zawartość tych struktur.
Zagrożenie pojawia się, gdy zmieniana jest struktura obiektu, w szczególności w przypadku słowników. Dodawanie lub usuwanie kluczy może wyzwolić ponowne haszowanie. Jeśli sprzętowy ISR uruchomi się podczas trwania ponownego haszowania i spróbuje uzyskać dostęp do elementu, może dojść do awarii. Wewnętrznie zmienne globalne są implementowane jako słownik. W konsekwencji program główny powinien utworzyć wszystkie niezbędne zmienne globalne przed uruchomieniem procesu generującego sprzętowe przerwania. Kod aplikacji powinien również unikać usuwania zmiennych globalnych.
MicroPython obsługuje liczby całkowite o dowolnej precyzji. Wartości między 230 -1 a -230 będą przechowywane w pojedynczym słowie maszynowym. Większe wartości są przechowywane jako obiekty Pythona. W konsekwencji zmiany długich liczb całkowitych nie mogą być uznawane za atomowe. Użycie długich liczb całkowitych w ISR jest niebezpieczne, ponieważ podczas zmiany wartości zmiennej może zostać podjęta próba alokacji pamięci.
Pokonywanie ograniczenia dotyczącego liczb zmiennoprzecinkowych¶
Ogólnie najlepiej jest unikać używania liczb zmiennoprzecinkowych w kodzie ISR: urządzenia sprzętowe zwykle operują na liczbach całkowitych, a konwersja na liczby zmiennoprzecinkowe jest zwykle wykonywana w głównej pętli. Istnieje jednak kilka algorytmów DSP, które wymagają liczb zmiennoprzecinkowych. Na platformach ze sprzętową obsługą liczb zmiennoprzecinkowych (takich jak kamery OpenMV Cam oparte na STM32) można użyć wbudowanego asemblera ARM Thumb, aby obejść to ograniczenie. Dzieje się tak, ponieważ procesor przechowuje wartości zmiennoprzecinkowe w słowie maszynowym; wartości mogą być współdzielone między ISR a kodem programu głównego za pośrednictwem tablicy liczb zmiennoprzecinkowych.
Używanie micropython.schedule¶
Ta funkcja umożliwia ISR zaplanowanie wywołania zwrotnego do wykonania „bardzo wkrótce”. Wywołanie zwrotne jest kolejkowane do wykonania, które nastąpi w momencie, gdy sterta nie jest zablokowana. Dzięki temu może tworzyć obiekty Pythona i używać liczb zmiennoprzecinkowych. Wywołanie zwrotne ma również gwarancję uruchomienia w momencie, gdy program główny zakończył wszelkie aktualizacje obiektów Pythona, więc wywołanie zwrotne nie napotka częściowo zaktualizowanych obiektów.
Typowym zastosowaniem jest obsługa sprzętu sensora. ISR pozyskuje dane ze sprzętu i umożliwia mu zgłoszenie kolejnego przerwania. Następnie planuje wywołanie zwrotne w celu przetworzenia danych.
Zaplanowane wywołania zwrotne powinny być zgodne z zasadami projektowania procedur obsługi przerwań opisanymi poniżej. Ma to na celu uniknięcie problemów wynikających z aktywności wejścia/wyjścia oraz modyfikacji współdzielonych danych, które mogą wystąpić w dowolnym kodzie wywłaszczającym główną pętlę programu.
Czas wykonania należy rozpatrywać w odniesieniu do częstotliwości, z jaką mogą występować przerwania. Jeśli przerwanie wystąpi podczas wykonywania poprzedniego wywołania zwrotnego, kolejna instancja wywołania zwrotnego zostanie zakolejkowana do wykonania; uruchomi się ona po zakończeniu bieżącej instancji. Utrzymująca się wysoka częstotliwość powtarzania przerwań niesie zatem ryzyko nieograniczonego wzrostu kolejki i ostatecznej awarii z błędem RuntimeError.
Jeśli wywołanie zwrotne, które ma zostać przekazane do schedule(), jest metodą związaną, rozważ uwagę w sekcji „Tworzenie obiektów Pythona”.
Wyjątki¶
Jeśli ISR zgłosi wyjątek, nie zostanie on propagowany do głównej pętli. Przerwanie zostanie wyłączone, chyba że wyjątek zostanie obsłużony przez kod ISR.
Współpraca z asyncio¶
Gdy ISR jest uruchomiony, może wywłaszczyć harmonogram asyncio. Jeśli ISR wykona operację asyncio, działanie harmonogramu może zostać zakłócone. Dotyczy to zarówno przerwania sprzętowego, jak i programowego, a także sytuacji, gdy ISR przekazał wykonanie do innej funkcji za pośrednictwem micropython.schedule. W szczególności tworzenie lub anulowanie zadań jest nieprawidłowe w kontekście ISR. Bezpiecznym sposobem interakcji z asyncio jest zaimplementowanie korutyny z synchronizacją wykonywaną przez asyncio.ThreadSafeFlag. Poniższy fragment ilustruje tworzenie zadania w odpowiedzi na przerwanie:
tsf = asyncio.ThreadSafeFlag()
def isr(_): # Interrupt handler
tsf.set()
async def foo():
while True:
await tsf.wait()
asyncio.create_task(bar())
W tym przykładzie wystąpi zmienne opóźnienie między wykonaniem ISR a wykonaniem foo(). Jest to nieodłączne dla harmonogramowania kooperacyjnego. Maksymalne opóźnienie zależy od aplikacji i platformy, ale zwykle może być mierzone w dziesiątkach ms.
Zagadnienia ogólne¶
Jest to jedynie krótkie wprowadzenie do tematu programowania czasu rzeczywistego. Początkujący powinni zwrócić uwagę, że błędy projektowe w programach czasu rzeczywistego mogą prowadzić do usterek, które są szczególnie trudne do zdiagnozowania. Dzieje się tak, ponieważ mogą one występować rzadko i w odstępach czasu, które są zasadniczo losowe. Kluczowe jest, aby od początku poprawnie zaprojektować program i przewidzieć problemy, zanim się pojawią. Zarówno procedury obsługi przerwań, jak i program główny muszą być zaprojektowane z uwzględnieniem następujących zagadnień.
Projektowanie procedur obsługi przerwań¶
Jak wspomniano powyżej, ISR powinny być projektowane tak, aby były jak najprostsze. Powinny zawsze powracać w krótkim, przewidywalnym czasie. Jest to ważne, ponieważ gdy ISR jest uruchomiony, główna pętla nie działa: główna pętla nieuchronnie doświadcza pauz w wykonywaniu w losowych punktach kodu. Takie pauzy mogą być źródłem trudnych do zdiagnozowania błędów, zwłaszcza jeśli ich czas trwania jest długi lub zmienny. Aby zrozumieć implikacje czasu wykonania ISR, wymagane jest podstawowe rozumienie priorytetów przerwań.
Przerwania są zorganizowane zgodnie ze schematem priorytetów. Kod ISR może sam zostać przerwany przez przerwanie o wyższym priorytecie. Ma to implikacje, jeśli oba przerwania współdzielą dane (zobacz Sekcje krytyczne poniżej). Jeśli takie przerwanie wystąpi, wprowadza ono opóźnienie do kodu ISR. Jeśli przerwanie o niższym priorytecie wystąpi podczas działania ISR, zostanie opóźnione do zakończenia ISR: jeśli opóźnienie jest zbyt długie, przerwanie o niższym priorytecie może zawieść. Kolejnym problemem z powolnymi ISR jest przypadek, gdy drugie przerwanie tego samego typu wystąpi podczas jego wykonywania. Drugie przerwanie zostanie obsłużone po zakończeniu pierwszego. Jednak jeśli częstotliwość napływających przerwań konsekwentnie przekracza zdolność ISR do ich obsługi, rezultat nie będzie pomyślny.
W konsekwencji konstrukcji pętlowych należy unikać lub je minimalizować. Operacji wejścia/wyjścia do urządzeń innych niż urządzenie przerywające należy zwykle unikać: operacje wejścia/wyjścia, takie jak dostęp do dysku, instrukcje print i dostęp do UART, są stosunkowo powolne, a ich czas trwania może się zmieniać. Kolejnym problemem jest tutaj fakt, że funkcje systemu plików nie są reentrantne: użycie operacji wejścia/wyjścia systemu plików w ISR i w programie głównym byłoby niebezpieczne. Co najważniejsze, kod ISR nie powinien oczekiwać na zdarzenie. Operacje wejścia/wyjścia są dopuszczalne, jeśli można zagwarantować, że kod powróci w przewidywalnym czasie, na przykład przełączenie pinu lub diody LED. Dostęp do urządzenia przerywającego za pośrednictwem I2C lub SPI może być konieczny, ale czas potrzebny na takie dostępy należy obliczyć lub zmierzyć i ocenić jego wpływ na aplikację.
Zazwyczaj istnieje potrzeba współdzielenia danych między ISR a główną pętlą. Można to zrobić za pomocą zmiennych globalnych lub za pośrednictwem zmiennych klasy lub instancji. Zmienne są zazwyczaj typu całkowitego lub logicznego, lub tablicami liczb całkowitych lub bajtów (wstępnie zaalokowana tablica liczb całkowitych oferuje szybszy dostęp niż lista). Gdy ISR modyfikuje wiele wartości, należy rozważyć przypadek, gdy przerwanie wystąpi w momencie, gdy program główny uzyskał dostęp do niektórych, ale nie wszystkich, wartości. Może to prowadzić do niespójności.
Rozważmy następujący projekt. ISR przechowuje napływające dane w bytearray, a następnie dodaje liczbę odebranych bajtów do liczby całkowitej reprezentującej całkowitą liczbę bajtów gotowych do przetworzenia. Program główny odczytuje liczbę bajtów, przetwarza bajty, a następnie zeruje liczbę gotowych bajtów. Będzie to działać do momentu, gdy przerwanie wystąpi tuż po odczytaniu liczby bajtów przez program główny. ISR umieszcza dodane dane w buforze i aktualizuje liczbę odebranych, ale program główny już odczytał tę liczbę, więc przetwarza dane pierwotnie odebrane. Nowo przybyłe bajty są tracone.
Istnieją różne sposoby unikania tego zagrożenia, z których najprostszym jest użycie bufora cyklicznego. Jeśli nie jest możliwe użycie struktury z nieodłącznym bezpieczeństwem wątkowym, inne sposoby opisano poniżej.
Reentrancja¶
Potencjalne zagrożenie może wystąpić, jeśli funkcja lub metoda jest współdzielona między programem głównym a jednym lub większą liczbą ISR, lub między wieloma ISR. Problem polega tutaj na tym, że funkcja może sama zostać przerwana i może zostać uruchomiona kolejna instancja tej funkcji. Jeśli ma to nastąpić, funkcja musi być zaprojektowana jako reentrantna. Sposób, w jaki się to robi, jest zaawansowanym tematem wykraczającym poza zakres tego samouczka.
Sekcje krytyczne¶
Przykładem krytycznej sekcji kodu jest sekcja, która uzyskuje dostęp do więcej niż jednej zmiennej, na którą może wpływać ISR. Jeśli przerwanie wystąpi pomiędzy dostępami do poszczególnych zmiennych, ich wartości będą niespójne. Jest to przypadek zagrożenia znanego jako stan wyścigu: ISR i główna pętla programu ścigają się o zmianę zmiennych. Aby uniknąć niespójności, należy zastosować środek zapewniający, że ISR nie zmieni wartości na czas trwania sekcji krytycznej. Jednym ze sposobów osiągnięcia tego jest wydanie machine.disable_irq() przed rozpoczęciem sekcji oraz machine.enable_irq() na jej końcu. Oto przykład tego podejścia:
import machine
import micropython
import array
import random
import time
micropython.alloc_emergency_exception_buf(100)
class BoundsException(Exception):
pass
ARRAYSIZE = const(20)
index = 0
data = array.array('i', [0] * ARRAYSIZE)
def callback1(t):
global data, index
for x in range(5):
data[index] = random.getrandbits(30) # simulate input
index += 1
if index >= ARRAYSIZE:
raise BoundsException('Array bounds exceeded')
tim = machine.Timer(-1, freq=100, callback=callback1, hard=True)
for loop in range(1000):
if index > 0:
irq_state = machine.disable_irq() # Start of critical section
for x in range(index):
print(data[x])
index = 0
machine.enable_irq(irq_state) # End of critical section
print('loop {}'.format(loop))
time.sleep_ms(1)
tim.deinit()
Sekcja krytyczna może składać się z pojedynczej linii kodu i pojedynczej zmiennej. Rozważmy następujący fragment kodu.
count = 0
def cb(): # An interrupt callback
count += 1
def main():
# Code to set up the interrupt callback omitted
while True:
count += 1
Ten przykład ilustruje subtelne źródło błędów. Linia count += 1 w głównej pętli niesie ze sobą specyficzne zagrożenie stanem wyścigu znane jako odczyt-modyfikacja-zapis. Jest to klasyczna przyczyna błędów w systemach czasu rzeczywistego. W głównej pętli MicroPython odczytuje wartość count, dodaje do niej 1 i zapisuje ją z powrotem. W rzadkich przypadkach przerwanie występuje po odczycie, a przed zapisem. Przerwanie modyfikuje count, ale jego zmiana jest nadpisywana przez główną pętlę po powrocie z ISR. W rzeczywistym systemie mogłoby to prowadzić do rzadkich, nieprzewidywalnych awarii.
Jak wspomniano powyżej, należy zachować ostrożność, jeśli instancja wbudowanego typu Pythona jest modyfikowana w głównym kodzie, a do tej instancji uzyskuje się dostęp w ISR. Kod wykonujący modyfikację należy traktować jako sekcję krytyczną, aby zapewnić, że instancja jest w prawidłowym stanie, gdy ISR jest uruchamiany.
Szczególną ostrożność należy zachować, jeśli zbiór danych jest współdzielony między różnymi ISR. Zagrożenie polega tutaj na tym, że przerwanie o wyższym priorytecie może wystąpić, gdy to o niższym priorytecie częściowo zaktualizowało współdzielone dane. Radzenie sobie z tą sytuacją jest zaawansowanym tematem wykraczającym poza zakres tego wprowadzenia, poza zaznaczeniem, że opisane poniżej obiekty mutex mogą być czasami używane.
Wyłączanie przerwań na czas trwania sekcji krytycznej jest zwykłym i najprostszym sposobem postępowania, ale wyłącza ono wszystkie przerwania, a nie tylko to, które potencjalnie może powodować problemy. Generalnie niepożądane jest wyłączanie przerwania na długo. W przypadku przerwań licznika czasu (timer) wprowadza to zmienność do momentu wystąpienia wywołania zwrotnego. W przypadku przerwań urządzeń może to prowadzić do zbyt późnej obsługi urządzenia z możliwą utratą danych lub błędami przepełnienia w sprzęcie urządzenia. Podobnie jak ISR, sekcja krytyczna w głównym kodzie powinna mieć krótki, przewidywalny czas trwania.
Podejściem do radzenia sobie z sekcjami krytycznymi, które radykalnie skraca czas wyłączenia przerwań, jest użycie obiektu zwanego mutexem (nazwa pochodzi od pojęcia wzajemnego wykluczania). Program główny blokuje mutex przed uruchomieniem sekcji krytycznej i odblokowuje go na końcu. ISR sprawdza, czy mutex jest zablokowany. Jeśli tak, omija sekcję krytyczną i powraca. Wyzwaniem projektowym jest zdefiniowanie, co ISR powinien zrobić w przypadku, gdy dostęp do zmiennych krytycznych zostanie odmówiony. Prosty przykład mutexu można znaleźć tutaj. Należy zauważyć, że kod mutexu wyłącza przerwania, ale tylko na czas trwania ośmiu instrukcji maszynowych: korzyścią tego podejścia jest to, że inne przerwania pozostają praktycznie niezakłócone.
Przerwania a REPL¶
Procedury obsługi przerwań, takie jak te związane z licznikami czasu (timery), mogą nadal działać po zakończeniu programu. Może to dawać nieoczekiwane rezultaty tam, gdzie można było oczekiwać, że obiekt wyzwalający wywołanie zwrotne wyszedł poza zakres. Na przykład na OpenMV Cam:
def bar():
foo = machine.Timer(-1, freq=4, callback=lambda t: print('.', end=''), hard=True)
bar()
Działa to nadal, dopóki licznik czasu (timer) nie zostanie jawnie wyłączony lub płytka nie zostanie zresetowana za pomocą Ctrl-D.