czwartek, 26 października 2017

[6] ESP32 - Konfiguracja wewnętrznego zegara RTC

W tym poście chciałbym opisać sposób konfiguracji wewnętrznego zegara RTC w układzie ESP32.

[Źródło: www.banggood.com]


Należy pamiętać w przypadku korzystania z wewnętrznego zegara RTC, że gdy nastąpi zanik zasilania w układzie dane odnośnie czasu zostaną utracone. Po ponownym zasileniu układu należy je ponownie wprowadzić, lub wykorzystać dodatkowe elementy do jego ustawienia jak usługa sieciowa SNTP, układ GPS bądź wykorzystywać zewnętrzny zegar np. DS3231.

Oprócz zaniku zasilania trzeba pamiętać także o taktowaniu układu. RTC, który wykorzystuje zegar 150kHz. Nie jest przez to bardzo precyzyjny do odmierzania czasu. Zwłaszcza na dłuższą metę. 

Program:


Do ustawiania RTC przygotowałem kilka funkcji odpowiedzialnych za ustawianie danych oraz za wypisywanie ich na ekranie.

Funkcja odpowiedzialna za ustawianie danych dla godziny oraz daty:

  1. uint8_t setTimeDateRTCIntern(uint8_t hour, uint8_t minutes, uint8_t seconds, uint8_t mday, uint8_t month, uint8_t year)
  2. {
  3.     struct timeval tv;
  4.     struct tm mytm;
  5.     char buf[256];
  6.     /* Checks data */
  7.     if(hour > 23)       { return 0; }
  8.     if(minutes > 59)    { return 0; }
  9.     if(seconds > 59)    { return 0; }
  10.     if(mday > 31)       { return 0; }
  11.     if(month > 12)      { return 0; }
  12.     if(year > 30)       { return 0; }
  13.     mytm.tm_hour = hour;
  14.     mytm.tm_min = minutes;
  15.     mytm.tm_sec = seconds;
  16.     mytm.tm_mday = mday;
  17.     mytm.tm_mon = month;
  18.     mytm.tm_year = 100 + year;
  19.     /* Set timezone */
  20.     setenv("TZ", "GMT-1GMT-2,M3.5.0/2,M10.5.0/3", 1);
  21.     tzset();
  22.     time_t t = mktime(&mytm);
  23.     ESP_LOGI(TAG, "time: %ld", t);

  24.     tv.tv_sec = t;
  25.     tv.tv_usec = 0;

  26.     settimeofday(&tv, NULL);

  27.     sprintf(buf,"%02d:%02d:%02d",mytm.tm_hour,mytm.tm_min,mytm.tm_sec);
  28.     printf("%s", buf);
  29.     sprintf(buf,"%04d-%02d-%02d",mytm.tm_year+1900,mytm.tm_mon+1,mytm.tm_mday);
  30.     printf("%s", buf);
  31.     return 1;
  32. }

Do funkcji przekazywane są dane odnośnie godziny oraz daty. Na początku sprawdzana jest ich poprawność. W przypadku błędu zwracana jest wartość 0. Jeśli są one poprawne to zostają wprowadzone do struktury. Przed wygenerowaniem wprowadzonego czasu ustawiana jest strefa czasowa.

Strefa czasowa jest ustawiana w funkcji setenv:

  1. int setenv(const char *name, const char *value, int overwrite);

Modyfikuje ona zmienną TZ. Podane dane do zmiany wprowadzamy na drugiej pozycji. Trzecia zmienna gdy będzie większa od 0 to oznacza, że nastąpi napisanie podanej zmiennej.

Wartości można wprowadzać w formacie Std + offseet. Dla europy wprowadza się CET + przesunięcie co w Polsce będzie CET-1. Co jest godzinę do przodu niż czas UTC. Minusem tego rozwiązania jest konieczność zmiany czasu letniego i zimowego na sztywno.

Druga opcja, którą można wykorzystać jest format CEST. Pozwala on na automatyczne dostosowanie czasu letniego oraz zimowego. Jego przykład jest umieszczony w przykładzie powyżej.

