- Radio RF Electronics


|
|
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.
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
|
|