Zegar z Arduino

Cześć! Skoro czytasz ten artykuł to prawdopodobnie właśnie zaczynasz swoją przygodę z Arduino oraz Starter-Kitem Nettigo! Przygotowaliśmy dla Ciebie serię artykułów przedstawiających ciekawe projekty, które można zbudować wykorzystując to, co znajdziesz w przygotowanym przez nas zestawie startowym. Pierwszym projektem, który chcemy Ci zaprezentować jest cyfrowy zegar z wyświetlaczem, który można złożyć używając tylko 4 przewodów podłączonych do Arduino. Zapraszamy do lektury. :)

Lista potrzebnych części

Jeśli jesteś posiadaczem Starter Kitu oraz płytki Arduino UNO to masz wszystko co potrzebne. Cały SK nie jest potrzebny w tym projekcie, użyjemy tych elementów:

Użyte elementy

Schemat podłączenia

Pierwszym krokiem, który musisz uczynić jest podłączenie wszystkich urządzeń według schematu przedstawionego poniżej. Prawda, że proste? Użyliśmy tak mało przewodów dzięki zastosowaniu magistrali I2C, o której powiemy kilka słów niżej. :)

Schemat połączeń

Modyfikacja DS3231

Zanim jednak włożysz baterię CR2032 do modułu zegara RTC musisz upewnić się, że Twój moduł nie będzie chciał jej ładować. Baterie CR2032 nie są przeznaczone do ładowania. Przy takim podłączeniu zasilania do modułu DS3231 bateria CR2032 będzie ładowana i dlatego jeśli chcesz być pewny/pewna to trzeba przerobić moduł.

Najłatwiej będzie przeciąć ścieżkę (np nożykiem introligatorskim) w miejscu oznaczonym czerwoną linią na poniższym zdjęciu. Jeżeli jednak czujesz się na siłach i nie obce jest Ci posługiwanie się lutownicą to alternatywnym rozwiązaniem jest wlutowanie z płytki diody oznaczonej żółtą ramką.

Modyfikacja DS3231

Poniżej możesz zobaczyć jak podłączyliśmy całość:

Po co komu to całe I2C?

I2C (znane też pod nazwą TWI) to magistrala, która poza liniami zasilania używa tylko 2 linii do komunikacji. Nazywają się one SCL (linia zegara) oraz SDA (linia danych). Podłączając w ten sposób moduły możemy skomunikować ze sobą w praktyce aż do 112 urządzeń. Każde urządzenie podłączone do magistrali posiada swój własny adres. W niektórych modułach możemy sami ustalić 3 ostatnie bity adresu za pomocą odpowiednich zworek.

Skąd wziąć adres urządzenia?

Wynik działania skanera I2C

Wystarczy, że podłączysz je do Arduino, wgrasz szkic I2C Scanner, który możesz pobrać tutaj i uruchomisz monitor portu szeregowego. Twoim oczom powinno ukazać się coś takiego: Jak widać skaner znalazł 3 urządzenia (mimo, że przecież podłączone są 2). Jeden z tych adresów to adres ekranu LCD, kolejny to adres zegara DS3231, a ten dodatkowy to adres modułu pamięci EEPROM, który znajduje się na płytce razem z zegarem (to ten mniejszy układ). Aby rozpoznać które adresy należą do którego urządzenia wystarczy odpiąć na chwilę zielony przewód (SDA) od danego urządzenia i jego adres powinien zniknąć. Możesz też przyjąć za pewnik że adres 0x68 należy do DS3231, gdyż wg. oficjalnej dokumentacji jest on ustalony na taki i nie da się go zmienić.

Obsługa ekranu LCD

Skoro wiesz już jak podłączyć i rozpoznać urządzenia na magistrali I2C możesz zająć się obsługą jednego z nich, a mianowicie ekranu LCD 16×2. Pierwszym, co należy zrobić jest pobranie z menedżera bibliotek (Szkic > Dołącz bibliotekę > Zarządzaj bibliotekami…) paczki o nazwie LiquidCrystal_PCF8574. Pozwoli ona w łatwy sposób sterować wyświetlaczem.

