fbpx

Wbudowany zegar RTC w STM32 ma tyle funkcji, że musiałem podzielić ich opis na dwa wpisy. Poprzednio pokazałem Ci bajery związane z “włamaniami”. Tym razem obudzimy śpiocha 🙂

Funkcje “budzące” RTC w STM32F4

Teraz zobaczymy sobie na dwie podobne, ale jednak różne funkcje zegara, które mogą nas obudzić, ale nie tylko nas:

  • Wake Up
  • Alarmy

Pozwól, że oprę się na płytce, którą wybrałem w poprzednim artykule. Jest to tzw. BlackPill z STM32F401 na pokładzie. Płytkę taką (lub nieco mocniejszą) możesz kupić u mnie.

stm32f411

Oprogramowanie, którego użyję to:

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

Będę również bazował na projekcie, który stworzyłem dla STM32F4.

Wake Up w STM32

Już sama nazwa zdradza Ci do czego to może służyć. Funkcja Wake Up służy do budzenia mikrokontrolera z trybów obniżonego poboru energii. W trybie tym nie pracują wszystkie zegary czy peryferia. Dużo zależy jaki tryb wybierzemy. Serie F mają ubogą listę możliwości, jeśli o to chodzi, ale już L-ki posiadają mnóstwo kombinacji tychże trybów.

Użycie tej funkcji w skrócie sprowadza się do ustawienia odpowiedniego timera w RTC i wejścia w tryb uśpienia. Po upłynięciu zadanego czasu nasz mikrokontroler zostanie obudzony przerwaniem, które oczywiście możemy obsłużyć chyba że to będzie “agresywny” tryb Low Power w którym nie są zapamiętywane pamięci i program ruszy od początku.

Jak to ustawić? W Cube musisz zaznaczyć, że chcesz skorzystać z Wake Up.

Pojawi Ci się w konfiguracji sekcja Wake UP, w której możesz ustawić taktowanie dedykowanego Timera oraz sam licznik.

Dla testów wybrałem taktowanie RTC podzielone na 16. Im większy dzielnik (mniejszy zegar), tym mniej energii będzie pobierał moduł Wake Up RTC podczas uśpienia MCU.

Samego licznika nie ustawiaj w Cube. To jest akurat bez sensu. Dlaczego?

Po pierwsze nie chcemy budzić się zaraz po starcie programu, gdy jeszcze nie wiemy, kiedy idziemy spać. Cube niestety ustawia od razu zegar podczas inicjalizacji i włącza jego przerwanie. Bzdura totalna.

Po drugie w funkcji inicjalizującej RTC mamy kawałek wyżej wstawiony wcześniej return, aby nie nadpisywać daty i godziny wartościami z Cube, więc kod nigdy nie dotrze do ustawienia WakeUp timera.

Kiedy i jak więc go użyć?

Ustawia się go chwilę przed wejściem w sleepa. Do tego utworzyłem specjalną funkcję Enter_LowPowerMode w sekcji użytkowanika nr 4 w której nastawiam licznik WakeUp oraz wchodzę w STOP Mode. Oparłem się na przykładzie z bibliotek HAL.

void Enter_LowPowerMode(void)
{
  /*## Enter STOP low power Mode ##########################################*/
  /**
  RTC Wakeup Interrupt Generation:
  Wakeup Time Base = (RTC_WAKEUPCLOCK_RTCCLK_DIV /(LSE or LSI))
  Wakeup Time = Wakeup Time Base * WakeUpCounter
              = (RTC_WAKEUPCLOCK_RTCCLK_DIV /(LSE or LSI)) * WakeUpCounter
  ==> WakeUpCounter = Wakeup Time / Wakeup Time Base

  To configure the wake up timer to 5 s the WakeUpCounter is set to 0x2FA8:
  RTC_WAKEUPCLOCK_RTCCLK_DIV = RTCCLK_Div16 = 16
  Wakeup Time Base = 16 /(~32.000KHz) = ~0,5 ms
  Wakeup Time = 5 s = 0,5ms  * WakeUpCounter
  ==> WakeUpCounter = 5/0,5ms = 0x2710
	**/
  HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0x2710, RTC_WAKEUPCLOCK_RTCCLK_DIV16);

  HAL_SuspendTick();      			/* To Avoid timer wake-up. */

  /**
	In PWR_MAINREGULATOR_ON mode, we measure 13.8/15.2uA on JP6
	**/
  HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON,PWR_STOPENTRY_WFI);

  /**
  In PWR_LOWPOWERREGULATOR_ON mode, we measure 1.3/2.7uA on JP6
  HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI);
	**/

  /* We are now waiting for TAMPERF1 or WAKEUP interrupts (or Reset) */

  HAL_ResumeTick();       /* Needed in case of Timer usage. */
  HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);

  SystemClock_Config();   /* Re-configure the system clock */
}

