Kurs STM32L4 – #5 – taktowanie układu, RTC, watchdog, quiz

Kurs STM32L4 – #5 – taktowanie układu, RTC, watchdog, quiz

Mikrokontroler używany w naszym kursie STM32 może pracować z częstotliwością 80 MHz. Temat ten jest jednak dość rozległy i jedna wartość to zbyt mało, aby opisać możliwości tego układu.

Pora, aby omówić dostępne źródła taktowania. Sprawdzimy, czym się różnią i jak wpływają na pracę układu. Wykorzystamy też watchdoga.

Czego dowiesz się z tej części kursu STM32L4?

Tym razem zajmiemy się źródłami taktowania, które potocznie nazywa się zegarami. Omówimy różne dostępne źródła taktowania i sprawdzimy, jak ich dokładność wpływa na działanie układu. Przy okazji nauczymy się również obsługi nowej zakładki w STM32CubeMX, która pozwala na konfigurację tego, jak taktowany jest cały układ. Podczas ćwiczeń uruchomimy zegar czasu rzeczywistego, który dostępny jest wewnątrz mikrokontrolera STM32L476RG, a na koniec przetestujemy watchdoga.

O co chodzi z taktowaniem układów?

Tak samo jak bijące serce wyznacza rytm pracy żywego organizmu, tak samo sygnał zegarowy reguluje pracę układu elektronicznego. Praktycznie każdy mikroprocesor i mikrokontroler wymaga do działania sygnału taktującego. Im wyższa jest częstotliwość tego sygnału, tym (mówiąc w skrócie) szybciej działa dany układ elektroniczny – dzięki temu może wykonywać bardziej zaawansowane zadania.

Informację o częstotliwości pracy danego układu znajdziemy w prawie każdej specyfikacji. Jesteśmy już przyzwyczajeni, że telefony i komputery pracują z częstotliwością gigaherców. Z kolei Arduino UNO to układ, który pracuje z częstotliwością 16 MHz. Nie inaczej jest z STM32L476RG. Na pudełku z płytą Nucleo widoczny jest napis „ARM Cortex-M4 80 MHz” – jak już wiemy (z drugiej części kursu), ARM Cortex-M4 to nazwa rdzenia, natomiast 80 MHz to maksymalna częstotliwość taktowania tego układu.

Informacja o maksymalnej częstotliwości pracy mikrokontrolera

Informacja o maksymalnej częstotliwości pracy mikrokontrolera

Temat zegarów jest bardziej skomplikowany, niż może się to wydawać. Sprowadzenie tego wszystkiego do jednej liczby należy raczej traktować jako marketingowy skrót. Co więcej, temat ten może być też dość trudny dla użytkowników Arduino, bo podczas pracy z tą platformą nie trzeba wcale poznawać tego tematu. Układy, które „napędzają” popularne Arduino, są tak proste, że całość po prostu działa i tyle. Mało kto zwraca uwagę na częstotliwość pracy danej płytki. Przed skorzystaniem ze zdecydowanie bardziej zaawansowanych mikrokontrolerów (takich jak STM32L4) trzeba najpierw poznać ten temat.

Jakie są typy sygnałów zegarowych?

W dokumentacji mikrokontrolera STM32L476RG znajdziemy wiele terminów odnoszących się do źródła taktowania. Warto więc zacząć od poznania kilku skrótowców, które będą pojawiały się w tej części:

  • LSI (ang. low-speed internal) – wbudowany generator RC niskiej częstotliwości (32 kHz),
  • LSE (ang. low-speed external) – zewnętrzny generator niskiej częstotliwości lub rezonator kwarcowy, najczęściej 32 768 Hz,
  • HSI (ang. high-speed internal) – wbudowany generator RC wysokiej częstotliwości (16 MHz),
  • MSI (ang. multi-speed internal) – wbudowany generator RC z opcją wyboru częstotliwości (od 100 kHz do 48 MHz),
  • HSE (ang. high-speed external) – zewnętrzny generator wysokiej częstotliwości lub rezonator kwarcowy (typowa częstotliwość 8 MHz).

Może wyglądać to nieco skomplikowanie, spróbujmy więc omówić źródła taktowania nieco dokładniej i trochę odczarować to pierwsze, niekorzystne wrażenie. Od razu warto zapamiętać, że mikrokontroler STM32L476RG może jednocześnie korzystać z dwóch sygnałów taktujących:

  • zegara niskiej częstotliwości: LSI lub LSE,
  • zegara wysokiej częstotliwości: HSI, MSI lub HSE.

Jaki jest cel różnych źródeł taktowania?

Źródło sygnału zegarowego niskiej częstotliwości przydaje się do wykonywania różnych operacji, ale jego najczęstszym zadaniem jest taktowanie zegara RTC (ang. real-time clock). Jest to peryferium, które wykorzystuje się do odmierzania czasu w „ludzkich” jednostkach (sekundy, minuty itd.). Natomiast generator wysokiej częstotliwości jest używany do taktowania rdzenia procesora, który wykonuje nasz główny program (w dużym uproszczeniu).

W przypadku mikrokontrolera STM32L4 oba te zegary (niskiej i wysokiej częstotliwości) mogą działać niezależnie. Możemy więc używać tylko zegara wysokiej częstotliwości i nie korzystać z zegara niskiej częstotliwości, oba mogą też działać jednocześnie. Ciekawsze jest jednak połączenie, w którym główny zegar mikrokontrolera może zostać tymczasowo zatrzymany, podczas gdy zegar niskiej częstotliwości będzie nadal działał i będzie np. taktować RTC.

Mikrokontroler może bez problemu przełączać się między źródłami sygnału zegarowego

Mikrokontroler może bez problemu przełączać się między źródłami sygnału zegarowego

Ogólna zasada jest taka, że im wyższa częstotliwość taktowania, tym większy pobór prądu. Dodatkowo pewne częstotliwości mogą być dla nas zwyczajnie „wygodniejsze” niż inne. Przykładowo częstotliwość taktowania 32 768 Hz bardzo dobrze nadaje się do RTC. Dzielenie przez 32 768 (czyli 2 do potęgi 15) można bardzo efektywnie zaimplementować w oprogramowaniu, uzyskując częstotliwość 1 Hz, czyli jeden cykl na sekundę

