fbpx

Zegar czasu rzeczywistego w mikrokontrolerach STM32 nie jest taki sam we wszystkich rodzinach. W zasadzie możemy spotkać dwa różne RTC. Jeden podstawowy, który znajduje się w starszych i mniej zaawansowanych mikrokontrolerach jak w serii F1, którym zająłem się nim w poprzednich artykułach. Jest jeszcze bardziej zaawansowany zegar, który możesz znaleźć, chociażby w rodzinie F4 i temu właśnie się tym razem przyjrzę.

RTC w STM32F4

Jak już wspomniałem w ST32F4 układ RTC jest bardziej zaawansowany. W F1 mieliśmy tylko jeden licznik 32-bitowy, który był w stanie zliczać pojedyncze sekundy bez rozróżnienia innych składowych daty czy czasu. To od programisty zależało, w jaki sposób wykorzysta te sekundy do złożenia daty i godziny.

W F4 natomiast zegar został wyposażony w dodatkowe liczniki, które są podzielone na mniejsze kawałki, które oznaczają już konkretnie godzinę, minutę czy sekundę.

Dodatkowo dodano osobny licznik, który ma za zadanie dbać o zliczanie daty. Można by powiedzieć, że ta najtrudniejsza część biblioteki została z nas zdjęta, co nie? Ostatnio przy F1 nieco musiałem się namęczyć, aby data poprawnie chodziła po podtrzymaniu zasilania… Zobaczymy jak będzie tutaj 🙂

Jednak to nie wszystkie dodatki, które są w RTC dla F4. Zobacz na porównanie ilości rejestrów z Reference Manuali STM32F103C8T6 i STM32F401CCU6.

Lista jest o wiele dłuższa! Można by rzec, że RTC stał się jednocześnie bardziej skomplikowany w obsłudze, bo chociazby procedury wpisywania do rejestrów są nieco bardziej wymagające w F4 w porównaniu do F1. Na szczęście HAL nam pozwala nie martwić się zbytnio tymi procedurami.

Dodatkowe funkcje RTC

Z dodatków, które doszły do RTC można zauważyć chociażby drugi alarm. Teraz są dwa – A i B. Swoją drogą napisz w komentarzu, w jaki sposób ciekawy można użyć alarmu? Najlepiej coś nieoczywistego 🙂

Fajnym ficzerem, który znajdziesz w zaawansowanym RTC są rejestry związane z “subsekundami”. Czym jest ta tajemna “subsekunda”? Znamy, chociażby milisekundy, którymi możemy się posługiwać w elektronice, ale jakieś sub??

Te subsekundy to również części sekund, ale sekunda dzielona jest nieco inaczej. Mamy możliwość ustawienia na ile części ma się dzielić sekunda. Jest jeszcze możliwość ustawienia preskalrea RTC jednak uważaj bo grzebiąc przy tych rejestrach możesz łatwo rozjechać sekundę. Domyślne ustawienia działają dobrze. Później pokażę Ci jak wyłuskać z tych liczb milisekundy.

Warto zwrócić uwagę na rejestry związane z funkcją Wake Up. Pozwala ona na wybudzenie mikrokontrolera z trybów obniżonego poboru energii. Bardzo przydatna rzecz, zwłaszcza w mikrokontrolerach serii L, których używamy właśnie do energooszczędnych aplikacji.

Jest też coś takiego jak Time Stamp. Można zatrzasnąć datę i godzinę na podstawie zbocza na specjalnym pinie. Zatrzaskiwanie działa w różnych trybach low-powerowych. Można to połączyć z funkcją Tamper, czyli czyszczeniem rejestrów backupowych po “włamaniu”.

Nie zabrakło oczywiście rejestrów backupowych, a także mamy do dyspozycji kilka dodatkowych rejestrów kalibracyjnych.

Platforma testowa

stm32f411

