Każda aplikacja osadzona na ogół zawiera w sobie zestaw funkcji. I nawet najprostsza, na przykład do regulowania temperatury, obejmuje szereg zadań, takich jak odczytywanie danych wejściowych od użytkownika lub z czujnika temperatury/wejścia ADC, wyświetlanie zasobów na wyświetlaczu LCD i sterowanie wyjściem zarządzającym pracą wentylatora/grzałki itp. Przepustowość sterownika musi być podzielona między wszystkie te funkcje w taki sposób, aby użytkownikowi końcowemu wydawało się, że są wykonywane równolegle. Zaprojektowanie tego obejmuje określenie działań w tle – tj. głównego procesu kontrolera – i zaniechanie pracy w regularnych odstępach czasu dla wszystkich innych zadań. Należy zauważyć, że mogą występować również przerwania asynchroniczne, na przykład, w czasie gdy master próbuje się komunikować ze sterownikiem podrzędnym w razie potrzeby. Właściwa obsługa danego procesu staje się zatem zadaniem krytycznym.
Przerwania muszą być traktowane ostrożnie i rozważnie, głównie dlatego, że niedbale rozpisana ich obsługa może prowadzić do występowania bardzo tajemniczych błędów w czasie wykonywania. Usterki te są trudne do wykrycia i zrozumienia, ponieważ kontroler może wejść w niezdefiniowany stan, zgłosić nieprawidłowe dane, zatrzymać się, zresetować lub zachować w inny, niezrozumiały dla programisty sposób. Znajomość kilku prostych praktyk obsługi przerwań może pomóc w zapobieganiu takim zdarzeniom.
W tej serii artykułów omawiamy wraz z odpowiednimi przykładami następujące proste, ale ważne modele użytkowe i tematy pomagające zapobiegać danym błędom, a dotyczące procesowania przerwań, w tym:
* Jak wybrać główny proces;
* Właściwe ustalanie priorytetu przerwań;
* Niech będą krótkie — używaj flag;
* Zachowaj prostotę — używaj maszyn stanu;
* Zmienne globalne — monitorowanie, kiedy są modyfikowane;
* Zmienne lokalne — poznaj swój kompilator;
* Korzystanie z buforów danych — uważaj na przepełnienia;
* Pamięć współdzielona — odczytanie kompletne od razu;
* Trochę więcej o buforach;
* Bufory wielobajtowe;
* Bufory strukturalne — poznaj dopełnienie struktury;
* Wywoływanie funkcji w ISR — bądź ostrożny;
* Zadania krytyczne czasowo — zrozum opóźnienia;
* Przerwanie LVD — spraw, aby blokowały.
Jak wybrać główny proces
Chociaż brzmi to łatwo, jest jednak istotne i dosyć złożone. Gdy kontroler ma wiele zadań do wykonania należy zrozumieć, że funkcja „main()” jest tylko procesem w tle. Ma również najniższy priorytet w tym sensie, że każde zaniechanie może zakłócić jej pracę, powodując, że procesor uruchomi procedurę przerwania, a nie główny proces.
Na przykład, można skanować klawiaturę matrycową w tle, gdzie opóźnienia spowodowane przez wszystkie inne przerwania razem wzięte są mniejsze niż szacowany czas przytrzymania klawisza przez użytkownika. Podczas gdy awaryjny przełącznik STOP musi być obsługiwany przerwaniem.
Właściwie ustal priorytety przerwań
Określenie kolejności wykonywania przerwań jest ważne, na przykład, w sytuacji, gdy dwa lub więcej danych procesów występuje jednocześnie. Tutaj ważność, pilność i częstotliwość zadań decydują o priorytecie.
Rozważmy również system ze sterownikiem wykorzystującym przetwornik cyfrowo-analogowy (DAC) i urządzenie podrzędne I²C. Możliwe są dwa scenariusze:
* Kontroler użytkuje przetwornik cyfrowo-analogowy do wyprowadzania przebiegu o stałej częstotliwości, podczas gdy master I²C komunikuje się niezależnie ze sterownikiem. W takim przypadku przetwornik cyfrowo-analogowy musi być aktualizowany w stałych odstępach czasu, aby kształt fali wyjściowej pozostał taki sam, niezależnie od tego, czy kontroler komunikuje się z masterem I²C, czy nie. Dlatego DAC musi mieć wyższy priorytet niż przerwanie pochodzące od I²C.
* Kontroler wykorzystuje przetwornik cyfrowo-analogowy do wyprowadzania przebiegu o zmiennej częstotliwości, przy czym częstotliwość jest ustalana przez mastera I²C. W takim przypadku przerwanie pochodzące od I²C może mieć priorytet, ponieważ samo taktowanie wyjścia DAC jest kontrolowane przez polecenia z I²C.
Często występujące przerwania powinny mieć wyższy priorytet, aby wszystkie żądania były przetwarzane. W przeciwnym ujęciu istnieje możliwość, że wiele żądań przerwań nagromadzi się i spowoduje obsłużenie wydarzenia tylko raz. Może się to zdarzyć, jeśli drugie, trzecie lub kilka przerwań wystąpi przed podjęciem pierwszej dyrektywy.
Niech będą krótkie – używaj flag
Dobrze zrozumianą i często podkreślaną poprawną praktyką przy implementacji obsługi przerwania jest to, że kod danego procesu powinien być jak najkrótszy. Gwarantuje to, że procesor może powrócić do głównego zadania w możliwie szybkim czasie.
Procedura obsługi przerwań powinna wykonywać tylko kod krytyczny; resztę zadania można przenieść do procesu głównego, ustawiając zmienne flagi. Warto zauważyć, że ponieważ flagi zazwyczaj przyjmują wartości binarne (0 lub 1), powinny one być deklarowane w pamięci bitowej, gdy tylko jest to możliwe (jak w układach 8051). Zmniejsza to narzut na push/pop i czas przetwarzania przerwania. Oto przykład:
Zachowaj prostotę – używaj maszyn stanu
Maszyny stanu sprawiają, że pozornie duże procedury są krótkie i proste do wykonania. Istnieje wiele przypadków, w których decyzje muszą być realizowane wewnątrz procedury obsługi przerwań (ISR), a funkcja wykonywana przez ISR zależy od stanu aplikacji przed wyzwoleniem przerwania. Rozważmy następujący hipotetyczny przypadek, na przykład, gdzie ISR timera ma być zaimplementowany w taki sposób, aby generować odcinki czasu o długości: 10 ms, 14 ms, 19 ms, przy czym po kolei, a następnie cykl powinien być kontynuowany. Prosty sposób na zmianę okresu wewnątrz ISR może wyglądać następująco:
Należy zwrócić uwagę, że dobrą praktyką w kodowaniu w C jest posiadanie instrukcji default w każdym przypadku wykorzystania switch. Pomaga to w łatwym odzyskaniu panowania nad procesorem po sytuacji wejścia do dowolnego niezdefiniowanego stanu.
W niektórych scenariuszach obejmujących bardzo mało stanów (2 do 3), konstrukcje: „if-else” mogą generować krótszy kod. Jednak w odniesieniu do większych maszyn stanu, konstrukcje „if-else” odpowiadają za znacznie pokaźniejszą listę wytycznych niż implementacja tabeli skoku formułowana przez instrukcję switch. Dlatego ogólną zasadą dla kodu o dowolnej złożoności jest użycie instrukcji switch, aby zachować elastyczność systemu.
Powyżej omówiono kilka podstawowych praktyk obsługi przerwań, które pomogą w zestawieniu właściwej struktury ISR-ów. W następnej części tego artykułu przedstawione zostaną aspekty ISR odnoszące się do pamięci. Przyjrzymy się implikacjom wynikającym ze zbyt liberalnego używania zmiennych globalnych/lokalnych, buforów danych, pamięci współdzielonej itp. Poruszone zostaną również zagadnienia związane z opóźnieniami w przerwaniach, następstwa wywołania zewnętrznych funkcji C wewnątrz ISR. A także przerwania LVD (wykrywanie niskiego napięcia).
Źródło: https://www.embedded.com/interrupts-short-and-simple-part-1-good-programming-practices/
Przerwania muszą być traktowane ostrożnie i rozważnie, głównie dlatego, że niedbale rozpisana ich obsługa może prowadzić do występowania bardzo tajemniczych błędów w czasie wykonywania. Usterki te są trudne do wykrycia i zrozumienia, ponieważ kontroler może wejść w niezdefiniowany stan, zgłosić nieprawidłowe dane, zatrzymać się, zresetować lub zachować w inny, niezrozumiały dla programisty sposób. Znajomość kilku prostych praktyk obsługi przerwań może pomóc w zapobieganiu takim zdarzeniom.
W tej serii artykułów omawiamy wraz z odpowiednimi przykładami następujące proste, ale ważne modele użytkowe i tematy pomagające zapobiegać danym błędom, a dotyczące procesowania przerwań, w tym:
* Jak wybrać główny proces;
* Właściwe ustalanie priorytetu przerwań;
* Niech będą krótkie — używaj flag;
* Zachowaj prostotę — używaj maszyn stanu;
* Zmienne globalne — monitorowanie, kiedy są modyfikowane;
* Zmienne lokalne — poznaj swój kompilator;
* Korzystanie z buforów danych — uważaj na przepełnienia;
* Pamięć współdzielona — odczytanie kompletne od razu;
* Trochę więcej o buforach;
* Bufory wielobajtowe;
* Bufory strukturalne — poznaj dopełnienie struktury;
* Wywoływanie funkcji w ISR — bądź ostrożny;
* Zadania krytyczne czasowo — zrozum opóźnienia;
* Przerwanie LVD — spraw, aby blokowały.
Jak wybrać główny proces
Chociaż brzmi to łatwo, jest jednak istotne i dosyć złożone. Gdy kontroler ma wiele zadań do wykonania należy zrozumieć, że funkcja „main()” jest tylko procesem w tle. Ma również najniższy priorytet w tym sensie, że każde zaniechanie może zakłócić jej pracę, powodując, że procesor uruchomi procedurę przerwania, a nie główny proces.
Na przykład, można skanować klawiaturę matrycową w tle, gdzie opóźnienia spowodowane przez wszystkie inne przerwania razem wzięte są mniejsze niż szacowany czas przytrzymania klawisza przez użytkownika. Podczas gdy awaryjny przełącznik STOP musi być obsługiwany przerwaniem.
Właściwie ustal priorytety przerwań
Określenie kolejności wykonywania przerwań jest ważne, na przykład, w sytuacji, gdy dwa lub więcej danych procesów występuje jednocześnie. Tutaj ważność, pilność i częstotliwość zadań decydują o priorytecie.
Rozważmy również system ze sterownikiem wykorzystującym przetwornik cyfrowo-analogowy (DAC) i urządzenie podrzędne I²C. Możliwe są dwa scenariusze:
* Kontroler użytkuje przetwornik cyfrowo-analogowy do wyprowadzania przebiegu o stałej częstotliwości, podczas gdy master I²C komunikuje się niezależnie ze sterownikiem. W takim przypadku przetwornik cyfrowo-analogowy musi być aktualizowany w stałych odstępach czasu, aby kształt fali wyjściowej pozostał taki sam, niezależnie od tego, czy kontroler komunikuje się z masterem I²C, czy nie. Dlatego DAC musi mieć wyższy priorytet niż przerwanie pochodzące od I²C.
* Kontroler wykorzystuje przetwornik cyfrowo-analogowy do wyprowadzania przebiegu o zmiennej częstotliwości, przy czym częstotliwość jest ustalana przez mastera I²C. W takim przypadku przerwanie pochodzące od I²C może mieć priorytet, ponieważ samo taktowanie wyjścia DAC jest kontrolowane przez polecenia z I²C.
Często występujące przerwania powinny mieć wyższy priorytet, aby wszystkie żądania były przetwarzane. W przeciwnym ujęciu istnieje możliwość, że wiele żądań przerwań nagromadzi się i spowoduje obsłużenie wydarzenia tylko raz. Może się to zdarzyć, jeśli drugie, trzecie lub kilka przerwań wystąpi przed podjęciem pierwszej dyrektywy.
Niech będą krótkie – używaj flag
Dobrze zrozumianą i często podkreślaną poprawną praktyką przy implementacji obsługi przerwania jest to, że kod danego procesu powinien być jak najkrótszy. Gwarantuje to, że procesor może powrócić do głównego zadania w możliwie szybkim czasie.
Procedura obsługi przerwań powinna wykonywać tylko kod krytyczny; resztę zadania można przenieść do procesu głównego, ustawiając zmienne flagi. Warto zauważyć, że ponieważ flagi zazwyczaj przyjmują wartości binarne (0 lub 1), powinny one być deklarowane w pamięci bitowej, gdy tylko jest to możliwe (jak w układach 8051). Zmniejsza to narzut na push/pop i czas przetwarzania przerwania. Oto przykład:
Kod: C / C++
Zachowaj prostotę – używaj maszyn stanu
Maszyny stanu sprawiają, że pozornie duże procedury są krótkie i proste do wykonania. Istnieje wiele przypadków, w których decyzje muszą być realizowane wewnątrz procedury obsługi przerwań (ISR), a funkcja wykonywana przez ISR zależy od stanu aplikacji przed wyzwoleniem przerwania. Rozważmy następujący hipotetyczny przypadek, na przykład, gdzie ISR timera ma być zaimplementowany w taki sposób, aby generować odcinki czasu o długości: 10 ms, 14 ms, 19 ms, przy czym po kolei, a następnie cykl powinien być kontynuowany. Prosty sposób na zmianę okresu wewnątrz ISR może wyglądać następująco:
Kod: C / C++
Należy zwrócić uwagę, że dobrą praktyką w kodowaniu w C jest posiadanie instrukcji default w każdym przypadku wykorzystania switch. Pomaga to w łatwym odzyskaniu panowania nad procesorem po sytuacji wejścia do dowolnego niezdefiniowanego stanu.
W niektórych scenariuszach obejmujących bardzo mało stanów (2 do 3), konstrukcje: „if-else” mogą generować krótszy kod. Jednak w odniesieniu do większych maszyn stanu, konstrukcje „if-else” odpowiadają za znacznie pokaźniejszą listę wytycznych niż implementacja tabeli skoku formułowana przez instrukcję switch. Dlatego ogólną zasadą dla kodu o dowolnej złożoności jest użycie instrukcji switch, aby zachować elastyczność systemu.
Powyżej omówiono kilka podstawowych praktyk obsługi przerwań, które pomogą w zestawieniu właściwej struktury ISR-ów. W następnej części tego artykułu przedstawione zostaną aspekty ISR odnoszące się do pamięci. Przyjrzymy się implikacjom wynikającym ze zbyt liberalnego używania zmiennych globalnych/lokalnych, buforów danych, pamięci współdzielonej itp. Poruszone zostaną również zagadnienia związane z opóźnieniami w przerwaniach, następstwa wywołania zewnętrznych funkcji C wewnątrz ISR. A także przerwania LVD (wykrywanie niskiego napięcia).
Źródło: https://www.embedded.com/interrupts-short-and-simple-part-1-good-programming-practices/
Fajne? Ranking DIY
