[122] Zegar z Frankfurtu czyli DCF77 cz. 2

[122] Zegar z Frankfurtu czyli DCF77 cz. 2

Wrócę jeszcze do sprzętu: jak widać, urządzenie składa się z małej płytki z elektroniką i zwykle odseparowanej na odległość kilkunastu centymetrów anteny, którą należy umieścić na pewnym dystansie, dającym odstęp od zakłóceń, które zawsze pojawiają się, gdy mamy do czynienia ze sprzętem cyfrowym. Z anteną jest związany jeszcze jeden problem: jest silnie kierunkowa. Aby uzyskać w miarę pewny odbiór, trzeba ją ustawić prostopadle do nadajnika. W południowej Polsce pręt powinien pokazywać południe i północ, w północnej należy go nieco odchylić na oś północny zachód – południowy wschód. A najlepiej po prostu skorzystać z mapy i zobaczyć gdzie znajduje się Frankfurt. O tych sprawach często się zapomina i potem zegar nie chce się ustawić przez pół dnia.


Płytki będą wyglądać różnie, ale zwykle znajdziemy na nich cztery wyprowadzenia: zasilanie, masę, wyjście danych i wejście usypiające, które zwykle jest nieaktywne, gdy podłączymy je do masy. Tak więc w zasadzie potrzebujemy trzech przewodów, które jak zwykle podłączę do płytki edukacyjnej TME. Należy także sprawdzić napięcie pracy modułu. Możemy założyć, że te wyciągnięte z bateryjnych stacji pogody będą się zadowalać trzema woltami, więc najbezpieczniej podłączyć je do stabilizatora tego napięcia, dostępnego na większości płytek Arduino. Dodam jeszcze, że siedząc z dala od okna musiałem przedłużyć przewody łączące płytkę z Arduino do dwóch metrów, by móc odbierać powtarzalne przekazy. Komputer i zasilacze impulsowe emitowały zbyt wiele zakłóceń.

Zanim przejdziemy do szkicu, można jeszcze obejrzeć sobie jakość impulsów za pomocą prezentowanego przeze mnie ostatnio software’owego oscyloskopu. Kiepski sygnał da nieregularne trzaski, które nie przenoszą żadnej informacji. Prawidłowy winien ukazać regularne impulsy wpadające co sekundę.

Przejdźmy do programu. Znalazłem co najmniej cztery biblioteki dekodujące ten nieskomplikowany przekaz, lecz żadna nie zadowalała mnie do końca. Już miałem napisać taką samemu, ale postanowiłem powalczyć z najbardziej obiecującą, choć napisaną niechlujnie i trochę niezgodnie z zasadami Arduino. Ponieważ jednak będziemy się spotykać z takimi sytuacjami, przeanalizujmy rzecz.

Tym razem oprócz pliku ze szkicem istotne będą też pliki biblioteki, a konkretnie trzy widoczne tutaj. Autor przeniósł tam bowiem kilka elementów, których nie damy wyłączyć w samym szkicu – między innymi echo wysyłanych komunikatów na monitor. Jeśli z jakiegoś powodu nie chcielibyśmy zbyt dużego ruchu na łączu szeregowym, komendy serial.print należy zamienić na komentarze. Narzucono tam także nieco niekonsekwentne nazewnictwo elementów. Postanowiłem je zmienić według stałego klucza i spolszczyłem przy okazji. I tutaj moja uwaga: polskie nazwy zmiennych nie służą tylko temu, by po prostu nazwać je w naszym języku, ale wyraźnie oddzielić od tego, co zmienną nie jest. Starym wyjadaczom to niepotrzebne, ale dla początkujących może być przydatne.

#include <basic_dcf77.h>                          // Biblioteka obsługi modułu DCF77.
#include <DebugProject.h>                         // Biblioteka obsługi modułu DCF77.
byte tablica[59];                                 // Deklaruj tablicę dla ramki.
daneDCF77 czas;                                   // Definicja obiektu zawierającego dane z modułu DCF77.
int odbiornikDCF77;                               // Status odbiornika.
const char dniTygodnia[] = { "PnWtSrCzPtSoNi" };  // Tablica z dniami tygodnia.

void setup() {
  Serial.begin(115200);  // Inicjuj monitor.
  wejscieDCF77(12);      // Pin czytający dane z DCF77.
  Serial.println();
  Serial.println("Inicjuje modul DCF77, czekaj 7 sekund.");
  delay(7000);  // Opóźnienie dla ustabilizowania się odbiornika.
}