W uproszczeniu można przyjąć, że zużycie energii jest liniowo zależne od częstotliwości. Czyli jeśli nasz mikrokontroler działa z częstotliwością 4 MHz, pobiera 20 razy mniej prądu, niż gdy taktujemy go z 80 MHz. Do poboru energii jeszcze wrócimy w kolejnej części kursu, ale łatwo sobie uświadomić, o ile wygodniejsze może być urządzenie, które wymaga ładowania co 20 dni, a nie codziennie. Zwłaszcza że bardzo często wcale nie potrzebujemy, aby układ pracował z dużymi częstotliwościami.

Kiedy przydają się dwa źródła sygnałów zegarowych?

Jeśli chcielibyśmy zbudować energooszczędne urządzenie, które ma raz dziennie wykonywać jakieś pomiary, to opcja łączenia dwóch zegarów sprawdzi się idealnie. Układ będzie pracował w uśpieniu pod kontrolą powolnego zegara RTC (pobór prądu będzie niewielki). Codziennie o godzinie 00:00 mikrokontroler będzie wybudzany i zacznie pracować na pełnych obrotach (taktowany z 80 MHz). Urządzenie dokona pomiarów, przetworzy dane, zapisze je w pamięci i… mikrokontroler znów przejdzie w stan uśpienia, aby zostać obudzonym kolejnego dnia o 00:00.

Pobór prądu przez energooszczędne urządzenie

Pobór prądu przez energooszczędne urządzenie

Wydaje się, że taka opcja nie będzie przydatna zbyt często. Jednak to tylko przykład, bo takie usypianie i wybudzanie układu może odbywać się co minutę lub sekundę. Mikrokontroler może wykonywać złożone obliczenia w ciągu milisekund, a resztę czasu spędzać w uśpieniu. Takie działanie daje bardzo duże i zauważalne oszczędności energii. Będzie to świetnie widoczne już w kolejnej części kursu, która została poświęcona właśnie trybom oszczędzania energii STM32L4 (stworzymy tam taki przykład).

Nawet obniżanie częstotliwości taktowania układu co minutę (lub sekundę) może dać zauważalne efekty

Nawet obniżanie częstotliwości taktowania układu co minutę (lub sekundę) może dać zauważalne efekty

Jakie są źródła sygnałów zegarowych?

Każdy sygnał zegarowy, z którego chcemy skorzystać, musi być w jakiś sposób wygenerowany. Zarówno w przypadku zegara wysokiej, jak i niskiej częstotliwości mamy do wyboru trzy opcje:

  • sygnał z zewnętrznego źródła,
  • rezonator kwarcowy,
  • wbudowany generator RC.

Zewnętrzny generator sygnału zegarowego

Pierwsza opcja to sygnał z zewnętrznego źródła. Może to być np. układ z wbudowanym generatorem kompensowanym temperaturowo – takie układy nazywa się TCXO (ang. temperature compensated crystal oscillator). Takie rozwiązanie sprawia, że otrzymujemy precyzyjne źródło sygnału zegarowego, które jest odporne na wahania temperatury. 

Opcji jest jednak znacznie więcej. Często sygnał zegarowy może być używany jednocześnie przez kilka modułów. Przykładowo do mikrokontrolera, który jest na programatorze wbudowanym w Nucleo jest podłączony rezonator kwarcowy 8 MHz. Istnieje opcja, aby mikrokontroler z programatora przekazywał sygnał taktujący do naszego STM32L476RB. Takie połączenie wymaga jednak skorzystania z lutownicy, bo trzeba zlutować odpowiednie zworki na płytce (szczegóły w dokumentacji płytki Nucleo).

Rezonator kwarcowy X1 będący częścią programatora

Rezonator kwarcowy X1 będący częścią programatora

Ciekawostka: miniaturowy zegar atomowy

Technologia pędzi do przodu i zadziwia. W ramach ciekawostki warto więc wiedzieć, że obecnie można kupić nawet miniaturowy moduł, który jest wyposażony w prawdziwy zegar atomowy. Dostępne są gotowe moduły wyposażone w generatory oparte na pierwiastku promieniotwórczym (rubidzie). Przykładem takiego układu jest LFRBXO059244, który zapewnia nam sygnał o częstotliwości 10 MHz.

W sprzedaży dostępne są źródła sygnałów zegarowych, które bazują np. na Rubidzie

W sprzedaży dostępne są źródła sygnałów zegarowych, które bazują np. na Rubidzie

Informacje o precyzji tego układu można znaleźć w jego nocie katalogowej, ale w dużym uproszczeniu można przyjąć, że używanie takiego źródła sygnału zegarowego sprawi, że nasz układ będzie „mylił się” o 1 s na ponad 20 lat. Cena tego układu raczej pozostaje poza zasięgiem amatorów, bo w momencie pisania tego kursu moduł ten kosztował ponad 8 tys. zł netto. Istnieją jednak zastosowania, w których precyzja jest najważniejsza, a cena „zginie” w kosztach całego projektu (np. projekty wojskowe).

Fragment noty katalogowej układu LFRBXO059244

Fragment noty katalogowej układu LFRBXO059244

Rezonator kwarcowy

Rezonator kwarcowy to opcja, którą spotyka się bardzo często. Do układu podłącza się „kwarc” (nazwa potoczna) z wybraną przez nas częstotliwością oraz dwa kondensatory (ich pojemność ma wpływ na częstotliwość pracy układu, można więc je wykorzystać do kalibracji). Takie rozwiązanie jest często prostsze i tańsze niż pierwsza opcja, czyli zewnętrzny generator. Trzeba jednak pamiętać, że zabiera nam ono dwa piny mikrokontrolera (bo trzeba do nich podłączyć kwarc). 

Srebrny rezonator kwarcowy (X2) i dwa kondensatory ceramiczne (C67 i C68)

