Oszczędzanie energii w mikrokontrolerach STM32

Zmniejszenie poboru energii przez mikrokontroler można osiągnąć poprzez zmniejszenie częstotliwości jego taktowania (co wiąże się ze spowolnieniem działania) lub przez selektywne wyłączanie układy peryferyjnych wbudowanych w jego strukturę. Ten drugi mechanizm doskonale sprawdza się w mikrokontrolerach STM32, które są konstrukcyjnie przystosowane do selektywnego włączania i wyłączania większości wbudowanych bloków peryferyjnych.

 

 

Zaczniemy od omówienia dostępnych trybów oszczędnościowych, których działanie przetestowano na zestawie STM3210B–EVAL/A.

Sleep mode
Ten tryb oszczędzania energii jest integralną częścią rdzenia Cortex-M3, dlatego informacji na jego temat należy szukać nie w dokumentacji przygotowanej przez producenta mikrokontrolerów STM32, ale w opisie rdzenia przygotowanej przez firmę ARM.
W trybie sleep mode wyłączany jest rdzeń, natomiast wszystkie bloki peryferyjne i źródła sygnałów zegarowych są aktywne. Czas wyjścia z trybu uśpienia i powrót do normalnej pracy mikrokontrolera jest najkrótszy ze wszystkich dostępnych pośród trybów obniżonego poboru mocy. Natężenie prądu pobieranego ze źródła zasilania zależy przede wszystkim od częstotliwości pracy generatorów przebiegów taktujących i aktywnych bloków peryferyjnych. W skrajnym przypadku, gdy mikrokontroler pracuje z zegarem 72 MHz (włączone HSE i PLL) i są włączone wszystkie peryferia, mikrokontroler może pobierać blisko 15 mA. Drugim skrajnym przypadkiem jest praca rdzenia z taktowaniem 125 kHz przy wykorzystaniu wewnętrznego oscylatora HSI i po wyłączeniu wszystkich peryferiów. W takich warunkach mikrokontroler pobiera prąd o natężeniu nie przekraczającym 0,5 mA.
Zależnie od zastosowanych mechanizmów „usypiania” i „wybudzania” rozróżniane jest kilka rodzajów trybów pracy. Za sterowanie tymi trybami odpowiadają dwie instrukcje: WFI (Wait For Interrupt) oraz WFE (Wait For Event). Są to rozkazy asemblerowe, ich użycie w programie napisanym w C wymaga zastosowania podwójnego podkreślenia w roli przedrostka. Przykładowo: żeby wywołać instrukcję WFI należy zastosować zapis:
__WFI();

Sleep-on-exit
Wykorzystanie instrukcji WFI umożliwia między innymi wprowadzenie rdzenia w stan sleep-on-exit. Rdzeń w takim przypadku jest wprowadzany w tryb uśpienia dopiero po zakończeniu obsługi wszystkich przerwań. Wyjście z trybu jest wówczas, gdy w systemie zostanie wykryte żądanie obsługi przerwania. Fragment programu, który działa w opisany sposób przedstawiono na list. 1. Nadzór nad trybami uśpienia sprawowany jest przez NVIC, dlatego wykorzystano funkcje sterujące jego pracą.

List. 1. Fragment programu ilustrującego tryb oszczędzania energii sleep-on-exit

Przerwanie od wyprowadzenia PD0 mikrokontrolera, do którego podłączono przycisk, skonfigurowano do reakcji na zbocze opadające. Po jego pojawieniu rdzeń wyjdzie z trybu uśpienia i nastąpi wywołanie funkcji obsługi przerwania.
Funkcja obsługi przerwania wygląda następująco:

Rdzeń wybudza się na czas około 2 sekund zaświecając cztery diody LED, gasi je i ponownie „usypia”.
Uruchomienie opisanego trybu w trakcie debugowania spowoduje przerwanie pracy debugera. Rdzeń nie pracuje, więc nie ma potrzeby poszukiwania błędów. Istnieje jednak możliwość takiej konfiguracji mikrokontrolera, że nawet w trybach obniżonego poboru mocy, połączenie debugera i mikrokontrolera nie zostanie zerwane. Do tego celu służy funkcja DBGMCU_Config().Przykładowo, jeśli wykorzystywany jest tryb uśpienia, to należy umieścić w programie linijkę:

Sleep-now
Działanie tego trybu polega na natychmiastowym wprowadzeniu rdzenia w tryb obniżonego poboru mocy. Można to zrobić zarówno za pomocą instrukcji WFI jak i WFE. Różnica polega na tym, że w pierwszym przypadku system będzie oczekiwał nadejścia przerwania, a w drugim zdarzenia. Czas potrzebny na wybudzenie rdzenia z trybu uśpienia w oczekiwaniu na zdarzenie (rozkaz WFE) jest prawie dwukrotnie krótszy i równy około 2 s. Wynika to z faktu, że nie jest potrzebny czas na obsługę przerwania. Nasuwa się wniosek, że wykorzystanie instrukcji WFE bardzo dobrze nadaje się do współpracy z zegarem czasu rzeczywistego RTC, którego zadaniem może być generowanie zdarzeń (alarmów).

