Captain's Universe Home
Captain's Universe Home
Cosmic Ray Muon DetectorTeleGarden Pages
Time on MarsBryophyllum Plants
Jupiter Radio AstronomyAncient Pages
Salzburg Tourist GuideEarth Magnetometer
  H O M E     AJAX & MORE     LINUX & MORE     RTAI     XENOMAI     ADEOS IPIPE      
    JAVA & BROWSERS     *NIX     ELECTRONICS     REVIEWS     ARTEMIA     FAIRY SHRIMP      



Atmel ATmega (ATmega16) DCF77 time signal decoder and C program

This is an example for connecting a DCF-77 time signal module to an Atmel ATmega16 microcontroller. The DCF-77 Time Signal broadcasted from Germany is a precision atomic clock signal broadcasted at 77.5kHz. Many clocks and wrist watches (Funkuhr, Funkarmbanduhr) use this signal for synchronization with the precision of an atomic clock.

Also see:


The hardware:
The DCF-77 module from CONRAD (mail-order for electronics) can be connected directly to the Atmel ATmega's interrupt pins - you just need a pull up resistor, since the output of the module is an open-collector output.
Atmel Atmega DCF-77 schematic



The firmware:
Use avr-gcc or WinAVR to compile it and the Atmel ATmega16 Parallel Port Programmer to program the uC.
The firmware sends a string ("testing printf") at startup and then returns any character sent via the serial port in the interrupt service routine (ISR).
The c-code also sends a time string via the serial port.

Linux software (serterm2) for receiving the initial string and the time string can be found at the PIC - MMC (Multi Media Card) Flash Memory Extension page. Also on this page, the program "ser". It sends three characters ("ABC") via the PC serial port and tries to receive them. These three characters are returned by the MCU via the interrupt service routine.

Furthermore the firmware blinks a LED connected to pin 19 (similar to avrledtest.c at the Atmel ATmega16 Parallel Port Programmer page).



NOTE:
"prinf" from stdio.h is only used for a convenient output of the current time via the serial port. The "printf" stuff ridiculously blows up the code for the already quite small flash-memory, so it is not recommended to use "printf" if you're already running low on program memory.

Based on the firmware by http://www.ulrichradig.de/ . Thanks :-)

dcf77.c
This is V2.0 - V1.0 can be found here
/*
dcf77.c V2.0 (C) www.captain.at (C) www.ulrichradig.de
Changelog from V1.0 to V2.0:
* fixed "59th second" problem - after the 58th bit was received,
  at 59.1s or 59.2s the data of the next minute was loaded into
  the date/time variables and on the first second of the next
  minute the second counter already was incremented to 1.
  This is now fixed.
* added current_time_is_valid_dcf_time flag - if this flag is 1,
  the actual DCF77 time was correctly received for the current
  minute. If the flag is 0, there might have been a parity error
  in the DCF signal, or there is no signal at all. In a production
  environment, one would use a second TIMER for incrementing real
  date/time variables and just sync date/time every 10 minutes or
  so. Don't forget to check the current_time_is_valid_dcf_time flag
  and before reading (synching) the date/time values, don't forget
  to disable the interrupts to avoid reading a bogus date/time. 
*/
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <inttypes.h>
#include <avr/iom16.h>

// crystal frequency in Hz
#define F_OSC 2457600
// oscillator frequencies that match well for the timer AND USART BAUD rate:
// frequency  div    timer count
//  2457600 / 1024 =  2400
//  7372800 / 1024 =  7200
// 11059200 / 1024 = 10800
// 14745600 / 1024 = 14400

#define UART_BAUD_RATE 9600
#define UART_BAUD_CALC(UART_BAUD_RATE,F_OSC) ((F_OSC)/((UART_BAUD_RATE)*16l)-1)

unsigned char sec   = 0;
unsigned char min   = 0;
unsigned char hour = 0;
unsigned char day   = 0;
unsigned char month = 0;
unsigned int  year = 0;
unsigned char bit_counter = 0;
// 64 bits: 59 bits are used
unsigned long long dcf77_buffer = 0;
unsigned int timercounts = 0;
unsigned int flag59 = 0;
unsigned int setbeginningofminute = 0;

unsigned int current_time_is_valid_dcf_time = 0;

#define DCF77_INT            SIG_INTERRUPT0
#define INT0_CONTROL         MCUCR
#define INIT_TIMER_COUNT   (unsigned int)(65535 - (F_OSC / 1024))
#define RESET_TIMER0         TCNT1 = INIT_TIMER_COUNT

