Precyzyjne opóźnienia w połączeniu z trybami oszczędzania energii w STM32, część 1
Najpierw zerujemy licznik TIMER. Konfigurujemy go, aby zgłosił przerwanie po osiągnięciu zadanej wartości opóźnienia. Startujemy licznik, a następnie zasypiamy. Po wybudzeniu sprawdzamy, czy przyczyną obudzenia było przerwanie zgłoszone przez TIMER. Jeśli źródłem przerwania nie był TIMER, ponownie zasypiamy. Ten algorytm wydaje się być poprawny, jednak zawiera bardzo subtelny błąd. Jeśli najpierw zostanie zgłoszony wyjątek (przerwanie) niepochodzący od licznika TIMER, warunek pętli będzie prawdziwy i zostanie ponownie wywołana instrukcja WFI. Między przeczytaniem odpowiedniego rejestru statusu licznika a ponownym zaśnięciem mikrokontroler wykona kilka instrukcji maszynowych: sprawdzenie warunku, skok warunkowy. Jeśli w tym czasie TIMER zgłosi przerwanie, to zostanie ono obsłużone przed wywołaniem instrukcji WFI i jej wywołanie uśpi mikrokontroler, aż pojawi się kolejny wyjątek. Opóźnienie wydłuży się nieprzewidywalnie.
Poprawnym rozwiązaniem jest użycie instrukcji WFE. Instrukcja ta współpracuje z jednobitowym rejestrem zdarzenia (one-bit event register). Rejestr ten jest testowany po wywołaniu tej instrukcji. Jeśli rejestr zdarzenia jest wyzerowany, mikrokontroler zasypia. Jeśli rejestr zdarzenia jest ustawiony, mikrokontroler zeruje go i kontynuuje wykonywanie programu – nie zasypia. Jeśli mikrokontroler zasnął na instrukcji WFE i następnie został ustawiony rejestr zdarzenia, mikrokontroler budzi się, a rejestr zdarzenia zostaje wyzerowany. Rejestru zdarzenia nie można odczytywać programowo. Rejestr ten jest zerowany tylko przez instrukcję WFE i może być ustawiany sprzętowo. Z pewnymi ograniczeniami, o których będzie napisane później, źródłami, które ustawiają ten rejestr, mogą być te same źródła, które wyzwalają wyjątki (przerwania). Co będzie dla nas bardzo ważne, rejestr zdarzenia może być też ustawiony programowo instrukcją SEV (send event). Poprawny algorytm jest następujący:
zeruj TIMER; konfiguruj TIMER, aby po osiągnięciu zadanej wartości ustawił rejestr zdarzenia i zatrzymał się; wystartuj TIMER; do WFE; while (TIMER nie osiągnął zadanej wartości);
Z poziomu języka C instrukcje WFE, WFI i SEV można wywoływać za pomocą, dostępnych w bibliotece CMSIS, makr:
void __WFE(void); void __WFI(void); void __SEV(void);
Żeby wprowadzić mikrokontroler w tryb głębokiego uśpienia, trzeba przed wywołaniem instrukcji WFE lub WFI ustawić bit SLEEPDEEP w rejestrze SCB_SCR. Dodatkowo dla trybu zatrzymania (stop mode) należy wyzerować bit PDDS (power down deep sleep) w rejestrze PWR_CR (power control register) i skonfigurować w tym rejestrze bit LPDS (low power deep sleep) zależnie od wybranego sposobu pracy regulatora 1,8 V. Wyzerowanie tego bitu oznacza pozostawienia regulatora włączonego, a jego ustawienie – wprowadzenie regulatora w stan niskiego poboru energii. Prostszym sposobem jest wywołanie funkcji bibliotecznej:
void PWR_EnterSTOPMode( uint32_t PWR_Regulator, uint8_t PWR_STOPEntry );
Parametr PWR_Regulator specyfikuje sposób pracy regulatora i może przyjmować wartość PWR_Regulator_ON lub PWR_Regulator_Low. Natomiast parametr PWR_STOPEntry specyfikuje instrukcję wywoływaną w celu zaśnięcia i może przyjmować wartość PWR_STOPEntry_WFI lub PWR_STOPEntry_WFE.
Aby wprowadzić mikrokontroler w tryb czuwania (standby) należy przed wywołaniem instrukcji WFE lub WFI, oprócz ustawienia bitu SLEEPDEEP, wyzerować bit WUF (wake-up flag) w rejestrze PWR_CR przez ustawienie bitu CWUF (clear wake-up flag) oraz ustawić bit PDDS w tym rejestrze. Jednak prostszym sposobem jest wywołanie funkcji bibliotecznej:
void PWR_EnterSTANDBYMode(void);
Ta funkcja wywołuje instrukcję WFI. W przypadku przechodzenia w tryb czuwania nie ma różnicy między działaniem instrukcji WFE a WFI.
Funkcje opóźniające
Plik nagłówkowy definiujący sygnatury funkcji opóźniających umieszczony jest na listingu 1.
List. 1. Sygnatury funkcji opóźniających umieszczone w pliku sleep.h
#ifndef _SLEEP_H #define _SLEEP_H 1 void USleep(unsigned); void MSleep(unsigned); void Sleep(unsigned); void Standby(unsigned); int SleepConfigure(unsigned); #endif
Funkcja USleep dopuszcza wartości parametru wejściowego z zakresu od 0 do 65535 i realizuje opóźnienie o zadaną wartość mikrosekund. Funkcja MSleep dopuszcza wartości parametru wejściowego z zakresu od 0 do 32767 i realizuje opóźnienie o zadaną wartość milisekund. Funkcja Sleep dopuszcza wartości parametru wejściowego z zakresu od 0 do 232–1 i realizuje opóźnienie o zadaną wartość sekund. Funkcja Standby wprowadza mikrokontroler w tryb czuwania na zadaną liczbę sekund i dopuszcza wartości parametru wejściowego z zakresu od 0 do 232–1. Funkcja SleepConfigure konfiguruje moduł realizujący opóźnienia i powinna być wywołana, zanim zostanie wywołana któraś z funkcji opóźniających. Jej argumentem jest częstotliwość taktowania układów licznikowych TIMx w MHz. W omawianym przykładzie konfigurujemy ją tak, aby była równa częstotliwości taktowania rdzenia, co zostanie omówione w drugiej części artykułu.