Obsługa przerwań w mikrokontrolerach i przykład FSM


9

Wstępne pytanie

Mam ogólne pytanie dotyczące obsługi przerwań w mikrokontrolerach. Korzystam z MSP430, ale myślę, że pytanie to może zostać rozszerzone na inne komputery PC. Chciałbym wiedzieć, czy dobrą praktyką jest włączanie / wyłączanie przerwań często w kodzie. Mam na myśli, jeśli mam część kodu, która nie będzie wrażliwa na przerwania (lub, co gorsza, nie może słuchać przerwań, z jakiegokolwiek powodu), czy lepiej:

  • Wyłącz przerwania przed, a następnie włącz je ponownie po sekcji krytycznej.
  • Umieść flagę wewnątrz odpowiedniego ISR i (zamiast wyłączania przerwania) ustaw flagę na false przed sekcją krytyczną i zresetuj ją do true zaraz po niej. Aby zapobiec wykonaniu kodu ISR.
  • Żadna z tych dwóch, więc sugestie są mile widziane!

Aktualizacja: zakłócenia i wykresy stanów

Podam konkretną sytuację. Załóżmy, że chcemy zaimplementować wykres stanu, który składa się z 4 bloków:

  1. Przejścia / efekt.
  2. Warunki wyjścia.
  3. Aktywność wstępna.
  4. Wykonuj aktywność.

Tego nauczył nas profesor na uniwersytecie. Prawdopodobnie nie najlepszym sposobem jest wykonanie tego schematu:

while(true) {

  /* Transitions/Effects */
  //----------------------------------------------------------------------
  next_state = current_state;

  switch (current_state)
  {
    case STATE_A:
      if(EVENT1) {next_state = STATE_C}
      if(d == THRESHOLD) {next_state = STATE_D; a++}
      break;
    case STATE_B:
      // transitions and effects
      break;
    (...)
  }

  /* Exit activity -> only performed if I leave the state for a new one */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(current_state)
    {
      case STATE_A:
        // Exit activity of STATE_A
        break;
      case STATE_B:
        // Exit activity of STATE_B
        break;
        (...)
    }
  }

  /* Entry activity -> only performed the 1st time I enter a state */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(next_state)
    {
      case STATE_A:
        // Entry activity of STATE_A
        break;
      case STATE_B:
        // Entry activity of STATE_B
        break;
      (...)
    }
  }

  current_state = next_state;

  /* Do activity */
  //----------------------------------------------------------------------
  switch (current_state)
  {
    case STATE_A:
      // Do activity of STATE_A
      break;
    case STATE_B:
      // Do activity of STATE_B
      break;
    (...)
  }
}

Załóżmy również, że od, powiedzmy STATE_A, chcę być wrażliwy na przerwanie pochodzące od zestawu przycisków (z systemem debouce itp.). Gdy ktoś naciśnie jeden z tych przycisków, generowane jest przerwanie, a flaga związana z portem wejściowym jest kopiowana do zmiennej buttonPressed. Jeśli odbijanie jest w jakiś sposób ustawione na 200 ms (watchdog timer, timer, counter, ...), jesteśmy pewni, że buttonPressednie można go zaktualizować o nową wartość przed 200 ms. O to proszę ciebie (i siebie :) oczywiście)

Czy muszę włączyć przerwanie aktywności DO STATE_Ai wyłączyć przed odejściem?

/* Do activity */
//-------------------------------------
switch (current_state)
{
  case STATE_A:
    // Do activity of STATE_A
    Enable_ButtonsInterrupt(); // and clear flags before it
    // Do fancy stuff and ...
    // ... wait until a button is pressed (e.g. LPM3 of the MSP430)
    // Here I have my buttonPressed flag ready!
    Disable_ButtonsInterrupt();
    break;
  case STATE_B:
    // Do activity of STATE_B
    break;
  (...)
}