Srebrny rezonator kwarcowy (X2) i dwa kondensatory ceramiczne (C67 i C68)

Generator RC

Trzecia opcja to wbudowany w mikrokontroler generator RC. Największą zaletą tego rozwiązania jest jego cena – wszystko mamy już na miejscu, wewnątrz mikrokontrolera, nie musimy więc nic dodawać.

Taki generator startuje też o wiele szybciej niż generator oparty na rezonatorze kwarcowym, pobiera mniej prądu, jest mniej wrażliwy na zakłócenia lub usterki (zwarcie lub przerwanie ścieżek na płytce). Niestety dokładność takiego generatora jest dość niska, więc nie zawsze można go zastosować.

Gotowe zestawy do kursów Forbota

 Komplet elementów  Gwarancja pomocy  Wysyłka w 24h

Zamów zestaw elementów i wykonaj ćwiczenia z tego kursu! W komplecie płytka NUCLEO-L476RG oraz m.in. wyświetlacz graficzny, joystick, enkoder, czujniki (światła, temperatury, wysokości, odległości), pilot IR i wiele innych.

Zamów w Botland.com.pl »

Dokładność źródeł sygnału zegarowego

Porównując możliwe rozwiązania, wspomnieliśmy o dokładności. Czytając parametry „marketingowe”, widzieliśmy 80 MHz, czyli dokładną, idealną wartość. Niestety w realnym życiu właściwie każda wartość obarczona jest błędem i nie inaczej jest w przypadku zegarów. Mówimy, że mają np. 80 MHz, ale jeśli dokładnie zmierzylibyśmy uzyskaną częstotliwość, to zapewne odbiegałaby ona nieco od tej wartości. To, jak duży błąd jesteśmy w stanie wybaczyć, zależy od docelowego zastosowania naszego urządzenia.

Najwyższą dokładność mogą zaoferować zewnętrzne źródła sygnału – tutaj dokładność zależy tylko od tego, co jest tym źródłem i ile jesteśmy w stanie na nie wydać. Ciężko o górne limity (tak jak wcześniej pokazane „atomowe” źródło za wiele tysięcy złotych). Na szczęście w przypadku innych źródeł sygnału zegarowego jest już znacznie lepiej, bo ceny są bardziej przyziemne (kilkadziesiąt groszy lub złotówki).

Dokładność kwarcu zegarkowego

Rezonatory kwarcowe zapewniają bardzo przyzwoity kompromis między ceną a dokładnością. Płytka NUCLEO-L476RG jest wyposażona w zegarkowy rezonator kwarcowy ABS25-32.768KHZ-6-T. Jego częstotliwość to 32 768 Hz, jest więc przeznaczony dla zegara niskiej częstotliwości.

Rezonator zegarkowy (X2) na pokładzie płytki Nucleo

Rezonator zegarkowy (X2) na pokładzie płytki Nucleo

Parametry tego rezonatora znajdziemy oczywiście w jego nocie katalogowej. Najbardziej interesuje nas dokładność, która wynosi 20 ppm oraz 0,045 ppm/C. Jeden procent to jedna setna. Jednostka ppm to jedna milionowa, czyli 0,000001 lub 0,0001%. Wydaje się zatem, że błąd na poziomie 20 ppm to bardzo mało. Czy na pewno? Spróbujmy policzyć, o ile taki zegar będzie się spóźniał lub spieszył.

Mnożąc 20 ppm przez liczbę sekund w miesiącu, dostajemy wynik: 52 s na miesiąc; policzmy też, jakiego błędu możemy spodziewać się np. po roku. Wychodzi, że około 10 min. Oznacza to, że jeśli wykorzystamy taki układ do budowy zegarka, to po roku będzie on pokazywał czas z dokładnością ±10 min. To niewielki błąd, w niektórych zastosowaniach pewnie dopuszczalny, lecz w innych być może będzie nam potrzebny droższy i dokładniejszy zewnętrzny generator.

Dokładność generatora RC

Porównajmy te wyniki z wbudowanym generatorem RC. Dla układu niskiej częstotliwości jego dokładność to tylko 5%. To daje nam błąd 36 godzin po miesiącu, a 66 dni po roku. Raczej nie będzie to idealny zegar… ba, układ taki nie nada się nawet na kalendarz. Jednak ten generator RC sprawdzi się świetnie w wielu innych zastosowaniach, więc nie warto go od razu skreślać.

Trochę lepiej sprawdza się wbudowany generator wysokiej częstotliwości – jego użycie zmniejszy błąd do 1%, co da 7,2 godziny po miesiącu i 3,65 dnia po roku. Widać, że po roku bez korekty czasu nasz układ nie spełni oczekiwań ani jako zegarek, ani jako kalendarz. Wbudowane generatory RC są tanie, ale nie idealne.

Konfiguracja zegarów w CubeMX

Czas wypróbować, jak wygląda konfiguracja źródeł taktowania w praktyce. Zaczynamy od utworzenia nowego projektu oraz ustawienia konfiguracji poznanej w poprzednich częściach kursu. Włączamy więc debugger przez SWD, komunikację za pomocą USART2 (w trybie asynchronicznym) oraz konfigurujemy pin PA5 jako wyjście LD2, a PC13 jako wejście USER_BUTTON.

Wstępna konfiguracja w STM32CubeMX

Wstępna konfiguracja w STM32CubeMX

Tym razem interesuje nas głównie zakładka Clock Configuration. Widoczny tam schemat na początku na pewno może wydawać się skomplikowany – spokojnie, bo to tylko niekorzystne pierwsze wrażenie. Gdyby nie ta zakładka, musielibyśmy spędzać długie godziny nad analizowaniem noty katalogowej, aby ręcznie ustawiać każde „połączenie” widoczne na schemacie, a tak wystarczy tylko kilka klików.

Zbliżenie na górny lewy róg schematu konfiguracji zegarów

Zbliżenie na górny lewy róg schematu konfiguracji zegarów

