Może nie każdy wie, ale zegary czasu rzeczywistego, czyli RTC są tak dokładne, jak dokładne jest ich źródło taktowania. Najczęściej układy takie napędzane są częstotliwością 32,768 kHz. STM32 posiada wbudowany układ RTC jednak jego tyczy się dokładnie ta sama zasada, jeżeli chodzi o dokładność. Na ogół to, co oferuje STM32 jako wewnętrzny oscylator RC dla RTC nie jest najwyższej jakości i jest podatny na zmiany temperatury. Podpinając zewnętrzny rezonator kwarcowy również nie możemy zapewnić tego, że będzie on działał identycznie w każdej możliwej temperaturze. Czy jest jakieś rozwiązanie? Tak – kompensacja temperaturowa częstotliwości oscylatora.

Ale halo, halo! Można przecież skompensować różnice w częstotliwości ręcznie. Tak. Można dokonać tzw. kalibracji częstotliwości oscylatora. Jest to proces lekko skomplikowany i niedający gwarancji w każdych warunkach. Nie pomaga również stosowanie tanich rezonatorów kwarcowych. Ich charakterystyka błędu do temperatury wygląda tak (z dokumentacji RTC STM32F103):

Przy temperaturze otoczenia ok 0°C dokładność spada poniżej -20 ppm. Według kalkulatora błąd na tym poziomie daje opóźnienie 1,73 sekund/dzień, czyli 10,51 minut/rok. Komu chce się co miesiąc nastawiać zegar? Mniejszy problem jest, gdy układ RTC ma cały czas zbliżone warunki jak np. w mieszkaniu. Wtedy można ratować się kalibracją programową. Co, jeżeli znajduje się w urządzeniu zewnętrznym? Powiedzmy, że zamontowany jest zimową porą w infobudce z ogromnym, grzejącym się wyświetlaczem. W słoneczny zimowy dzień, gdy budka jest czynna temperatura w skrzyni elektroniki powiedzmy, że za dnia w skrajnym momencie ma 40ºC dając błąd na poziomie -10 ppm. Gdy zbliża się wieczór, układ schładza się powoli jadąc po charakterystyce w lewą stronę. Fajnie, bo błąd się przez jakiś czas zmniejsza. Niestety w nocy, gdy urządzenie jest uśpione temperatura spada do -5ºC i dokładność oscylatora wynosi -30 ppm. Czy jesteśmy w stanie idealnie kompensować programowo takie błędy? Pewnie możemy ale komu by się chciało…

Zwłaszcza wiedząc, że za niewielkie pieniądze można mieć układ RTC, który skompensuje takie błędy automatycznie. Jednym z takich precyzyjnych układów na rynku jest DS3231.

DS3231 – ekstremalnie dokładny RTC

Panie Mateuszu dokładny to znaczy jaki? Według dokumentacji jest to:

  • ±2 ppm dla temperatur 0 ÷ +40ºC
  • ±3,5 ppm dla temperatur -40 ÷ +85ºC

Wpisując 2 ppm w przytoczony wyżej kalkulator wychodzi, że DS3231 w ciągu roku spóźni się o… minutę. Codziennie będzie to 0,17 sekundy. Jest to do przeżycia? O wiele bardziej niż 10 minut rocznie! Jest to bardzo dobry wynik.

Jak to się dzieje, że jest taki dokładny? Otóż układ Maxim’a ma zintegrowany oscylator kwarcowy razem z kwarcem. Oscylator dodatkowo posiada temperaturową kompensację temperatury. Sprawia to, że producent ma pod kontrolą wszystkie najważniejsze komponenty, które decydują o dokładności. Dzięki kompensacji wyciska z nich swoiste maksimum czego efektem są te ±2 ppm. 

W Polsce dokładne testy kompensacji DS3231 przeprowadzał Mirek Kardaś. Pozwól, że ja nie będę popalał układu podłączonego do oscyloskopu 🙂

Najważniejsze rzeczy ze specyfikacji