Poprzednio używałem BluePill, więc teraz wezmę coś podobnego. Jest to niejako jego następna zwany BlackPill. Opisywałem niedawno te płytki. Wziąłem wersję z STM32F401CC.

Oprogramowanie, którego użyję to:

  • STM32CubeIDE v1.2.0
  • STM32CubeMX v5.5.0 wbudowany w IDE
  • HAL F4 v1.24.2

Konfiguracja w CubeMX

Do celów testowych, oprócz RTC potrzebuję jeszcze kilku innych rzeczy.

Najpierw zegary:

  • HCLK ustawiłem na maksymalną wartość 84 MHz korzystając z wewnętrznego oscylatora HSI.
  • Jako zegar dla RTC na początek wziąłem LSI. Oczywiście sprawdzę też działanie na LSE.

Do celów debugowych użyłem:

  • UART2 w konfiguracji 115200 8n1
  • Serial Wire w zakładce System Core > SYS > Debug > Serial Wire
  • Wejście GPIO PA10 oznaczone jako TEST do ręcznego ustawiania daty i czasu.

Teraz można ze spokojem ustawić RTC w zakładce Timers.

Konfiguracja jest podobna do tej z F1. Zaznacz ptaszek Activate Clock Source, aby podać taktownie i włączyć RTC.

Dodatkowo aktywuj kalendarz. Tym razem nie będzie to dołączenie programowego kalendarza z biblioteki jak poprzednio, a użycie sprzętowego dostarczanego przez RTC.

Ustaw sobie datę i godzinę, jaką chcesz. W STM32F4 masz też informację o tym, czy jest to czas letni, czy zimowy. Rok zapisujesz w zakresie 0-99.

Tak skonfigurowany projekt możesz wygenerować.

Kod RTC

W projekcie dostaliśmy wygenerowaną bibliotekę do użycia RTC. Podobnie jak poprzednio znajdują się w niej struktury odpowiadające za trzymanie czasu i daty.

/**
  * @brief  RTC Time structure definition
  */
typedef struct
{
  uint8_t Hours;            /*!< Specifies the RTC Time Hour.
                                 This parameter must be a number between Min_Data = 0 and Max_Data = 12 if the RTC_HourFormat_12 is selected.
                                 This parameter must be a number between Min_Data = 0 and Max_Data = 23 if the RTC_HourFormat_24 is selected  */

  uint8_t Minutes;          /*!< Specifies the RTC Time Minutes.
                                 This parameter must be a number between Min_Data = 0 and Max_Data = 59 */

  uint8_t Seconds;          /*!< Specifies the RTC Time Seconds.
                                 This parameter must be a number between Min_Data = 0 and Max_Data = 59 */

  uint8_t TimeFormat;       /*!< Specifies the RTC AM/PM Time.
                                 This parameter can be a value of @ref RTC_AM_PM_Definitions */

  uint32_t SubSeconds;     /*!< Specifies the RTC_SSR RTC Sub Second register content.
                                 This parameter corresponds to a time unit range between [0-1] Second
                                 with [1 Sec / SecondFraction +1] granularity */

  uint32_t SecondFraction;  /*!< Specifies the range or granularity of Sub Second register content
                                 corresponding to Synchronous pre-scaler factor value (PREDIV_S)
                                 This parameter corresponds to a time unit range between [0-1] Second
                                 with [1 Sec / SecondFraction +1] granularity.
                                 This field will be used only by HAL_RTC_GetTime function */

  uint32_t DayLightSaving;  /*!< Specifies DayLight Save Operation.
                                 This parameter can be a value of @ref RTC_DayLightSaving_Definitions */

  uint32_t StoreOperation;  /*!< Specifies RTC_StoreOperation value to be written in the BCK bit
                                 in CR register to store the operation.
                                 This parameter can be a value of @ref RTC_StoreOperation_Definitions */
}RTC_TimeTypeDef;

/**
  * @brief  RTC Date structure definition
  */