Nas na początek interesują zegary niskiej i wysokiej częstotliwości. W lewym górnym rogu tego okna widoczne są znane nam już skróty: LSE, LSI, MSI, HSI, HSE. Zegar niskiej częstotliwości jest domyślnie wyłączony – poznajemy to po tym, że m.in. pole „To RTC (kHz) jest nieaktywne. Natomiast zegar wysokiej częstotliwości jest taktowany z generatora MSI (MSI RC 4000 kHz).

Włączenie RTC

Na płytce Nucleo nie mamy rezonatora kwarcowego dla zegara wysokiej częstotliwości. Zacznijmy więc od dokładniejszego zapoznania się z zegarem niskiej częstotliwości. Jak widzieliśmy, jest on domyślnie wyłączony, musimy więc go włączyć. Wracamy do zakładki, której używamy do konfiguracji peryferiów. Rozwijamy kategorię Timers i wybieramy moduł RTC, a następnie zaznaczamy Activate Clock Source.

Teraz możemy wrócić do widoku konfiguracji zegarów i zobaczyć, co uległo zmianie. Po pierwsze pole opisane jako „To RTC (kHz) jest już aktywne, po drugie aktywny stał się również moduł „RTC/LCD Source MUX – widać w nim niebieską kropkę, która oznacza, że wybrano taktowanie z LSI.

Efekt ręcznej aktywacji zegara RTC

Efekt ręcznej aktywacji zegara RTC

Należy to rozumieć tak, że aktywowaliśmy w układzie drugie źródło taktowania, a konkretnie zegar RTC, który będzie taktowany z LSI RC. Warto zwrócić uwagę na częstotliwość – LSI generuje 32 000 Hz, a nie (typowe dla RTC) 32 768 Hz. Biblioteka HAL automatycznie uwzględni różnice w konfiguracji.

Teraz możemy przystąpić do wypróbowania naszego zegara RTC. Zapisujemy ustawienia projektu oraz generujemy szablon kodu. Następnie wstawiamy do projektu odpowiednie pliki nagłówkowe i funkcje pomocnicze, które napisaliśmy podczas poprzednich części kursu.

Dla przypomnienia nagłówki:

Zawartość funkcji:

Teraz możemy przystąpić do pisania nowego kodu. Zacznijmy od odczytu daty i godziny z RTC. W tym celu przydadzą nam się dwie funkcje:

Jak łatwo się domyślić, pierwsza odczytuje aktualny czas, a druga datę. Na początek chcemy odczytać tylko czas, ale te funkcje muszą być zawsze wywoływane razem i po odczycie czasu konieczny jest odczyt daty. Nie musimy tych danych wykorzystać, ale musimy je odczytać.

Pierwszy kod korzystający z zegara RTC może wyglądać tak jak poniżej (jest to zawartość pętli while). Tworzymy dwie zmienne, zapisujemy do nich aktualną datę i czas, a następnie wyświetlamy odczytaną godzinę za pomocą funkcji printf, odczekujemy 200 ms i powtarzamy całą operację.

Do odczytu czasu i daty potrzebne są nam zmienne typów RTC_TimeTypeDefRTC_DateTypeDef. Są to struktury, które przechowują aktualną godzinę, minutę i sekundę, a w przypadku daty – rok, miesiąc i dzień. Więcej informacji znajdziemy w dokumentacji biblioteki.

Musimy jeszcze tylko zwrócić uwagę na format danych. STM32 domyślnie używa kodu BCD, ale jeśli wolimy zwykłe liczby, do funkcji HAL_RTC_GetTime oraz HAL_RTC_GetDate przekazujemy dodatkowo parametr RTC_FORMAT_BIN. Dzięki temu biblioteka HAL odczyta wartość z rejestrów sprzętowych i zamieni na typową zmienną typu uint8_t, którą bez problemu wypiszemy za pomocą printf.

Pierwsze uruchomienie programu z zegarem RTC

Pierwsze uruchomienie programu z zegarem RTC

Zegar wystartował od 00:00:00 i odlicza kolejne sekundy. Co ciekawe, układ ten będzie liczył czas tak długo, jak długo nasza płytka będzie zasilana. Jeśli odczekamy kilka sekund i zrestartujemy ten sam kod, to odliczanie wcale nie rozpocznie się od 00:00:00, tylko od „naliczonego” wcześniej czasu.

Wskazania zegara RTC, które są niezależne np. od restartu

Wskazania zegara RTC, które są niezależne np. od restartu

Jak ustawić czas zegara RTC?

Jak zauważyliśmy, odczytywana godzina niekoniecznie odpowiada rzeczywistości, bo nasz program po prostu nie wie, która jest obecnie godzina, i nie ma jak się o tym dowiedzieć, aby ustawić ją podczas startu (to nie komputer, który może pobrać tę informację z Internetu).

Ustawić aktualną godzinę musimy „ręcznie”, korzystając z poniższej funkcji:

Jej użycie jest bardzo proste i przypomina odczyt czasu. Różnica jest tylko taka, że najpierw musimy w zmiennej typu RTC_TimeTypeDef ustawić aktualny czas, a następnie wywołać HAL_RTC_SetTime, aby ustawić wskazania zegara w naszym STM32.

Niestety problemem (nadal) jest uzyskanie aktualnego czasu. Dobrym rozwiązaniem byłoby przesłanie aktualnego czasu za pomocą łącza szeregowego, czyli znanego nam już modułu UART. Można też dodać interfejs użytkownika bazujący na przyciskach i ustawić nim aktualny czas. Przydatny byłby także enkoder, który omówimy w jednej z kolejnych części tego kursu.

Tym razem zajmujemy się zegarem RTC, więc nie będziemy się rozpraszać i pisać takich rozbudowanych programów. Stwórzmy zatem najprostszą możliwą wersję – nasz kod będzie miał na sztywno zapisaną godzinę, która ma być ustawiona po naciśnięciu i zwolnieniu przycisku USER_BUTTON. Proste i mało eleganckie, ale na pewno zadziała skutecznie.

Przykładowy program ustawiający aktualną godzinę wygląda następująco:

