Precyzyjne opóźnienia w połączeniu z trybami oszczędzania energii w STM32, część 2

W pierwszej części artykułu przedstawiono funkcje realizujące precyzyjne odmierzanie opóźnień w połączeniu z trybami oszczędzania energii dla mikrokontrolerów STM32 z rdzeniem Cortex-M3. W części drugiej omówiono konfigurowanie sygnałów zegarowych, przykładowy program testowy i układ, na którym wykonano testy. Zamieszczono też wyniki pomiarów prądu zasilania w poszczególnych trybach obniżonego poboru energii oraz rzeczywistych czasów opóźnień.

Dystrybucja sygnałów zegarowych w STM32

Aby wykorzystać opisane w pierwszej części artykułu funkcje opóźniające, musimy jeszcze skonfigurować oscylator kwarcowy mikrokontrolera oraz sygnały taktujące rdzeń i peryferie.

 

 Rys. 1. Budowa systemu taktującego w STM32F107

Rys. 1. Budowa systemu taktującego w STM32F107

 

Dystrybucję sygnałów zegarowych w STM32F107 w uproszczony sposób przedstawiono na rysunku 1. Rdzeń jest taktowany sygnałem HCLK, którego maksymalna częstotliwość wynosi 72 MHz. Peryferie są taktowane sygnałami PCLK1 i PCLK2 otrzymywanymi z HCLK za pomocą dzielników wstępnych (ang. prescalers) APB1 i APB2. Sygnały PCLK1 i PCLK2 mogą mieć maksymalne częstotliwości odpowiednio 36 i 72 MHz, dlatego ustawiamy wartości dzielników APB1 i APB2 odpowiednio na 2 i 1. Częstotliwość taktowania liczników TIM2, TIM3, TIM4 i TIM5 zależy od współczynnika podziału APB1. Jeśli ma on wartość 1, to liczniki te są taktowane sygnałem PCLK1 o tej samej częstotliwości co HCLK. Jeśli natomiast dzielnik APB1 ma wartość różną od 1, to PCLK1 ma częstotliwość APB1 razy mniejszą niż HCLK, ale omawiane liczniki są taktowane sygnałem o częstotliwości dwa razy większej niż PCLK1. W naszym przypadku APB1 ma wartość 2, zatem liczniki te są taktowane z częstotliwością HCLK.

 

List. 4. Plik nagłówkowy clock.h

Sygnały taktujące są konfigurowane za pomocą funkcji ClockConfigure, której argumentem jest częstotliwość HCLK w MHz, jaką chcemy ustawić. Dopuszczalne wartości argumentu to 12, 14, 16, 18, 24, 28, 32, 36, 48, 56, 64, 72 MHz. Funkcja ta zwraca zero, gdy konfiguracja powiedzie się lub wartość ujemną, gdy podano błędną wartość argumentu, gdy nie uda się wystartować oscylatora kwarcowego HSE lub któraś z PLL nie zsynchronizuje się. Odpowiednia deklaracja jest umieszczona na listingu 4. Znajduje się tam też deklaracja funkcji AllPinsDisable, która konfiguruje wszystkie porty wejścia-wyjścia, tak aby zminimalizować pobór prądu przez mikrokontroler. Implementacja zamieszczona jest na listingu 5.

 

List. 5. Implementacja konfiguracji sygnałów taktujących, umieszczona w pliku clock.c