typedef struct
{
  uint8_t WeekDay;  /*!< Specifies the RTC Date WeekDay.
                         This parameter can be a value of @ref RTC_WeekDay_Definitions */

  uint8_t Month;    /*!< Specifies the RTC Date Month (in BCD format).
                         This parameter can be a value of @ref RTC_Month_Date_Definitions */

  uint8_t Date;     /*!< Specifies the RTC Date.
                         This parameter must be a number between Min_Data = 1 and Max_Data = 31 */

  uint8_t Year;     /*!< Specifies the RTC Date Year.
                         This parameter must be a number between Min_Data = 0 and Max_Data = 99 */

}RTC_DateTypeDef;

Dodano do nich zmienne odpowiadające dodatkowym funkcjom jak format czasu(12/24H) czy czas letni/zimowy. Jest tutaj też informacja o subsekundach i to na jej podstawie będziemy obliczać milisekundy.

Czytanie danych z RTC wygląda dosłownie tak samo jakw przypadku F1.

HAL_RTC_GetTime(&hrtc, &RtcTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &RtcDate, RTC_FORMAT_BIN);

ALE jest tu jeden warunek. MUSISZ zawsze odczytać najpierw czas, a później datę. Nawet jest to napisane w komentarzu do tych funkcji.

  * @note You must call HAL_RTC_GetDate() after HAL_RTC_GetTime() to unlock the values
  *        in the higher-order calendar shadow registers to ensure consistency between the time and date values.
  *        Reading RTC current time locks the values in calendar shadow registers until current date is read.

Związane jest to z tak zwanymi shadow registers. W momencie rozpoczęcia odczytu czasu dane są zatrzaskiwane po to, aby zachować spójność danych i poprawność odczytu. W trakcie czytania RTC dalej zlicza właśnie w tych ukrytych rejestrach, natomiast te dostępne dla użytkownika są zatrzaśnięte.

Musisz odczytać wszystko, aby rejestry znów się odblokowały. Wiem, że to sprawia mnóstwo problemów początkującym 🙂

Podmiana daty na starcie

Tutaj niestety też jest ten problem w bibliotece i rozwiązujemy go podobnie jak w bibliotece do STM32F1 poprzez return w sekcji odzysku z rejestrów backupowych.

  /* USER CODE BEGIN Check_RTC_BKUP */
  HAL_RTC_GetTime(&hrtc, &RtcTime, RTC_FORMAT_BIN);
  HAL_RTC_GetDate(&hrtc, &RtcDate, RTC_FORMAT_BIN);
  return;
  /* USER CODE END Check_RTC_BKUP */

Przy okazji startu możemy odczytać to, co znajduje się w RTC.

I to tyle! Na prawdę. Patrząc na moje poprzednie męki z biblioteką ST aż ciężko jest w to uwierzyć. Jednak dla F4 biblioteka jest lepiej napisana. Śmiem twierdzić, że najpierw napisano tę dla F4, a później bardziej lub mniej bezmyślnie ją przeportowano na F1.

Zasilanie bateryjne BlackPill

Pamiętasz dziwne zachowanie BluePilla, które polegało na tym, że podpinając baterię do VBAT, RTC nadal nie chodził bez zasilania MCU?

Problem polegał na braku kondensatora przy pinie VBAT. Dodanie 100 nF załatwiło sprawę. Jak jest w BlackPill? Tutaj według schematu jest taki kondensator położony. Jednak nie wiadomo jaką on ma wartość. Sprawdzę jednak, czy działa.

I… to działa. RTC chodzi pod samą baterią. Spora przewaga BlackPilla nad BluePill, jeśli zaczynasz z programowaniem i praktycznie nie masz szans na wyłapanie takiego błędu samemu.

Zewnętrzny rezonator LSE

