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
}
}
interrupt_flag
jakoint
, a przyrost to za każdym razem istnieje przerwanie. Następnie zmieńif(has_flag)
nawhile (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?Odpowiedzi:
Zazwyczaj w tej sprawie jest jakieś wsparcie sprzętowe. Np.
sei
Instrukcja AVR dotycząca włączenia przerywa włączanie odroczenia do czasu wykonania poniższej instrukcji. Dzięki niemu można zrobić: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.
źródło
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:
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_key
metody. 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.źródło
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 ():
źródło