Spójrzmy ponownie na rysunek 1. Do wejścia dzielnika AHB podłączony jest sygnał SYSCLK. Może to być sygnał 8 MHz z wewnętrznego oscylatora RC HSI (tak dzieje się po włączeniu zasilania, po resecie lub po wyjściu z trybu głębokiego uśpienia), sygnał HSE z oscylatora kwarcowego lub sygnał PLLCLK z wyjścia PLL1. Do wejścia PLL1 można dołączyć sygnał HSI podzielony przez 2 lub sygnał z wyjścia dzielnika częstotliwości PREDIV1. Do wejścia dzielnika PREDIV1 można dołączyć sygnał HSE lub sygnał PLL2CLK z wyjścia PLL2. Do wejścia PLL2 podłączony jest sygnał HSE przez dzielnik PREDIV2. Dobierając wartości współczynników podziału i mnożniki PLL, możemy uzyskać szereg różnych częstotliwości HCLK taktowania rdzenia.
W omawianym przykładzie zakładamy, że oscylator kwarcowy HSE ma częstotliwość 5, 10, 15, 20 lub 25 MHz. Sygnał z tego oscylatora przez dzielnik PREDIV2 jest podawany na wejście PLL2. Wartość dzielnika dobieramy tak, aby na wejściu PLL2 mieć zawsze sygnał o częstotliwości 5 MHz. Mnożnik PLL2MUL ustawiamy na 8, zatem na wyjściu PLL2 uzyskujemy sygnał PLL2CLK o częstotliwości 40 MHz. Sygnał ten podajemy na wejście dzielnika PREDIV1, który ustawiamy na 5. Zatem na wyjściu dzielnika PREDIV1 mamy sygnał o częstotliwości 8 MHz, który podajemy na wejście PLL1. Dobierając mnożnik PLLMUL spośród wartości 6, 7, 8 lub 9, uzyskujemy na wyjściu PLL1 sygnał PLLCLK o częstotliwości odpowiednio 48, 56, 64 lub 72 MHz, który podajemy na wejście dzielnika AHB. Ustawiając wartość tego dzielnika na 1, 2 lub 4, uzyskujemy pożądaną częstotliwość sygnału HCLK.
Omawiany przykład napisany jest na mikrokontroler STM32F107, który należy do linii zorientowanej na komunikację (ang. connectivity line). Używamy Standard Peripheral Library. Podczas kompilacji wszystkich plików źródłowych przykładu oraz plików bibliotecznych muszą być zdefiniowane stałe preprocesora STM32F10X_CL i USE_STDPERIPH_DRIVER. Stałe te należy wyspecyfikować w konfiguracji projektu lub w wierszu poleceń kompilatora. Analogicznie powinna też zostać zdefiniowana wartość stałej HSE_VALUE, która oznacza częstotliwość podłączonego do mikrokontrolera kwarcu w Hz. Przyjęta konfiguracja sygnałów taktujących dopuszcza częstotliwości kwarcu będące wielokrotnościami 5 MHz. Taka sama wartość tej stałej musi być użyta podczas kompilowania Standard Peripheral Library. Należy dodać, że jeśli nie zdefiniujemy tej stałej, to przy zdefiniowanej stałej STM32F10X_CL domyślna wartość częstotliwości wynosi 25000000 Hz. Taki właśnie kwarc jest zamontowany na „motylu”, na którym testowany był omawiany przykład. Należy też zwrócić uwagę, że w wersjach biblioteki starszych niż 3.2.0 stała ta nazywała się HSE_Value. Trzeba wtedy przed pierwszym użyciem stałej HSE_VALUE dodać definicję:

Program i układ testowy

Układ testowy przedstawiony jest na rysunku 2 (zestaw STM32Butterfly lub STM32Butterfly2). Używamy dwóch diod świecących. LED1 wykorzystujemy do informowania o poprawnym lub niepoprawnym zakończeniu konfigurowania mikrokontrolera lub do sygnalizacji, że mikrokontroler jest w stanie aktywnym. LED2 sygnalizuje, że mikrokontroler jest w trakcie obsługi przerwania, które jest zgłaszane narastającym zboczem na wyprowadzeniu PA0 za pomocą przycisku SW. Pozwala to testować wybudzanie z trybów uśpienia i zatrzymania w celu obsłużenia przerwania. Po jego obsłużeniu mikrokontroler powinien ponownie zasnąć. Czas opóźnienia nie powinien zależeć od ilości zgłoszonych i obsłużonych w jego trakcie przerwań. Przycisk SW ma jeszcze jedną funkcję. Wyprowadzenie PA0 skonfigurowaliśmy tak, aby narastające zbocze na nim wyprowadzało mikrokontroler z trybu czuwania. Wyjście FREQ służy do pomiaru czasu opóźnień poprzez pomiar częstotliwości generowanego na nim przebiegu.

 

 Rys. 2. Układ testowy

Rys. 2. Układ testowy

 

List. 6. Sygnatury funkcji obsługujących wyjścia, umieszczone w pliku out.h

Sygnatury funkcji obsługujących wyjścia przedstawiono na listingu 6. Funkcje te są standardowe i dlatego nie zamieszczono ich szczegółowej implementacji. Funkcja OutConfigure konfiguruje porty wyjściowe. Wyprowadzenia LED i FREQ konfigurujemy jako wyjścia przeciwsobne (ang. push-pull). Wyjścia LED konfigurujemy dla maksymalnej częstotliwości 2 MHz, a wyjście FREQ ? 50 MHz. Funkcje LED1on, LED1off, LED2on i LED2off, zgodnie z nazwami, włączają lub wyłączają odpowiednią LED. Funkcja FREQon ustawia wysoki poziom na wyjściu FREQ, a funkcja FREQoff ? poziom niski. Funkcja Error gasi LED1, a potem wykonuje podaną jako argument liczbę mignięć LED1, po czym odczekuje dłuższą chwilę. Funkcja ta sygnalizuje błędy w połączeniu z makrem error_check. Makro to wywołujemy, aby sprawdzić, czy jakaś funkcja zakończyła się poprawnie. Jako pierwszy argument tego makra umieszczamy wywołanie funkcji, która zwraca zero przy poprawnym zakończeniu, a wartość ujemną, gdy wystąpi błąd. Jako drugi argument podajemy numer błędu – liczbę mignięć LED1. Przykładowo:

