Kolejny zegar czasu rzeczywistego: podłączenie ds1307 do Arduino – i kilka słów o i2c

W poprzednim poście  podłączałem do Arduino zegar PCF8563. Ale PCF8563 to nie jedyny układ, który możecie użyć jako odniesienie czasu. DS1307 to inny popularny czip tego typu. I podobnie jak pcf – podłącza się go do szyny i2c. Przyjrzymy się ds1307 – oraz bibliotekom umożliwiającym korzystanie z niego.

Piny

Rozmieszczenie pinów masy (GND) i zasilania (Vcc) jest identyczne jak dla PCF. DS1307 wymaga napięcia zasilania w granicach 4.5-5.5v. Jest to znacznie węższy zakres niż PCF, który działa nawet przy 1v.

Podobnie jak dla PCF, piny 5 i 6 to SDA/SCL szyny i2c. Linie tych pinów muszą być „podciągnięte” – podłączone rezystorami (np. 10kΩ) pod zasilanie. I tu uwaga: za pomocą i2c można łączyć układy o różnych poziomach napięcia. Jeżeli łączycie Arduino (5v) z Raspberry (3.3v) – podciągnijcie SDA i SCL do linii zasilania o niższym napięciu. Wtedy nie musicie używać żadnych konwerterów. Ale: to zadziała jedynie dla układów, których poziomy logicznej „1” zazębiają się. Arduino „zrozumie” stan wysoki z Raspberry jako stan wysoki. Jeżeli jednak napięcie jednego z układów będzie zbyt różne (np. micro:bit zasilany przez 2v), układy nie „dogadają” się, ponieważ logiczna „1” układu o niższym napięciu nie będzie rozpoznana przez urządzenie pracujące na wyższym napięciu.

Wtedy konieczna będzie konwersja sygnałów do odpowiednich poziomów. W przypadku SDA zachodzi komunikacja 2-kierunkowa, więc zwykły dzielnik napięcia nie może być zastosowany. SCL jest pinem wejściowym (jednokierunkowa komunikacja). Możecie użyć specjalny konwerter, na przykład taki jak tutaj:

Piny 1 i 2 służą do podłączenia kwarcu (32kHz).

W odróżnieniu do PCF, ds1307 ma specjalny pin Vbat służący do podłączenia dodatkowego zasilania. W tej roli producent zaleca baterie cr2032. Plus baterii podłącza się do VBat a minus – do masy.

Ciekawostką jest wbudowane 56 bajtów pamięci. Możecie ją użyć do zapisywania dodatkowych danych. Zawartość tej pamięci zniknie po odłączeniu zasilania.

Oprogramowanie

Pod Arduino, do obsługi DS1307 najłatwiej użyć bibliotekę RTCLib dostarczanej przez Adafruit. Poniżej znajdziecie kilka przydatnych funkcji:

 Funkcja  Opis Przykładowy kod
RTC_DS1307 rtc;  Stwórz obiekt zegara
begin()  Inicjalizacja zegara, zwraca true jeżeli wszystko w porządku – a false, jeżeli błąd inicjalizacji,   if( !rtc.begin()){
//…zegar nie dziala
}
isrunning() Status zegara, jeżeli 0 – zegar zatrzymany, 1: działa  if( !rtc.isrunning()){
//…zegar nie działa
}
adjust(DateTime(rok, miesiąc,dzień,godzina, minuta,sekunda)  Ustawia datę i czas; uruchamia zegar (CH=0)  // January 21, 2014, 3:00
rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
now(); Pobiera czas z czipu   DateTime now = rtc.now();
readnvram() i writenvram()  Odczyt i zapis pamięci nvram
writeSqwPinMode() Ustawienie przebiegu prostokątnego na pinie SQW/OUT rtc.writeSqwPinMode(SquareWave1HZ);
rtc.writeSqwPinMode(ON);
rtc.writeSqwPinMode(OFF);gdzie: SquareWave4kHz (4kHz), SquareWave8kHz (8kHz) i SquareWave32kHz (32kHz).

Zwróćcie uwagę na funkcję isrunning(). Funkcja ta zwraca „0” jeżeli zegar nie jest uruchomiony – na przykład tuż po włączeniu zasilania czipu. Jeżeli isrunning() zwraca zero, powinniście ustawić czas za pomocą funkcji adjust(). Inaczej zegar nie zacznie odliczania.

Dokładniej, isrunning() czyta bit CH (clock halt) czipu. Bit ten musi być wyzerowany, żeby zegar wystartował. Więcej o tym bicie znajdziecie w dalszej części tekstu.

Podstawowy przykład zawierający procedury ustawiania i odczytania daty znajdziecie w Plik->Przykłady->RTClib->ds1307. Uprośćmy go, na potrzeby nauki:

Po kolei:

    • Dodajcie bibliotekę „Wire” i „RTClib”,
    • Stwórzcie obiekt zegara:
    • W setup() zainicjalizujcie zegar:
    • …i ustawcie datę, jeżeli zegar dopiero włączono:
  • W pętli „loop()”, pobierzcie czas z układu:
  • …i wyświetlcie go na konsoli.

Efekty specjalne

Układ można skonfigurować tak, żeby na wyjściu SQW/OUT generował przebieg prostokątny. Jeżeli:

  • funkcją „rtc.writeSqwPinMode(SquareWave1HZ);” ustawicie 1Hz na wyjściu SQW/OUT (czyli dwie zmiany na sekundę):
  • podłączycie do tego pinu diodę,
  • drugi koniec diody podłączycie przez rezystor (np. 100Ω) do „+”…

…będziecie mieli migającą co 1 sekundę diodę:)

A teraz trochę inaczej…

