Go Down

Topic: DCF77 Time Module (Read 8787 times) previous topic - next topic

BajK

#15
Oct 21, 2010, 07:55 pm Last Edit: Oct 21, 2010, 07:58 pm by BajK Reason: 1
It's near Stuttgart (South-west-Germany), so shouldn't be that much of a problem.
Code: [Select]
/**
* Arduino DCF77 decoder v0.2
* Copyright (C) 2006 Mathias Dalheimer (md@gonium.net)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <MsTimer2.h>

/**
* Where is the DCF receiver connected?
*/
#define DCF77PIN 2
/**
* Where is the LED connected?
*/
#define BLINKPIN 13
/**
* Turn debugging on or off
*/
#define DCF_DEBUG 1
/**
* Number of milliseconds to elapse before we assume a "1",
* if we receive a falling flank before - its a 0.
*/
#define DCF_split_millis 140
/**
* There is no signal in second 59 - detect the beginning of
* a new minute.
*/
#define DCF_sync_millis 1200
/**
* Definitions for the timer interrupt 2 handler:
* The Arduino runs at 16 Mhz, we use a prescaler of 64 -> We need to
* initialize the counter with 6. This way, we have 1000 interrupts per second.
* We use tick_counter to count the interrupts.
*/
#define INIT_TIMER_COUNT 6
#define RESET_TIMER2 TCNT2 = INIT_TIMER_COUNT
int tick_counter = 0;
/**
* DCF time format struct
*/
struct DCF77Buffer {
 unsigned long long prefix      :21;
 unsigned long long Min      :7;      // minutes
 unsigned long long P1            :1;      // parity minutes
 unsigned long long Hour      :6;      // hours
 unsigned long long P2            :1;      // parity hours
 unsigned long long Day      :6;      // day
 unsigned long long Weekday      :3;      // day of week
 unsigned long long Month      :5;      // month
 unsigned long long Year      :8;      // year (5 -> 2005)
 unsigned long long P3            :1;      // parity
};
struct {
     unsigned char parity_flag      :1;
     unsigned char parity_min            :1;
     unsigned char parity_hour      :1;
     unsigned char parity_date      :1;
} flags;
/**
* Clock variables
*/
volatile unsigned char DCFSignalState = 0;  
unsigned char previousSignalState;
int previousFlankTime;
int bufferPosition;
unsigned long long dcf_rx_buffer;
/**
* time vars: the time is stored here!
*/
volatile unsigned char ss;
volatile unsigned char mm;
volatile unsigned char hh;
volatile unsigned char day;
volatile unsigned char mon;
volatile unsigned int year;
   

/**
* used in main loop: detect a new second...
*/
unsigned char previousSecond;
   
/**
* Initialize the DCF77 routines: initialize the variables,
* configure the interrupt behaviour.
*/
void DCF77Init() {
previousSignalState=0;
previousFlankTime=0;
bufferPosition=0;
dcf_rx_buffer=0;
ss=mm=hh=day=mon=year=0;
#ifdef DCF_DEBUG
Serial.println("Initializing DCF77 routines");
Serial.print("Using DCF77 pin #");
Serial.println(DCF77PIN);
#endif
pinMode(BLINKPIN, OUTPUT);
pinMode(DCF77PIN, INPUT);

#ifdef DCF_DEBUG
Serial.println("Initializing timerinterrupt");
#endif
MsTimer2::set(1000, advanceClock); // 500ms period

#ifdef DCF_DEBUG
Serial.println("Initializing DCF77 signal listener interrupt");
#endif
attachInterrupt(0, int0handler, CHANGE);
}

/**
* Append a signal to the dcf_rx_buffer. Argument can be 1 or 0. An internal
* counter shifts the writing position within the buffer. If position > 59,
* a new minute begins -> time to call finalizeBuffer().
*/
void appendSignal(unsigned char signal) {
#ifdef DCF_DEBUG
 Serial.print(", appending value ");
 Serial.print(signal, DEC);
 Serial.print(" at position ");
 Serial.println(bufferPosition);
#endif
 dcf_rx_buffer = dcf_rx_buffer | ((unsigned long long) signal << bufferPosition);
 // Update the parity bits. First: Reset when minute, hour or date starts.
 if (bufferPosition ==  21 || bufferPosition ==  29 || bufferPosition ==  36) {
     flags.parity_flag = 0;
 }
 // save the parity when the corresponding segment ends
 if (bufferPosition ==  28) {flags.parity_min = flags.parity_flag;};
 if (bufferPosition ==  35) {flags.parity_hour = flags.parity_flag;};
 if (bufferPosition ==  58) {flags.parity_date = flags.parity_flag;};
 // When we received a 1, toggle the parity flag
 if (signal == 1) {
   flags.parity_flag = flags.parity_flag ^ 1;
 }
 bufferPosition++;
 if (bufferPosition > 59) {
   finalizeBuffer();
 }
}