Po zainstalowaniu paczki możesz zacząć tworzyć szkic. Na początku programu załącz nowo pobraną bibliotekę oraz Wire.h, która odpowiada za komunikację I2C.

#include <Wire.h>
#include <LiquidCrystal_PCF8574.h>

Zostawiając linijkę odstępu utwórz sobie stałą, do której wpiszesz adres wyświetlacza.

#define LCD_ADDR  0x27  // Adres I2C wyświetlacza LCD

Kolejna pusta linijka dla oddzielenia globalnych zmiennych od bloku dyrektyw #define i można zainicjować zmienną lcd, która będzie używana do sterowania ekranem.

LiquidCrystal_PCF8574 lcd(LCD_ADDR);

Czas na nawiązanie komunikacji z wyświetlaczem oraz ustawienie podświetlenia. Zrealizuj to w funkcji, która wykona się tylko raz podczas działania programu (na samym początku). Mowa tu o funkcji setup():

void setup() {
  Wire.begin();
  lcd.begin(16, 2);
  lcd.setBacklight(true);
}

To już wszystko z konfiguracji. Teraz możesz wyświetlać znaki na ekranie. Do tego celu posłużą Ci głównie 3 metody:

  • lcd.clear() czyści ekran i ustawia kursor na lewy górny róg,
  • lcd.setCursor(int x, int y) ustawia kursor w pozycji (x,y). Parametry x = 0 oraz y = 0 to lewy górny róg,
  • lcd.print(tekst) wypisuje na ekran wpisany jako parametr tekst (może być też liczba).

Możesz np. na końcu funkcji setup() dopisać takie oto linijki:

lcd.print("Zagar");
lcd.setCursor(0, 1);
lcd.print("Arduino");
delay(1000);
lcd.clear();

Spowodują one zaraz po uruchomieniu Arduino wyświetlenie na LCD tekstu

Zegar
Arduino

przez sekundę, a następnie wyczyszczenie ekranu.

Ustawienie godziny na zegarze

Odłóżmy teraz na chwilę szkic z ekranem i zajmijmy się ustawieniem właściwej daty na zegarku. Aby w ogóle móc komunikować się z modułem potrzebujesz z menedżera ściągnąć bibliotekę o nazwie DS3231. Kiedy to zrobisz możesz załadować przykładowy szkic (Plik > Przykłady > DS3231 > DS3231_set), który pozwoli Ci na wprowadzenie odpowiedniego czasu przez port szeregowy. Ustawić czas można na 2 sposoby:

  • Wysłać przez monitor portu szeregowego ciąg znaków w formacie YYMMDDwHHMMSSx, czyli 2 cyfry roku, 2 cyfry miesiąca, 2 cyfry dnia, 1 cyfra dnia tygodnia, 2 cyfry godziny, 2 cyfry minuty, 2 cyfry sekundy oraz znak x na końcu.
  • Korzystając z programu, który dla Ciebie przygotowaliśmy. Można go ściągnąć tutaj (DS3231_Sync).
DS3231 Sync

Aby zsynchronizować czas z komputera za pomocą programu przed połączeniem należy upewnić się, że na danym porcie szeregowym nie jest uruchomiony np. Monitor portu szeregowego Arduino.

Wyświetlenie godziny na ekranie

Nadszedł czas na dodanie funkcji wyświetlania daty i godziny na ekranie. W tym celu należy bezpośrednio pod blokiem operacji #include dodać linijkę

#include <DS3231.h>

a pod inicjalizacją zmiennej lcd utworzyć obiekt klasy DS3231, do którego później będziemy się odwoływali w celu pobrania danych

DS3231 zegar;