List. 2. Program wybudzający mikrokontroler z trybu sleep-now co 30 sekund

 
W programie z list. 2 RTC skonfigurowano w taki sposób, aby wybudzać mikrokontroler co pół minuty. W trybie normalnej pracy świeci dioda LD1. Dodatkowo w tym przykładzie status aktualnego zadania osiąga najwyższy priorytet, a co za tym idzie NVIC nie przerwie jego wykonywania na rzecz obsługi przerwania. Można to zaobserwować, jeśli włączy się np. przerwanie od przycisku (PB9), a w funkcji obsługi przerwania umieści się fragment kodu, który będzie powodował widoczne efekty na płytce ewaluacyjnej.
Przykład takiej funkcji:

W celu sprawdzenia, czy omawiana aplikacja działa poprawnie można uruchomić program z list. 2, z dodaną na początku głównej, nieskończonej pętli następującą linijką:

Przerwania nie są blokowane, więc naciśnięcie przycisku w stanie aktywnym powinno spowodować zaświecenie diody LD4.

Deep-sleep
Ostatnim trybem obniżonego poboru mocy rdzenia jest „głębokie uśpienie”. Nie jest to kolejny oddzielny tryb. Uzyskuje się go łącząc tryb natychmiastowego uśpienia (sleep-now), lub z trybem uśpienia po obsłużeniu wszystkich przerwań (sleep-on-exit). Tryb aktywowany jest przez ustawienie bitu SLEEPDEEP w rejestrze kontrolnym zarządzania zasilaniem kontrolera przerwań NVIC.
Programista używający biblioteki API nie musi zagłębiać się w te szczegóły, wystarczy użyć odpowiedniej funkcji: jeżeli aplikacja wymaga wprowadzenia rdzenia w stan głębokiego uśpienia, to należy umieścić w kodzie dodatkowo linijkę:

Konsekwencją aktywizacji Deep-sleep jest wyłączenie zegara taktującego rdzeń, łącznie z układem PLL. Pozwala to dalsze, w stosunku do poprzednich trybów, obniżenie poboru mocy. Negatywnym efektem zastosowania głębokiego uśpienia jest dłuższy czas potrzebny na całkowite wybudzenie i rozpoczęcie normalnej pracy. Jest to związane z tym, że pętla synchronizacji fazowej wymaga czasu, aby wygenerować stabilny sygnał zegarowy.

Tryb zatrzymania (stop mode)
Wykorzystuje on omówiony wcześniej tryb głębokiego uśpienia. Wyłączone zostają wszystkie sygnały zegarowe (układ PLL i oba szybkie oscylatory: HSI i HSE). Ponadto wewnętrzny regulator napięcia 1,8 V można wprowadzić kosztem późniejszego dłuższego startu systemu w stan obniżonego poboru mocy.
W stop mode nadal pracuje kilka bloków. Należy do nich między innymi układ niezależnego watchdoga. Należy brać to pod uwagę w aplikacjach wykorzystujących IWDG i pracujących w trybach obniżonego poboru mocy.
Jeżeli wcześniej nie zostaną wyłączone zegar czasu rzeczywistego i wolne oscylatory (LSE i LSI), to we wszystkich trybach obniżonego poboru mocy są w stanie aktywnej pracy.

List. 3. Sposób wprowadzenie mikrokontrolera w tryb stop

Sposób wprowadzenie mikrokontrolera w tryb stop przedstawiono na list. 3. Ten prosty program ma zadanie wprowadzać mikrokontroler w tryb zatrzymania co ok. 2 sekundy, przy czym samo zatrzymanie trwa 10 sekund. Sygnalizacja normalnej pracy odbywa się przez zaświecenie diody LD1. Wybudzanie następuje pod wpływem alarmów generowanych za pomocą zegara czasu rzeczywistego. Po wyczyszczeniu flagi od zdarzenia od RTC następuje ustawienie czasu, po jakim ma być uformowany kolejny sygnał alarmu. W przykładzie mikrokontroler ma się wybudzać co 10 sekund, więc za każdym razem do aktualnej wartości licznika RTC należy dodać wartość 10. Osiągnięcie przez licznik otrzymanej w ten sposób liczby będzie skutkowało alarmem. Zadaniem kolejnej wywołanej funkcji PWR_EnterSTOPMode() jest właściwe wprowadzenie układu w stan zatrzymania. W programie, ze względu na brak specjalnych wymagań co do minimalnego czasu startu systemu, regulator napięcia wprowadzany jest w tryb low power.
W tym miejscu praca mikrokontrolera zostaje wstrzyma i od tego miejsca rozpocznie się po wybudzeniu. W związku z tym, że w trybie zatrzymania wyłączane są zarówno pętla synchronizacji fazowej jak i szybkie oscylatory, to po powrocie do normalnej pracy należy je ponownie włączyć. Jeżeli aplikacja korzysta z wewnętrznego oscylatora HSI i nie wykorzystuje układu PLL, to ponowna konfiguracja tych urządzeń nie jest potrzebna: system automatycznie startuje biorąc HSI za źródło swojego sygnału zegarowego.
 