#define INIT_TIMER_COUNT_59   (unsigned int)(65535 - (F_OSC / 1024) - 100)
#define RESET_TIMER0_59         TCNT1 = INIT_TIMER_COUNT_59

#define PERCENT9X (unsigned int)( (F_OSC / 1024) / 100 * 98) // 98% percent

//#define DEBUG
//#define DEBUGEDGES
#define TIMEMSGS

struct DCF77 {
   unsigned long long bits       :16; // bits 1 to 16: reserved or not needed here
   unsigned long long mez_mesz   :1; // 1 at transition from MEZ to MESZ or vice versa
   unsigned long long zone1      :1; // 0=MEZ, 1=MESZ
   unsigned long long zone2      :1; // 0=MESZ, 1=MEZ
   unsigned long long leapsecond :1; // If 1, a leap-second is inserted at end of hour
   unsigned long long start      :1; // start bit is always 1
   unsigned long long min        :7; // 7 bits for minutes
   unsigned long long parity_min :1; // parity bit for minutes
   unsigned long long hour       :6; // 6 bits for hour
   unsigned long long parity_hour :1; // parity bit for hour
   unsigned long long day        :6; // 6 bits for day
   unsigned long long weekday    :3; // 3 bits for weekday
   unsigned long long month      :5; // 5 bits for month
   unsigned long long year       :8; // 8 bits for year (5 = 2005)
   unsigned long long parity_date :1; // parity bit for date
};

struct {
   unsigned char parity_flag   :1;
   unsigned char parity_min      :1;
   unsigned char parity_hour   :1;
   unsigned char parity_date   :1;
} flags;

void delay_ms(unsigned short ms) {
   unsigned short outer1, outer2;
   outer1 = 200; 
   while (outer1) {
      outer2 = 1000;
      while (outer2) {
         while ( ms ) ms--;
         outer2--;
      }
      outer1--;
   }
}

int usart_putc(char c) { // output one character via RS232
   // wait until UDR ready
   while(!(UCSRA & (1 << UDRE)));
   UDR = c; // send character
   return (0);
}

void uart_puts (char *s) { // output string via RS232
   //  loop until *s != NULL
   while (*s) { usart_putc(*s); s++; }
}

void init(void) {
   // set baud rate
   UBRRH = (uint8_t)(UART_BAUD_CALC(UART_BAUD_RATE,F_OSC) >> 8);
   UBRRL = (uint8_t)UART_BAUD_CALC(UART_BAUD_RATE,F_OSC);
   // Enable receiver and transmitter; enable RX interrupt
   UCSRB = (1 << RXEN) | (1 << TXEN) | (1 << RXCIE);
   //asynchronous 8N1
   UCSRC = (1 << URSEL) | (3 << UCSZ0);
   // All printf's are sent via the serial port (RS232)
   // This is just for a neat output of the date/time data:
   // printf incredibly blows up the code
   fdevopen(usart_putc, NULL, 0);
}

// INTERRUPT can be interrupted
// SIGNAL can't be interrupted
SIGNAL (SIG_UART_RECV) { // USART RX interrupt
   unsigned char c;
   // read received character
   c = UDR;
   // and just send it back
   usart_putc(c);
}

void add_second (void) { // incomplete implementation
   sec++;
   if (sec == 60) {
      sec = 0;
      min++;
      if (min == 60) {
         min = 0;
         hour++;
         if (hour == 24) {
            hour = 0;
            day++;
         }
      }
   }
}

SIGNAL (SIG_OVERFLOW1) {   
   // UPDATE: 59th second is now set correctly 1 second after the 58th second
   if (flag59 == 1) {
      RESET_TIMER0_59; // slightly longer timer interval, so that we don't
                       //   overlap with the actual DCF77 signal at the beginning
                   //   of the minute
      add_second();
      flag59 = 0;
      // next DCF77 pulse is the beginning of the new minute
      setbeginningofminute = 1;

   } else {
      RESET_TIMER0;
      // we only end up here if there is no DCF77 signal and the TIMER0
      //   is not reset timely => clock runs on internal timer
      add_second();
#ifdef TIMEMSGS      
      printf("SIG_OVERFLOW1: NO DCF77 - INTERNAL MODE\r");
#endif
      bit_counter = 0;
      dcf77_buffer = 0;
      current_time_is_valid_dcf_time = 0;
   }

   // This is just for a neat output of the date/time data:
   // printf incredibly blows up the code
   printf("%d %d-%d-%d %d:%d:%d        \r", current_time_is_valid_dcf_time, 
   day, month, year, hour, min, sec);

}