/**
* Evaluates the information stored in the buffer. This is where the DCF77
* signal is decoded and the internal clock is updated.
*/
void finalizeBuffer(void) {
 if (bufferPosition == 59) {
#ifdef DCF_DEBUG
   Serial.println("Finalizing Buffer");
#endif
   struct DCF77Buffer *rx_buffer;
   rx_buffer = (struct DCF77Buffer *)(unsigned long long)&dcf_rx_buffer;
   if (flags.parity_min == rx_buffer->P1  &&
       flags.parity_hour == rx_buffer->P2  &&
       flags.parity_date == rx_buffer->P3)
   {
#ifdef DCF_DEBUG
     Serial.println("Parity check OK - updating time.");
#endif
     //convert the received bits from BCD
     mm = rx_buffer->Min-((rx_buffer->Min/16)*6);
     hh = rx_buffer->Hour-((rx_buffer->Hour/16)*6);
     day= rx_buffer->Day-((rx_buffer->Day/16)*6);
     mon= rx_buffer->Month-((rx_buffer->Month/16)*6);
     year= 2000 + rx_buffer->Year-((rx_buffer->Year/16)*6);
   }
#ifdef DCF_DEBUG
     else {
       Serial.println("Parity check NOK - running on internal clock.");
   }
#endif
 }
 // reset stuff
 ss = 0;
 bufferPosition = 0;
 dcf_rx_buffer=0;
}

/**
* Dump the time to the serial line.
*/
void serialDumpTime(void){
 Serial.print("Time: ");
 Serial.print(hh, DEC);
 Serial.print(":");
 Serial.print(mm, DEC);
 Serial.print(":");
 Serial.print(ss, DEC);
 Serial.print(" Date: ");
 Serial.print(day, DEC);
 Serial.print(".");
 Serial.print(mon, DEC);
 Serial.print(".");
 Serial.println(year, DEC);
}


/**
* Evaluates the signal as it is received. Decides whether we received
* a "1" or a "0" based on the
*/
void scanSignal(void){
   if (DCFSignalState == 1) {
     int thisFlankTime=millis();
     if (thisFlankTime - previousFlankTime > DCF_sync_millis) {
#ifdef DCF_DEBUG
       Serial.println("####");
       Serial.println("#### Begin of new Minute!!!");
       Serial.println("####");
#endif
       finalizeBuffer();
     }
     previousFlankTime=thisFlankTime;
#ifdef DCF_DEBUG
     Serial.print(previousFlankTime);
     Serial.print(": DCF77 Signal detected, ");
#endif
   }
   else {
     /* or a falling flank */
     int difference=millis() - previousFlankTime;
#ifdef DCF_DEBUG
     Serial.print("duration: ");
     Serial.print(difference);
#endif
     if (difference < DCF_split_millis) {
       appendSignal(0);
     }
     else {
       appendSignal(1);
     }
   }
}

/**
* The interrupt routine for counting seconds - increment hh:mm:ss.
*/
void advanceClock() {
ss++;
if (ss==60) {
ss=0;
mm++;
if (mm==60) {
mm=0;
hh++;
if (hh==24)
hh=0;
}
}
}

/**
* Interrupthandler for INT0 - called when the signal on Pin 2 changes.
*/
void int0handler() {
int length = millis() - previousFlankTime;
if (length < 10) {
// ignore jitter
return;
}
// check the value again - since it takes some time to
// activate the interrupt routine, we get a clear signal.
DCFSignalState = digitalRead(DCF77PIN);
}


/**
* Standard Arduino methods below.
*/

void setup(void) {
 // We need to start serial here again,
 // for Arduino 007 (new serial code)
 Serial.begin(9600);
 DCF77Init();
}

void loop(void) {
 if (ss != previousSecond) {
   serialDumpTime();
   previousSecond = ss;
 }
 if (DCFSignalState != previousSignalState) {
   scanSignal();
   if (DCFSignalState) {
     digitalWrite(BLINKPIN, HIGH);
   } else {
     digitalWrite(BLINKPIN, LOW);
   }
   previousSignalState = DCFSignalState;
 }
   //delay(20);
}

It's that example from http://gonium.net/md/2007/01/06/arduino-dcf77-v02-released/ (with some changes that someone in the comments made, using Mstimer2 instead)

Is there a way how to know whether it is just not working or I am out of range? Because atm I cannot go outside with my Arduino. I first have to wire together my lcd and box which requires me for the light to be attached as well. So I can at soonest by Saturday test it outside.

mem

Why not try the code linked earlier in this thread?

BajK

Because it's a different receiver and the pin logic is totally different in the first post. I tried the DCF77 tutorial in playground as well which ecplicitly states it uses the DCF receiver of Conrad.

Mike Mc

I never did get my Conrad receiver to work with this.

BajK

Did you manage it and how?

yesyes

Has anyone got this working with a UK MSF signal yet?
I'm looking into building a fairly large LED clock that must be radio controlled. I wouldn't mind using an Arduino for that project but it does look complicated.
Chris

Location: Berkshire, UK
My Astro and DIY projects website: http://yesyes.info/

yesyes

Oh, actually, never mind. I just found a far easier way of getting atomic time on the Arduino. Using a GPS module.  ;D

It has a serial interface that just sends a string with UTC time and current coordinates. And a cheap one costs about the same as a MSF receiver.
Chris

Location: Berkshire, UK
My Astro and DIY projects website: http://yesyes.info/

Jarkman

I suspect GPS may not work very well indoors, unless you get a really sensitive receiver or use an external antenna.

I've just ordered an MSF module (£7 from PV Electronics, also sold on eBay by yashu75) :
http://www.pvelectronics.co.uk/index.php?main_page=index&cPath=9

They also do DCF and WWVB modules. And Sparkfun do a similar (not identical) module for WWVB, as well:
http://www.sparkfun.com/products/10060


My plan is to extend/modify RadioTime to do MSF instead of DCF. MSF docs are here:
http://www.pvelectronics.co.uk/rftime/msf/MSF_Time_Date_Code.pdf


Mem - you said you had made a start on the MSF code - do you have any bits to share, or should I just start in ? Or, does anyone else have MSF code running ?

Ta!

Richard


Jarkman

I've got MSF code running happily now against one of those modules.

Details and code are here:

http://www.jarkman.co.uk/catalog/robots/msftime.htm


Jose Francisco

Hello.

I'm very interested in the DCF77 Time Module and I have order one from PVElectronics, but I have some questions about its technical data:

Supply voltage range: Max 5V, Min 1.8.
What voltage are you using? 3.3 or 5v?

PON Input Level High: Min 0.85.
Is this enough to arduino? is some interface transistor needed?

Thank you very much.

Jarkman

I have powered mine from the 3.3v rail on a Uno, and I have not had any interface issues. I just wired the output pin of the module directly to analogue 0 on the Arduino. I'm using the pin as a digital input, not an analogue one.

I have wired the PON pin to ground, which keeps the receiver powered up all the time, so I am not interested in the PON levels.

Jose Francisco

OK.  PON = ENABLE.

Is there any special reason to use analog instead of digital?

Thank you very much.

Jarkman

Yes, I am using it with a LOLShield and it ties up all the digital pins. Analogue is all that was left! :-)

Jose Francisco

Jarkman, are you using any pull-up resistor like:
http://gonium.net/md/2006/11/05/arduino-dcf77-radio-clock-receiver/
?

Thanks

Jarkman

No, I found that my receiver did not require one.

Incidentally, I have found that my receiver's reliability varies with the time of day. It generally gets a fix first time in the evening (which means it knows the time within 120 seconds of boot), but during the daytime it can take 15 minutes.

I think the difference is down to radio propagation changes with time of day. I intent to rework some of my code to make it less vulnerable to short spikes, which ought to help.

Go Up