Nazwy M3.5.0 oznacza marzec czyli 3, ostatni tydzień to będzie 5. Ostatnia zmienna odpowiada za dzień tygodnia czyli Niedziele. Podobnie sprawa wygląda dla zmiany czasu na zimowy (M10,5,0 czyli październik, ostatni tydzień i niedziela).

Funkcja wywołana po niej czyli tzset() inicjalizuje ustawioną strefę czasową. Po niej tworzony jest czas z podanych zmiennych.

Na końcu wypisywana jest ustawiona data oraz godzina. Funkcja przyjmuje wartość roku po 2000, natomiast do struktury przekazuje się rok po 1900. 

Ustawiania samej godziny:

  1. uint8_t setTimeRTCIntern(uint8_t hour, uint8_t minutes, uint8_t seconds)
  2. {
  3.     struct timeval tv;
  4.     struct tm mytm;
  5.     char buf[256];
  6.     /* Checks data */
  7.     if(hour > 23)       { return 0; }
  8.     if(minutes > 59)    { return 0; }
  9.     if(seconds > 59)    { return 0; }
  10.     mytm.tm_hour = hour;
  11.     mytm.tm_min = minutes;
  12.     mytm.tm_sec = seconds;
  13.     /* Set timezone */
  14.     setenv("TZ", "GMT-1GMT-2,M3.5.0/2,M10.5.0", 1);
  15.     tzset();
  16.     time_t t = mktime(&mytm);
  17.     ESP_LOGI(TAG, "time: %ld", t);
  18.     tv.tv_sec = t;
  19.     tv.tv_usec = 0;
  20.     settimeofday(&tv, NULL);
  21.     sprintf(buf,"%02d:%02d:%02d",mytm.tm_hour,mytm.tm_min,mytm.tm_sec);
  22.     printf("%s", buf);
  23.     return 1;
  24. }

Jest to analogiczna funkcja do tej przedstawionej powyżej. Jedyną różnicą jest pominięcie ustawienia daty.

Wypisywanie danych na ekranie:

  1. void printTime(void)
  2. {
  3.     char strftime_buf[64];
  4.     time_t now = 0;
  5.     struct tm timeinfo;
  6.     while(1)
  7.     {
  8.         time(&now);
  9.         /* Update struct tm with new data */
  10.         localtime_r(&now, &timeinfo);
  11.         strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
  12.         ESP_LOGI(TAG, "CET DST: %s", strftime_buf);
  13.         printf("%02d:%02d:%02d",timeinfo.tm_hour,timeinfo.tm_min,timeinfo.tm_sec);
  14.         printf("%04d-%02d-%02d",timeinfo.tm_year+1900,timeinfo.tm_mon+1,timeinfo.tm_mday);
  15.         printfTimeDateInfo(&now, &timeinfo);
  16.         ESP_LOGI(TAG, "CET DST: %s\n", strftime_buf);
  17.         vTaskDelay(1000 / portTICK_RATE_MS);
  18.     }
  19. }

Funkcja powyżej wypisuje dane w dwóch formatach. Pierwszy z nich wyświetla dane sformatowane w funkcji printf. Druga formatuje je z danej strftime_buf.

Główna funkcja wygląda następująco:

  1. void app_main(void)
  2. {
  3.     uint8_t rtcSetFlag = 0;
  4.     nvs_flash_init()
  5.     printf("123...\n\n");
  6.     if(rtcSetFlag == 0)
  7.     {
  8.         setTimeDateRTCIntern(15, 24, 34, 24, 11, 17);
  9.         setTimeRTCIntern(11, 30, 1);
  10.         rtcSetFlag = 1;
  11.     }
  12.     xTaskCreate(printTime, "printTime", 4096, NULL, 5, NULL);
  13. }


Pliki z projektem dla RTC I2C oraz wbudowanego można znaleźć na dysku Google pod tym linkiem.