void loop() {
  Serial.println("Oczekuje poczatku ramki z odbiornika DCF77, czekaj do 60 sekund.");
  odbiornikDCF77 = odebraneDCF77(tablica, 59);  // Ładuj dane z odbiornika DCF77

  if (odbiornikDCF77 == poprawnyOdbior) {                      // Jeśli dane są poprawne...
    if (dekodujDCF77(tablica, 59, &czas) == poprawnyOdbior) {  // także po zdekodowaniu...

      Serial.println();

      Serial.print("20");      // Pierwsza para cyfr roku, protokół nie przenosi setek i tysięcy lat.
      Serial.print(czas.rok);  // Druga para cyfr roku.
      Serial.print("-");

      if (czas.miesiac < 10) { Serial.print("0"); }  // Jeśli liczba miesięcy jest mniejsza od 10, dopisz wiodące zero.
      Serial.print(czas.miesiac);                    // Miesiąc.
      Serial.print("-");

      if (czas.dzien < 10) { Serial.print("0"); }
      Serial.print(czas.dzien);  // Dzień.
      Serial.print(" ");

      Serial.print(dniTygodnia[2 * czas.tydzien - 2]);  // Wzorzec dnia tygodnia (pierwsza litera).
      Serial.print(dniTygodnia[2 * czas.tydzien - 1]);  // Wzorzec dnia tygodnia (druga litera).
      Serial.print(" ");

      if (czas.godzina < 10) { Serial.print("0"); }
      Serial.print(czas.godzina);  // Godzina.
      Serial.print(":");

      if (czas.minuta < 10) { Serial.print("0"); }
      Serial.println(czas.minuta);  //Minuta.

      if (czas.transmisja != poprawnyOdbior) {  // Jeśli znaleziono błędy w transmisji...
        Serial.println("Sygnal jest zbyt slaby, odczyt bitow jest niemozliwy.");
      } else if (czas.zmianaCzasu) {  // Jeśli ma nastąpić zmiana czasu...
        Serial.println("Wkrótce nastąpi zmiana czasu.");
      }
    } else Serial.println("Sygnal jest niestabilny, pakiet zostal stracony.");
  } else if (odbiornikDCF77 == przekroczonoCzas) {  // Jeśli bity nie zostały wykryte...
    Serial.println("Sygnal jest zbyt slaby, przekroczono tolerowany czas oczekiwana.");
  }
}

Tak więc na początku zaczytujemy dwie biblioteki. Deklarujemy także tablicę o długości 59 bitów, którą będziemy wypełniać podczas kompletowania ramki. Definiujemy obiekt zawierający dane czasowe i status odbiornika. W końcu zadeklarowałem tablicę z nazwami dni tygodnia, bo standard zwraca je jako cyfry od jednego do siedmiu, zaczynając od poniedziałku. Na początku inicjujemy transmisję, deklarujemy pin, na który będą wchodzić dane i wysyłamy sobie komunikat powitalny.

W nieskończonej pętli będziemy czekać na początek transmisji, czyli brak impulsu, po czym kolejne 59 bitów będzie interpretowanych jako zera albo jedynki i będą wypełniać tablicę. Tutaj autor uczynił pewną pomoc: każdy nadchodzący bit do czasu synchronizacji będzie wysyłał kropkę, a te po zidentyfikowaniu początku transmisji od razu będą widoczne jako jedynki albo zera. Jest to przydatne podczas testów i pomaga ustawić antenę.

Gdy status odbiornika wykaże poprawne odebranie ramki, będziemy mogli wyciągać z tablicy kolejne elementy: od roku do minuty. Setki i tysiące lat nie są kodowane, więc trzeba dopisać dwudziestkę. Liczby mniejsze od dziesięciu wypada sformatować, dokładając zero wiodące, by sąsiednie wyniki nie przesuwały się nieładnie. Autor dodał jeszcze szereg wskaźników słabego odbioru, niekompletnej ramki czy zmiany czasu, która ma nastąpić w ciągu godziny. To wszystko możemy wykorzystać albo zignorować. Skompilujmy szkic i przyjrzyjmy się monitorowi.

Na początku trzeba czekać na synchronizację. Każda kropka to sekunda i zarazem zignorowany bit. Gdy braknie bitu, Arduino postanowi zacząć zbierać kolejne bity do tablicy. Po 59 bicie całość została obrobiona i wyświetlona w ludzkim już języku – i od początku. W kolejnych transmisjach postanowiłem poprzeszkadzać nieco i bity pogubiły się. Dostaliśmy stosowny komunikat i próba synchronizacji pojawiła się ponownie.

Gdy sygnał jest kiepski, bity są wykrywane, ale nie wszystkie, a w szczególności brakuje określenia jednoznacznego początku transmisji. Kropek przybywa w nieskończoność. Wystarczy jednak znowu przestawić antenę, by wszystko wróciło do normy.

#include <basic_dcf77.h>                          // Biblioteka obsługi modułu DCF77.
#include <DebugProject.h>                         // Biblioteka obsługi modułu DCF77.
byte tablica[59];                                 // Deklaruj tablicę dla ramki.
daneDCF77 czas;                                   // Definicja obiektu zawierającego dane z modułu DCF77.
int odbiornikDCF77;                               // Status odbiornika.
const char dniTygodnia[] = { "PnWtSrCzPtSoNi" };  // Tablica z dniami tygodnia.

