Obsługa zegara RTC w Teensy na przykładzie licznika sylwestrowego

672127_bb_00_fb-eps_1000Nie wszyscy o tym wiedzą, ale producent płytek Teensy od wersji 3.0 uraczył nas wbudowanym zegarem czasu rzeczywistego (RTC). Dziś zajmiemy się właśnie podłączeniem, kalibracją oraz ogólną obsługą zegarka wbudowanego w naszą małą płytkę.

Najpierw kwarc

Właściciele Teensy 3.5 oraz 3.6 mogą pominąć ten akapit, albowiem na tych płytkach rezonator kwarcowy jest już fabrycznie wlutowany. Ci, którzy posiadają starsze płytki muszą wlutować w nie rezonator kwarcowy 32.768 kHz, który powinien być dostępny w większości sklepów elektronicznych, na allegro i może nawet u zegarmistrza. Taki rezonator nie jest spolaryzowany, więc nie ważne czy wlutujemy nóżkę 1 w otwór 2, czy odwrotnie (i tak będzie działał jak trzeba). Miejsce na wlutowanie kwarcu jest na spodniej stronie Teensy, mniej więcej po środku, oznaczone pustym prostokątem. W moim Teensy 3.2 po wlutowaniu rezonatora wygląda to tak:

dsc_0027

Inne połączenia

Jedynym komponentem wymaganym, aby zegar działał poprawnie nawet po odcięciu zasilania jest podłączenie baterii 3V do pinów GND oraz VBat. W różnych wersjach Teensy pin VBat może znajdować się gdzie indziej, dlatego też należy znaleźć jego lokalizację na stronie producenta: https://www.pjrc.com/teensy/pinout.html

W naszym projekcie dodatkowo posłużymy się ekranem LCD 16×02 podpiętym przez magistralę I2C. Przykład podłączenia takiego ekranu można znaleźć w poprzednich moich artykułach np. tutaj.

Omówienie wybranych funkcji biblioteki TimeLib.h

Odczyt wartości:

  • hour() – aktualna godzina z zakresu 0-23
  • minute() – aktualna minuta z zakresu 0-59
  • second() – aktualna sekunda z zakresu 0-59
  • day() – aktualny dzień miesiąca z zakresu 1-31
  • weekday() – aktualny dzień tygodnia, niedziela jest reprezentowana jako 1
  • month() – aktualny miesiąc z zakresu 1-12
  • year() –  aktualny rok w formacie 4 cyfr
  • now() – zwraca ilość sekund, które upłynęły od 1 stycznia 1970 (tzw. format unix timestamp)

Do podanych powyżej funkcji (poza now()) możemy także przekazać jedną zmienną w formacie unix timestamp. Funkcja zwróci wtedy odpowiednią wartość dla podanego timestamp’a, a nie dla aktualnie wskazywanego przez zegar.

Aktualizacja zegara:

  • setTime(t) – ustawia aktualny czas, gdzie t to data podana w formacie unix timestamp.
  • setTime(godzina, minuta, sekunda, dzien, miesiac, rok) – ustawia aktualny czas zgodnie z przekazanymi parametrami.
  • setSyncProvider(getTimeFunction) – ustawia zewnętrzną funkcję jako źródło, z którego będzie pobierana informacja o aktualnym czasie w celu synchronizacji zegara. Funkcja ta musi zwracać typ time_t (unix timestamp).
  • setSyncInterval(interval) – ustawia co ile sekund zegar ma się synchronizować.
  • timeStatus() – zwraca typ wyliczeniowy:
    • timeNotSet – czas nie został ustawiony,
    • timeNeedsSync – czas ustawiony, ale wymagana jest synchronizacja,
    • timeSet – czas ustawiony i zsynchronizowany poprawnie.

Kod programu, który odlicza do Nowego Roku

Cały kod do pobrania tutaj.

Część kodu (odpowiedzialna za automatyczną synchronizację zegara) pobrana jest przykładowego szkicu dla tej biblioteki (Time > TimeTeensy3). Są to funkcje getTeensy3Time, processSyncMessage, oraz początek kodu funkcji loop() odpowiedzialny za synchronizację danych przez port szeregowy.

Musimy pamiętać, aby na początku pliku załączyć bibliotekę TimeLib.h

W funkcji setup jedyne co musimy zrobić, to zainicjalizować wyświetlacz (co robiliśmy już setki razy :) ) oraz wywołać funkcję setSyncProvider. W funkcji loop() poza kodem aktualizującym jest wywołanie naszej funkcji printTimeLeft() oraz odczekanie sekundę poprzez delay(1000).

Przyjrzyjmy się bliżej funkcji printTimeLeft():

void printTimeLeft() {
  lcd.setCursor(0,1);
  const time_t targetTimestamp = 1483228800;
  time_t timeLeft = targetTimestamp - now();
  
  int daysLeft = timeLeft / (60 * 60 * 24);
  timeLeft -= daysLeft * 60 * 60 * 24;

  int hoursLeft = timeLeft / (60 * 60);
  timeLeft -= hoursLeft * 60 * 60;

  int minutesLeft = timeLeft / 60;
  timeLeft -= minutesLeft * 60;
  
  lcd.print(daysLeft);
  lcd.print("d ");

  lcd.print(hoursLeft);
  lcd.print("h ");

  lcd.print(minutesLeft);
  lcd.print("m ");

  lcd.print(timeLeft);
  lcd.print("s     ");
}

Stała targetTimestamp zawiera datę w formacie unix timestamp, do której będziemy odliczali. Datę taką możemy wygenerować np. na stronie http://www.unixtimestamp.com/

Następnie deklarujemy i inicjalizujemy zmienną timeLeft, mówiącą nam ile sekund pozostało do naszego wydarzenia. Teraz do akcji wchodzi prosta matematyka. Aby otrzymać liczbę dni pozostałą do wydarzenia musimy podzielić całkowicie liczbę sekund przez 60*60*24 czyli 60 sekund w minucie razy 60 minut w godzinie razy 24 godziny w dobie. Teraz możemy odjąć otrzymaną ilość dni wyrażoną w sekundach (czyli przemnożoną przez 60*60*24) od całkowitego pozostałego czasu wyrażonego w sekundach. Analogicznie postępujemy obliczając pozostałą liczbę godzin oraz minut. Po tych wszystkich działaniach wartość naszej zmiennej timeLeft będzie odpowiadała ilości sekund pozostałych do wydarzenia i będzie się mieściła w zakresie 0-59, więc nie musimy już w żaden sposób jej przetwarzać.

Teraz wystarczy wypisać nasze obliczone dane na ekran. Warto zwrócić uwagę na ostatnią linijkę, mianowicie lcd.print(„s     „);

Dlaczego po s jest 5 spacji? Otóż najkrótszy ciąg jaki może wyjść z naszych obliczeń będzie zawierał po jednej cyfrze z każdej wartości, tzn będzie wyglądał mniej więcej tak: „*d *h *m *s” – taki ciąg daje 11 znaków. Nigdzie w kodzie nie czyścimy ekranu, więc jeżeli w pewnym momencie pojawi się tam 12 znaków, a później znów 11, to ostatni znak pozostanie tam. Z tego też powodu dopisujemy 5 spacji, aby wypełnić ekran pustymi znakami do samego końca.

Na koniec wrzucam jeszcze zdjęcia jak układ prezentuje się u mnie:

dsc_0025
dsc_0026