W taki sposób, że jestem pewien, że następnym razem, gdy wykonam blok 1 (przejście / efekty) przy następnej iteracji, jestem pewien, że warunki sprawdzone wzdłuż przejść nie pochodzą z kolejnego przerwania, które zastąpiło poprzednią wartość buttonPressedtego I potrzeba (chociaż nie jest to możliwe, ponieważ musi upłynąć 250 ms).


3
Trudno jest wydać zalecenie, nie wiedząc więcej o swojej sytuacji. Ale czasami konieczne jest wyłączenie przerwań w systemach wbudowanych. Zaleca się, aby przerwania były wyłączone tylko przez krótki czas, aby nie zostały pominięte. Możliwe może być wyłączenie tylko poszczególnych przerwań, a nie wszystkich przerwań. Nigdy nie przypominam sobie, aby kiedykolwiek stosowałem technikę flagowania wewnątrz ISR, więc sceptycznie podchodzę do tego, czy to najlepsze rozwiązanie.
kkrambo 10.04.15

Odpowiedzi:


17

Pierwszą taktyką jest takie zaprojektowanie całego oprogramowania układowego, aby w dowolnym momencie było możliwe przerwanie. Konieczność wyłączenia przerwań, aby kod pierwszego planu mógł wykonać sekwencję atomową, powinna być wykonywana oszczędnie. Często można to obejść architektonicznie.

Jednak maszyna służy ci, a nie na odwrót. Ogólne zasady mają na celu jedynie powstrzymanie złych programistów przed napisaniem naprawdę złego kodu. O wiele lepiej jest dokładnie zrozumieć , jak działa maszyna, a następnie opracować dobry sposób na wykorzystanie tych możliwości do wykonania żądanego zadania.

Należy pamiętać, że o ile nie jesteś naprawdę napięty cyklami lub lokalizacjami pamięci (może się zdarzyć), w przeciwnym razie chcesz zoptymalizować pod kątem przejrzystości i łatwości obsługi kodu. Na przykład, jeśli masz 16-bitową maszynę, która aktualizuje 32-bitowy licznik w przerwaniu taktowania zegara, musisz upewnić się, że kiedy kod pierwszego planu odczytuje licznik, to dwie połówki są spójne. Jednym ze sposobów jest wyłączenie przerwań, przeczytanie dwóch słów, a następnie ponowne włączenie przerwań. Jeśli opóźnienie przerwania nie jest krytyczne, jest to całkowicie do przyjęcia.

W przypadku, gdy musisz mieć niskie utajenie przerwań, możesz na przykład odczytać wysokie słowo, odczytać niskie słowo, przeczytać ponownie wysokie słowo i powtórzyć, jeśli uległo zmianie. Spowalnia to nieco kod pierwszego planu, ale w ogóle nie dodaje opóźnienia. Istnieją różne małe sztuczki. Innym może być ustawienie flagi w procedurze przerwania, która wskazuje, że licznik musi być zwiększany, a następnie wykonaj to w głównej pętli zdarzeń w kodzie pierwszego planu. Działa to dobrze, jeśli szybkość przerwań licznika jest wystarczająco wolna, aby pętla zdarzeń wykonała przyrost, zanim flaga zostanie ponownie ustawiona.

Lub zamiast flagi użyj licznika składającego się z jednego słowa. Kod na pierwszym planie przechowuje osobną zmienną, która zawiera ostatni licznik, do którego zaktualizował system. Odejmuje bez znaku odliczanie licznika na żywo minus zapisana wartość, aby określić, ile tyknięć w danym momencie musi obsłużyć. Pozwala to kodowi pierwszego planu pominąć do 2 zdarzeń N -1 jednocześnie, gdzie N jest liczbą bitów w natywnym słowie, które ALU może obsłużyć atomowo.

Każda metoda ma swój własny zestaw zalet i wad. Nie ma jednej właściwej odpowiedzi. Ponownie zrozum, jak działa maszyna, wtedy nie będziesz potrzebować praktycznych reguł.


7

Jeśli potrzebujesz sekcji krytycznej, musisz upewnić się, że operacja chroniąca sekcję krytyczną ma charakter atomowy i nie można jej przerwać.