SIGNAL (DCF77_INT) {
   struct DCF77 *tmp_buffer;
   // if rising edge, pulse signal starts
   if (bit_is_set(INT0_CONTROL, ISC00)) {
#ifdef DEBUGEDGES
      printf("DCF77_INT rising edge \r");
#endif
      unsigned int pulse_width = TCNT1;
      // add elapsed timer counts to timercounts
      timercounts = (unsigned int)(timercounts + pulse_width - INIT_TIMER_COUNT);
      // PIN5 PORTD set -> LED on
      PORTD |= (1<<PD5);
      // reset timer
      RESET_TIMER0;
      // UPDATE: beginning of the new minute, so don't add a second, but use the
      //         received data to set date/time
      if (setbeginningofminute == 1) {
         tmp_buffer = (struct DCF77 *)(unsigned long long)&dcf77_buffer;
         setbeginningofminute = 0;
#ifdef DEBUG
      printf("SIG_OVERFLOW1 bc=%d %d %d %d %d %d %d \r", bit_counter,
             flags.parity_min, tmp_buffer->parity_min,
             flags.parity_hour, tmp_buffer->parity_hour,
             flags.parity_date, tmp_buffer->parity_date);
#endif
         if (bit_counter == 59 && 
            flags.parity_min == tmp_buffer->parity_min && 
            flags.parity_hour == tmp_buffer->parity_hour &&
            flags.parity_date == tmp_buffer->parity_date) {
            // convert all values from BCD (1,2,4,8,10,20,...) to decimal
            min = tmp_buffer->min - ((tmp_buffer->min/16)*6);
            hour = tmp_buffer->hour - ((tmp_buffer->hour/16)*6);
            day = tmp_buffer->day - ((tmp_buffer->day/16)*6);
            month = tmp_buffer->month - ((tmp_buffer->month/16)*6);
            year = 2000 + tmp_buffer->year - ((tmp_buffer->year/16)*6);
            // we received all 59 bits, so we have the start of the minute
            sec = 0;
            current_time_is_valid_dcf_time = 1;
#ifdef TIMEMSGS
            printf("DCF77_INT: TIME SET\r");
#endif
         } else {
            current_time_is_valid_dcf_time = 0;
         }
         // clear bit counter and dcf77 buffer, regardless if DCF77 time was
         //    received correctly or not. Either we have no signal, or we are
         //    at the beginning of the minute, so time sync will work at the
         //    beginning of the next minute
         bit_counter = 0;
         dcf77_buffer = 0;
         timercounts = 0;
         // since the pulse' edge was rising, next interrupt must be triggered
         //    at the falling edge of the DCF-77 pulse
         INT0_CONTROL &= ~(1 << ISC00);
      } else { // UPDATE: no beginning of the minute: just add one second
         // check if at least 9X% of one second have elapsed
         if (timercounts > PERCENT9X) {
#ifdef DEBUG
            printf("timercounts=%u pulse_width=%u\r", timercounts, pulse_width);
#endif
            add_second();
            timercounts = 0;
            // since the pulse' edge was rising, next interrupt must be triggered
            //    at the falling edge of the DCF-77 pulse
            INT0_CONTROL &= ~(1 << ISC00);
         }
      }
      
      // This is just for a neat output of the date/time data:
      // printf incredibly blows up the code
      printf("%d %d-%d-%d %d:%d:%d        \r", current_time_is_valid_dcf_time, 
      day, month, year, hour, min, sec);
      
   } else {
#ifdef DEBUGEDGES
      printf("DCF77_INT falling edge \r");
#endif
      // Falling edge of pulse detected
      // Now we check for the actual pulse width
      unsigned int pulse_width = TCNT1;
      // PIN5 PORTD clear -> LED off
      PORTD &= ~(1<<PD5);
      // Reset timer: DCF-77 signal seems to be working, so we must not add
      //    one second in the timer interrupt service routine (ISR)
      // Remember: this additional stuff is necessary, since we're reseting the
      // timer to avoid false second counts due the timer interrupt
      // UPDATE: don't reset TIMER0 when the 58th bit comes in, so that
      //         the TIMER0 overflows when the 59th bit would occur (it doesn't
      //         occur, since DCF77 doesn't broadcast a 59th bit, but we want
      //         an accurate 59th second count)
      if (bit_counter < 58) {
         RESET_TIMER0;
      }
#ifdef DEBUG
      printf("bit_counter=%d\r", bit_counter);
#endif
      if (bit_counter == 58) { // = 59 after the end of this SIGNAL
         flag59 = 1;
      }
      // add elapsed timer counts to timercounts
      timercounts = (unsigned int)(timercounts + pulse_width - INIT_TIMER_COUNT);
      // Checking of the parity
      // We check only minute, hour and the date for partiy, so reset the
      // temporaty parity flag when minute, hour or date bit-stream starts
      if (bit_counter ==  21 || bit_counter ==  29 || bit_counter ==  36) {
         flags.parity_flag = 0;
      }
      // If the last bit of minute, hour or data is received, save our caluculated
      // parity flag
      if (bit_counter ==  28) {flags.parity_min = flags.parity_flag;};
      if (bit_counter ==  35) {flags.parity_hour = flags.parity_flag;};
      if (bit_counter ==  58) {flags.parity_date = flags.parity_flag;};
      // check if we received a 0 or a 1 bit: 100ms = 0; 200ms = 1
      // When there is a rising edge, we restart the timer. When the falling edge
      //   is detected, we save the current timer count register TCNT1 in pulse_width
      // We just check if the pulse width is bigger than 150ms, so we have a 1
      // (F_OSC / 1024)/100*85 = 85% of the counts of the timer (= 1 second)
      // 65535 - (F_OSC / 1024)/100*85) = count of the timer when 150ms are elapsed
      if (pulse_width > (65535 - (F_OSC / 1024)/100*85)) {
         // set the bit "bit_counter" in "dcf77_buffer" to 1
         dcf77_buffer = dcf77_buffer | ((unsigned long long) 1 << bit_counter);
         // If we've just set the initial 1 in i.e. the "day", we toggle the 
         //    parity to 1 to match the "day" value to even parity
         // That means, "day" + parity-bit = even number
         // If we've added the second 1, we toggle the parity back to 0
         flags.parity_flag = flags.parity_flag ^ 1;
      }
      // we just detected the falling edge; next interrupt is at rising edge again
      INT0_CONTROL |= (1 << ISC00);
      // prepare the bit_counter for the next received bit by incrementing it
      bit_counter++;
   }
}

void DCF77_init (void) {
   //DCF77_INT_ENABLE();
   GICR |= (1 << INT0);
   // set INT0 to edge detection
   INT0_CONTROL |= (1 << ISC01);
   // timer overflow interrupt enable
   timer_enable_int(_BV(TOIE1));
   // timer prescaler to 1024 
   TCCR1B |= (1<<CS10 | 0<<CS11 | 1<<CS12); 
   // init timer
   RESET_TIMER0;
}
// end DCF77

int main(void) {
   init();
   sei();
   
   printf("testing printf  \r");

   DCF77_init();

   // enable  PD5 as output
   DDRD |= (1<<PD5);
   while (1) {
   }
   return 0;
}
Makefile:
MCU=atmega16
CC=avr-gcc
OBJCOPY=avr-objcopy
# optimize for size:
CFLAGS=-g -mmcu=$(MCU) -Wall -Wstrict-prototypes -Os -mcall-prologues
#-------------------
all: dcf77.hex
#-------------------
dcf77.hex : dcf77.out 
	$(OBJCOPY) -R .eeprom -O ihex dcf77.out dcf77.hex 
dcf77.out : dcf77.o 
	$(CC) $(CFLAGS) -o dcf77.out -Wl,-Map,dcf77.map dcf77.o 
dcf77.o : dcf77.c 
	$(CC) $(CFLAGS) -Os -c dcf77.c
# you need to erase first before loading the program.
# load (program) the software into the eeprom:
load: dcf77.hex
	uisp -dlpt=/dev/parport0 --erase  -dprog=dapa
	uisp -dlpt=/dev/parport0 --upload if=dcf77.hex -dprog=dapa  -v=3 --hash=32
#-------------------
clean:
	rm -f *.o *.map *.out
#-------------------



Last-Modified: Thu, 01 Mar 2007 21:57:34 GMT

Google
 
Web www.captain.at
go to top
© 1996-2010 . All rights reserved.
No reproduction, distribution, publishing or transmission of the copyrighted materials at this site is permitted. Policy
go to top