#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze HD44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD

void setup() {
  Serial.begin(115200);  // Inicjuj monitor.
  wejscieDCF77(12);      // Pin czytający dane z DCF77.
  Serial.println();
  Serial.println("Inicjuje modul DCF77, czekaj 7 sekund.");
  lcd.begin(16, 2);     // Inicjalizacja wyświetlacza LCD
  lcd.setCursor(0, 0);  // Ustaw kursor.
  lcd.print("     DCF77      ");
  lcd.setCursor(0, 1);
  lcd.print("    Czekaj...   ");
  delay(7000);  // Opóźnienie dla ustabilizowania się odbiornika.
}

void loop() {
  Serial.println("Oczekuje poczatku ramki z odbiornika DCF77, czekaj do 60 sekund.");
  odbiornikDCF77 = odebraneDCF77(tablica, 59);  // Ładuj dane z odbiornika DCF77

  if (odbiornikDCF77 == poprawnyOdbior) {                      // Jeśli dane są poprawne...
    if (dekodujDCF77(tablica, 59, &czas) == poprawnyOdbior) {  // także po zdekodowaniu...

      Serial.println();
      lcd.setCursor(0, 0);

      Serial.print("20");  // Pierwsza para cyfr roku, protokół nie przenosi setek i tysięcy lat.
      lcd.print(" 20");
      Serial.print(czas.rok);  // Druga para cyfr roku.
      lcd.print(czas.rok);
      Serial.print("-");
      lcd.print("-");

      if (czas.miesiac < 10) {  // Jeśli liczba miesięcy jest mniejsza od 10, dopisz wiodące zero.
        Serial.print("0");
        lcd.print("0");
      }
      Serial.print(czas.miesiac);  // Miesiąc.
      lcd.print(czas.miesiac);
      Serial.print("-");
      lcd.print("-");

      if (czas.dzien < 10) {
        Serial.print("0");
        lcd.print("0");
      }
      Serial.print(czas.dzien);  // Dzień.
      lcd.print(czas.dzien);
      Serial.print(" ");
      lcd.print("  ");

      Serial.print(dniTygodnia[2 * czas.tydzien - 2]);  // Wzorzec dnia tygodnia (pierwsza litera).
      Serial.print(dniTygodnia[2 * czas.tydzien - 1]);  // Wzorzec dnia tygodnia (druga litera).
      lcd.print(dniTygodnia[2 * czas.tydzien - 2]);
      lcd.print(dniTygodnia[2 * czas.tydzien - 1]);
      Serial.print(" ");
      lcd.print(" ");
      lcd.setCursor(0, 1);
      lcd.print("      ");

      if (czas.godzina < 10) {
        Serial.print("0");
        lcd.print("0");
      }
      Serial.print(czas.godzina);  // Godzina.
      lcd.print(czas.godzina);
      Serial.print(":");
      lcd.print(":");

      if (czas.minuta < 10) {
        Serial.print("0");
        lcd.print("0");
      }
      Serial.println(czas.minuta);  //Minuta.
      lcd.print(czas.minuta);       //Minuta.
      lcd.print("     ");

      if (czas.transmisja != poprawnyOdbior) {  // Jeśli znaleziono błędy w transmisji...
        Serial.println("Sygnal jest zbyt slaby, odczyt bitow jest niemozliwy.");
      } else if (czas.zmianaCzasu) {  // Jeśli ma nastąpić zmiana czasu...
        Serial.println("Wkrótce nastąpi zmiana czasu.");
      }
    } else Serial.println("Sygnal jest niestabilny, pakiet zostal stracony.");
  } else if (odbiornikDCF77 == przekroczonoCzas) {  // Jeśli bity nie zostały wykryte...
    Serial.println("Sygnal jest zbyt slaby, przekroczono tolerowany czas oczekiwana.");
  }
}

Na koniec – praca samodzielna. Jak to było w przypadku zegara GPS, skorzystałem z obecności wyświetlacza na mojej płytce TME i przekierowałem tam odczyty, a raczej je zdublowałem. Nie wszystkie, a tylko wyświetlanie czasu, więc pozostawiłem oryginalne strumienie danych słanych na monitor. Nie będę omawiał tego szkicu, bo nic tu odkrywczego nie znajdziemy. Po szczegóły zapraszam do artykułu o GPS-ie.

Jak pisałem, nie powinniśmy wprost słać danych na wyjście użytkownika, a synchronizować wewnętrzny zegar, który nie musi być tak dokładny. Trzeba pamiętać, że każdy brak odbioru to minuta spóźnienia wskazań, więc te winny pochodzić z zegara lokalnego, a sygnał DCF powinien ten zegar regulować od czasu do czasu i tylko wówczas, gdy sam będzie poprawnie zdekodowany.

Powiązane tematy

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2Sprawdź tutaj

Przeczytaj również

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2025 arduino.pl