Tak więc wyłączenie przerwań, które jest najczęściej obsługiwane przez instrukcję pojedynczego procesora (i wywoływane za pomocą funkcji wewnętrznej kompilatora), jest jednym z najbezpieczniejszych zakładów, jakie możesz podjąć.

W zależności od systemu mogą wystąpić pewne problemy, na przykład przerwanie. Niektóre mikrokontrolery ustawiają flagi niezależnie od stanu globalnego zezwolenia na przerwanie, a po opuszczeniu sekcji krytycznej przerwania są wykonywane i są tylko opóźniane. Ale jeśli masz przerwanie, które występuje z dużą częstotliwością, możesz przegapić drugi raz przerwanie, które nastąpiło, jeśli zablokujesz przerwania na zbyt długi czas.

Jeśli twoja sekcja krytyczna wymaga tylko jednego przerwania, ale nie powinna zostać wykonana, to drugie podejście wydaje się wykonalne.

Programowanie procedur obsługi przerwań jest tak krótkie, jak to możliwe. Więc po prostu ustawiają flagę, która jest następnie sprawdzana podczas normalnych procedur programu. Ale jeśli to zrobisz, uważaj na warunki wyścigowe, czekając na ustawienie tej flagi.

Istnieje wiele opcji i na pewno nie ma jednej poprawnej odpowiedzi na ten temat, jest to temat, który wymaga starannego zaprojektowania i zasługuje na nieco więcej przemyśleń niż inne rzeczy.


5

Jeśli ustaliłeś, że sekcja kodu musi działać nieprzerwanie, z wyjątkiem nietypowych okoliczności, należy wyłączyć przerwania na minimalny możliwy czas, aby ukończyć zadanie, a następnie włączyć je ponownie.

Umieść flagę wewnątrz odpowiedniego ISR i (zamiast wyłączania przerwania) ustaw flagę na false przed sekcją krytyczną i zresetuj ją do true zaraz po niej. Aby zapobiec wykonaniu kodu ISR.

To wciąż pozwalałoby na wystąpienie przerwania, skoku kodu, sprawdzenia, a następnie powrotu. Jeśli Twój kod jest w stanie poradzić sobie z tak dużą liczbą zakłóceń, prawdopodobnie powinieneś po prostu zaprojektować ISR, aby ustawić flagę, zamiast wykonywać sprawdzenie - byłoby to krótsze - i obsługiwać flagę w normalnej procedurze kodu. Brzmi to tak, jakby ktoś włożył zbyt dużo kodu w przerwanie i używa przerwania do wykonywania dłuższych akcji, które powinny mieć miejsce w zwykłym kodzie.

Jeśli masz do czynienia z kodem, w którym przerwania są długie, sugerowana flaga może rozwiązać problem, ale nadal lepiej będzie po prostu wyłączyć przerwania, jeśli nie będziesz mógł ponownie rozłożyć współczynnika, aby wyeliminować nadmiar kodu w przerwaniu .

Główny problem z robieniem tego w sposób flagowy polega na tym, że w ogóle nie wykonuje się przerwania - co może mieć później konsekwencje. Większość mikrokontrolerów będzie śledzić flagi przerwań nawet wtedy, gdy przerwania są globalnie wyłączone, a następnie wykonają przerwanie po ponownym włączeniu przerwań:

  • Jeśli w sekcji krytycznej nie wystąpiły żadne przerwania, żadne z nich nie zostanie wykonane po.
  • Jeśli jedno przerwanie wystąpi podczas sekcji krytycznej, jedno zostanie wykonane po.
  • Jeśli w sekcji krytycznej wystąpi wiele przerwań, tylko jedno zostanie wykonane po.

Jeśli twój system jest złożony i musi pełniej śledzić przerwania, będziesz musiał zaprojektować bardziej skomplikowany system do śledzenia przerwań i odpowiedniego działania.