W komentarzach możesz zobaczyć, że należy wyliczyć wartość licznika, którą nastawiamy. Jest na to prosty wzór.

  • Wakeup Time Base = (RTC_WAKEUPCLOCK_RTCCLK_DIV /(LSE or LSI)), czyli bazowy tick timera WakeUp. Wybrałem dzielnik o wartości 16 oraz korzystam z wewnętrznego oscylatora, więc 16/32000 = 0,0005 s, czyli 0,5 ms.
  • Wakeup Time = Wakeup Time Base * WakeUpCounter, czyli czas, po jakim MCU ma się obudzić. Jeśli chcesz, aby robił to co około 5 sekund to wzór ten wygląda następująco
    5 = 0,0005 * WakeUpCounter 
  • WakeUpCounter = Wakeup Time / Wakeup Time Base, czyli zamieniając matematycznie strony nasze obliczenia będą wyglądały tak
    WakeUpCounter = 5 / 0,0005 = 10000 (dec) = 0x2710 (hex) i tę wartość trzeba wpisać do licznika Wake Up

Przed wejściem w STOP Mode warto zatrzymać SysTick, bo jego przerwanie również może wybudzić MCU 🙂

Funkcja HAL_PWR_EnterSTOPMode wchodzi w tryb STOP Mode z możliwością wybudzenia przerwaniem, więc RTC przerwaniem WakeUp jest w stanie wybudzić nasz MCU. To, co się dzieje zaraz za tą funkcją wykonywane jest już po obudzeniu.

Po obudzeniu wznawiam SyStick, wyłączam WakeUp i dalej działam. Testowy program akurat będzie cyklicznie usypiał i budził MCU. Na terminalu wygląda to tak.

Budzi się i zasypia. Taka rola magika. Pełny kod przykładu znajdziesz tutaj.

Alarm RTC w STM32F4

Ostatnią funkcją, którą chciałem Ci przedstawić jest Alarm. Działa on podobnie jak budzik, który nastawiasz codziennie wieczorem.

Po zrównaniu się zegara z alarmem generowane jest przerwanie. Przerwanie to oczywiście można obsłużyć lub użyć do wybudzenia mikrokontrolera z uśpienia. Jak uruchomić alarm?

Wystarczy …włączyć go w Cube 🙂

Ustawienie to powoduje przeniesienie sygnalizacji alarmu wewnątrz MCU. Można również aktywować alarm na pinie mikrokontrolera.

Oczywiście pojawi się dodatkowa konfiguracja związana z Alarmem.

Znajdziesz tutaj ustawienie nie tylko godziny i daty. Można ustawić nieco więcej kombinacji jak np. generowanie alarmu co minutę. Wszystko to za pomocą masek. Polecam się pobawić o potestować.

Jednak dużo lepiej jest ustawiać alarm z kodu i tak zrobiłem ja. Napisałem prostą funkcję, która ustawia alarm pięć sekund dalej niż aktualna godzina. Uwaga, nie działa to na dłuższą metę. Przy zmianie godziny algorytm już się sypie. Napisanie dobrze działającego algorytmu nie było moim celem 🙂

Wykorzystuję do tego osobną funkcję.