Z BluePillem był jeszcze jeden problem. Tym razem związany z rezonatorem zewnętrznym dla RTC. Błąd polegał na jednoczesnym wyprowadzeniu pinów od oscylatora na goldpiny, co powodowało fluktuacje częstotliwości i niepoprawne zliczanie czasu. Jak jest w BlackPill? Również zerknę na schemat.

Niestety tutaj popełniono ten sam błąd. Jeśli chciałbyś skorzystać z położonego na płytce oscylatora, musisz usunąć goldpiny dla PC14 i PC15 bo inaczej RTC będzie źle zliczał.

Milisekundy

Wspomniałem na początku, że jesteśmy w stanie wyciągnąć ze wbudowanego RTC w F4 milisekundy. Mogą się przydać, chociażby do printowania logów z działania mikrokontrolera.

Do policzenia milisekund wykorzystam zmienne SubSeconds oraz SecondFraction, które znajdują się w strukturze przechowującej czas. Dane te pobierane są z rejestrów RTC.

W opisie do funkcji pobierania czasu mamy taką notkę.

  * @note  You can use SubSeconds and SecondFraction (sTime structure fields returned) to convert SubSeconds
  *        value in second fraction ratio with time unit following generic formula:
  *        Second fraction ratio * time_unit= [(SecondFraction-SubSeconds)/(SecondFraction+1)] * time_unit
  *        This conversion can be performed only if no shift operation is pending (ie. SHFP=0) when PREDIV_S >= SS

Jest w niej podany wzór, z którego trzeba skorzystać. Wynik, jaki otrzymasz wyrażony jest w setnych częściach sekundy np. 0,67 s, czyli 67 setnych. Nie chciałbym przechowywać liczby milisekund w zmiennym przecinku, więc pomnożę wynik razy 1000. Jednak to nadal nie będzie działać, bo i tak trzeba będzie wykonać po drodze dzielenie na float.

Ostatecznie kod liczący liczbę milisekund wygląda tak.

Milliseconds = ((RtcTime.SecondFraction-RtcTime.SubSeconds)/((float)RtcTime.SecondFraction+1) * 100);

Teraz można wyprintować milisekundy. Działa to dosyć fajnie 🙂

Podsumowanie

Z pracą na RTC w STM32F4 mam o wiele lepsze doświadczenia niż na F1. Głównie za sprawą lepiej działającej biblioteki od ST, którą można z powodzeniem używać od ręki. A Ty jaką masz opinię?

W kolejnym artykule sprawdzimy te dodatkowe funkcje w RTC. Pokażę Ci jak je ustawić i jak działają takie rzeczy jak Alarm, WakeUp czy Time Stamp.

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.

5/5 - (8 votes)

Podobne artykuły

.

17 komentarzy

FlexCut · 04/12/2021 o 13:46

Cześć, Bardzo dobrze wszystko opisane tylko nie podany jaden szczegół. Autor pisze, że podpinając baterię do VBAT, RTC nadal nie chodził bez zasilania MCU tak było też w moim przypadku z blackPill ale problem polegał nie na źle dobranym kondensatorze przy VBAT tylko na tym, że w przykładzie autora na github układ RTC taktowany od wewnętrznego LSI który przy odłączeniu napięcia od VDD nie działa. Wystarczy tylko przełączyć RTC na LSE i wszystko działa dobrze.

    Mateusz Salamon · 06/12/2021 o 20:41

    Racja, powinien być LSE. LSI nie jest zasilany z VBAT 🙂

Mario · 06/10/2021 o 16:27

Witam wszystkich a w szczególności autora bloga.
Chcę zacząć programowanie w STM. Aktualnie uczę się na AVR w “C”.
Teraz moje pytanie.
Z tego artykułu wynika (ja tak wywnioskowałem) że, STM-y posiadają wbudowany RTC? Czyli jak np: układ DS3231?
Jeśli źle zrozumiałem to proszę mnie poprawić i doinformować. Dopiero mam zamiar startować z STM (mam już nucleo f303) i chciałbym spróbować. Dobrze to rozumuję, czy coś mnie zmyliło?
Pozdrawiam wszystkich.
Mariusz.

    Mateusz Salamon · 06/10/2021 o 16:55

    Tak, mają RTC wbudowany. Tylko nie jest aż tak dokładny jak DS3231 🙂

