Quelltext des Programms
Blockschaltbild
Details
/**
* Arduino DCF77-Decodierung und Anzeige mit RTC
*
* S. Taborek
* 14.10.2019 - 25.10.2019
* 10.12.2019 . 12.12.2019
*  
* D=Deutschland C=Langwellensender F=Nähe Frankfurt 77,5KHz
*
* Programmierport am MAC: /dev/cu.usbserial-14340
*/

// Wireing:
// LCD Pin  1: Masse
// LCD Pin  2: +5V
// LCD Pin  3: + Kontrast
// LCD Pin  4: RS
// LCD Pin  5: RW
// LCD Pin  6: Enable
// LCD Pin ..:
// LCD Pin 14: D4
// LCD Pin 15: D5
// LCD Pin 16: D6
// LCD Pin 17: D7
// LCD Pin 18: + Hintergrundlicht 3..5V
// LCD Pin 19: -       "

#define DCFPIN_Rise 2    //  DCF Sekundenpuls 
#define DCFPIN_Fall 3
#define BlinkPIN 13      //  Led Sek-Takt
#define SommerPIN 8      //  Led Sommerzeit
#define Plus1SecPIN 9    //  Led Schaltsekunde ankündigen


/**
* DCF Telegrammstruktur
*/
/*.......................................................*/
//  sonstige 15;
  const byte ResAnt       =  15; // Reserve-Antenne
  const byte ZonPre       =  16; // Ankündigung Zonenzeit
  const byte ZoneTime1    =  17; // Zonenzeit 1=Sommerzeit
  const byte ZoneTime2    =  18;
  const byte InsertSec    =  19; // Ankündigung Schaltsekunde
  const byte StartBit     =  20; // Start-Bit immer 1
  const byte mi_Bit1      =  21; // Minute  Wert 1
//const byte mi_Bit2      =  22; // Minute       2
//const byte mi_Bit3      =  23; // Minute       4
//const byte mi_Bit4      =  24; // Minute       8
//const byte mi_Bit5      =  25; // Minute      10
//const byte mi_Bit6      =  26; // Minute      20
  const byte mi_Bit7      =  27; // Minute      40
  const byte ParityBit_mi =  28; // Minute Prüfbit
  const byte hh_Bit1      =  29; // Stunde  Wert 1
//const byte hh_Bit2      =  30; // Stunde       2
//const byte hh_Bit3      =  31; // Stunde       4
//const byte hh_Bit4      =  32; // Stunde       8
//const byte hh_Bit5      =  33; // Stunde      10
  const byte hh_Bit6      =  34; // Stunde      20
  const byte ParityBit_hh =  35; // Stunde Prüfbit
  const byte dd_Bit1      =  36; // Kalendertag  1
//const byte dd_Bit2      =  37; // Kalendertag  2
//const byte dd_Bit3      =  38; // Kalendertag  4
//const byte dd_Bit4      =  39; // Kalendertag  8
//const byte dd_Bit5      =  40; // Kalendertag 10
  const byte dd_Bit6      =  41; // Kalendertag 20
  const byte wd_Bit1      =  42; // Wochentag    1
  const byte wd_Bit2      =  43; // Wochentag    2
  const byte wd_Bit3      =  44; // Wochentag    4
  const byte mm_Bit1      =  45; // Monat        1
//const byte mm_Bit2      =  46; // Monat        2
//const byte mm_Bit3      =  47; // Monat        4
//const byte mm_Bit4      =  48; // Monat        8
  const byte mm_Bit5      =  49; // Monat       10
  const byte yy_Bit1      =  50; // Jahr         1
//const byte yy_Bit2      =  51; // Jahr         2
//const byte yy_Bit3      =  52; // Jahr         4
//const byte yy_Bit4      =  53; // Jahr         8
//const byte yy_Bit5      =  54; // Jahr        10
//const byte yy_Bit6      =  55; // Jahr        20
//const byte yy_Bit7      =  56; // Jahr        40
  const byte yy_Bit8      =  57; // Jahr        80
  const byte ParityBit_dt =  58; // Prüfbit Kalenderdaten
  const byte Synchron     =  59; //
/*.......................................................*/


/**
* Variablen für DCF
*/
  byte rxBuffer[62];
  volatile byte rxPtr;        // Zeiger auf Puffer-Position

  volatile unsigned char DCF_Bit;
  volatile unsigned long t1;
  volatile unsigned long t2;
  volatile long diff1;
  volatile long diff2;
  volatile bool Telegramm_Ende = false;
  volatile bool Telegramm_Decode = false;
  volatile bool Timer_DCF_synchron;
  volatile unsigned long errPulsCnt;