void SetNextAlarm(void)
{
	RTC_AlarmTypeDef sAlarm = {0};

	/** Enable the Alarm A
	 * WARNING: It doesn't work if hour changes and further.
	*/
	sAlarm.AlarmTime.Hours = RtcTime.Hours;
	sAlarm.AlarmTime.Minutes = RtcTime.Minutes + ((RtcTime.Seconds + 5) / 60);
	sAlarm.AlarmTime.Seconds = (RtcTime.Seconds + 5) % 60;
	sAlarm.AlarmTime.SubSeconds = 0;
	sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
	sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
	sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
	sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
	sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
	sAlarm.AlarmDateWeekDay = RtcDate.Date;
	sAlarm.Alarm = RTC_ALARM_A;
	if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
	{
		Error_Handler();
	}
}

Natomiast sam alarm obsługuję ten sposób, że w jego przerwaniu ustawiam jedynie globalną flagę.

void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
	AlarmFlag = 1;
}

A flagą tą zajmuję się już w pętli głównej pomiędzy czytaniem aktualnej godziny z RTC.

  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  HAL_RTC_GetTime(&hrtc, &RtcTime, RTC_FORMAT_BIN);
	  Milliseconds = ((RtcTime.SecondFraction-RtcTime.SubSeconds)/((float)RtcTime.SecondFraction+1) * 100);
	  HAL_RTC_GetDate(&hrtc, &RtcDate, RTC_FORMAT_BIN);

	  if(RtcTime.Seconds != CompareSeconds)
	  {
		  MessageLen = sprintf((char*)Message, "Date: %02d.%02d.20%02d Time: %02d:%02d:%02d:%02d\n\r", RtcDate.Date, RtcDate.Month, RtcDate.Year, RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds, Milliseconds);
		  HAL_UART_Transmit(&huart2, Message, MessageLen, 100);
		  CompareSeconds = RtcTime.Seconds;
	  }

	  if(AlarmFlag == 1)
	  {
		MessageLen = sprintf((char*)Message, "Budzik?! Ustawie drzemke za 5 sekund...\n\r");
		HAL_UART_Transmit(&huart2, Message, MessageLen, 100);

		SetNextAlarm();
		AlarmFlag = 0;
	  }

	  if(GPIO_PIN_RESET == HAL_GPIO_ReadPin(TEST_GPIO_Port, TEST_Pin))
	  {
		 while(GPIO_PIN_RESET == HAL_GPIO_ReadPin(TEST_GPIO_Port, TEST_Pin))
		 {}
		 SetRTC();
	  }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

Efekt działania?

Drzemka co 5 sekund. Jakbym widział siebie co rano 🙂

Pełny kod przykładu znajdziesz tutaj.

Podsumowanie

Poznałeś kolejne dwie funkcje wbudowanego RTC. Alarmy mogą nam przypominać o pewnych rzeczach odległych w czasie. Dzięki temu odciążamy CPU od ciągłego sprawdzania, “czy to już?”. Funkcja Wake Up może okazać się niesamowicie przydatna w urządzeniach zasilanych bateryjnie.

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 - (4 votes)

Podobne artykuły

.

13 komentarzy

Jacek · 25/05/2022 o 12:18

Witaj.Mateusz.
Borykam się z następującym problemem:
Wprowadzam STM32L0 w STOP mode, aktywuje watchdoga na około 27 sekund i podobnie jak w przykładzie wybudzam się co około 25 sekund aby go zrestartować. Wszystko działa dobrze.
Dodaję AlarmA. Gdy nastaje AlarmA to nastaje tylko raz. Wygląda na to, że ten wakeUp timer do resetowania watchdoga coś mi miesza i powoduje że AlarmA kolejny raz nie odpala. Proszę o podpowiedź w czym tkwi problem?
Pozdrawiam serdecznie

    Mateusz Salamon · 06/06/2022 o 19:31

    Sprawdź czy przerwanie nie “znika” po wyjściu ze STOPa, bo on sporo zegarów wyłącza 🙂

Michał · 14/12/2021 o 10:07

Witam.

Kolejny super materiał 🙂
Potrzebuje zrobić taki myk, żeby gdy procesor śpi wybudzać go cyklicznie i resetować watchdoga.
Czy to wybudzenie poprzez RTC posiada jakiegoś callbacka, w którym mógłbym właśnie cyklicznie resetować IWDG?
Pozdrawiam 🙂

    Mateusz Salamon · 24/12/2021 o 12:58

    Możesz wykorzystać Alarm i przerwanie od niego 🙂

Krzysiek · 01/02/2021 o 18:41

