/* DCF77.c - DCF77 library Copyright (c) Thijs Elenbaas 2012 This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 11 Apr 2012 - initial release 23 Apr 2012 - added UTC support 2 Jul 2012 - minor bugfix and additional noise rejection */ #include <DCF77.h> //https://github.com/thijse/Arduino-Libraries/downloads #include <Time.h> //http://www.arduino.cc/playground/Code/Time #include <Utils.h> #define _DCF77_VERSION 0_9_7 // software version of this library using namespace Utils; /** * Constructor */ DCF77::DCF77(int DCF77Pin, int DCFinterrupt, bool OnRisingFlank) { dCF77Pin = DCF77Pin; dCFinterrupt = DCFinterrupt; pulseStart = OnRisingFlank ? HIGH : LOW; if (!initialized) { pinMode(dCF77Pin, INPUT); initialize(); } initialized = true; } /** * Initialize parameters */ void DCF77::initialize(void) { leadingEdge = 0; trailingEdge = 0; PreviousLeadingEdge = 0; Up = false; runningBuffer = 0; FilledBufferAvailable = false; bufferPosition = 0; flags.parityDate = 0; flags.parityFlag = 0; flags.parityHour = 0; flags.parityMin = 0; CEST = 0; } /** * Start receiving DCF77 information */ void DCF77::Start(void) { attachInterrupt(dCFinterrupt, int0handler, CHANGE); } /** * Stop receiving DCF77 information */ void DCF77::Stop(void) { detachInterrupt(dCFinterrupt); } /** * Initialize buffer for next time update */ inline void DCF77::bufferinit(void) { runningBuffer = 0; bufferPosition = 0; } /** * Interrupt handler that processes up-down flanks into pulses and stores these in the buffer */ void DCF77::int0handler() { int flankTime = millis(); byte sensorValue = digitalRead(dCF77Pin); // If flank is detected quickly after previous flank up // this will be an incorrect pulse that we shall reject if ((flankTime-PreviousLeadingEdge)<DCFRejectionTime) { LogLn("rCT"); return; } // If the detected pulse is too short it will be an // incorrect pulse that we shall reject as well if ((flankTime-leadingEdge)<DCFRejectPulseWidth) { LogLn("rPW"); return; } if(sensorValue==pulseStart) { if (!Up) { // Flank up leadingEdge=flankTime; Up = true; } } else { if (Up) { // Flank down trailingEdge=flankTime; int difference=trailingEdge - leadingEdge; if ((leadingEdge-PreviousLeadingEdge) > DCFSyncTime) { finalizeBuffer(); } PreviousLeadingEdge = leadingEdge; // Distinguish between long and short pulses if (difference < DCFSplitTime) { appendSignal(0); } else { appendSignal(1); } Up = false; } } } /** * Add new bit to buffer */ inline void DCF77::appendSignal(unsigned char signal) { Log(signal, DEC); runningBuffer = runningBuffer | ((unsigned long long) signal << bufferPosition); bufferPosition++; if (bufferPosition > 59) { // Buffer is full before at end of time-sequence // this may be due to noise giving additional peaks LogLn("EoB"); finalizeBuffer(); } } /** * Finalize filled buffer */ inline void DCF77::finalizeBuffer(void) { if (bufferPosition == 59) { // Buffer is full LogLn("BF"); // Prepare filled buffer and time stamp for main loop filledBuffer = runningBuffer; filledTimestamp = now(); // Reset running buffer bufferinit(); FilledBufferAvailable = true; } else { // Buffer is not yet full at end of time-sequence LogLn("EoM"); // Reset running buffer bufferinit(); } } /** * Returns whether there is a new time update available * This functions should be called prior to getTime() function. */ bool DCF77::receivedTimeUpdate(void) { // If buffer is not filled, there is no new time if(!FilledBufferAvailable) { return false; } // if buffer is filled, we will process it and see if this results in valid parity if (!processBuffer()) { LogLn("Invalid parity"); return false; } // Since the received signal is error-prone, and the parity check is not very strong, // we will do some sanity checks on the time time_t processedTime = latestupdatedTime + (now() - processingTimestamp); if (processedTime<MIN_TIME || processedTime>MAX_TIME) { LogLn("Time outside of bounds"); return false; } // If received time is close to internal clock (2 min) we are satisfied time_t difference = abs(processedTime - now()); if(difference < 2*SECS_PER_MIN) { LogLn("close to internal clock"); storePreviousTime(); return true; } // Time can be further from internal clock for several reasons // We will check if lag from internal clock is consistent time_t shiftPrevious = (previousUpdatedTime - previousProcessingTimestamp); time_t shiftCurrent = (latestupdatedTime - processingTimestamp); time_t shiftDifference = abs(shiftCurrent-shiftPrevious); storePreviousTime(); if(shiftDifference < 2*SECS_PER_MIN) { LogLn("time lag consistent"); return true; } else { LogLn("time lag inconsistent"); } // If lag is inconsistent, this may be because of no previous stored date // This would be resolved in a second run. return false; } /** * Store previous time. Needed for consistency */ void DCF77::storePreviousTime(void) { previousUpdatedTime = latestupdatedTime; previousProcessingTimestamp = processingTimestamp; } /** * Calculate the parity of the time and date. */ void DCF77::calculateBufferParities(void) { // Calculate Parity flags.parityFlag = 0; for(int pos=0;pos<59;pos++) { bool s = (processingBuffer >> pos) & 1; // Update the parity bits. First: Reset when minute, hour or date starts. if (pos == 21 || pos == 29 || pos == 36) { flags.parityFlag = 0; } // save the parity when the corresponding segment ends if (pos == 28) {flags.parityMin = flags.parityFlag;}; if (pos == 35) {flags.parityHour = flags.parityFlag;}; if (pos == 58) {flags.parityDate = flags.parityFlag;}; // When we received a 1, toggle the parity flag if (s == 1) { flags.parityFlag = flags.parityFlag ^ 1; } } } /** * Evaluates the information stored in the buffer. This is where the DCF77 * signal is decoded */ bool DCF77::processBuffer(void) { ///// Start interaction with interrupt driven loop ///// // Copy filled buffer and timestamp from interrupt driven loop processingBuffer = filledBuffer; processingTimestamp = filledTimestamp; // Indicate that there is no filled, unprocessed buffer anymore FilledBufferAvailable = false; ///// End interaction with interrupt driven loop ///// // Calculate parities for checking buffer calculateBufferParities(); tmElements_t time; bool proccessedSucces; struct DCF77Buffer *rx_buffer; rx_buffer = (struct DCF77Buffer *)(unsigned long long)&processingBuffer; // Check parities if (flags.parityMin == rx_buffer->P1 && flags.parityHour == rx_buffer->P2 && flags.parityDate == rx_buffer->P3 && rx_buffer->CEST != rx_buffer->CET) { //convert the received buffer into time time.Second = 0; time.Minute = rx_buffer->Min-((rx_buffer->Min/16)*6); time.Hour = rx_buffer->Hour-((rx_buffer->Hour/16)*6); time.Day = rx_buffer->Day-((rx_buffer->Day/16)*6); time.Month = rx_buffer->Month-((rx_buffer->Month/16)*6); time.Year = 2000 + rx_buffer->Year-((rx_buffer->Year/16)*6) -1970; latestupdatedTime = makeTime(time); CEST = rx_buffer->CEST; //Parity correct return true; } else { //Parity incorrect return false; } } /** * Get most recently received time * Note, this only returns an time once, until the next update */ time_t DCF77::getTime(void) { if (!receivedTimeUpdate()) { return(0); } else { // Send out time, taking into account the difference between when the DCF time was received and the current time time_t currentTime =latestupdatedTime + (now() - processingTimestamp); return(currentTime); } } /** * Get most recently received time in UTC * Note, this only returns an time once, until the next update */ time_t DCF77::getUTCTime(void) { if (!receivedTimeUpdate()) { return(0); } else { // Send out time UTC time int UTCTimeDifference = (CEST ? 2 : 1)*SECS_PER_HOUR; time_t currentTime =latestupdatedTime - UTCTimeDifference + (now() - processingTimestamp); return(currentTime); } } /** * Initialize parameters */ int DCF77::dCF77Pin=0; int DCF77::dCFinterrupt=0; byte DCF77::pulseStart=HIGH; // Parameters shared between interupt loop and main loop volatile unsigned long long DCF77::filledBuffer = 0; volatile bool DCF77::FilledBufferAvailable= false; volatile time_t DCF77::filledTimestamp= 0; // DCF Buffers and indicators int DCF77::bufferPosition = 0; unsigned long long DCF77::runningBuffer = 0; unsigned long long DCF77::processingBuffer = 0; // Pulse flanks int DCF77::leadingEdge=0; int DCF77::trailingEdge=0; int DCF77::PreviousLeadingEdge=0; bool DCF77::Up= false; // DCF77 and internal timestamps time_t DCF77::latestupdatedTime= 0; time_t DCF77::previousUpdatedTime= 0; time_t DCF77::processingTimestamp= 0; time_t DCF77::previousProcessingTimestamp=0; unsigned char DCF77::CEST=0; DCF77::ParityFlags DCF77::flags = {0,0,0,0};