Sygnatura funkcji KeyConfigure konfigurującej wyprowadzenie, do którego podłączono przycisk jest przedstawiona na listingu 7.

 

List. 7. Sygnatura funkcji konfigurującej przycisk, umieszczona w pliku key.h

Jej implementacja zamieszczona jest na listingu 8. Znajduje się tam też procedura obsługi przerwania EXTI0_IRQHandler, która symuluje wykonywanie jakichś obliczeń za pomocą funkcji Delay.

 

List. 8. Implementacja obsługi przycisku, umieszczona w pliku key.c

 

List. 9. Program testowy umieszczony w pliku main.c

Na listingu 9 zamieszczono przykładowy program główny. Częstotliwość w MHz taktowania rdzenia i liczników ustawiamy za pomocą stałej HCLK_MHZ. Rodzaj testu wybieramy za pomocą makrodefinicji:

Pobór prądu zasilania możemy mierzyć, definiując stałą SUPPLY_CURRENT. Zakomentowanie tej definicji umożliwia pomiar czasu opóźnień mikrosekundowych i milisekundowych. Na wyjściu FREQ pojawia się wtedy sygnał o częstotliwości będącej w przybliżeniu połową odwrotności opóźnienia zadanego definicjami USLEEP_TIME i MSLEEP_TIME. Jeśli zdefiniowana jest stała  USLEEP_TIME, to w pętli wywoływana jest dwukrotnie funkcja USleep z argumentem równym tej stałej. Analogicznie jeśli zdefiniowana jest stała MSLEEP_TIME, to w pętli wywoływana jest dwukrotnie funkcja MSleep z argumentem równym tej stałej. W czasie pierwszego wywołania każdej z tych funkcji na wyjściu FREQ jest poziom wysoki, a w czasie drugiego ? poziom niski. Jeśli żadna z tych stałych nie jest zdefiniowana, mierzymy częstotliwość iteracji „pustej” pętli, bez funkcji opóźniających. Zmierzony czas opóźnienia obliczamy jako połowę różnicy miedzy okresem iteracji pętli z funkcjami opóźniającymi a okresem iteracji pętli bez funkcji opóźniających. Sposób pracy regulatora 1,8 V w trybie zatrzymania wybieramy za pomocą stałej PWR_REGULATOR_MODE zdefiniowanej w pliku sleep.c, co zostało omówione w pierwszej części artykułu.
Aby skompilować przykład, potrzebny jest jeszcze plik nagłówkowy delay.h zawierający sygnaturę funkcji Delay, która została przedstawiona na początku pierwszej części artykułu. W pliku delay.c należy umieścić jej implementację, a w pliku out.c ? implementacje funkcji, których sygnatury są na listingu 6. Ponadto Standard Peripheral Library wymaga obecności pliku stm32f10x_conf.h, który w naszym przypadku zawiera tylko puste makro:

Wyniki testów

Układ z rysunku 2 można uruchomić na „motylu”, który trzeba doposażyć w kwarc zegarkowy i dwa kondensatory ? odpowiednie miejsce zostało przewidziane na płytce. Potrzebne dwie diody świecące są podłączone tak samo jak na rysunku 2. Do wyprowadzenia PA0 trzeba podłączyć rezystor ściągający do masy i przycisk do zasilania. Wyprowadzenie PA0 jest dostępne na PIN2 złącza JP1 modułu ethernetowego ? w omawianym przykładzie moduł ten nie jest używany. Do styków 1 i 2 złącza JP6 zamiast zworki podłączamy amperomierz, który mierzy prąd pobierany przez mikrokontroler. Do wyprowadzenia FREQ (PE7) podłączamy częstościomierz.

 

Tab. 1. Prąd zasilania w zależności od częstotliwości taktowania w poszczególnych trybach oszczędzania energii