Powyższe informacje wystarczą, żeby podłączyć i obsłużyć układ ds1307. Tym razem jednak… chciałem Wam pokazać coś więcej:) Na podstawie jednej funkcji isrunning() „odczaruję” trochę kodu dla Arduino. Ale nie tego, który wpisujecie w szkicu. Tego, który szkic wywołuje, udostępnionego jako biblioteka.

Skoncentrujmy się na niewielkim fragmencie szkicu:

Wywołanie to sprawdza, czy RTC działa. Jeżeli układ dopiero zasilono wartość isrunning() będzie równa 0. Po ustawieniu daty (wywołanie adjust()), isrunning() zwróci 1.

Spójrzcie do instrukcji:

Bit 7 of Register 0 is the clock halt (CH) bit. When this bit is set to 1, the oscillator is disabled. When cleared to 0, the oscillator is enabled. On first application of power to the device the time and date registers are typically reset to 01/01/00 01 00:00:00 (MM/DD/YY DOW HH:MM:SS). The CH bit in the seconds register will be set to a 1.

Mapa rejestrów wygląda tak:

Bit 7 CH (clock halt) znajduje się na adresie 00h (‚h’ oznacza liczbę heksadecymalną – w odróżnieniu do dziesiętnych, dla „0” nie ma to znaczenia).  Po włączeniu zasilania, ustawiony jest na wartość 1 z domyślną datą i czasem. Dopóki CH ustawione jest na „1”, zegar nie działa. Dopiero po ustawieniu CH na wartość ‚0’, zegar zacznie tykać.

Teraz spójrzmy ma kod isrunning() w bibliotece RTClib. Znajdziecie go w pliku RTClib.cpp:

Biblioteka RTClib znajduje znajduje się w podkatalogu wskazanym przez konfigurację Arduino IDE. Możecie to sprawdzić w okienku  Plik/Ustawienia w polu: „Lokalizacja szkicowników”.

Definicję DS1307_ADDRESS znajdziecie w pliku RTClib.h;

0x68 to adres ds1307 na szynie i2c. (0x oznacza wartość heksadecymalną, 0x68 to dziesiętnie 6*16 + 8 = 104). Adres ten jest charakterystyczny dla tego czipu , czyli każdy ds1307 będzie miał taki sam adres.

Ale: niektóre układy mogą mieć taki sam adres i2c, zob. lista urządzeń i2c). W artykule o pc8563 znajdziecie kod do skanowania urządzeń i2c. Kod ten „odpytuje” pewien zakres adresów. Jeżeli jakieś urządzenie na odpowie – adres jest wypisywany na konsoli.

beginTransmission() i endTransmission() są funkcjami (dokładniej: metodami klasy) z biblioteki Wire. Bibliotekę Wire znajdziecie w katalogu instalacyjnym Arduino IDE, podkatalog: ./hardware/arduino/avr/libraries/Wire/src. Funkcje te przygotowują i kończą zapis do urządzenia i2c o podanym adresie.

beginTransmission() inicjuje wysyłanie do urządzenia o podanym adresie (samo nic nie wysyła). Dane do zapisania dodawane są przez funkcję _I2C_WRITE. Tutaj zapisana będzie jedna wartość: „0”. Paczka danych wysyłana jest po wywołaniu endTransmission().

Zwróćcie uwagę na rysunek 4 (figure 4), który opisuje transmisję od mastera (np. Arduino) do slave (ds1307). Po kolei wysyłane są:

  • Adres „slave” – czyli ds1307 (0x68),
  • Adres pamięci, którą master jest zainteresowany,
  • Dodatkowe dane.

Gdy master (czyli np. Arduino) zapisuje na i2c adres pamięci „0”, informuje ds1307, że chodzi mu o tą część pamięci, gdzie znajduje się bit CH oraz sekundy (ustawia wskaźnik na odpowiedni adres pamięci). Gdybym wysłał „1”:

… ustawiłbym wskaźnik na rejestr minut. „8” pozwoli na zapianie pamięci – wtedy może podać nawet 56 bajtów danych (Data(n)…Data(n+X)).

Spójrzmy dalej:

Funkcja requestFrom odczytuje z ds1307 kolejne bajty pamięci (tutaj tylko jeden) – zaczynając od adresu poprzednio wskazanego przez ‚_I2C_WRITE’.  Zgodnie z mapą pamięci, czip powinien teraz zwrócić 8 bitów zawierających pola CH oraz sekundowe. Tuż po uruchomieniu zasilania, czip zwróci „128”, która binarnie wygląda tak:
1000 0000.

Jedyna ‚1’ to właśnie 8 bit CH. Najłatwiej będzie go „odzyskać” przesuwają wartość w prawo o 7 miejsc, do czego służy operator „>>”:
(1000 0000) >> 1 = 0100 0000
(0100 0000) >> 1 = 0010 0000
(0010 0000) >> 1 = 0001 0000
(0001 0000) >> 1 = 0000 1000
(0000 1000) >> 1 = 0000 0100
(0000 0100) >> 1 = 0000 0010
(0000 0010) >> 1 = 0000 0001

W ten sposób dziesiątkowo otrzymamy wartość „1”. „1” oznacza, że zegar nie działa. Ponieważ funkcja nazywa się „isrunning” – czyli „czy pracuje” – w ostatnim kroku „1” jest „odwracana”. Funkcja zwraca „0” gdy CH jest ustawione na „1”, czyli zegar nie działa. Jeżeli CH jest ustawione na zero, wyjściem z „isrunning” jest „1” – zegar działa.

Podobną analizę można przeprowadzić dla każdej kolejnej funkcji biblioteki RTClib. Jak sami widzicie, jeżeli nie rozumiecie kodu – warto przeglądnąć dokumentację czipu.

Źródła

Dodaj komentarz