Oprócz dokładności mogą zainteresować Cię jeszcze inne rzeczy. Najważniejsze jest chyba to, jak układ łączy się z mikrokotrolerem. Interfejsem komunikacyjnym jest I²C więc prościzna. Oprócz pinów SDA i SCL nasz RTC ma jeszcze kilka innych wyjść.

Jak to bywa w układach zegarowych chcielibyśmy mieć możliwość podłączenia baterii dla podtrzymania pomiarów. Tutaj jest dedykowany pin, a wewnątrz obudowy znajduje się układ detekcji zaniku zasilania i automatycznego przełączenia na zasilanie bateryjne.

DS3231 ma dwa wyjścia zegarowe. Jedno jest to 32,768 kHz prosto ze wbudowanego oscylatora z kompensacją temperaturową i nazywa się 32kHz. Źródło to idealnie nadaje się do taktowania innych układów w trybie niskiego poboru mocy.

Jest jeszcze jedno wyjście zegarowe. Znajduje się ono na pinie z podwójną funkcją o nazwie INT/SQW. Tutaj jest możemy dostać jedną z czterech programowalnych częstotliwości: 1Hz, 1024 Hz, 4096 Hz lub 8192 Hz. Jest to odpowiednio podzielona częstotliwość oscylatora.

Drugą funkcją pinu INT/SQW jak wskazuje pierwszy człon nazwy są przerwania. Według dokumentacji przerwanie wystawiane jest wtedy, gdy aktywny jest alarm oraz nadejdzie jego czas. Czy ktoś używa alarmów?

Co ważne – obydwa wyjścia zegarowe są typu Open Drain. Trzeba założyć in rezystory podciągające, które na szczęście w gotowych modułach już są położone na PCB.

W jaki sposób przechowywany jest czas i data? Oczywiście znajdują się tutaj rejestry godziny, minut i sekund. Jeżeli chodzi o kalendarz to jest on w miarę rozbudowany. Oprócz roku, miesiąca i dnia jest jeszcze: dzień tygodnia, stulecie czy obsługa roku przestępnego.

Schemat i oprogramowanie

Dzisiaj skorzystam z płytki Nucleo z STM32F410RB którą otrzymałem od firmy Kamami, za co bardzo dziękuję! 🙂

Podłączenie modułu do Nucleo jest jak zwykle banalnie proste. Potrzebujesz podłączyć jedynie piny I²C oraz pin INT/SQW dla przerwania z RTC.

Do oprogramowania oczywiście skorzystałem z STM32CubeIDE w wersji 1.0.2. Biblioteki HAL F4 są w wersji 1.24.1. Jazda!

Mógłbym pokazać Ci klasyka, czyli chamskie i blokujące czytanie z RTC co jakiś czas w pętli main. Lepszym rozwiązaniem byłoby oczywiście czytanie w przerwaniu od RTC, które następuje co sekundę. Jednak takie chamskie, blokujące czytanie w przerwaniu też nie jest rozwiązaniem optymalnym…

Pamiętaj, że STM32 posiada takie coś, jak DMA którego jestem ogromnym fanem. Przecież nie musisz czekać bezczynnie, kiedy wykonuje się komunikacja po I²C. Zebraniem danych może zająć się właśnie DMA, a CPU wejdzie później do przekonwertowania BCD na DEC. Pokażę Ci porównanie pod koniec.

Konfiguracja i obsługa DMA pod RTC

Po pierwsze musisz skonfigurować interfejs I²C. Ja wybrałem I2C1. Wybierz prędkość interfejsu. Ja polecam 400 kHz, zwłaszcza że DS3231 obsługuje tryb Fast Mode. Jeżeli Twój mikrokontroler nie może pracować z takim zegarem, może być klasyczne 100 kHz. Piny, które wolisz. Ja ustawiłem na PB9 i PB8, ponieważ są wyprowadzone na złącze Arduino. Dzięki temu w piny Morpho mogę wpiąć się analizatorem stanów logicznych.

Kolejną rzeczą, jaką musisz ustawić jest DMA dla naszego interfejsu I2C1. Możesz zrobić to wygodnie z poziomu konfiguracji I²C. Ustaw DMA zarówno na odbiór, jak i nadawanie. 