Program jest oczywiście bardzo prosty, bo jego głównym celem było poznanie, jak działa ustawianie i odczyt aktualnej godziny. Po wgraniu programu każde naciśnięcie przycisku USER_BUTTON sprawi, że nasz zegar RTC zostanie ustawiony na godzinę 7:45.

Zmiana ustawień zegara po naciśnięciu przycisku

Zmiana ustawień zegara po naciśnięciu przycisku

Pomimo prostoty kodu warto użyć go do przetestowania dokładności RTC. Jako źródło zegara wykorzystujemy wbudowany generator RC, który – jak podaje producent – ma dokładność na poziomie 5%. Spróbujmy więc uruchomić program, ustawić czas i porównać jego wskazania z rzeczywistością. W tym celu warto np. nacisnąć przycisk ustawiający czas RTC na godzinę 7:45 i jednocześnie uruchomić minutnik na telefonie, który powinien odliczać jedną minutę. Gdy minutnik zadzwoni, odłączamy kabel USB od komputera i sprawdzamy wynik.

Próba sprawdzenia dokładności zegara RTC

Próba sprawdzenia dokładności zegara RTC

W naszym przypadku po minucie był widoczny błąd, który wynosił około 1–2 s (trzeba pamiętać też o niedokładności ręcznej metody pomiarowej) – błąd ten mieścił się w spodziewanych 5%. Mamy więc zegar czasu rzeczywistego, ale jego dokładność pozostawia wiele do życzenia.

Użycie rezonatora kwarcowego dla RTC

Teraz możemy wrócić do naszych długich rozważań o źródłach taktowania. Jak pamiętamy i widzieliśmy przed chwilą, wbudowany generator RC nie grzeszy dokładnością. Na szczęście mamy do dyspozycji rezonator kwarcowy o częstotliwości 32,768 kHz. Jego dokładność powinna być dużo lepsza, musimy go tylko użyć.

Wracamy do CubeMX i z kategorii System Core wybieramy RCC. Domyślnie opcja Low Speed Clock jest ustawiona na wartość Disable, co oznacza po prostu wyłączenie zewnętrznego źródła taktowania. Jeśli rozwiniemy listę dostępnych opcji, znajdziemy na niej jeszcze dwie możliwości:

  • BYPASS Clock Source – pozwala na użycie sygnału z zewnętrznego generatora,
  • Crystal/Ceramic Resonator – generuje sygnał zegarowy, używając rezonatora kwarcowego.

Na płytce mamy rezonator kwarcowy, wybieramy więc drugą opcję. Po włączeniu rezonatora piny PC14 i PC15 zostaną automatycznie zaznaczone jako wykorzystane, a ich funkcje zmienią się odpowiednio na RCC_OSC32_IN oraz RCC_OSC32_OUT. Oczywiście rezonator kwarcowy zamontowany na Nucleo jest podłączony właśnie do tych pinów.

Został nam jeszcze ostatni krok, czyli zmiana ustawień samego zegara. Wracamy do konfiguracji zegara i zamiast LSI wybieramy LSE. Robimy to, klikając w niebieską kropkę obok LSE w bloku, który opisany jest jako RTC/LCD Source Mux.

Bloki, które mają w nazwie „Mux” to multiplksery, które w bardzo dużym uproszczeniu można rozumieć jako przełączniki wielopozycyjne (wiele wejść, jedno wyjście). Wybieramy więc, który z wielu sygnałów wejściowych ma pojawić się na wyjściu multipleksera.

Nowe źródło taktowania dla RTC

Nowe źródło taktowania dla RTC

Teraz możemy zapisać projekt, wygenerować kod, a następnie skompilować i wgrać ten sam program co poprzednio. Główną część programu zostawiamy bez zmian, więc kod powinien działać identycznie. Jednak dokładność wskazania czasu powinna być już o wiele lepsza, co zobaczymy nawet podczas naszego „ręcznego” eksperymentu. Tym razem wskazania zegara powinny być bliskie temu, co odlicza minutnik z telefonu.

Powtórzenie eksperymentu z zegarem RTC przy innym źródle taktowania

Powtórzenie eksperymentu z zegarem RTC przy innym źródle taktowania

Odczytywanie daty z RTC

W poprzednich przykładach używaliśmy aktualnego czasu, zobaczmy więc, jak można wykorzystać odczyt i zapis daty. Sam odczyt widzieliśmy już wcześniej, ponieważ wywołanie HAL_RTC_GetDate było niezbędne, nawet jeśli odczytaną datę ignorowaliśmy. Wystarczy zatem, że wypiszemy na ekranie nie tylko godzinę, ale i datę. W tym celu zmieniamy kod na poniższy:

Jak widzimy, data zawiera rok, miesiąc i dzień. Jedyne, o czym trzeba pamiętać, to format zapisu lat – są to ostatnie dwie cyfry, czyli żeby zobaczyć pełną datę, musimy dodać 2000.

Odczytywanie daty z zegara RTC

Odczytywanie daty z zegara RTC

Zapisywanie daty do RTC

Na koniec warto jeszcze wspomnieć o ustawianiu daty. Struktury RTC_DateTypeDef używaliśmy już wcześniej do odczytu, a teraz wykorzystamy ją do zapisu. Wystarczy, że poniższy kod wykonamy np. raz przed pętlą while.

Większość pól tej struktury już znamy. Są to rok, miesiąc i dzień. Nowością jest dla nas jednak pole WeekDay, które przechowuje aktualny dzień tygodnia – w naszym przykładzie to wtorek. Gdy teraz uruchomimy nasz program, zobaczymy „aktualną” datę i godzinę.

Potwierdzenie poprawnego zapisywania daty