Sprawdziłem jak wygląda współpraca z RTC na Nucleo-F411RE:
Bateria podtrzymująca działa bez żadnych kondensatorów.
Piny PC14, PC15 są standardowo odłączone. Można korzystać z oscylatora X2 32.768kHz. Istnieją mostki SB48 i SB49, które tak jak pisze producent standardowo są rozwarte. Po przełączeniu na oscylator X2 32.768kHz czas odmierza się poprawnie (na oko).

Jakie zauważyłem niespodzianki:
Podłączenie baterii podtrzymującej powoduje, że świeci się czerwona dioda od zasilania i dioda programatora. Trzeba odlutować zworę SB45.
Odlutowałem zworę SB45 i diody przestały świecić podczas podtrzymywania bateryjnego.
Nie rozumiem dlaczego producent standardowo nie rozłączył tej zwory. Jej połączenie, nie daje podtrzymania, gdy zaniknie zasilanie. Potrzeba i tak podłączyć baterię. Zwarcie zwory powoduje tylko kłopot, gdy podamy podtrzymanie bateryjne, niepotrzebnie rozładowuje diodami baterie.

Ustawienie daty i następne odczytanie jej nie powoduje, że wylicza się dzień tygodnia. Dla F1 było wyliczane.

Dla amatorów, takich jak ja, warto dodać, że do używania alarmu trzeba dodać aktywację przerwania na RTC. Ja nie od razu na to wpadłem. 😉

    Mateusz Salamon · 03/02/2021 o 13:08

    Dzień tygodnia w F1 był programowy, więc stąd automatyczne wyliczenie. Faktycznie szkoda, że krzem nie robi tego za nas 🙂 Nie próbowałem nigdy do Nucleo podłączać bateryjki… Jednak skoro dioda nadal się pali to słabo to wygląda 😀

Andrzej · 11/06/2020 o 11:19

Może podpowiesz jak korzystając z “Alarm mask” ustawić sobie przerwanie np co sekundę lub co minutę? System maskowania jest bardzo nie intuicyjny i dla mnie nie jednoznaczny.

    Mateusz Salamon · 11/06/2020 o 16:06

    Faktycznie może być skomplikowane 😀 dopisałem do listy tematów 🙂

      Andrzej · 11/06/2020 o 17:19

      sprawdziłem, że przy ustawieniach alarmu na: Hours / Minutes / Seconds / Sub Seconds = 0
      i odblokowaniu:
      Alarm Mask Date Week day – enable
      Alarm Mask Hours – enable
      Alarm Mask Minutes – enable
      Alarm Mask Seconde – enable

      zegar RTC generuje przerwanie co 1 sekundę. Dalsza zabawa może być uciążliwa 🙂
      W ogóle nie mam pojęcia co ustawiają następne trzy parametry.

      Mateusz Salamon · 15/06/2020 o 09:58

      Te maski powodują to, że przerwanie będzie się pojawiało o “dowolnej” wartości spod tego, co one maskują 🙂

      Andrzej · 15/06/2020 o 16:13

      Próbowałem zmieniać te flagi aby otrzymać przerwanie co minutę ale mi się nie udało. 🙁

SaS · 12/05/2020 o 12:05

F4 maja rejestr kalibracyjny. Warto go opisać. W nocie jest wzór na wyliczenie wartości rejestru. Z precyzyjnym pomiarem częstotliwości problemu nie mam ale nie mam pomysłu, jak po wykonaniu korekty szybko zweryfikować dokładność pracy zegara? Z tego powodu, na razie, do konstrukcji dodaję trymer.
Dużo lepiej niż w F4 zrobiono korektę w DS3231. Tam są dołączane/odłączane pojemności do oscylatora, dzięki czemu, prawie on-line, można mierzyć częstotliwość oscylatora. W F4 co (jak pamiętam) 32 sekundy dodawane lub odejmowane są impulsy licznika. Z tego co wiem, nie da się wystawić sygnału minutowego na TAMPER, można 1 sekundę i chyba oscylator. pozostaje jakaś proteza programowa.

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

    Jasne – można kalibrować. Tylko początkujący raczej tego nie zrobi 🙂

Dodaj komentarz

Avatar placeholder

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