Teraz w głównej pętli programu, która będzie się wykonywała cyklicznie zaraz po tym jak program zakończy działanie funkcji setup(). Mowa tu o funkcji loop(). Możemy wpisać do niej następującą zawartość:

void loop() {
  displayDateTime();
  delay(1000);
}

Jak widać to jedyne co ta funkcja robi, to wywołuje displayDateTime() oraz zatrzymuje działanie programu na 1000 ms. Nie mamy jednak funkcji displayDateTime(). Skąd ją wziąć? Trzeba sobie napisać :)

void displayDateTime() {
  lcd.clear();
  lcd.print(getDateString());
  lcd.setCursor(0, 1);
  lcd.print(getTimeString());
}

Funkcja ta czyści ekran, wypisuje ciąg znaków z datą, ustawia kursor na pierwszym znaku drugiej linijki i wypisuje ciąg znaków z godziną. Należy teraz zaimplementować kolejne dwie funkcje (getDateString() oraz getTimeString()).

String getDateString() {
  bool century;
  
  String day = toStringWithLeadingZeros(clock.getDate());
  String month = toStringWithLeadingZeros(clock.getMonth(century));
  String year = String(clock.getYear() + 2000);

  return day + "." + month + "." + year + "r.";
}

String getTimeString() {
  bool h12, pm;
  
  String hour = toStringWithLeadingZeros(clock.getHour(h12, pm));
  String minute = toStringWithLeadingZeros(clock.getMinute());
  String second = toStringWithLeadingZeros(clock.getSecond());

  return hour + ":" + minute + ":" + second;
}

Jak widać są one do siebie bardzo podobne. Na początku każdej z nich mamy zadeklarowane zmienne typu bool. Są one wymagane do podania jako parametry metod getMonth() oraz getHour() i pomimo, że nie będziemy ich do niczego używali musimy je umieścić w programie. Następnie za pomocą metod obiektu clock wyciągamy dane z zegara i przerabiamy je na ciągi znaków (Stringi). Po wszystkim zwracamy sformatowany ciąg znaków.

Pewnie zastanawiasz się co robi funkcja toStringWithLeadingZeros(). Otóż pobiera ona numer, a następnie przetwarza go na String oraz dopisuje wiodące zero, jeżli liczba jest mniejsza od 10.

String toStringWithLeadingZeros(byte number) {
  String result = String(number);
  if(number < 10) {
    result = "0" + result;
  }
  return result;
}

Tak napisany program możesz wgrać na Arduino i cieszyć się własnoręcznie zrobionym zegarem :)

Podsumowanie podstawowych metod biblioteki DS3231

Do odczytu danych używamy:

byte getSecond(); 
byte getMinute(); 
byte getHour(bool& h12, bool& PM);
byte getDoW(); 
byte getDate(); 
byte getMonth(bool& Century);
byte getYear();

Zmienne h12, PM, Century informują nas kolejno o tym, czy zegar liczy czas w trybie 12 godzinnym, czy aktualna godzina jest godziną popołudniową oraz czy nasz zegar przeszedł od ostatniego ustawiania z roku 99 do 00. Zmienne te są przekazywane przez tzw. referencję czyli jeżeli podamy je jako parametr funkcji to po wykonaniu jej mogą one zmienić swoją wartość (czyli funkcja ta może modyfikować zmienne które są do niej przekazywane).

Natomiast do zapisu:

void setSecond(byte Second); 
void setMinute(byte Minute); 
void setHour(byte Hour); 
void setDoW(byte DoW); 
void setDate(byte Date); 
void setMonth(byte Month); 
void setYear(byte Year); 
void setClockMode(bool h12);

Kod całości do pobrania: Zegar_z_Arduino.

Kod został sprawdzony w lipcu 2023. Kompilacja w Arduino IDE 2.1.1, użyte biblioteki:

  • [Wire@1.0]
  • [LiquidCrystal_PCF8574@2.2.0]
  • [DS3231@1.1.2]