Potwierdzenie poprawnego zapisywania daty

    Ustawianie i podtrzymywanie zegara RTC

    Jak wspominaliśmy na początku, przykłady opisane w niniejszym kursie są uproszczone. Ustawianie daty i godziny na wartość „zaszytą” w programie na stałe jest na ogół złym rozwiązaniem i zamiast tego potrzebna jest jakaś forma ustawiania aktualnego czasu.

    Największą wadą naszego zegara jest jednak gubienie ustawień po odłączeniu zasilania. Większość zewnętrznych modułów RTC jest wyposażona w dodatkowe zasilanie podtrzymujące pracę zegara. Może to być np. bateria lub kondensator. Podobne rozwiązanie może być wykorzystane również w przypadku mikrokontrolera STM32L476RG. Wystarczy podłączyć zasilanie do pinu VBAT, jednak na płytce Nucleo jest ono połączone z zasilaniem całej płytki (można to zmienić odlutowując jedną zworę).

    Autokalibracja MSI

    Dużo miejsca poświęciliśmy na zapoznanie się z zegarem RTC. Mamy jednak nadzieję, że od teraz różnica między wbudowanym generatorem RC a zewnętrznym rezonatorem kwarcowym jest bardziej zrozumiała i możemy wrócić do tego, co z punktu widzenia programisty jest najważniejsze, czyli do zegara wysokiej częstotliwości, od którego zależy prędkość działania naszego programu.

    Domyślnie wygenerowany przez CubeMX program ustawiał jako źródło taktowania generator MSI. A zatem było to podobnie rozwiązanie jak LSI, jednak ten generator RC ma kilka istotnych różnic. Po pierwsze można wybierać jego częstotliwość pracy – w zakresie od 100 kHz do 48 MHz. Po drugie jego dokładność działania jest nieco wyższa niż LSI – producent deklaruje, że jest to 1%.

    Natomiast najważniejsza jest możliwość wykorzystania rezonatora kwarcowego niskiej częstotliwości do autokalibracji MSI. Oznacza to, że nawet jeśli nie mamy rezonatora wysokiej częstotliwości, nasz generator MSI będzie korzystał z rezonatora podłączonego do wejścia LSE. Dzięki temu jego błąd maleje do 0,25%. To już bardzo dobry wynik jak na sprzęt, którym dysponujemy.

    Gdy zmienialiśmy konfigurację modułu RCC i włączaliśmy rezonator kwarcowy, CubeMX automatycznie zmienił ustawienie MSI. Możemy to sprawdzić, przechodząc do zakładki konfigurującej peryferia, dalej do System Core > RCC, a na dole w ustawieniach zobaczymy następującą informację:

    Automatyczne włączenie kalibracji MSI

    Automatyczne włączenie kalibracji MSI

    Dzięki temu korzystaliśmy z dokładniejszego źródła taktowania, nawet o tym nie wiedząc, i to jest jedna z tych cech CubeMX, która dzieli programistów na dwa obozy. Jedni uważają, że to świetna pomoc, a drudzy oburzają się, że CubeMX bez pytania wprowadza tak istotne zmiany w ustawieniach sprzętu. My stoimy po stronie osób, które uważają, że to świetna pomoc, ale pod warunkiem, że korzysta się z tego programu w świadomy sposób, co staramy się pokazać w tym kursie.

    Główny zegar systemowy

    Teraz do omówienia został nam jeszcze jeden istotny element, czyli zegar systemowy. A mówiąc mniej technicznie, nareszcie przejdziemy do tego „głównego” zegara, który napędza cały układ, i nareszcie zobaczymy to słynne 80 MHz, które przewijało się do tej pory tylko na opakowaniu Nucleo.

    Na schemacie, którego używamy do konfiguracji zegarów, są dwie ważne wartości: SYSCLK oraz HCLK. Między nimi znajduje się dzielnik ustawiony domyślnie na wartość równą 1, więc w naszej konfiguracji oba zegary mają identyczną częstotliwość i dla uproszczenia będziemy je traktować jako jeden.

    SYSCLK i HCLK na schemacie konfiguracyjnym

    SYSCLK i HCLK na schemacie konfiguracyjnym

    Częstotliwość HCLK określa taktowanie procesora, czyli ma bezpośredni wpływ na prędkość działania naszego programu. Ta wartość jest tak ważna, że producent umieścił ją na opakowaniu płytki Nucleo – od niej zależy wydajność obliczeniowa układu. Na lewo od SYSCLK widzimy multiplekser, który określa źródło taktowania naszego układu. MSI, HSI oraz HSE już znamy, ale nie wspominaliśmy jeszcze o PLL.

    PLL (ang. phase locked loop) to dość skomplikowany układ elektroniczny, który (pisząc w największym uproszczeniu) jest modułem potrafiącym mnożyć częstotliwość. Możemy więc na jego wejściu podać np. 4 MHz, a na wyjściu uzyskać 80 MHz.

    CubeMX potrafi sam dobrać konfigurację naszego układu, wystarczy zatem, że w pole opisane jako HCLK wpiszemy wartość 80 i naciśniemy klawisz Enter, a CubeMX jak czarodziej z magiczną różdżką wprowadzi za nas komplet zmian. Jednak najpierw zostaniemy poinformowani o tym, że uzyskanie tej częstotliwości nie jest możliwe przy obecnych ustawieniach, i musimy zgodzić się na to, aby CubeMX „poszukał” dalej – na co oczywiście chętnie się zgadzamy.

    Informacja o braku rozwiązania przy aktualnych ustawieniach

    Informacja o braku rozwiązania przy aktualnych ustawieniach

    Po kilkunastu sekundach zobaczymy gotowe rozwiązanie. Na schemacie zmieni się całkiem sporo (całe szczęście, że nie musimy wyklikiwać tego ręcznie). W tym przypadku całość najłatwiej zrozumieć, jeśli będzie się czytało poniższy fragment schematu od prawej do lewej.

    Nowa konfiguracja zegarów

    Nowa konfiguracja zegarów

    Analizę zaczynamy od pola, w które wpisaliśmy oczekiwaną częstotliwość 80 MHz, czyli HLCK. Tak jak poprzednio wartość ta jest równa SYSCLK, który pochodzi tym razem ze źródła PLLCLK, wybranego w multiplekserze System Clock Mux.

    Ustawienia pętli PLL zostały również wygenerowane automatycznie – widać je na jasnoniebieskim tle. Pętla PLL nie generuje sama sygnału zegarowego, może tylko zwiększać lub zmniejszać częstotliwość. Musi więc otrzymać bazowy sygnał taktujący, który później (w tym przypadku) pomnoży. Dlatego na wejście PPL podajemy sygnał, który wybierany jest na multiplekserze PPL Source Mux. Tym razem mamy tam wybrany generator MSI, który działa z częstotliwością 4 MHz (co widoczne jest w polu znajdującym się trochę wyżej).

    Reasumując: źródłem taktowania mikrokontrolera jest generator MSI pracujący z częstotliwością 4 MHz oraz autokalibrowany względem rezonatora kwarcowego 32 768 Hz. Sygnał wygenerowany przez MSI jest mnożony ×20 w pętli PLL i podawany jako systemowy sygnał taktujący 80 MHz. Proste? Niekoniecznie, ale o ile byłoby trudniejsze, gdybyśmy musieli to wszystko sami napisać w kodzie.

    Watchdog – kontrola działania układu

    Na zakończenie tej części wspomnimy o jeszcze jednym module peryferyjnym, który używa zegara niskiej częstotliwości. Dzięki temu będzie nam nieco łatwiej zrozumieć, dlaczego nie nazwano tego zegara po prostu RTC. Wracamy do kodu programu. Usuwamy z pętli głównej dotychczasowe wpisy i nad pętlą while dodajemy poniższy kod:

    Dzięki takiemu rozwiązaniu będziemy mogli wyraźnie „zobaczyć” moment, gdy układ jest resetowany, bo po prostu każdy restart rozpocznie się od sekwencji migania diodą. To bardzo ważne, ale i pomocne rozwiązanie – na naszym forum wiele razy pojawiały się pytania związane z niepoprawnie działającym programem, co w rzeczywistości wynikało z niespodziewanego resetowania układu. Dodanie kilku mrugnięć na początku programu pozwala na wykrycie takich sytuacji i może zaoszczędzić mnóstwo czasu podczas poszukiwania błędu.

    Teraz możemy przystąpić do napisania głównego programu. Niech będzie to prosty program, który po prostu powoli miga diodą. Program moglibyśmy oprzeć na znanych nam już funkcjach HAL_Delay(), ale wtedy program byłby blokowany w trakcie oczekiwania.

    Użyjemy więc funkcji HAL_GetTick() zamiast HAL_Delay(). Zwraca ona liczbę milisekund, które minęły od uruchomienia systemu, jest więc odpowiednikiem funkcji millis(), znanej z Arduino. Chcąc uzyskać efekt migania diody bez blokowania procesora, piszemy następujący program:

    W zmiennej last_ms zapamiętujemy czas ostatniej zmiany stanu LED. Gdy upłynie 500 ms od ostatniej zmiany, wywołujemy HAL_GPIO_TogglePin oraz zapamiętujemy nowy czas. Dzięki temu powinniśmy uzyskać miganie diodą z częstotliwością 1 Hz. Program możemy jak zwykle przetestować.

    Efekt działania głównej pętli programu

    Efekt działania głównej pętli programu

    Teraz celowo dodajmy do programu perfidny błąd. Po naciśnięciu przycisku użytkownika program będzie wchodził w nieskończoną pętlę. Jeśli uruchomimy poniższy kod i naciśniemy przycisk, dioda przestanie migać, a jedyną metodą na ponowne przywrócenie działania tego programu jest reset.

    W naszym programie specjalnie wprowadziliśmy błąd, lecz podobne sytuacje niestety zdarzają się w mniej oczekiwanych momentach. Ręczne zresetowanie naszej płytki nie jest problemem, jednak w przypadku naprawdę używanych urządzeń może to być znacznie trudniejsze. Wyobraźmy sobie np. sterownik urządzenia umieszczonego na szczycie wysokiego masztu – pewnie nikt nie będzie zachwycony, musząc się wspinać tylko w celu zresetowania układu.

    W takiej sytuacji pomóc może moduł tzw. watchdoga. Jest to zwykły licznik sprzętowy, który zmniejsza wartość co wskazany czas i wykonuje reset mikrokontrolera, jeśli doliczy do zera. Poprawnie działający program powinien restartować watchdoga, zanim wartość licznika osiągnie zero. To bardzo prosty, ale skuteczny mechanizm – po prostu nasz program ma nowy „obowiązek”, tzn. musi resetować watchdoga, a jeśli tego nie zrobi, to znaczy, że stało się coś złego, i watchdog zresetuje wtedy cały mikrokontroler (z nadzieją, że to rozwiąże problem).

    Watchdog to niezależny moduł, który pilnuje czy nasz układ działa poprawnie

    Watchdog to niezależny moduł, który pilnuje czy nasz układ działa poprawnie

    Mikrokontroler jest wyposażony w dwa moduły watchdoga: WWDG oraz IWDG. Pierwszy używa tego samego zegara co mikrokontroler, więc jeśli zostanie zatrzymany zegar, watchdog nie będzie w stanie zadziałać. Z kolei IWDG (ang. independent watchdog) może działać nawet wtedy, gdy nasz główny zegar oraz cały program przestaną działać. Oczywiście każde z tych rozwiązań ma swoje wady i zalety, ale ogólnie można przyjąć, że IWDG jest w tym przypadku lepszy.

    Na początek musimy uruchomić IWDG. Odnajdujemy go w kategorii System Core i zaznaczamy opcję określoną jako Activated. Domyślnie preskaler watchdoga ustawiony jest na wartość 4. Żeby łatwiej było testować program, wybieramy opcję IWDG counter clock prescaler i zmieniamy na 32

    Ustawienie zegara preskalera dla IWDG

    Ustawienie zegara preskalera dla IWDG

    IWDG jest zawsze taktowany z wbudowanego generatora LSI. Jak pamiętamy, używaliśmy go na początku jako źródła dla RTC i efekty nie były zachwycające. Okazuje się jednak, że dokładność na poziomie 5% jest w zupełności wystarczająca dla watchdoga. Natomiast wykorzystanie niezależnego generatora, który jest wbudowany w mikrokontroler, zapewnia najwyższy poziom bezawaryjności

    Dla pewności warto podejrzeć ustawienia w zakładce z konfiguracją zegarów. Widzimy tam, że dla RTC mamy teraz wykorzystywany zewnętrzny generator LSE, natomiast watchdog działa na wbudowanym LSI o częstotliwości 32 kHz.

    Niezależne ustawienie zegara dla watchdoga

    Niezależne ustawienie zegara dla watchdoga

    W konfiguracji watchdoga ustawiliśmy dzielnik częstotliwości na 32, czyli licznik watchdoga będzie liczył z częstotliwością 32 kHz / 32 = 1 kHz. Zostawiliśmy domyślną wartość początkową watchdoga, która wynosi 4095, więc zanim nasz watchdog doliczy do zera, upłynie około 4 s.

    Gdybyśmy uruchomili tak skonfigurowany projekt, to co 4 s następowałby reset niezależnie od działania naszego programu. Musimy więc dodać do niego kod, który będzie informował watchdoga, że wszystko nadal działa poprawnie. Posłuży nam do tego funkcja HAL_IWDG_Refresh:

    Do pętli głównej naszego programu musimy dodać jej wywołanie – każde miejsce oprócz pętli while, która generuje specjalny „błąd”, będzie dobre.

    Po uruchomieniu nasz program będzie działał jak na początku, tzn. będzie migał diodą. Gdy naciśniemy przycisk, program się zawiesi, a po upływie około 4 s nastąpi reset i zacznie on działać od początku, co zauważymy dzięki temu, że dioda zacznie migać dużo szybciej.

    Efekt działania watchdoga – automatyczny reset układu

    Efekt działania watchdoga – automatyczny reset układu

    Quiz – sprawdź, ile już wiesz!

    Przygotowaliśmy aż cztery quizy, dzięki którym sprawdzisz, jak dużo zapamiętałeś z tego kursu STM32L4. Masz już za sobą pięć części kursu, więc śmiało możesz zabrać się za pierwszy quiz – składa się on z 15 pytań testowych (jednokrotnego wyboru), a limit czasu to 15 min. W rankingu liczy się pierwszy wynik, ale w quizie będziesz mógł później wziąć udział wielokrotnie (w ramach treningu).

    Przejdź do quizu nr 1 z 4 »

    Bez stresu! Postaraj się odpowiedzieć na pytania zgodnie z tym, co wiesz, a w przypadku ewentualnych problemów skorzystaj ze swoich notatek. To nie są wyścigi – ten quiz ma pomóc w utrwaleniu zdobytej już wiedzy i wyłapaniu tematów, które warto jeszcze powtórzyć. Powodzenia!

    Quiz - najnowsze wyniki

    Oto wyniki 10 osób, które niedawno wzięły udział w quizie. Teraz pora na Ciebie! Uwaga: wpisy w tej tabeli mogą pojawiać się z opóźnieniem, pełne wyniki są dostępne „na żywo” na stronie tego quizu.

    # Użytkownik Data Wynik
    1Leroy16.07.2021, 20:5193%, w 159 sek.
    2szymon81219.07.2021, 20:4193%, w 195 sek.
    3Dominikzz07.07.2021, 01:1193%, w 257 sek.
    4Pixpix31.07.2021, 14:0280%, w 138 sek.
    5oxfrd09.07.2021, 11:4580%, w 181 sek.
    6MC2Systems12.07.2021, 21:3080%, w 219 sek.
    7marville30.07.2021, 00:2473%, w 121 sek.
    8Rafal31523.07.2021, 11:2573%, w 216 sek.
    9monsiw26.07.2021, 13:0173%, w 248 sek.
    10Jolo44322.07.2021, 14:4640%, w 213 sek.

    Zadanie domowe

    1. Rozwiąż powyższy quiz.
    2. Prześledź dokładnie w CubeMX zakładkę, która służy do generowania ustawień zegarów. Zobacz, co zmienia się w układzie, gdy wybierzesz inną częstotliwość dla głównego zegara niż 80 MHz. Sprawdź również, jak wygląda kod, który CubeMX wygenerował, aby uzyskać odpowiednie ustawienia zegarów. Informacje te znajdziesz w funkcji SystemClock_Config (w pliku main.c).
    3. Sprawdź eksperymentalnie czy zegar (a właściwie kalendarz) RTC obsługuje lata przestępne.

    Podsumowanie – co powinieneś zapamiętać?

    Mikrokontrolery STM32L4 to rozbudowane układy, które mają wiele opcji związanych z konfiguracją zegarów. Na początku mogą one przytłaczać, ale z czasem okazują się niezwykle intuicyjne i przydatne. Po tej części kursu powinieneś umieć samodzielnie skonfigurować RTC, ustawić główną częstotliwość pracy układu na 80 MHz oraz wiedzieć, jak wykorzystać w programie opisaną wersję watchdoga.

    Czy wpis był pomocny? Oceń go:

    Średnia ocena 5 / 5. Głosów łącznie: 14

    Nikt jeszcze nie głosował, bądź pierwszy!

    Artykuł nie był pomocny? Jak możemy go poprawić? Wpisz swoje sugestie poniżej. Jeśli masz pytanie to zadaj je w komentarzu - ten formularz jest anonimowy, nie będziemy mogli Ci odpowiedzieć!

    W kolejnej części kursu zajmiemy się tematem oszczędzania energii. Sprawdzimy, co można zrobić, aby nasz układ działał jak najdłużej – nawet jeśli korzystalibyśmy tylko z małej baterii. Oczywiście temat ten będzie związany m.in. z zegarami i częstotliwością taktowania układu.

    Nawigacja kursu

    Główny autor kursu: Piotr Bugalski
    Współautor: Damian Szymański, ilustracje: Piotr Adamczyk
    Oficjalnym partnerem tego kursu jest firma STMicroelectronics
    Zakaz kopiowania treści kursów oraz grafik bez zgody FORBOT.pl

    kurs, kursSTM32L4, RTC, stm32, zegary

    Trwa ładowanie komentarzy...