Jeśli jednak zawsze projektujesz swoje przerwania tak, aby wykonywały minimum pracy niezbędnej do osiągnięcia ich funkcji i opóźniałyby wszystko inne do regularnego przetwarzania, wtedy przerwania rzadko będą miały negatywny wpływ na twój inny kod. Poproś o przechwycenie lub zwolnienie danych przerwań w razie potrzeby lub ustaw / zresetuj wyjścia itp., Jeśli to konieczne, a następnie poproś główną ścieżkę kodu, aby zwracał uwagę na flagi, bufory i zmienne, na które wpływa przerwanie, aby można było wykonać długie przetwarzanie w głównej pętli, zamiast przerywania.

To powinno wyeliminować wszystkie, ale bardzo, bardzo nieliczne sytuacje, w których możesz potrzebować nieprzerwanej sekcji kodu.


Zaktualizowałem post, aby lepiej wyjaśnić sytuację, w której pracuję :)
Umberto D.

1
W twoim przykładowym kodzie rozważałbym wyłączenie określonego przerwania przycisku, gdy nie jest potrzebne, i włączenie go, gdy jest to potrzebne. Robienie tego często nie jest problemem z założenia. Pozostaw globalne przerwania włączone, aby w razie potrzeby później dodać do kodu inne przerwania. Alternatywnie, po prostu zresetuj flagę, gdy przejdziesz do stanu A i w przeciwnym razie zignoruj ​​ją. Jeśli przycisk zostanie naciśnięty, a flaga ustawiona, kogo to obchodzi? Zignoruj ​​to, dopóki nie wrócisz do stanu A.
Adam Davis,

Tak! To może być rozwiązanie, ponieważ w rzeczywistym projekcie często wchodzę w LPM3 (MSP430) z włączonymi globalnymi przerwaniami i wychodzę z LPM3, wznawiając wykonywanie, jak tylko wykryte zostanie przerwanie. Tak więc rozwiązaniem jest to, które przedstawiłeś, o którym myślę, że zostało zgłoszone w drugiej części kodu: włącz przerwania, jak tylko zacznę wykonywać czynność do stanu, który tego potrzebuje i wyłącz przed przejściem do bloku przejść. Czy innym możliwym rozwiązaniem może być wyłączenie przerwania tuż przed opuszczeniem „Czy blok aktywności” i ponowne włączenie później (kiedy?)?
Umberto D.

1

Umieszczenie flagi w ISR, tak jak to opisujesz, prawdopodobnie nie zadziała, ponieważ w zasadzie ignorujesz zdarzenie, które wywołało przerwanie. Globalne wyłączanie przerwań jest zwykle lepszym wyborem. Jak powiedzieli inni, nie powinieneś robić tego zbyt często. Należy pamiętać, że jakiekolwiek odczytywanie lub zapisywanie za pomocą jednej instrukcji nie powinno wymagać ochrony, ponieważ instrukcja albo wykonuje się, albo nie.

Wiele zależy od tego, jakie zasoby chcesz udostępnić. Jeśli podajesz dane z ISR do programu głównego (lub odwrotnie), możesz zaimplementować coś takiego jak bufor FIFO. Jedyną operacją atomową byłoby zaktualizowanie wskaźników odczytu i zapisu, co minimalizuje czas spędzany z wyłączonymi przerwaniami.


0

Istnieje drobna różnica, którą należy wziąć pod uwagę. Możesz wybrać „opóźnienie” obsługi przerwania lub „zignorowanie” i przerwanie.

Często w kodzie mówimy, że wyłączamy przerwanie. To, co prawdopodobnie się wydarzy, ze względu na funkcje sprzętowe, polega na tym, że po włączeniu przerwań nastąpi wyzwolenie. To w pewien sposób opóźnia przerwanie. Aby system działał niezawodnie, musimy znać maksymalny czas, który możemy opóźnić w obsłudze tych przerwań. A następnie upewnij się, że wszystkie przypadki, w których przerwania są wyłączone, zostaną zakończone w krótszym czasie.

Czasami chcemy nie brać pod uwagę przerw. Najlepszym sposobem może być zablokowanie przerwania na poziomie sprzętowym. Często jest kontroler przerwań lub podobny, w którym możemy powiedzieć, które wejścia powinny generować przerwania.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.