Częstotliwość nominalna HCLK 12 MHz 18 MHz 24 MHz 36 MHz 48 MHz 72 MHz
Częstotliwość nominalna SYSCLK 48 MHz 72 MHz 48 MHz 72 MHz 48 MHz 72 MHz
Dzielnik AHB 4 4 2 2 1 1
Tryb aktywny 10,8 ± 0,5 mA 15,4 ± 0,8 mA 17,3 ± 1,0 mA 24,5 ± 1,3 mA 28,3 ± 1,3 mA 35,7 ± 1,4 mA
Tryb uśpienia 5,9 ± 0,2 mA 7,6 ± 0,4 mA 7,1 ± 0,4 mA 9,4 ± 0,6 mA 9,3 ± 0,6 mA 12,7 ± 1,0 mA
Tryb zatrzymania ? regulator 1,8 V włączony 35 ± 1,5 µA
Tryb zatrzymania ? regulator 1,8 V w stanie niskiego poboru energii 27 ± 1,4 µA
Tryb czuwania 3,36 ± 0,04 µA

 

W tabeli 1 zestawiono zmierzone wartości prądu zasilania dla sześciu wartości HCLK, po trzy dla minimalnej i maksymalnej częstotliwości SYSCLK, jakie można uzyskać z wyjścia PLL1. Pomiary zostały wykonane przy odłączonym interfejsie JTAG, którego działanie wpływa na ich wyniki, szczególnie istotnie zwiększając pobór prądu w trybie zatrzymania. W trybie aktywnym i uśpienia prąd zasilania powinien zależeć od częstotliwości taktowania. Widać jednak, że nie jest on dokładnie proporcjonalny do częstotliwości HCLK i zależy też od częstotliwości SYSCLK. Najprawdopodobniej istotna część prądu zasilania jest pobierana przez układy taktujące. Potwierdzają to pomiary dla trybu uśpienia, w którym przestają być taktowane rdzeń i pamięci. Ponieważ w testowanym układzie aktywna jest stosunkowo niewielka część peryferii, to za pobór prądu w tym trybie muszą odpowiadać głównie oscylator HSE i pętle PLL. W trybach głębokiego uśpienia (zatrzymania i czuwania) pobór prądu nie zależy od częstotliwości taktowania, gdyż wtedy HSE jest wyłączony. Wartości prądu zasilania w tych trybach są, zgodnie z oczekiwaniem, rzędu mikroamperów. Należy podkreślić, że dla uzyskania tak niskiego poboru prądu konieczne jest wywołanie funkcji AllPinsDisable. Jej pominięcie i pozostawienie „wiszących” wejść niweczy uzyskane oszczędności, zwłaszcza w trybie zatrzymania, gdyż mikrokontroler pobiera wtedy dodatkowo ponad 1,5 mA.

 

Tab. 2. Pomiary rzeczywistych czasów opóźnień

Częstotliwość nominalna HCLK 18 MHz 36 MHz 72 MHz
Częstotliwość f0 iteracji pętli bez funkcji opóźniających 300 kHz 563 kHz 912 kHz
Częstotliwość f1 iteracji pętli z opóźnieniami 1 µs + 1 µs 47,9 kHz 85,3 kHz 136,4 kHz
Częstotliwość f20 iteracji pętli z opóźnieniami 20 µs + 20 µs 22,6 kHz 24,3 kHz 24,4 kHz
Częstotliwość f1m iteracji pętli z opóźnieniami 1 ms + 1 ms 0,498 kHz 0,499 kHz 0,500 kHz
Najkrótsze możliwe opóźnienie: 0,5(1/f1 ? 1/f0) 8,8 ± 0,1 µs 4,97 ± 0,04 µs 3,12 ± 0,02 µs
Opóźnienie 20 µs: 0,5(1/f20 ? 1/f0) 20,4 ± 0,4 µs 19,7 ± 0,3 µs 20,0 ± 0,3 µs
Opóźnienie 1 ms: 0,5(1/f1m ? 1/f0) 1002 ± 7 µs 1001 ± 7 µs 999,5 ± 7 µs

Dokładność pomiaru częstotliwości ± (0,1% + 3)

 

W tabeli 2 zestawiono wyniki pomiarów opóźnień. Minimalne opóźnienie, jakie daje się uzyskać, wołając funkcję USleep z parametrem 1, zależy od częstotliwości taktowania rdzenia, czego należało oczekiwać, gdyż wynika ono z ilości instrukcji, które musi wykonać ta funkcja i procedura obsługi przerwania licznika. Przy maksymalnej dopuszczalnej częstotliwości taktowania 72 MHz nie udało się uzyskać opóźnienia krótszego niż 3 µs. Należy uznać, że dokładność realizacji opóźnień jest zadowalająca.
Marcin Peczarski

Autor: