DCF-77 PIC LED Clock
An atomic clock with automatic time synchronization
A PIC16F84A-based LED clock with DCF-77 atomic time synchronization.
Overview
This project uses a PIC16F84A with a 4x7 segment LED display. It supports hours/minutes or minutes/seconds display modes. The DCF77 atomic clock provides automatic time set-up. The firmware uses only 861 bytes of ROM and 62 bytes of RAM.
What is DCF77?
DCF77 is a German radio station broadcasting on 77.5 KHz, transmitting atomic clock time from Frankfurt. The signal encodes time in a 59-bit frame: a 100ms pulse represents a 0 bit, a 200ms pulse represents a 1 bit. One pulse is sent per second, with a 2-second gap marking the minute synchronization point. The signal range is approximately 2000 km.
Building a DCF77 Receiver
Ready-made DCF77 receiver modules are available for 12-18 euros. They have 3 pins: Gnd, Pulse, and Vcc. The firmware handles both positive and negative logic modules. The ferrite antenna must be oriented perpendicular to the direction of Frankfurt for best reception.
Circuit Schematic
How to Use
When not locked to the DCF77 signal, the display shows the current frame bit number and pulse signal indicator. Once locked, the display shows hours:minutes. Pressing RB7 toggles to minutes:seconds display mode.
C Source Code
/*
* DCF-77 LED CLOCK
*
* PIC16F84A
* 10 Mhz crystal, HS clock
*
* PORTA.0->3, out : 7 segment cathod control
* PORTA.4, in ; DCF pulse input
*
* PORTB.0->7, out : 7 segment output
* PORTB.7, in : button input
*
* Author : Bruno Gavand, november 2005
* see more details on www.micro-examples.com
*
*/
/*
* constant definitions
*/
#define MAXCOUNT 9766 // number of TMR0 overflows in 1 second
#define ADJUST 96 // extra ticks in 1 second
/*
* this values are reduced in practice
* to give some flexibility to DCF-77 pulse reception
*/
#define timer_d_min 14000 // number of TMR0 overflows in 2 seconds : 19531
#define timer_h_0 640 // number of TMR0 overflows in 0.1 second : 976
#define timer_h_1 1400 // number of TMR0 overflows in 0.2 second : 1953
/*
* RAM variables
*/
unsigned int tmrh ; // count of TMR0 overflows since pulse is high
unsigned int tmrd ; // count of TMR0 overflows since pulse is down
unsigned char bitnum = 0 ; // number of last valid bit received
char last_bit ; // value of last valid bit received
unsigned char parity ; // count of positive bits received
unsigned char full = 0 ; // set to 1 when DCF frame is complete
unsigned char locked = 0 ; // set to 1 when clock has been adjusted
unsigned char mode = 1 ; // 0:positive logic receiver, 1:negative logic receiver
unsigned char mn ; // next minutes in DCF frame
unsigned char hh ; // next hours in DCF frame
unsigned int scaler ; // count of TMR0 overflows for RTC
unsigned char rhh = 12, rmn = 34, rss = 56 ; // RTC clock : hours, minutes, seconds
unsigned char digiled[4] ; // 7 segment for each 4 displays
/*
* 7 segment encoding for numbers from 0 to 9
*/
unsigned char septSeg[10] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7c, 0x07, 0x7f, 0x67 } ;
unsigned char digit ; // current digit in multiplexing sequence
unsigned char kp ; // keypad : button to press for displaying mn:ss instead of hh:mn
unsigned char dp = 0 ; // decimal points to be reported on display
unsigned char i ; // general purpose register
/*
* interrupt routine called 2500000/256 times by seconds :
* the register TMR0 is increased each 4 clock cycles (quartz frequency is 10 Mhz),
* and overflows when reseting from 255 to 0,
* fetching to the interrupt precedure with bit T0IF set
*/
void interrupt(void)
{
// if(INTCON.T0IF) // TMR0 overflow ?
{
/*
* test DCF pulse, inverts the level if negative logic flag 'mode' is set
*/
if(PORTA.F4 ^ mode) // DCF pulse is high ?
{
tmrh++ ; // yes, increment my high timer
if(tmrd > timer_d_min) // down timer is greater than pulse pause ?
{
bitnum = 0 ; // yes, reset bit number
if(full) // is the DCF frame complete ?
{
rhh = hh ; // yes, set hours, min and seconds of RTC
rmn = mn ;
rss = 0 ;
scaler = 0 ; // reset my scaler
locked = 1 ; // locked flag is set, to enable display
}
mn = hh = 0 ; // reset min and hours for next frame
parity = 0 ; // reset parity counter
full = 0 ; // reset full flag
dp.F3 = 1 ; // set frame flag
}
tmrd = 0 ; // reset down timer
}
else
{
tmrd++ ; // pulse is low
if(tmrh > 0) // test if a high pulse was present
{
if(tmrh > timer_h_1) // yes, was longer than delay for a 1 ?
{
last_bit = 1 ; // yes, last bit was a 1
switch(bitnum) // depending on number of frame bit :
{
case 21: mn++ ; break ; // add minutes weights
case 22: mn += 2 ; break ;
case 23: mn += 4 ; break ;
case 24: mn += 8 ; break ;
case 25: mn += 10 ; break ;
case 26: mn += 20 ; break ;
case 27: mn += 40 ; break ;
case 29: hh++ ; break ; // add hours weights
case 30: hh += 2 ; break ;
case 31: hh += 4 ; break ;
case 32: hh += 8 ; break ;
case 33: hh += 10 ; break ;
case 34: hh += 20 ; break ;
}
/*
* is it a data bit or a parity bit ?
*/
if((bitnum != 28) && (bitnum != 35) && (bitnum != 58))
{
parity++ ; // data bit, increment parity
}
bitnum++ ; // next frame bit
}
else if(tmrh > timer_h_0) // was last pulse a 0 ?
{
if(bitnum == 20) // yes, start bit ?
{
last_bit = -1 ; // bad karma, should be 1 !
bitnum = 0 ; // reset frame bit counter
dp.F3 = 0 ; // clear frame flag indicator
}
else
{
last_bit = 0 ; // not a start bit, is ok
bitnum++ ; // next frame bit
}
}
else
{
last_bit = -1 ; // ouch, last bit was garbage
bitnum = 0 ; // reset frame bit counter
dp.F3 = 0 ; // clear frame flag indicator
}
if(bitnum == 21) // bit 20 is passed ?
{
parity = 0 ; // yes, clear parity counter
}
/*
* last bit was a parity bit ?
*/
if((bitnum == 29) || (bitnum == 36) || (bitnum == 59))
{
if((parity & 1) != last_bit) // yes, but parity is incorrect
{
bitnum = 0 ; // reset frame bit counter
dp.F3 = 0 ; // clear frame flag indicator
}
parity = 0 ; // clear parity counter
}
if(bitnum == 59) // is the frame complete ?
{
full++ ; // yes, should have a pulse pause
}
}
tmrh = 0 ; // clear high pulse counter
}
/*
* real time clock
*/
scaler++ ; // increment the scaler
if(scaler == MAXCOUNT) // a second is passed ?
{
TMR0 += ADJUST ;
scaler = 0 ; // yes, clear scaler
rss++ ; // next second
if(rss == 60) // seconds overflow ?
{
rss = 0 ; // yes, clear second
rmn++ ; // next minute
if(rmn == 60) // minutes overflow ?
{
rmn = 0 ; // yes, clear minute
rhh++ ; // next hour
if(rhh == 24) // hours overflow ?
{
rhh = 0 ; // yes, clear hour
}
}
}
}
dp.F1 = PORTA.F4 ^ mode ; // copy the pulse level to the decimal point for display
INTCON.T0IF = 0 ; // clear interrupt flag to enable next call on overflow
}
}
/*
* program entry point
*/
main()
{
TRISA = 0b00010000 ; // see header
TRISB = 0x00 ; // see header
INTCON = 0b10100000 ; // T0IF and GIE enabled
OPTION_REG = 0b11011000 ; // no prescaler
/*
* main loop
*/
for(;;)
{
if(locked > 0) // is the RTC up to date ?
{
if(kp) // yes, is the key pressed ?
{
/*
* yes, prepare display for MN:SS
*/
digiled[0] = septSeg[rmn / 10] ; // minutes tenth
digiled[1] = septSeg[rmn % 10] ; // minutes unit
digiled[2] = septSeg[rss / 10] ; // seconds tenth
digiled[3] = septSeg[rss % 10] ; // seconds unit
}
else
{
/*
* no key, prepare display for HH:MN
*/
digiled[0] = (rhh < 10) ? 0 : septSeg[rhh / 10] ; // hours tenth, blank if null
digiled[1] = septSeg[rhh % 10] ; // hours unit
digiled[2] = septSeg[rmn / 10] ; // minutes tenth
digiled[3] = septSeg[rmn % 10] ; // minute unit
}
}
else
{
/*
* the RTC is not up to date, display DCF frame info
*/
digiled[0] = 0 ; // nothing on first 2 digits
digiled[1] = 0 ;
digiled[2] = septSeg[bitnum / 10] ; // number of bit tenth
digiled[3] = septSeg[bitnum % 10] ; // number of bit unit
}
/*
* set each decimal points,
* F1 is pulse repeater,
* F3 is frame progress
*/
digiled[0].F7 = dp.F0 ;
digiled[1].F7 = dp.F1 ;
digiled[2].F7 = dp.F2 ;
digiled[3].F7 = dp.F3 ;
PORTA = 0 ; // shut down display
PORTB = 0 ; // clear segment outputs
TRISB = 0x80 ; // set PORTB.F7 as input
kp = PORTB.F7 ; // read key
TRISB = 0x00 ; // set PORTB.F7 as output again
digit++ ; // next digit to be displayed
if(digit > 3) // all done ?
{
digit = 0 ; // yes, start again with first
i = 0x01 ; // assign bit mask for PORTA first display led cathod control
}
else
{
i = 0x01 << digit ; // shift bit for next led display cathod activztion
}
PORTB = digiled[digit] ; // write 7 segment output
PORTA = i ; // light on selected 7 segment display
}
}
Download
DCF-77 LED Clock project files
Download the source code and pre-compiled HEX file:
- dcf77clock.c — mikroC source code (15 KB)
- dcf77clock.hex — pre-compiled HEX for PIC16F84A (10 MHz crystal, 5.5 KB)