Do podglądu danych z RTC skonfiguruj jeszcze klasycznie UART2, który jest wyprowadzony na USB ST-Linka. Ja pozostawiam standardowe wartości, czyli 115200 8n1.

Będziesz jeszcze potrzebował pinu do obsługi przerwania od układu DS3231. Możesz ustawić dowolny GPIO na funkcję GPIO_EXTI. U mnie wybór padł na pin PA9. Nazwij go DS3231_INT i ustaw reakcję na zbocze opadające. Tak naprawdę nie ma znaczenia, na które ustawisz 🙂 Ważne, aby na jedno z dwóch, aby niepotrzebnie nie czytać dwukrotnie tej samej godziny. Pamiętaj również o włączeniu przerwania od właściwego EXTI w CubeMX!

Zegar możesz ustawić domyślny 84 MHz, jeżeli generowałeś projekt “z płytki”, a nie po wyborze MCU. Jeżeli masz inaczej, to wklep 84 w miejsce HCLK, abyśmy mieli spójne projekty. Po tak skonfigurowanym projekcie możesz go zapisać i wygenerować kod. Pamiętaj też o zaznaczeniu generowanie oddzielnych plików dla każdego z peryferium.

Kod

Przygotowałem prostą bibliotekę dla DS3231. Nie jest to majstersztyk, który obsługuje każdą funkcję układu. Pominąłem w niej alarmy oraz odczyt temperatury układu (skoro jest kompensacja temperaturowa to i jest możliwość pomiaru temperatury). Myślę, że najważniejsze i najbardziej użytecznie jest i tak czytanie godziny i daty. 

Interesować Cię będzie jeden lub dwa define’y. Są to #define DS3231_USE_DMA#define DS3231_ADDRESS (0x68<<1). Pierwszy, jeżeli jest odkomentowany, wybiera funkcję do czytania z RTC przez DMA. Jeżeli jest zakomentowany, będzie działał z klasycznymi, blokującymi.

Drugi define natomiast jest adresem układu. Moduł bez zworek ma adres 0x68. Jeżeli masz lub położyłeś zworki, będziesz musiał zmienić tę linijkę według swojego adresu.

Przejdźmy do funkcji, które Cię interesują. Pierwszą z nich jest oczywiście inicjalizacja.

1
void DS3231_Init(I2C_HandleTypeDef *hi2c);

Podajesz w niej handler do wybranego interfejsu I²C. Wewnątrz inicjalizacji wstawiłem funkcje ustawiające przebieg o częstotliwości 1 Hz na wyjściu SQW oraz wyłączam wyjście 32 kHz.

Kolejną ważną funkcją jest ustawianie zegara.

1
void DS3231_SetDateTime(RTCDateTime *DateTime);

Jako argument podaje się wskaźnik do przygotowanej wcześniej struktury RTCDateTime, która jest zdefiniowana w bibliotece DS3231.

1
2
3
4
5
6
7
8
9
10
typedef struct
{
uint16_t Year;
uint8_t Month;
uint8_t Day;
uint8_t Hour;
uint8_t Minute;
uint8_t Second;
uint8_t DayOfWeek;
}RTCDateTime;

Ostatnim ważnym elementem jest odebranie danych z RTC i przekonwertowanie ich do struktury RTCDateTime. W zależności od tego, jaki rodzaj obsługi układu wybierzesz będziesz miał do wyboru w przypadku blokującej/przerwań:

1
void DS3231_GetDateTime(RTCDateTime *DateTime);

Ta funkcja pobierze przez I²C dane oraz wrzuci je do zmiennej spod wskaźnika *DateTime.

No i najważniejsze z perspektywy obsługi przez DMA. Funkcje odbierające dane oraz wrzucające je do struktury.

1
2
void DS3231_ReceiveDateTimeDMA(void);
void DS3231_CalculateDateTime(RTCDateTime *DateTime);

Dlaczego dwie? Już tłumaczę.

Odbiór DMA

Funkcję DS3231_ReceiveDateTimeDMA wykorzystałem w przerwaniu EXTI, które generuje układ RTC dzięki sygnałowi 1 Hz na wyjściu SQW. Jej zadaniem jest wystartowanie odbioru z DS3231 przez DMA wszystkich danych dotyczących godziny i daty. Na szczęście producenci układów RTC umieszczają te dane jedna za drugą.