Standby mode
Użycie tego trybu pozwala na najbardziej drastyczne obniżenie poboru energii. Podobnie do trybu zatrzymania, także tryb standby korzysta z głębokiego uśpienia rdzenia (deep-sleep), jednak stabilizator 1,8 V jest wyłączany bez dodatkowej interwencji użytkownika. Wejście w tryb czuwania powoduje utratę danych zawartych w pamięci RAM inaczej, niż w poprzednich trybach. Nienaruszone zostają natomiast dane zawarte w rejestrach chronionych przed utratą oraz nadal pracują wolne oscylatory (backup domain), pod takim warunkiem, że do mikrokontrolera doprowadzono zasilanie awaryjne bateryjne Vbatt. Pracę kontynuuje układ niezależnego watchdoga (o ile został wcześniej włączony) i układy wybudzania.
Czas, jaki jest potrzebny na powrót do pracy z trybu czuwania wynosi minimum 50 s, dla startu systemu z wewnętrznym oscylatorem HSI. Jeśli układ będzie się wybudzał do pracy z zewnętrznym oscylatorem HSE, a do uzyskania użytecznego sygnału zegarowego będzie wykorzystywał układ PLL, to czas potrzebny na wybudzanie będzie wielokrotnie dłuższy.
Pobór prądu, gdy włączone są: wolny oscylator i układ zegara czasu rzeczywistego, waha się od 3…3,5 mA, zależnie od wartości napięcia zasilającego.
Mikrokontroler wybudza się, pojawi się sygnał zerowania (Reset), po przepełnieniu niezależnego watchdoga (IWDG), przy narastającym zboczu sygnału na linii Wakeup, lub po alarmie zegara RTC. Układ rozpoczyna normalną pracę w taki sam sposób, jak po wystąpieniu sprzętowego sygnału Reset. Z tego powodu czas potrzebny na osiągnięcie normalnej pracy systemu jest najdłuższy. Oznaką tego, że uruchomienie następuje po wybudzeniu z trybu czuwania jest ustawiona flaga SBF w rejestrze PWR_CSR. W bibliotece funkcji API nazywa się ona PWR_FLAG_SB.

List. 4. Fragment programu ilustrującego wykorzystanie trybu czuwania

Na list. 4 przedstawiono fragment programu wykorzystującego tryb czuwania. Aplikacja o stanie pracy mikrokontrolera informuje za pomocą diody LD1. Jej świecenie sygnalizuje normalną pracę, natomiast zgaszenie tryb czuwania. Układ powraca do normalnego stanu po naciśnięciu przycisku RESET lub WAKEUP.
Poza wymienionymi funkcjami, konfigurowany jest również układ zegara czasu rzeczywistego. Dzięki temu podczas debugowania można zaobserwować aktualny stan mikrokontrolera. Ponowne wprowadzenie trybu standby następuje po naciśnięciu przycisku (PB9).
Zadaniem głównej, nieskończonej pętli programu jest nieustanne sprawdzanie stanu linii PB9. Gdy pojawi się na niej stan niski, to system jest przygotowywany do wejścia w tryb czuwania. Ostatecznie, wywołanie funkcji PWR_EnterSTANDBYMode() powoduje włączenie trybu standby.
Na początku programu, po standardowym skonfigurowaniu systemu do pracy, włączana jest możliwość debugowania mikrokontrolera po wprowadzeniu do trybu czuwania oraz obsługa Wakeup i dostęp do rejestrów chronionych przed utratą. Kolejną, istotną czynnością, jest sprawdzenie, czy mikrokontroler rozpoczyna pracę po wybudzeniu, czy też nie. Jeżeli flaga PWR_FLAG_SB jest ustawiona, to zaświecana jest diody LD2, a wywołanie funkcji PWR_ClearFlag() powoduje wyzerowanie flagi trybu czuwania. Na koniec funkcja RTC_WaitForSynchro() sprawia, że mikrokontroler czeka na synchronizację z układem zegara czasu rzeczywistego. Jest ona niezbędna, ponieważ układ ten podłączony jest do wewnętrznej magistrali APB1, a w czasie czuwania jej taktowanie zostało wyłączone.
W przypadku, gdy flaga PWR_FLAG_SB była wyzerowana, konfigurowany jest układ zegara czasu rzeczywistego i jego sygnał zegarowy.

Krzysztof Paprocki

Autor: