Precyzyjne opóźnienia w połączeniu z trybami oszczędzania energii w STM32, część 1
Częstym problemem, który pojawia się przy pisaniu programów na mikrokontrolery, jest zaimplementowanie opóźnienia. W artykule przedstawiono sposób precyzyjnego odmierzania opóźnień w połączeniu z trybami oszczędzania energii dla mikrokontrolerów STM32 z rdzeniem Cortex-M3. Do odmierzania czasu wykorzystano układy licznikowe. Na czas opóźnienia mikrokontroler jest usypiany w jednym z trybów oszczędzania energii. Mikrokontroler może zostać wybudzony wcześniej, przed upływem zadanego czasu, jeśli zajdzie potrzeba obsłużenia jakiegoś przerwania, a po jego obsłużeniu powraca do trybu oszczędzania energii, aby dokończyć „drzemkę”.
Jedna z najprostszych funkcji opóźniających, jaką da się napisać w języku C, wygląda następująco:
void Delay(volatile unsigned count) { while(count--); }
W wielu zastosowaniach jest to rozwiązanie zadowalające. Jednak funkcja ta ma kilka poważnych wad. Realizowane opóźnienie jest nieprecyzyjne. Zależnie od ustawionej dla kompilatora opcji optymalizacji i opóźnień wprowadzanych przez pamięć programu (flash latency) jedna iteracja pętli może trwać od kilku do kilkunastu taktów zegara. Jeśli w trakcie wykonywania tej funkcji zostanie zgłoszone przerwanie, to czas opóźnienia wydłuży się o czas jego obsługi. Ponadto mikrokontroler przez cały czas kręcenia się w pętli pobiera energię, która jest marnowana. Chyba jedyną zaletą tej funkcji jest prostota.
Można pokusić się o implementację bardziej precyzyjnych funkcji opóźniających. Przykładowy program został napisany w języku C na mikrokontroler STM32F107 i można go uruchomić na „motylu”, czyli zestawie uruchomieniowym STM32Butterfly, co zostanie zaprezentowane w drugiej części artykułu. Program bardzo łatwo daje się przenieść na inne modele z rodziny STM32, gdyż korzysta ze Standard Peripheral Library i biblioteki CMSIS (Cortex Microcontroller Software Interface Standard), które są dostarczane przez STMicroelectronics. Do precyzyjnego odmierzania krótkich czasów od kilku do ok. 65535 mikrosekund lub od 1 do 32767 milisekund zostały wykorzystane dwa spośród liczników TIM2, TIM3, TIM4 lub TIM5, taktowane zegarem systemowym przez odpowiednio dobrane dzielniki wstępne (prescaler). Do realizacji długich opóźnień, wyrażanych w sekundach, zastosowano zegar czasu rzeczywistego (real time clock), taktowany kwarcem zegarkowym 32768 Hz, tykający z częstotliwością 1 Hz. Podczas czekania na zakończenie opóźnienia mikrokontroler jest przełączany w tryb oszczędzania energii. Gdy zostaje zgłoszone przerwanie, mikrokontroler wraca na czas jego obsługi do trybu aktywnego. Po zakończeniu obsługi przerwania ponownie jest wprowadzany w tryb oszczędzania energii. Czas obsługi przerwania nie wydłuża opóźnienia.
Tryby oszczędzania energii
Mikrokontrolery STM32 wyposażono w kilka trybów oszczędzania energii. W trybie uśpienia (sleep mode) wstrzymywane jest taktowanie rdzenia i pamięci, wszystkie układy peryferyjne pozostają taktowane. W trybach głębokiego uśpienia (deep sleep mode) wstrzymywane jest taktowanie rdzenia, pamięci i większości peryferii. Są dwa tryby głębokiego uśpienia: tryb zatrzymania (stop mode) i tryb czuwania (standby).
W trybie zatrzymania wyłączone są wszystkie sygnały taktujące w domenie 1,8 V. Wstrzymane są wewnętrzny oscylator RC o częstotliwości nominalnej 8 MHz (HSI – high speed internal), zewnętrzny oscylator kwarcowy (HSE – high speed external) i wszystkie synchroniczne pętle fazowe (PLL – phase locked loop). Wewnętrzny regulator napięcia 1,8 V może pozostać w stanie normalnej pracy albo zostać przełączony w stan niskiego poboru energii (low power mode). W trybie zatrzymania zawartość rejestrów i pamięci pozostaje niezmieniona. Wybudzenie z tego trybu może nastąpić w wyniku zgłoszenia jednego z 16 przerwań zewnętrznych (EXTI – external interrupt), przerwania wygenerowanego przez programowalny detektor napięcia zasilania (PVD – programmable voltage detector), na skutek wyzwolenia alarmu RTC lub za pomocą sygnału budzenia (wake-up) wygenerowanego przez interfejs USB lub Ethernet.
W trybie czuwania wewnętrzny regulator napięcia 1,8 V jest wyłączony. Oscylatory HSI, HSE i wszystkie PLL są również wyłączone. Po wejściu do tego trybu zawartość pamięci i większości rejestrów zostaje utracona. Zachowywana jest jedynie zawartość rejestrów zapasowych (backup registers). Wybudzenie z trybu czuwania może nastąpić za pomocą wyprowadzenia RESET, resetu wywołanego przez niezależnego nadzorcę (IWDG – independent watchdog), narastające zbocze na wyprowadzeniu WKUP-PA0 lub alarm RTC. Tryb czuwania różni się od pozostałych jeszcze tym, że po wybudzeniu z niego wykonywanie programu rozpoczyna się od początku. W pozostałych trybach wykonywanie programu jest wznawiane od miejsca, gdzie nastąpiło uśnięcie. Program wykorzystujący tryb czuwania musi sprawdzić przyczynę rozpoczęcia wykonania, czy jest to wynik resetu, włączenia zasilania czy wybudzenia.
We wszystkich trybach oszczędzania energii mogą pozostawać włączone:
- wewnętrzny oscylator RC o częstotliwości nominalnej 40 kHz (LSI – low speed internal), zwykle taktujący układ nadzorcy IWDG;
- zewnętrzny oscylator kwarcowy 32768 Hz (LSE – low speed external), taktujący zegar czasu rzeczywistego (RTC – real time clock).
Według noty katalogowej producenta wybudzanie z trybu uśpienia trwa typowo 1,8 µs, z trybu zatrzymania z włączonym regulatorem napięcia – typowo 3,6 µs, z trybu zatrzymania z regulatorem napięcia w stanie niskiego poboru energii – typowo 5,4 µs, a z trybu czuwania – typowo 50 µs. Jeśli korzystamy z oscylatora kwarcowego HSE, to przy wybudzaniu z głębokiego uśpienia (tryby zatrzymania i czuwania) do powyższych czasów należy jeszcze doliczyć, wynoszący typowo 2 ms, czas startu tego oscylatora (wyżej podane czasy obejmują jedynie start wewnętrznego oscylatora RC HSI) oraz czasy synchronizacji PLL, wynoszące maksymalnie 350 µs dla każdej pętli. Pętle pracują zwykle kaskadowo, więc czasy ich synchronizacji sumują się.
Do aktywowania trybu oszczędzania energii w mikrokontrolerach z rdzeniem Cortex-M3 służą instrukcje asemblerowe WFI (wait for interrupt) i WFE (wait for event). Dla ścisłości należy wspomnieć, że mikrokontroler w tryb uśpienia można też przełączyć, ustawiając bit SLEEPONEXIT w rejestrze SCB_SCR (system control block – system control register). Bit ten można też ustawić za pomocą funkcji bibliotecznej NVIC_SystemLPConfig. Mikrokontroler zaśnie wtedy po zakończeniu obsługi najbliższego przerwania, a następnie każdorazowo, gdy zostanie zgłoszone przerwanie, obudzi się, aby go obsłużyć i zaśnie po zakończeniu jego obsługi. Jednak w omawianym przykładzie nie korzystamy z funkcjonalności sleep-on-exit.
Po wywołaniu instrukcji WFI mikrokontroler natychmiast zasypia. Budzi się, gdy wystąpi wyjątek (exception), który ma ustawiony dostatecznie wysoki priorytet, aby wywołać procedurę obsługi. Wyjątkami są wszystkie przerwania zgłaszane przez peryferie oraz przerwania sprzętowe i programowe generowane przez sam rdzeń. Po obudzeniu mikrokontroler wykonuje procedurę obsługi zgłoszonego wyjątku (przerwania), a potem kontynuuje wykonywanie kodu znajdującego się bezpośrednio za instrukcją WFI, która spowodowała uśpienie. Ten mechanizm można spróbować wykorzystać do realizacji opóźnień. Algorytm mógłby wyglądać następująco:
zeruj TIMER; konfiguruj TIMER, aby po osiągnięciu zadanej wartości zgłosił przerwanie i zatrzymał się; wystartuj TIMER; do WFI; while (TIMER nie osiągnął zadanej wartości);