Gdy odbiór się zakończy, zostanie wywołane przerwanie od zakończenia odbioru DMA. Tam właśnie wsadziłem funkcję DS3231_CalculateDateTime rozpakowującą odebrane dane na wartości godziny i daty. 

To tyle filozofii 🙂 Jak te funkcje wykorzystać w przerwaniach? Musisz nadpisać domyślne funkcje _weak, które posiadają biblioteki HAL. Ja zrobiłem to w pliku main i wygląda to następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == DS3231_INT_Pin)
{
DS3231_ReceiveDateTimeDMA();
}
}

void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
DS3231_CalculateDateTime(&amp;r);
}
/* USER CODE END 4 */

I to tyle! Prawda, że proste?

Efekty działania, prędkości

Jeżeli czytasz mnie nie pierwszy raz to wiesz, że lubię sprawdzić czasy wykonywania się kodu, a zwłaszcza czas pracy samego CPU. Tym razem również nie odpuszczę!

Na pierwszy ogień pokażę Ci jak wygląda blokujący odbiór danych z DS3231. Najpierw 100 kHz.

i 400 kHz

Hmm… 920 µs i 237 µs. Patrząc, że kod ten wykonuje się dokładnie co sekundę, można łatwo policzyć, że odczyt przy 100 kHz zajmuje 0,92% czasu procesora, a dla 400 kHz jedyne 0,23%. Kurde, Salamon czego Ty jeszcze chcesz od tego?

Zauważ, że CPU oczekuje bezczynnie na odebranie danych z I²C. To oznacza, że jest jeszcze niewielkie pole do poprawy i to dzięki DMA. Efekt mojego kodu z DMA prezentuje się tak:

100 kHz400kHzTeraz są dwa krótsze odcinki w których jest potrzeba, aby użyć CPU. Pierwszy to zlecenie odbioru DMA, drugi to sama konwersja BCD do DEC i przypisanie do zmiennej strukturalnej. Aby policzyć czas zajęcia CPU, trzeba je zsumować co jest zadaniem prostym. Tak oto dla 100 kHz suma to 301 µs, a dla 400 kHz jedyne 85 µs. W przeliczeniu na % czasu procesora to odpowiednio 0,3% i 0,08%.

W obu przypadkach mamy do czynienia z około 3-krotnym zyskiem. Może być? Myślę, że tak 🙂 Taka operacja, jak odczytanie godziny powinna być jak najmniej obciążająca dla systemu. Ten w końcu musi zajmować się poważniejszymi rzeczami.

Podsumowanie

Mam nadzieję, że jeżeli nie byłeś to, choć przekonałeś się do wysokiej dokładności zegara RTC, jakim jest DS3231. Choć mam wrażenie, że przydałoby się jeszcze jakieś porównanie do innych układów jeżeli chodzi o dokładność pomiaru czasu. Mam już pewien pomysł 🙂

Przyznaj też, że dałem Ci kolejny powód na to, aby korzystać z DMA. Nawet w tak – jakby się wydawało – banalnych sytuacjach warto to robić.

A Ty co myślisz o takim RTC? Warto taki kupić? O dziwo moduły czasami są tańsze niż inne, mniej dokładne układy, które potrzebują jeszcze kilku komponentów wokół. Sprawdź moduły RTC u mnie sklepie. Kupując u mnie, wspierasz rozwój bloga oraz dostajesz super naklejkę np. na laptopa 😉

Pełny projekt wraz z biblioteką znajdziesz jak zwykle na moim GitHubie: link

Jeśli zauważyłeś jakiś błąd, nie zgadzasz się z czymś, chciałbyś coś dodać istotnego lub po prostu uważasz, że chciałbyś podyskutować na ten temat, napisz komentarz. Pamiętaj, że dyskusja ma być kulturalna i zgodna z zasadami języka polskiego.


Raz na jakiś czas wyślę Ci też e-mail z ciekawymi rzeczami które znalazłem


Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *