Stan wyścigu w trybie uśpienia mikrokontrolera

11

Biorąc pod uwagę mikrokontroler, który uruchamia następujący kod:

volatile bool has_flag = false;

void interrupt(void) //called when an interrupt is received
{
    clear_interrupt_flag(); //clear interrupt flag
    has_flag = true; //signal that we have an interrupt to process
}

int main()
{
    while(1)
    {
        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}

Załóżmy, że if(has_flag)warunek ma wartość false, a my zamierzamy wykonać instrukcję uśpienia. Tuż przed wykonaniem instrukcji snu otrzymujemy przerwanie. Po wyjściu z przerwania wykonujemy instrukcję snu.

Ta sekwencja wykonywania jest niepożądana, ponieważ:

  • Mikrokontroler poszedł spać zamiast budzić się i dzwonić process().
  • Mikrokontroler może nigdy się nie obudzić, jeśli nie zostanie odebrane żadne przerwanie.
  • Połączenie z urządzeniem process()jest odraczane do następnego przerwania.

Jak można napisać kod, aby zapobiec wystąpieniu tego stanu wyścigu?

Edytować

Niektóre mikrokontrolery, takie jak ATMega, mają bit aktywacji uśpienia, który zapobiega występowaniu tego stanu (dziękuję Kvegaoro za zwrócenie na to uwagi). JRoberts oferuje przykładową implementację, która ilustruje to zachowanie.

Inne mikroskopy, takie jak PIC18, nie mają tego bitu i problem nadal występuje. Jednak te mikroskopy są zaprojektowane w taki sposób, że przerwania mogą nadal budzić rdzeń, niezależnie od tego, czy ustawiony jest bit globalnego włączania przerwań (dziękuję supercat za wskazanie tego). W przypadku takich architektur rozwiązaniem jest wyłączenie globalnych przerwań tuż przed snem. Jeśli przerwanie zostanie wyzwolone tuż przed wykonaniem instrukcji uśpienia, procedura obsługi przerwań nie zostanie wykonana, rdzeń się obudzi, a gdy globalne przerwania zostaną ponownie włączone, procedura obsługi przerwań zostanie wykonana. W pseudokodzie implementacja wyglądałaby następująco:

int main()
{
    while(1)
    {
        //clear global interrupt enable bit.
        //if the flag tested below is not set, then we enter
        //sleep with the global interrupt bit cleared, which is
        //the intended behavior.
        disable_global_interrupts();

        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            enable_global_interrupts();  //set global interrupt enable bit.

            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}
TRISAbity
źródło
Czy to pytanie praktyczne czy teoretyczne?
AndrejaKo
teoretycznie używasz timera, który budzi cię raz na każde (wprowadź dopuszczalną wartość) ms, a następnie wraca do trybu uśpienia, jeśli nic nie trzeba robić.
Grady Gracz
1
Chciałbym zrobić interrupt_flagjako int, a przyrost to za każdym razem istnieje przerwanie. Następnie zmień if(has_flag)na while (interrupts_count)a potem spać. Niemniej jednak przerwanie może wystąpić po wyjściu z pętli while. Jeśli jest to problem, to czy samo przetwarzanie jest przetwarzane?
angelatlarge
1
cóż, zależy to od tego, z jakiego mikroukładu korzystasz. Gdyby to był ATmega328, prawdopodobnie mógłbyś wyłączyć tryb uśpienia podczas przerwania, więc jeśli opisany stan wyścigu się zdarzy, wówczas funkcja uśpienia zostanie zastąpiona, ponownie zapętlony i przetworzysz przerwanie z niewielkim opóźnieniem. Ale świetnym rozwiązaniem byłoby również
użycie budzika
1
@TRISAbits: W przypadku PIC 18x podejście opisane w mojej odpowiedzi działa dobrze (jest to mój normalny projekt podczas korzystania z tej części).
supercat

Odpowiedzi:

9

Zazwyczaj w tej sprawie jest jakieś wsparcie sprzętowe. Np. seiInstrukcja AVR dotycząca włączenia przerywa włączanie odroczenia do czasu wykonania poniższej instrukcji. Dzięki niemu można zrobić:

forever,
   interrupts off;
   if has_flag,
      interrupts on;
      process interrupt;
   else,
      interrupts-on-and-sleep;    # won't be interrupted
   end
end

Przerwanie, którego pominięto by w tym przykładzie, byłoby w tym przypadku wstrzymane, dopóki procesor nie zakończy sekwencji uśpienia.

JRobert
źródło
Świetna odpowiedź! Podany algorytm działa naprawdę dobrze w AVR. Dzieki za sugestie.
TRISAbits
3

Na wielu mikrokontrolerach, oprócz możliwości włączania lub wyłączania określonych przyczyn przerwań (zwykle w module kontrolera przerwań), w rdzeniu procesora znajduje się flaga master, która określa, czy żądania przerwań będą akceptowane. Wiele mikrokontrolerów opuści tryb uśpienia, jeśli żądanie przerwania dotrze do rdzenia, niezależnie od tego, czy rdzeń rzeczywiście go zaakceptuje.

W takiej konstrukcji prostym podejściem do uzyskania niezawodnego zachowania podczas snu jest wyczyszczenie flagi w głównej pętli, a następnie sprawdzenie, czy zna ona powód, dla którego procesor powinien być przebudzony. Każde przerwanie występujące w tym czasie, które może wpłynąć na którykolwiek z tych powodów, powinno ustawić flagę. Jeśli główna pętla nie znalazła żadnego powodu, aby nie zasnąć, a jeśli flaga nie jest ustawiona, główna pętla powinna wyłączyć przerwania i ponownie sprawdzić flagę [być może po kilku instrukcjach NOP, jeśli jest możliwe, że przerwanie staje się w toku podczas instrukcji wyłączania-przerywania może być przetwarzana po wykonaniu operacji pobierania argumentu powiązanej z następującą instrukcją]. Jeśli flaga nadal nie jest ustawiona, idź spać.

W tym scenariuszu przerwanie, które nastąpi zanim pętla główna wyłączy przerwania, ustawi flagę przed końcowym testem. Przerwanie, które staje się zbyt późne, aby można je było obsłużyć przed instrukcją uśpienia, uniemożliwi procesorowi przejście w tryb uśpienia. Obie sytuacje są w porządku.

Sleep-on-exit jest czasem dobrym modelem do użycia, ale nie wszystkie aplikacje naprawdę go „pasują”. Na przykład urządzenie z energooszczędnym wyświetlaczem LCD można najłatwiej zaprogramować za pomocą kodu, który wygląda następująco:

void select_view_user(int default_user)
{
  int current_user;
  int ch;
  current_user = default_user;
  do
  {
    lcd_printf(0, "User %d");
    lcd_printf(1, ...whatever... );
    get_key();
    if (key_hit(KEY_UP)) {current_user = (current_user + 1) % MAX_USERS};
    if (key_hit(KEY_DOWN)) {current_user = (current_user + MAX_USERS-1) % MAX_USERS};
    if (key_hit(KEY_ENTER)) view_user(current_user);
  } while(!key_hit(KEY_EXIT | KEY_TIMEOUT));
}

Jeśli nie zostaną naciśnięte żadne przyciski i nic się nie dzieje, nie ma powodu, dla którego system nie powinien spać podczas wykonywania get_keymetody. Chociaż możliwe jest, aby klucze wyzwalały przerwanie i zarządzały wszystkimi interakcjami interfejsu użytkownika za pośrednictwem automatu stanów, kod podobny do powyższego jest często najbardziej logicznym sposobem obsługi wysoce modalnych przepływów interfejsu użytkownika typowych dla małych mikrokontrolerów.

supercat
źródło
Dzięki supercat za świetną odpowiedź. Wyłączenie przerwań, a następnie przejście do trybu uśpienia jest fantastycznym rozwiązaniem, pod warunkiem, że rdzeń obudzi się z dowolnego źródła przerwań, niezależnie od tego, czy globalny bit przerwań jest ustawiony / skasowany. Rzuciłem okiem na schemat sprzętowy przerwań PIC18 i to rozwiązanie zadziała.
TRISAbits
1

Zaprogramuj mikrofon, aby budził się po przerwie.

Konkretne szczegóły będą się różnić w zależności od używanej mikro.

Następnie zmodyfikuj procedurę main ():

int main()
{
    while(1)
    {
        sleep();
        process(); //process the interrupt
    }
}
jwygralak67
źródło
1
W pytaniu zakłada się architekturę budzenia na przerwanie. Nie sądzę, że twoja odpowiedź rozwiązuje pytanie / problem.
angelatlarge
Punkt @angelatlarge został zaakceptowany. Dodałem przykład kodu, który moim zdaniem pomaga.
jwygralak67
@ jwygralak67: Dziękuję za sugestię, ale podany kod po prostu przenosi problem do procedury process (), która musi teraz sprawdzić, czy przerwanie nastąpiło przed wykonaniem treści procesu ().
TRISAbits
2
Jeśli przerwanie nie nastąpiło, dlaczego nie śpimy?
JRobert
1
@JRobert: Mogliśmy obudzić się z poprzedniego przerwania, wykonać procedurę (), a kiedy zakończymy test if (has_flag) i tuż przed snem, otrzymamy kolejne przerwanie, co powoduje problem, który opisałem w pytanie.
TRISAbits