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 wbudowanych układów peryferyjnych. Ten mechanizm doskonale sprawdza się w mikrokontrolerach STM32, które są konstrukcyjnie przystosowane do selektywnego włączania i wyłączania większości wewnętrznych bloków peryferyjnych.
W artykule przedstawiamy tryby oszczędzania energii dostępne w mikrokontrolerach STM32. Przedstawione procedury przetestowano na zestawie ewaluacyjnym STM3210B-EVAL.

Sleep mode
Ten tryb oszczędnościowy jest integralną częścią rdzenia Cortex-M3, dlatego informacji na jego temat należy szukać nie w dokumentacji przygotowanej przez producenta mikrokontrolerów, ale w opisie rdzenia przygotowanym 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 generatora 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. I tak, ż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ń jest w takim przypadku 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 ten 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. Ilustracja wykorzystania trybu 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 sekundy 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 – wynosi około 2 ?s. Wynika to z faktu, że nie jest potrzebny czas na obsługę przerwania. Nasuwa się wniosek, że wykorzystanie instrukcji WFE jest dobrym sposobem współpracy z zegarem czasu rzeczywistego RTC, którego zadaniem może być generowanie zdarzeń (alarmów).

 

List. 2. Sposób konfiguracji RTC powodujący wybudzanie CPU co pół minuty

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 STM3210B-EVAL.
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 pętli nieskończonej, 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ą włączenia trybu deep-sleep jest wyłączenie zegara rdzenia, łą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.

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” generatory: 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 urządzeń. 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” generatory (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 stop mode przedstawiono na list. 3. Ten prosty program ma za 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” generatory, to po powrocie do normalnej pracy należy je ponownie włączyć. Jeżeli aplikacja korzysta z wewnętrznego generatora 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, standby mode 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” generatory (backup domain), pod takim warunkiem, że do mikrokontrolera doprowadzono zasilanie awaryjne bateryjne Vbatt. Pracę kontynuuje niezależny watchdog (o ile został wcześniej włączony) i układy wybudzania.
Czas potrzebny na powrót do pracy z trybu czuwania wynosi minimum 50 ?s, jeżeli system jest taktowany za pomocą wewnętrznego generatora HSI. Jeśli układ będzie się wybudzał do pracy z zewnętrznym generatorem 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” generator 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 wykorzystującego standby mode

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 mode następuje po naciśnięciu przycisku (w zestawie STM3210B-EVAL dołączonego do 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 standby mode.
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 świeci się dioda LD2. 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