Najlepszy wzór dla WFI (oczekiwanie na przerwanie) w mikrokontrolerach Cortex (ARM)

18

Zastanawiam się nad stworzeniem oprogramowania zasilanego bateryjnie za pomocą kontrolerów EFM Gekko (http://energymicro.com/) i chciałbym, aby kontroler zasnął, ilekroć nie ma w tym nic użytecznego. W tym celu wykorzystywana jest instrukcja WFI (Wait For Interrupt); spowoduje przejście procesora w tryb uśpienia do momentu wystąpienia przerwania.

Gdyby sen był zaangażowany poprzez przechowywanie czegoś w innym miejscu, można użyć operacji wyłączających ładowanie / wyłączających przechowywanie, aby wykonać coś takiego:

  // dont_sleep zostanie załadowane 2 za każdym razem, gdy coś się stanie
  // powinien zmusić główną pętlę do cyklowania przynajmniej raz. Jeśli przerwanie
  // występuje, że powoduje zresetowanie go do 2 podczas następującej instrukcji,
  // zachowanie będzie wyglądało tak, jakby przerwanie nastąpiło po nim.

  store_exclusive (load_exclusive (dont_sleep) >> 1);

  while (! dont_sleep)
  {
    // Jeśli wystąpi przerwanie między kolejną instrukcją a wyjątkiem store_exlusive, nie śpij
    load_exclusive (SLEEP_TRIGGER);
    if (! dont_sleep)             
      store_exclusive (SLEEP_TRIGGER);
  }

Jeśli wystąpiłoby przerwanie między operacjami load_exclusive i store_exclusive, efektem byłoby pominięcie opcji store_exclusive, co spowodowałoby, że system ponownie uruchomiłby pętlę (aby sprawdzić, czy przerwanie ustawiło dont_sleep). Niestety Gekko używa instrukcji WFI zamiast adresu zapisu, aby uruchomić tryb uśpienia; pisanie kodu jak

  if (! dont_sleep)
    WFI ();

naraziłoby się na ryzyko, że może wystąpić przerwa między „if” a „wfi” i ustawi dont_sleep, ale wfi i tak wykona polecenie. Jaki jest najlepszy wzór, aby temu zapobiec? Ustaw PRIMASK na 1, aby zapobiec przerwaniu przez procesor procesora tuż przed wykonaniem WFI, i wyczyść go natychmiast po? Czy jest jakaś lepsza sztuczka?

EDYTOWAĆ

Zastanawiam się nad bitem zdarzenia. Według ogólnego opisu, chciałby, aby był przeznaczony do obsługi wieloprocesorowej, ale zastanawiał się, czy może działać coś takiego:

  if (dont_sleep)
    SEV (); / * Powoduje usunięcie flagi zdarzenia WFE, ale nie uśpienie * /
  WFE ();

Każde przerwanie, które ustawia Don't_sleep, powinno również wykonać instrukcję SEV, więc jeśli przerwanie nastąpi po teście „if”, WFE wyczyści flagę zdarzenia, ale nie przejdzie w tryb uśpienia. Czy to brzmi jak dobry paradygmat?

supercat
źródło
1
Instrukcja WFI nie powoduje uśpienia rdzenia, jeśli jego warunek wznowienia jest spełniony podczas wykonywania instrukcji. Na przykład, jeśli występuje niewykonane przerwanie IRQ podczas wykonywania WFI, działa ono jak NOP.
Mark
@ Mark: Problem polegałby na tym, że jeśli zostanie wykonane przerwanie między „if (! Dont_sleep)” a „WFI”, warunek przerwania nie będzie już oczekiwany podczas wykonywania WFI, ale przerwanie mogło spowodować ustawienie dont_sleep, ponieważ zrobił coś, co usprawiedliwiłoby główną pętlę z uruchomieniem kolejnej iteracji. W mojej aplikacji Cypress PSOC wszelkie przerwania, które powinny powodować przedłużone wybudzanie, zepsułyby stos, jeśli kod linii głównej miałby spać, ale wydaje się to dość niepewne i rozumiem, że ARM zniechęca do takich manipulacji stosami.
supercat
@supercat Przerwanie może, ale nie musi zostać skasowane podczas wykonywania WFI. To zależy od Ciebie i kiedy / gdzie zdecydujesz się usunąć przerwanie. Pozbądź się zmiennej dont_sleep i po prostu użyj zamaskowanego przerwania, aby zasygnalizować, kiedy chcesz pozostać czujny lub spać. Możesz po prostu pozbyć się instrukcji if i pozostawić WFI na końcu głównej pętli. Jeśli obsłużyłeś wszystkie żądania, wyczyść IRQ, abyś mógł spać. Jeśli musisz nie zasnąć, uruchom IRQ, jego maskowanie, aby nic się nie wydarzyło, ale gdy WFI spróbuje go wykonać, NOP.
Mark
2
@ superuper Na bardziej podstawowym poziomie wydaje się, że próbujesz połączyć projekt sterowany przerwaniami z projektem „dużej pętli głównej”, który zwykle nie jest krytyczny czasowo, często oparty na sondowaniu i ma minimalne przerwania. Mieszanie ich może być dość brzydkie raczej szybkie. Jeśli to w ogóle możliwe, wybierz jeden paradygmat projektowy lub drugi, którego chcesz użyć. Pamiętaj, że dzięki nowoczesnym kontrolerom przerwań w zasadzie uzyskujesz zapobiegawczą wielozadaniowość między przerwaniami i to, co stanowi kolejki zadań (obsługa jednego przerwania, a następnie następnego wyższego priorytetu itp.). Wykorzystaj to na swoją korzyść.
Mark
@Mark: Opracowałem system, który całkiem dobrze wykorzystywał PIC 18x w aplikacjach zasilanych z baterii; z powodu ograniczeń stosu, nie mógł obsłużyć zbyt wiele w przerwaniu, więc zdecydowana większość rzeczy jest obsługiwana w głównej pętli tak wygodnie. Przeważnie działa całkiem dobrze, choć istnieje kilka miejsc, w których rzeczy są blokowane na sekundę lub dwie z powodu długotrwałych operacji. Jeśli przeprowadzę migrację do ARM, mogę użyć prostego RTOS, aby ułatwić podział długotrwałych operacji, ale nie jestem pewien, czy zastosować zapobiegawczą, czy kooperacyjną wielozadaniowość.
supercat,

Odpowiedzi:

3

Nie w pełni to zrozumiałem dont_sleep, ale jedną z rzeczy, które możesz wypróbować, jest wykonanie „głównej pracy” w module obsługi PendSV, ustawionej na najniższy priorytet. Następnie wystarczy zaplanować PendSV od innych programów obsługi za każdym razem, gdy potrzebujesz czegoś zrobić. Zobacz tutaj, jak to zrobić (dotyczy M1, ale M3 nie różni się zbytnio).

Inną rzeczą, której możesz użyć (być może razem z poprzednim podejściem), jest funkcja Sleep-on-exit. Jeśli włączysz tę funkcję, procesor przejdzie w tryb uśpienia po wyjściu z ostatniej procedury obsługi ISR, bez konieczności wywoływania WFI. Zobacz kilka przykładów tutaj .

Igor Skochinsky
źródło
5
instrukcja WFI nie wymaga włączenia przerwań w celu wznowienia procesora, bity F i I w CPSR są ignorowane.
Mark
1
@ Mark Musiałem przegapić ten fragment w dokumentacji, czy masz jakieś linki / wskaźniki na ten temat? Co dzieje się z sygnałem przerwania, który obudził rdzeń? Czy pozostaje zawieszony, dopóki przerwanie nie zostanie ponownie włączone?
Igor Skochinsky,
Instrukcja odniesienia ASM jest tutaj: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/... bardziej szczegółowe informacje na Cortex-M3 tutaj: infocenter.arm.com/help/ index.jsp? topic = / com.arm.doc.dui0552a /… w skrócie, gdy zamaskowane przerwanie staje w oczekiwaniu, rdzeń budzi się i kontynuuje działanie po instrukcji WFI. Jeśli spróbujesz wydać inny WFI bez wyjaśnienia, że ​​oczekiwanie na przerwanie, WFI działałoby jak NOP (rdzeń nie zasnąłby, ponieważ warunek Wake dla WFI jest spełniony).
Mark
@ Mark: Jedną z rzeczy, które rozważałem, byłoby to, aby każdy moduł obsługi przerwań, który ustawia dont_sleep, wykonywałby również instrukcję SEV („Set Event”), a następnie używał WFE („Wait For Event”) zamiast WFI. Przykłady Gekko wydają się korzystać z WFI, ale sądzę, że WFE może również działać. jakieś pomysły?
supercat,
10

Umieść go w krytycznej części. ISRs nie będą działać, więc nie ryzykujesz, że dont_sleep zmieni się przed WFI, ale nadal będą budzić procesor i ISR ​​wykonają się, gdy tylko sekcja krytyczna się skończy.

uint8 interruptStatus;
interruptStatus = EnterCriticalSection();
if (!dont_sleep)
  WFI();
ExitCriticalSection(interruptStatus);

Twoje środowisko programistyczne prawdopodobnie ma krytyczne funkcje sekcji, ale mniej więcej tak:

EnterCriticalSection to:

MRS r0, PRIMASK /* Save interrupt state. */
CPSID i /* Turn off interrupts. */
BX lr /* Return. */

ExitCriticalSection to:

MSR PRIMASK, r0 /* Restore interrupt states. */
BX lr /* Return. */
Kenzi
źródło
2
Co ciekawe, wiele bibliotek ARM używa implementacji sekcji krytycznej, która używa globalnego licznika zamiast lokalnego zachowania statusu. Uważam, że to zadziwiające, ponieważ podejście do licznika jest bardziej skomplikowane i zadziała tylko wtedy, gdy cały kod w całym systemie używa tego samego licznika.
supercat
1
Czy przerwania nie zostaną wyłączone, dopóki nie wyjdziemy z sekcji krytycznej? Jeśli tak, to czy WFI nie spowoduje, że procesor będzie czekać w nieskończoność?
Corneliu Zuzu,
1
Odpowiedź @Kenzi Shrimp wskazuje na wątek dyskusyjny na temat Linuksa, który odpowiada na moje poprzednie pytanie. Zredagowałem jego odpowiedź i twoją, aby to wyjaśnić.
Corneliu Zuzu,
@CorneliuZuzu Edytowanie odpowiedzi innej osoby w celu wstawienia własnej dyskusji nie jest dobrym pomysłem. Dodanie cytatu w celu ulepszenia odpowiedzi „tylko link” to inna sprawa. Jeśli masz prawdziwe pytanie o wygraną, możesz zadać je jako pytanie i link do tego.
Sean Houlihane,
1
@SeanHoulihane Nie unieważniłem ani nie usunąłem niczego z jej odpowiedzi. Krótkie wyjaśnienie, dlaczego to działa, nie jest osobną dyskusją. Szczerze mówiąc, nie uważam, że ta odpowiedź zasługuje na głosowanie bez wyjaśnienia WFI, ale zasługuje na nią najbardziej.
Corneliu Zuzu,
7

Twój pomysł jest w porządku, to właśnie implementuje Linux. Zobacz tutaj .

Przydatny cytat z wyżej wspomnianego wątku dyskusyjnego, aby wyjaśnić, dlaczego WFI działa nawet przy wyłączonych przerwaniach:

Jeśli zamierzasz bezczynnie do następnego przerwania, musisz się trochę przygotować. Podczas tego przygotowania może być aktywne przerwanie. Takie przerwanie może być zdarzeniem pobudki, którego szukasz.

Bez względu na to, jak dobry jest twój kod, jeśli nie wyłączysz przerwań, zawsze będziesz miał wyścig między przygotowaniem się do snu a faktycznym zaśnięciem, co spowoduje utratę zdarzeń budzenia.

Właśnie dlatego wszystkie znane mi procesory ARM obudzą się, nawet jeśli są zamaskowane na rdzeniu procesora (bit CPSR I).

Coś jeszcze i powinieneś zapomnieć o użyciu w trybie bezczynności.

Krewetka
źródło
1
Masz na myśli wyłączenie blokowania przerwań podczas instrukcji WFI lub WFE? Czy widzisz jakieś znaczące rozróżnienie między użyciem WFI lub WFE do tego celu?
supercat
1
@supercat: Zdecydowanie użyłbym WFI. WFE IMO służy głównie do wskazówek synchronizacji między rdzeniami w systemie wielordzeniowym (na przykład wykonywanie WFE przy braku blokady spinlock i wydawanie SEV po wyjściu z blokady). Poza tym WFE bierze pod uwagę flagę maskowania przerwań, więc nie jest tak przydatna jak WFI. Ten wzór naprawdę działa dobrze w systemie Linux.
Krewetki
2

Przy założeniu, że:

  1. Główny wątek uruchamia zadania w tle
  2. Przerwania uruchamiają tylko zadania o wysokim priorytecie i nie działają w tle
  3. Główny wątek można przerwać w dowolnym momencie (zwykle nie maskuje przerwań)

Następnie rozwiązaniem jest użycie PRIMASK do blokowania przerwań między sprawdzaniem poprawności flagi a WFI:

mask_interrupts();
if (!dont_sleep)
    wfi();
unmask_interrupts();
hdante
źródło
0

Co z trybem Sleep on Exit? To automatycznie przechodzi w tryb uśpienia za każdym razem, gdy kończy się procedura obsługi IRQ, więc po skonfigurowaniu tak naprawdę nie działa żaden „tryb normalny”. Nastąpi przerwanie IRQ, budzi się, uruchamia moduł obsługi i wraca do snu. Nie wymaga WFI.

Billt
źródło
2
Jak najlepiej radzić sobie z faktem, że rodzaj snu, w który powinien wpaść procesor, może się różnić w zależności od czegoś, co dzieje się podczas przerwania? Na przykład zdarzenie zmiany pinów może wskazywać, że dane szeregowe mogą nadchodzić, a zatem procesor powinien utrzymywać główny oscylator zegarowy podczas oczekiwania na dane. Jeśli główna pętla usunie flagę zdarzenia, sprawdzi, co się dzieje, i przełączy procesor w odpowiedni tryb uśpienia za pomocą WFI, wówczas każde przerwanie, które mogło mieć wpływ na odpowiedni tryb,
ustawiłoby
... i porzuć sen. Posiadanie jednego modułu obsługi pętli głównej sprawia, że ​​tryb uśpienia wydaje się czystszy niż martwienie się nim przy każdym przerwaniu. Konieczność „zakręcenia” tą częścią głównej pętli przy każdym przerwaniu może nie być optymalnie wydajna, ale nie powinna być taka zła, szczególnie jeśli wszystkie przerwania, które mogą wpływać na zachowanie podczas snu, uderzą w jakąś flagę.
supercat