/**
* Variablen für die Zeit-Information
*/

struct  // DCF
{
  uint8_t sec;
  uint8_t mi;
  uint8_t hh;
  uint8_t dd;
  uint8_t mm;
  uint8_t yy;
  uint8_t wd;
  long ok_cnt;
} DCF;


struct  // DCFprev
{
  uint8_t sec;
  uint8_t mi;
  uint8_t hh;
  uint8_t dd;
  uint8_t mm;
  uint8_t yy;
  uint8_t wd;
} DCFprev;


struct  // Zeit
{
  uint8_t sec;
  uint8_t mi;
  uint8_t hh;
  uint8_t dd;
  uint8_t mm;
  uint8_t yy;
  uint8_t wd;
} aktuell;


struct  // Uhr
{
  uint8_t sec;
  uint8_t mi;
  uint8_t hh;
  uint8_t sec_prev;
} iUhr;




struct  // RTC 1307
{
  uint8_t sec;
  uint8_t mi;
  uint8_t hh;
  uint8_t dd;
  uint8_t mm;
  uint8_t yy;
  uint8_t wd;
  bool period;  // pm  am
  bool syncronisiert;
  bool used;
  String wdStr = "MoDiMiDoFrSbSo";
} RTC;


  volatile uint8_t mm_p, wd_p, dd_p, hh_p, mi_p, dd_prev_sync;
  volatile uint16_t yy_p;

  String s1;
  String s2;
  String s3;

  const byte time_zl = 1;
  const byte time_sp = 0;
  const byte date_zl = 0;
  const byte date_sp = 0;



#include <Wire.h>
#include <MsTimer2.h>
#include "RTCDS1307.h"
#include <LiquidCrystal.h>
 
  LiquidCrystal lcd(12, 11, 6, 5, 4, 7);

  RTCDS1307 rtc(0x68);  //



/**
* Initialisieren der DCF77 Interrupt-Routinen
*/
/*---------------------------------------------------------*/
void DCF_Init()
/*---------------------------------------------------------*/
{
  rxPtr = 0;
  Telegramm_Ende = false;
  Timer_DCF_synchron = false;
  //
  pinMode(DCFPIN_Rise, INPUT);
  pinMode(DCFPIN_Fall, INPUT);
  // beide ISR-Pins sind parallel geschaltet:
  attachInterrupt(digitalPinToInterrupt(DCFPIN_Rise), ISR_RisingEdge, RISING);
  attachInterrupt(digitalPinToInterrupt(DCFPIN_Fall), ISR_FallingEdge, FALLING);
}



/*---------------------------------------------------------*/
void RTC_ReadTime()
/*---------------------------------------------------------*/
{
  rtc.getTime(RTC.hh, RTC.mi, RTC.sec, RTC.period);
  rtc.getDate(RTC.yy, RTC.mm, RTC.dd, RTC.wd);
  RTC.wd = RTC.wd - 1;                  // Angleichung an DCF
  iUhr.sec = RTC.sec;
  aktuell.hh = RTC.hh; aktuell.mi = RTC.mi;
  aktuell.yy = RTC.yy; aktuell.mm = RTC.mm;
  aktuell.dd = RTC.dd; aktuell.wd = RTC.wd;
}


/*---------------------------------------------------------*/
void RTC_WriteTime()
/*---------------------------------------------------------*/
{
  RTC.yy = aktuell.yy; RTC.mm = aktuell.mm; RTC.dd = aktuell.dd;
  RTC.hh = aktuell.hh; RTC.mi = aktuell.mi; RTC.sec = 0;
  rtc.setTime(RTC.hh, RTC.mi, RTC.sec);
  rtc.setDate(RTC.yy, RTC.mm, RTC.dd);
}


/*---------------------------------------------------------*/
void Timer_Setup()
/*---------------------------------------------------------*/
{
  MsTimer2::stop();
  MsTimer2::set(1000, Timer_secTakt); // Sekundentakt
  MsTimer2::start();
  iUhr.sec = 0;
  Timer_DCF_synchron = true;
}


/*---------------------------------------------------------*/
void Timer_secTakt()
/*---------------------------------------------------------*/
{
  iUhr.sec++;
  if (iUhr.sec > 59){iUhr.sec = 0;}
}
   


/*---------------------------------------------------------*/
void RTC_Synchronisieren()
/*---------------------------------------------------------*/
{
  if (aktuell.dd != dd_prev_sync) // täglich synchronisieren
  {
    RTC.syncronisiert = false;
  }
  //
  if (RTC.syncronisiert == false)
  {
    if (DCF.ok_cnt > 2)
    {        
      DCF.ok_cnt = 0;
      RTC_WriteTime();
      RTC.syncronisiert = true;
      dd_prev_sync = aktuell.dd;
    }
  }  
}




/*---------------------------------------------------------*/
bool TelegrammVerifizieren()
/*---------------------------------------------------------*/
{
  // Verifizieren der Daten:
  // Vergleich mit Information der vorhergehenden Minute:
  // DCF_ok_cnt wird hochgezählt oder bei Fehler auf 0 gesetzt.
  // RTC stellen nur, wenn > 2 aufenanderfolgende Telegramme
  // korrekt empfangen worden sind.

  if ((DCF.yy < 19) || (DCF.yy > 31)) {return false;}
  if ((DCF.hh > 23) || (DCF.mi > 59)) {return false;}
  if ((DCF.dd < 1)  || (DCF.dd > 31)) {return false;}
  if ((DCF.mm < 1)  || (DCF.mm > 12)) {return false;}
  if ((DCF.mm == 2) && (DCF.dd > 29)) {return false;}
  //   
  // folgende Tests ergeben erst dann true, wenn auch die
  // vorangegangene Minute korrekt empfangen worden war.
  
  if (DCF.mi > 0) { if (DCF.mi != mi_p +1) {return false;};}
 
  if (DCF.mi == 0) { if (mi_p != 59) {return false;};}

  if (DCF.hh > 0) {if (DCF.hh != hh_p) {return false;};}

  if (DCF.mi > 0) { if (DCF.dd != dd_p) {return false;};}
 
  if (DCF.dd < 29) { if (DCF.mm != mm_p) {return false;};}
   
  return true;
}




/* Unterprogramm zum Umwandeln BCD nach Dezimal            */
/*---------------------------------------------------------*/
int bcdToDez(byte vonPos, byte bisPos, byte pariCtrl)
/*---------------------------------------------------------*/
/* von und bis bezieht sich auf den dcf_rx_buffer          */
{
  const byte decodeVal[8] = {1, 2, 4, 8, 10, 20, 40, 80};
  int wert = 0;
  int j = 0;
  byte dcfbit;
  volatile byte xorBit;
  //
  if (bitRead(pariCtrl, 1)) {xorBit = 0;}
  for (int i = vonPos; i <= bisPos; i++)
  {
    dcfbit = rxBuffer[i];
    wert = wert + dcfbit * decodeVal[j];
    if (dcfbit == 1) {xorBit = xorBit ^ 1;}
    j++;
  }
  // Paritätsbit auswerten:
//  if (bitRead(pariCtrl, 2)) {if (rxBuffer[bisPos+1] != xorBit) {wert = 255;};}
  return wert;
}   




/*---------------------------------------------------------*/
bool TelegrammDecodieren(void)
/*---------------------------------------------------------*/
/*
* Pufferinhalt überprüüfen und dekodieren
* Zeit-Variablen setzen
* Bit 1..14 Wetterdienst
* Bit 15=1 Reserveantenne sendet
* Bit 16=1 Ankündigung Sommerzeit (1h vorher)
* Bit 17=1 18=0 es ist Sommerzeit
* Bit 19=1 Ankündigung für 1 zusätzliche Sekunde:
// Pos: 21       29        36      42   45     50
//      mi       hh        dd      wd   mm     yy
// 1: 1001100:1  001010:0  011001  011  00001  10011000:1 = 14:19 Date: 26.10.19
// 1: 0000010:1  001010:0  011001  011  00001  10011000:1 = 14:20 Date: 26.10.19
{
  // errPulse sind in diesem Programm solche, die kürzer als
  // 75ms Low waren
  // sie entstehen nur bei gestörtem Empfang –
  // siehe ISR_RisingEdge()
 
  if (errPulsCnt == 0) 
  {
    // BCD-Zeitinformationen aus dem Puffer lesen:
    // Paritätsctrl: 3 : xorBit = 0 und Paritätsbit auswerten
    //               2 : xorBit x   und Paritätsbit auswerten
    //               1 : xorBit = 0 und nicht auswerten
    //               0 : -
    DCF.mi = bcdToDez(mi_Bit1, mi_Bit7, 3); // PariCtrl
    DCF.hh = bcdToDez(hh_Bit1, hh_Bit6, 3);
    //
    DCF.dd = bcdToDez(dd_Bit1, dd_Bit6, 1);
    DCF.wd = bcdToDez(wd_Bit1, wd_Bit3, 0);
    DCF.mm = bcdToDez(mm_Bit1, mm_Bit5, 0);
    DCF.yy = bcdToDez(yy_Bit1, yy_Bit8, 2);
    //..................................
    if (TelegrammVerifizieren() == true)
    {
      // DCF-Zeit übernehmen
      aktuell.mi = DCF.mi; aktuell.hh = DCF.hh;
      aktuell.wd = DCF.wd; aktuell.yy = DCF.yy;
      aktuell.mm = DCF.mm; aktuell.dd = DCF.dd;
      //
      DCFprev.mi = DCF.mi; DCFprev.hh = DCF.hh;
      DCFprev.dd = DCF.dd; DCFprev.mm = DCF.mm; 
      // yy wird hier nicht übergeben
      //
      DCF.ok_cnt = DCF.ok_cnt + 1;
      RTC.used = false;
     
      RTC_Synchronisieren();  // bedingt
   
      if (rxBuffer[ZoneTime1] == true)
      {digitalWrite(SommerPIN, LOW); }
      else {digitalWrite(SommerPIN, HIGH); }
      if (rxBuffer[ZoneTime2] == true)
      {digitalWrite(Plus1SecPIN, LOW); }
      else {digitalWrite(Plus1SecPIN, HIGH); }
    }
    else
    {RTC.used = true;}
  }
  else
  {RTC.used = true;}

  errPulsCnt = 0;  // rücksetzen für neue Minute
 
  // Vergleichsvariable zuweisen auch wenn Empfang gestört ist
  // darf nicht in einen obigen else-Zweig erfolgen
  yy_p = DCF.yy; mm_p = DCF.mm; dd_p = DCF.dd;
  hh_p = DCF.hh; mi_p = DCF.mi;
  //................... 
  if (RTC.used == true)
  {
    Timer_DCF_synchron = false;
    DCF.ok_cnt = 0;
    if (RTC.syncronisiert) {s1 ="  RTC";}  // Kennzeichnung
    else                   {s1 ="  RT-";}
  }
  else {s1 ="  DCF";}
 
  noInterrupts();
  lcd.setCursor(11, date_zl);
  lcd.print(s1);
  interrupts();            
  //   
}



/*---------------------------------------------------------*/
void Zeit_Anzeige()
/*---------------------------------------------------------*/
{
  byte i;
    s1 = "";
    // Uhrzeit anzeigen
    if (aktuell.hh < 10) { s1 = "0"; }
    s1 = s1 + String(aktuell.hh) + ":"; 
    //  
    if (aktuell.mi < 10) { s1 = s1 + "0";}
    s1 = s1 + String(aktuell.mi) + ":00"; 
    //
      // Datum anzeigen
      s2 = ""; 
      if (aktuell.dd < 10) { s2 = s2 + "0"; }
      s2 = s2 + String(aktuell.dd) + ".";
      
      if (aktuell.mm < 10) { s2 = s2 + "0"; }
      s2 = s2 + String(aktuell.mm) + "."; 
     
      word yyyy = aktuell.yy + 2000;
      s2 = s2 + String(yyyy) + "   ";
      //
      // Wochentag anzeigen
      s3  = "";
      if ((aktuell.wd < 8) && (aktuell.wd > 0)) 
      {
        i = 2 * (aktuell.wd - 1);
        s3 = RTC.wdStr.substring(i, i+2); // Wochentag
      }
    //
    noInterrupts();
      lcd.setCursor(time_sp, time_zl);
      lcd.print(s1);
      lcd.setCursor(13, time_zl);
      lcd.print(s3);
      //      
      lcd.setCursor(date_sp, date_zl);
      lcd.print(s2);  
    interrupts();
}



/**
* Das aktuelle Bit in den Puffer schreiben
* Der Pointer wird hochgezählt.
*/
/*---------------------------------------------------------*/
void RxBufferWrite(byte dcfBit)
/*---------------------------------------------------------*/
{
  DCF_Bit = dcfBit;
  //
  if (rxPtr > 58) {Telegramm_Ende = true;}
 
  if (Telegramm_Ende == true)    // 1. Bit startet neue Minute
  {
    // die 0. Sekunde kommt in der Regel mit rxPtr=59 
    // der Pointer wird erst hier korrigiert d.h. rxPtr=0
    rxPtr = 0;                                  // Sekunde 00
    Telegramm_Ende = false;
  }
  rxBuffer[rxPtr] = dcfBit;  // Eintrag in den Telegrammpuffer
  rxPtr++;
}


/*
* Interrupthandler für DCF_Pin bei steigender Flanke
* Beide ISR-Pins sind parallel angeschlossen
* Jede Sekunde beginnt mit fallender Flanke.
* Ermitteln, ob empfangenes Bit 1 oder 0 ist.
* Erkennung der 59. Sekunde
*/
/*---------------------------------------------------------*/
void ISR_RisingEdge()
/*---------------------------------------------------------*/
/* Das DCF_Pin erhält das Signal wie unten dargestellt.    */
/* diff = t1 - t2                                          */
/*          1             2         3           4          */
/*  _____    ________      ______    ________              */
/*       |  |        |    |      |  |        |             */
/*       |  |        |    |      |  |        |             */
/*       |__|        |____|      |__|        |__           */
/*       ^  ^        ^    ^      ^  ^        ^             */
/* Pegel             0    1      0  1        0             */
/*       t2 t1      t2   t1     t2 t1       t2             */
/* Diff            900  200    800  100    900             */
/*                                                         */
/* Absenkung liegt exakt am Beginn einer Sekunde           */
/* Start einer Minute beginnt mit Bit=0 (Low-Pegel)        */
{
  t1 = millis();  // Zeitmessung
  diff1 = t1 - t2;       
  /* wenn die Dauer des Pulses < 140ms ist, dann Low */
  if (diff1 > 75)
  {
    if (diff1 < 140) {RxBufferWrite(0);}
    else
    {if (diff1 < 250) {RxBufferWrite(1);}}
  }
  else {errPulsCnt++;}
  digitalWrite(BlinkPIN, LOW);                 
}

/*---------------------------------------------------------*/
void ISR_FallingEdge()
/*---------------------------------------------------------*/
/* Interrupt bei jedem fallende Pegel                      */
/* Das DCF_Pin erhält das Signal wie oben dargestellt.     */
{
  t2 = millis();  // Zeitmessung
  diff2 = t2 - t1;      
  /* wenn die Dauer des Pulses > 1600ms ist, dann codiert er das Ende der 59. Sekunde*/
  if (diff2 > 1200) {Telegramm_Ende = true; Telegramm_Decode = true;}  // siehe loop
  digitalWrite(BlinkPIN, HIGH);
}



/*=========================================================*/
void setup(void) /*=========================================================*/
{
  Wire.begin();
  pinMode(BlinkPIN, OUTPUT);
  pinMode(SommerPIN, OUTPUT);
  pinMode(Plus1SecPIN, OUTPUT);

  lcd.begin(16,2); 
  //
  lcd.setCursor(0, 0);  // sp zl
  lcd.print("Initializing RT");
  lcd.setCursor(0, 1);
  lcd.print("Position:   ");
  aktuell.mi = aktuell.hh = 0;
  aktuell.dd = aktuell.mm = aktuell.yy = aktuell.wd = 0;
  delay(2300);
  //
  Timer_Setup();
  iUhr.sec = 0;
  iUhr.sec_prev = 0;
  iUhr.mi = 0;
  iUhr.hh = 0;
  //
  rtc.begin();
  rtc.setMode(0);
  delay(10);
  RTC_ReadTime();
  RTC.used = true;
  RTC.syncronisiert = false;
  //
  Zeit_Anzeige();  //
  //
  DCF_Init();
  dd_prev_sync = 0;
  errPulsCnt = 0;
  noInterrupts();
  delay(3300);
  interrupts();
  lcd.setCursor(0, 1);
  lcd.print("          ");
  //
}






/*=========================================================*/
void loop(void)
/*=========================================================*/

  if (iUhr.sec != iUhr.sec_prev)
  {
    iUhr.sec_prev = iUhr.sec;
    if (iUhr.sec == 0) // Start einer neuen Timer-Minute
    {
      if (RTC.used) {RTC_ReadTime();}
      Zeit_Anzeige();
    }
    else
    {  
      // Sekunden-Anzeige:
      if (iUhr.sec < 10) {s1 = "0";} else {s1 = "";}
      s1 = s1 + String(iUhr.sec) + " " + String(DCF_Bit) + "  ";
      noInterrupts();
      lcd.setCursor(6, time_zl);
      lcd.print(s1);
      interrupts();
    } 
  }
  //
  if (Telegramm_Decode == true)  // Start neuer DCF-Minute
  {
    Telegramm_Decode = false;
    TelegrammDecodieren();
    //
    if (Timer_DCF_synchron == false)
    {delay(80); Timer_Setup();} // setzt auch sec = 0;
    scnt = 0;
  }
}



ArduinoSketch