Sketch für das DCF77 Emulator-Modul

#include <Time.h>
#include <TimeLib.h>

/*

 Emulator DCF77
 Simulate a DCF77 radio receiver with a ESP32
 Emits a complete three minute pulses train from the GPIO2 output
 the train is preceded by a single pulse at the lacking 59° pulse to allow some clock model synchronization
 of the beginning frame
 after the three pulses train one more single pulse is sent to safely close the frame
 get the time from the ntp service
 This code is in the public domain.

 */

#include <WiFi.h>
#include <WiFiUdp.h>
#include <Ticker.h>
//#include <Time.h>


char ssid[] = "Routername";
char pass[] = "passwort des Routers";


unsigned int localPort = 2390;      // local port to listen for UDP packets

/* Don't hardwire the IP address or we won't get the benefits of the pool.
 *  Lookup the IP address for the host name instead */
IPAddress timeServerIP; // time.nist.gov NTP server address
const char* ntpServerName = "0.nl.pool.ntp.org";

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message

byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

// A UDP instance to let us send and receive packets over UDP
WiFiUDP udp;

//udp reply missing counter
int UdpNoReplyCounter = 0;

//routine timer 100 msec
Ticker DcfOutTimer;

#define LedPin 2

//how many total pulses we have
//three complete minutes + 2 head pulses and one tail pulse
#define MaxPulseNumber 183
#define FirstMinutePulseBegin 2
#define SecondMinutePulseBegin 62
#define ThirdMinutePulseBegin 122


//complete array of pulses for three minutes
//0 = no pulse, 1=100msec, 2=200msec
int PulseArray[MaxPulseNumber];

int PulseCount = 0;
int DCFOutputOn = 0;
int PartialPulseCount = 0;
int ThisHour,ThisMinute,ThisSecond,ThisDay,ThisMonth,ThisYear,DayOfW;

const int timeZone = 1;     // Central European Time
//const int timeZone = 2;     // Central European Time

int Dls;                    //DayLightSaving

void setup()
{
  Serial.begin(9600);
  Serial.println();
  Serial.println("INIT DCF77 emulator");
 
  pinMode(LedPin, OUTPUT);
  digitalWrite(LedPin, LOW);
  
  //handle DCF pulses
  DcfOutTimer.attach_ms(100, DcfOut);

  //first 2 pulses: 1 + blank to simulate the packet beginning
  //il primo bit e' un 1
  PulseArray[0] = 1;
  //missing pulse indicates start of minute
  PulseArray[1] = 0;

  //last pulse after the third 59° blank
  PulseArray[MaxPulseNumber - 1] = 1;
  
  PulseCount = 0;
  DCFOutputOn = 0;    //we begin with the output OFF

  //first connect to wifi network
  //NOTE testing WiFi.status() BEFThisHour the FIRST WiFi.begin() seems to hang the system
  //so we attempt a first connection BEFORE the main loop
  ConnectToWiFi();
}

void loop(){
  
  //check the WLAN status
  if (WiFi.status() == WL_CONNECTED)
     ReadAndDecodeTime();
  else
    ConnectToWiFi();

  delay(60000);

}


void ConnectToWiFi(){
  int Timeout;

  Serial.begin(115200);
   WiFi.begin(ssid, pass);
   delay(150);
   Serial.println("Connecting");
    while (WiFi.status() != WL_CONNECTED) {
        
         Serial.println("Ich verbinde mich mit dem Internet...");
  }
  Serial.println("Ich bin mit dem Internet verbunden!");
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());
  
}

void ReadAndDecodeTime() {

  int DayToEndOfMonth,DayOfWeekToEnd,DayOfWeekToSunday;

  //get a random server from the pool
  WiFi.hostByName(ntpServerName, timeServerIP); 

  Serial.println("Starting UDP");
  udp.begin(localPort);
//  Serial.print("Local port: ");
//  Serial.println(udp.localPort());

  sendNTPpacket(timeServerIP); // send an NTP packet to a time server
  // wait to see if a reply is available
  delay(1000);
  
  int cb = udp.parsePacket();
  if (!cb) {
    Serial.println("no packet yet");
    //try max 3 times (every minute) after that we force the wifi to reconnect
    if (UdpNoReplyCounter++ == 3){
      Serial.println("Too many UDP errors");
      ConnectToWiFi();
      UdpNoReplyCounter = 0;
    };
  } else {
    UdpNoReplyCounter = 0;
    Serial.print("packet received, length=");
    Serial.println(cb);
    // We've received a packet, read the data from it
    udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

    //the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. First, esxtract the two words:

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    Serial.print("Seconds since Jan 1 1900 = " );
    Serial.println(secsSince1900);

    // now convert NTP time into everyday time:
    Serial.print("Unix time = ");
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;
    // subtract seventy years:
    //note: we add two minutes because the dcf protocol send the time of the FOLLOWING minute
    //and our transmission begins the next minute more
    time_t ThisTime = secsSince1900 - seventyYears + ( timeZone * 3600 ) + 120;
    // print Unix time:
    Serial.println(ThisTime);

    //calculate actual day to evaluate the summer/winter time of day ligh saving
    DayOfW = weekday(ThisTime);
    ThisDay = day(ThisTime);
    ThisMonth = month(ThisTime);
    ThisYear = year(ThisTime);
    Serial.print("Local time");       // UTC is the time at Greenwich Meridian (GMT)
    Serial.print(ThisDay);
    Serial.print('/');
    Serial.print(ThisMonth);
    Serial.print('/');
    Serial.print(ThisYear);
    Serial.print(' ');
  
    //check daylight saving
    Dls = 0;    //default winter time
/*
    //From April to september we are surely on summer time
    if (ThisMonth > 3 && ThisMonth < 10) {
      Dls = 1;
    };
    //March, month of change winter->summer time, last Sunday of the month
    //March has 31 days so from 25 included on Sunday we can be in summer time
    if (ThisMonth == 3 && ThisDay > 24) {
      DayToEndOfMonth = 31 - ThisDay;
      DayOfWeekToSunday = 7 - DayOfW;
      if (DayOfWeekToSunday >= DayToEndOfMonth)
        Dls = 1;
    };
    //October, month of change summer->winter time, last Sunday of the month
    //October has 31 days so from 25 included on Sunday we can be in winter time
    if (ThisMonth == 10) {
      Dls = 1;
      if (ThisDay > 24) {
        DayToEndOfMonth = 31 - ThisDay;
        DayOfWeekToEnd = 7 - DayOfW;
        if (DayOfWeekToEnd >= DayToEndOfMonth)
        Dls = 0;
      };
    };
*/
    Serial.print("Dls:");
    Serial.print(Dls);
    Serial.print(' ');
    //add one hour if we are in summer time
    if (Dls == 1)
      ThisTime += 3600;

//***Zeitverschiebung für Istanbul*******************
    ThisTime += 7200;
    
    //now that we know the dls state, we can calculate the time to
    // print the hour, minutes and seconds:
    ThisHour = hour(ThisTime);
    ThisMinute = minute(ThisTime);
    ThisSecond = second(ThisTime);
    Serial.print(ThisHour); // print the hour
    Serial.print(':');
    Serial.print(ThisMinute); // print the minute
    Serial.print(':');
    Serial.println(ThisSecond); // print the second

    //if we are over about the 56° second we risk to begin the pulses too late, so it's better
    //to skip at the half of the next minute and NTP+recalculate all again
    if (ThisSecond > 56){
      delay(30000);
      return;      
    }

    //calculate bis array for the first minute
    CalculateArray(FirstMinutePulseBegin);

    //add one minute and calculate array again for the second minute
    ThisTime += 60;
    DayOfW = weekday(ThisTime);
    ThisDay = day(ThisTime);
    ThisMonth = month(ThisTime);
    ThisYear = year(ThisTime);
    ThisHour = hour(ThisTime);
    ThisMinute = minute(ThisTime);
    ThisSecond = second(ThisTime);
    CalculateArray(SecondMinutePulseBegin);

    //one minute more for the third minute
    ThisTime += 60;
    DayOfW = weekday(ThisTime);
    ThisDay = day(ThisTime);
    ThisMonth = month(ThisTime);
    ThisYear = year(ThisTime);
    ThisHour = hour(ThisTime);
    ThisMinute = minute(ThisTime);
    ThisSecond = second(ThisTime);
    CalculateArray(ThirdMinutePulseBegin);

   
    //how many to the minute end ?
    //don't forget that we begin transmission at second 58°
    int SkipSeconds = 58 - ThisSecond;
    delay(SkipSeconds * 1000);
    //begin
    DCFOutputOn = 1;

    //three minutes are needed to transmit all the packet
    //then wait more 30 secs to locate safely at the half of minute
    //NB 150+60=210sec, 60secs are lost from main routine
    delay(150000);

  };

  udp.stop() ;

}





void CalculateArray(int ArrayOffset) {
  int n,Tmp,TmpIn;
  int ParityCount = 0;

  //first 20 bits are logical 0s
  for (n=0;n<20;n++)
    PulseArray[n+ArrayOffset] = 1;

  //DayLightSaving bit
  if (Dls == 1)
    PulseArray[17+ArrayOffset] == 2;
  else
    PulseArray[18+ArrayOffset] == 2;
    
  //bit 20 must be 1 to indicate time active
  PulseArray[20+ArrayOffset] = 2;

  //calculate minutes bits
  TmpIn = Bin2Bcd(ThisMinute);
  for (n=21;n<28;n++) {
    Tmp = TmpIn & 1;
    PulseArray[n+ArrayOffset] = Tmp + 1;
    ParityCount += Tmp;
    TmpIn >>= 1;
  };
  if ((ParityCount & 1) == 0)
    PulseArray[28+ArrayOffset] = 1;
  else
    PulseArray[28+ArrayOffset] = 2;

  //calculate hour bits
  ParityCount = 0;
  TmpIn = Bin2Bcd(ThisHour);
  for (n=29;n<35;n++) {
    Tmp = TmpIn & 1;
    PulseArray[n+ArrayOffset] = Tmp + 1;
    ParityCount += Tmp;
    TmpIn >>= 1;
  }
  if ((ParityCount & 1) == 0)
    PulseArray[35+ArrayOffset] = 1;
  else
    PulseArray[35+ArrayOffset] = 2;
   ParityCount = 0;
  //calculate day bits
  TmpIn = Bin2Bcd(ThisDay);
  for (n=36;n<42;n++) {
    Tmp = TmpIn & 1;
    PulseArray[n+ArrayOffset] = Tmp + 1;
    ParityCount += Tmp;
    TmpIn >>= 1;
  }
  //calculate weekday bits
  TmpIn = Bin2Bcd(DayOfW);
  for (n=42;n<45;n++) {
    Tmp = TmpIn & 1;
    PulseArray[n+ArrayOffset] = Tmp + 1;
    ParityCount += Tmp;
    TmpIn >>= 1;
  }
  //calculate month bits
  TmpIn = Bin2Bcd(ThisMonth);
  for (n=45;n<50;n++) {
    Tmp = TmpIn & 1;
    PulseArray[n+ArrayOffset] = Tmp + 1;
    ParityCount += Tmp;
    TmpIn >>= 1;
  }
  //calculate year bits
  TmpIn = Bin2Bcd(ThisYear - 2000);   //a noi interesa solo l'anno con ... il millenniumbug !
  for (n=50;n<58;n++) {
    Tmp = TmpIn & 1;
    PulseArray[n+ArrayOffset] = Tmp + 1;
    ParityCount += Tmp;
    TmpIn >>= 1;
  }
  //date parity
  if ((ParityCount & 1) == 0)
    PulseArray[58+ArrayOffset] = 1;
  else
    PulseArray[58+ArrayOffset] = 2;

  //last missing pulse
  PulseArray[59+ArrayOffset] = 0;

  /* for debug: print the whole 180 secs array
   * Serial.print(':');
  for (n=0;n<60;n++)
    Serial.print(PulseArray[n+ArrayOffset]);*/

}


// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address)
{
  Serial.println("sending NTP packet...");
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  udp.beginPacket(address, 123); //NTP requests are to port 123
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
}


//called every 100msec
//for DCF77 output
void DcfOut() {

  if (DCFOutputOn == 1) {
    switch (PartialPulseCount++) {
      case 0:
        if (PulseArray[PulseCount] != 0)
          digitalWrite(LedPin, 0);
        break;
      case 1:
        if (PulseArray[PulseCount] == 1)
          digitalWrite(LedPin, 1);
        break;
      case 2:
        digitalWrite(LedPin, 1);
        break;
      case 9:
        if (PulseCount++ == (MaxPulseNumber -1 )){     //one less because we FIRST tx the pulse THEN count it
          PulseCount = 0;
          DCFOutputOn = 0;
        };
        PartialPulseCount = 0;
        break;
    };
  };

}

int Bin2Bcd(int dato) {
  int msb,lsb;

  if (dato < 10)
    return dato;
  msb = (dato / 10) << 4;
  lsb = dato % 10; 
  return msb + lsb;
}

Leave a Reply

Your email address will not be published. Required fields are marked *