tataOlka · 11/03/2021 o 23:19

Primo: Zapis 0,67 to nie zapis w setnych częściach sekundy, tylko nadal w sekundach (a że ułamek, to nic nie zmienia – jednostką jest sekunda).
Secundo: Wydawało mi się do dzisiaj, że w sekundzie jest 1000 milisekund, nie 100, ale może się myliłem 🙂 Zdrówka życzę.

    Mateusz Salamon · 14/03/2021 o 19:56

    Czasem każdemu się coś pomyli 😉

    arek · 27/09/2021 o 14:24

    w sekundie jest 1000ms, co wy pleciecie 🙂 100ms to 0,1s itd. z samej jednostki mili=tysieczna czesc…. ehhh pokolenie arduino 🙂

Krzysiek · 28/01/2021 o 22:06

Dlaczego RTC_DateTypeDef.Date nie nazywa się RTC_DateTypeDef.Day? Można to wytłumaczyć?

    Mateusz Salamon · 29/01/2021 o 09:06

    Pewnie jakoś się da 🙂 Może tym, że w angielskim tak wychodzi, że dzień miesiąca to raczej data niż dzień – tak po prostu 🙂

yaceq · 28/09/2020 o 10:57

Witam
Mam problem z alarmem B. Ustawiłem w Cubie alarm A i B niestety przerwanie otrzymuję tylko od alarmu A. Czy w callbacku po obróbce przerwania od A powinienem jakoś aktywować alarm B?

    Mateusz Salamon · 01/10/2020 o 11:18

    Piszę z głowy, więc mogę się pomylić 😛 Callback do alarmu B jest inny. Alarm B jest chyba w funkcjach HALowych Extended, więc będzie nawet w innym pliku niż ten od A 🙂

      Michał · 19/01/2022 o 11:51

      Cześć Mateusz. Czy mógłbyś podpowiedzieć gdzie znajdę callbacka do alarmu B? 🙂
      Alarm A ma taki callback:
      HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc);
      Zgodnie z sugestią Twoją sprawdziłem ten plik extended:
      stm32l0xx_hal_rcc_ex.c (używam procesora L0) ale nie znalazłem.
      Czy mógłbyś pomóc ruszyć z tym tematem?
      Pozdrawiam

      Mateusz Salamon · 19/01/2022 o 11:52

      Z tego co pamiętam to jest on w Exach, ale nazywa się po prostu ‘Event’ zamiast ‘AlarmB’.

    Michał · 19/01/2022 o 12:01

    ok już znalazłem. Zajrzałem nie do tego pliku co trzeba…

    przerwanie od Alarmu B nazywa się tak:
    void HAL_RTCEx_AlarmBEventCallback(RTC_HandleTypeDef *hrtc)
    i jest w pliku: stm32l0xx_hal_rtc_ex.h
    a nie w:
    stm32l0xx_hal_rcc_ex.h

    Michał · 19/01/2022 o 12:03

    Bardzo Ci dziękuje za pomoc 🙂
    Serdecznie pozdrawiam.

Piotrek · 13/05/2020 o 18:28

Super opisane, dużo zrozumiałem, jednak mam jeden problem, dlaczego jak w cubie aktywuje przerwanie od RTC i włączę internal wakeup po wejściu w sleep od razu jestem budzony? Żeby wejść w sleepa daje:
HAL_SuspendTick();
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);

    Mateusz Salamon · 18/05/2020 o 13:56

    Pewnie masz jeszcze jakieś inne przerwanie 🙂

Dodaj komentarz

Avatar placeholder

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *