DCF77 Zeitsignal Erkennung mit AVR Microcontroller
DCF77 ist das Signal, welches Funkuhren ansteuert. Es ist ein Langwellensignal auf 77,5 Khz. In einem Zeitraum von einer Minute werden 59 Informationsbits in dem Langwellensignal codiert. Dieser Datensatz von 59 Bits enthält Uhrzeit, Datum und ein paar weitere Informationen. Jedes Bit wird in einem Zeitraum von einer Sekunde gesendet. Innerhalb dieser Sekunde bedeutet ein Low-Pegel von der Dauer von 10ms eine logische "0" und ein Low-Pegel von der Dauer von 20ms eine logische "1". Die negative Flanke markiert dabei den Beginn eines Bits und auch den Beginn einer Sekunde. Nach dem 58. Bit bleibt das Signal "1" bis zur negativen Flanke der Sekunde 0 (und des ersten Bits) der nächsten Minute. Somit bleibt das Signal mindestens eine Sekunde auf "1", damit kann man den Beginn der Übertragung eines neuen Datensatzes erkennen. Genaueres zu DCF77 kann man hier nachlesen.
Kaum ein AVR-Fan wird um die Implementierung einer Uhr herumkommen, die das DCF77-Signal decodiert.
Der eigentliche Empfänger für das Zeitsignal kann als fertiges Modul beschafft werden, z.B. bei Conrad (ca. 11€). Der Empfänger hat eine kleine Feritantenne, und gibt das Signal direkt und invertiert ab. An diesen Empfänger habe ich noch eine kleine Transistorstufe angeschlossen und das Signal dann so direkt in den AVR hineingeführt. Wenn man den Empfänger an ein Oszilloskop anschliesst, sieht man bei korrekter Synchronisation des Empfängers (Zeitablenkung auf 10ms einstellen) das Zucken der Signale im Sekundentakt. Wenn man da nichts sieht, ist die Antenne nicht sauber ausgerichtet. Obwohl (oder gerade weil?) ich in Frankfurt wohne, muss ich die Antenne nach Süden ausrichten, sonst bekomme ich kein Signal. Der DCF77-Sender steht in Mainflingen ca. 20km südöstlich von Frankfurt am Main.
DCF77
Empfänger. Der Empfänger besitzt 4 Anschlüsse (GND, Vcc, Ausgangssignal
und Ausgangssignal invertiert (offener Kollektor)).
Das am invertierten Ausgang vorhandene Signal wird an die
Transistorstufe rechts angeschlossen, dort noch mal invertiert
so dass am AVR das Signal wie im Text beschrieben anliegt.
Das empfangene Signal ist der Zeitwert für die folgende Minute. Es werden keine expliziten Sekundenwerte geliefert. D.h. wenn das 59. Bit kommt, kann man den bisher gelesenen Zeitwert als korrekte Uhrzeit nehmen und den Sekundenwert der AVR-internen Software-Uhr auf Null setzen. Diese Software-Uhr muss man implementieren, z.B. mit einem Timer und Timer-Interrupt. Die Uhr läuft also auch ohne DCF77 brav vor sich hin.
Die Bedeutung der einzelnen Bits des Signals ist unter http://de.wikipedia.org/wiki/DCF77 ausführlich beschrieben.
Das DCF77-Signal kann man über einen der externen Interrupts (INT0/1) des AVR softwaremässig aufnehmen. Bei jeder negativen Flanke kann man den Interrupt auslösen lassen und dann einen weiteren Timerwert ab Null hochzählen. Man konfiguriert gleichzeitig die Interruptbedingung so um, dass der nächste Interrupt bei einer positiven Signalflanke ausgelöst wird. Wenn die positive Flanke kommt, kann man dann aus dem Timerwert ablesen, ob es sich um eine Null oder eine 1 handelt. Wenn z.B. 200-mal pro Sekunde der Timerwert hochgezählt wird, bedeutet ein Wert um die 20 dass 10ms verstrichen sind (eine "0"), ein Wert um die 40 dass 20 ms verstrichen sind (eine "1").
Die einkommende Bitfolge kann man direkt auswerten oder in einem Array ablegen. Aus der Bedeutung der Bits heraus können dann die Werte für Uhrzeit, Datum etc. berechnet werden.
Wenn der Start einer Minute eintritt (negative Flanke für Bit 0), werden die DCF77-Werte in die interne Uhr des AVR kopiert.
Damit hat man die Funktion einer Funkuhr im wesentlichen implementiert.
#define DEBOUNCE 200L /* timer isr is called as
many times per second */
volatile uint16_t clock_timer; // clock timer value
volatile uint16_t bit_timer; // bit timer value
volatile uint16_t bit_ticks_0; // ticks with value "0"
volatile uint16_t bit_sequence; // ticks with value "0" or "1", i.e.
whole bit sequence
volatile uint8_t irq_falling_edge; // 1= irq o falling, 1= irq on
raising edge
uint8_t bits[60]; // array for each bit received
// define for "undefined time value"
#define UNDEF_TIME 99
// vars h,m,s for the timer driven internal clock
volatile uint8_t s=0, h=0,m=0;
// vars for dcf driven clock and date values
volatile uint8_t dcf_day=UNDEF_TIME, dcf_y=UNDEF_TIME,
dcf_mo=UNDEF_TIME, dcf_d=UNDEF_TIME,
dcf_h=UNDEF_TIME, dcf_m=UNDEF_TIME;
// Interrupt Service Routine
// This routine is called when the Timer Value TCNT1 reaches the
Output Compare Register Value OCR1A
//
ISR(TIMER1_COMPA_vect) {
bit_timer++;
#if SYSCLK % DEBOUNCE
OCR1A = SYSCLK / DEBOUNCE - 1;
#endif
if (--clock_timer==0) {
clock_timer=DEBOUNCE;
s++;
#if SYSCLK % DEBOUNCE
OCR1A = SYSCLK / DEBOUNCE +
SYSCLK % DEBOUNCE - 1;
#endif
}
}
// Interrupt Service Routine
// This routine is called when IRQ0 occurs
//
ISR(INT0_vect) {
if (irq_falling_edge==1) {
bit_sequence=bit_timer; // save
value
bit_timer=0; // reset bit_timer
irq_falling_edge=0; // wait for
next raise
MCUCR =
(1<<ISC00)|(1<<ISC01); // raise int0 on rising edge
} else {
bit_ticks_0=bit_timer; // save
value
irq_falling_edge=1; // wait for
next fall
MCUCR = (1<<ISC00); //
raise int0 on falling edge
}
}
Im Hauptprogramm macht man dann folgendes:
int main(void) {
int bit_number=0;
volatile uint8_t bit_value; // the resulting bit
value, 0 or 1
char *str; // some string for text
ioinit();
fdevopen(uart_putchar, NULL );
printf("\n\nDCF Clock\n\n");
// Initialisierung:
// (1<<CS10) : Timer1 Vorteiler = 001 = 1. Der
Zähler wird also mit f=8Mhz hochgezählt
// OCR1A=XTAL/DEBOUNCE-1 -> Bei Erreichen dieses
Wertes (39999) wird die ISR besucht
// die ISR wird also alle 1/200s aufgerufen. Wenn
man dort also bis 200 hochzählt, ist genau
// 1 Sekunde um! 200 ist der Wert der Variable
clock_timer und wird über das define DEBOUNCE festgelegt
TCCR1B = (1<<CS10) ^
(1<<WGM12); // Prescaler of 1 | CTC mode
OCR1A = SYSCLK/DEBOUNCE-1;
// Output compare register value
TCNT1 = 0; // Start value for timer register
s=0; // Initialize second value (s) to zero
clock_timer = DEBOUNCE;
TIMSK |= (1<<OCIE1A); //
activate timer interrupts which starts timer run
// INT0
bit_ticks_0=0;
bit_sequence=0;
irq_falling_edge=1; // start with falling edge
detection
MCUCR = (1<<ISC00); // raise int0 on falling
edge
GIMSK |= (1<<INT0); // enable external int0
/*
* now enable interrupt, since UART and TIMER
library is interrupt controlled
*/
sei();
for(;;) {
// timer driven clock: the second
value (s) is increased by interrupt.
// set up m and h according to s
changes
if (s>=60) {
s=0;
m++;
if (m>=60) {
m=0;
h++;
if (h>=24) {
h=0;
}
}
}
//printf( "%02d:%02d:%02d\r", h,
m, s);
// check what bit value was
received (0 or 1)
if (bit_ticks_0>0) {
if(bit_ticks_0>25)
bit_value=1;
else
bit_value=0;
if
(bit_sequence>250) {
// we are at falling edge after second 59
bit_number=0;
str="- Start of Minute";
// now copy DCF clock values to timer driven clock
set_clock();
} else
str="";
// decode bits
as far as possible
decode(
bit_number, bit_value, str );
// clear
timers for next bit
bit_sequence=0;
bit_ticks_0=0;
// increase
bit counter
bit_number++;
}
}
}
Die Funktion decode() berechnet die DCF77 Werte aus den Bits, die Funktion set_clock() setzt die internen Uhrenwerte aus den DCF77-Werten:
//
// incrementally decode bits collected so far.
// for meaning of bits, see DCF description
//
void decode( uint8_t i, uint8_t bit_value, char *string ) {
printf("bits[%02d]=%d (%d of %d ticks low) %s\n", i,
bit_value, bit_ticks_0, bit_sequence, string );
// save bit value
bits[i] = bit_value;
if (i==28) {
// minute value 0..59 complete
dcf_m =
bits[21]+bits[22]*2+bits[23]*4+bits[24]*8+bits[25]*10+bits[26]*20+bits[27]*40;
}
if (i==35) {
// hour value 0..24 complete
dcf_h =
bits[29]+bits[30]*2+bits[31]*4+bits[32]*8+bits[33]*10+bits[34]*20;
}
if (i==41) {
// date value 1..31 complete
dcf_d =
bits[36]+bits[37]*2+bits[38]*4+bits[39]*8+bits[40]*10+bits[41]*20;
}
if (i==44) {
// weekday 0..6 complete
dcf_day =
bits[42]+bits[43]*2+bits[44]*4;
}
if (i==49) {
// month 1..12 complete
dcf_mo =
bits[45]+bits[46]*2+bits[47]*4+bits[48]*8+bits[49]*10;
}
if (i==57) {
// year 00..99 complete
dcf_y =
bits[50]+bits[51]*2+bits[52]*4+bits[53]*8+bits[54]*10+bits[55]*20+bits[56]*40;
}
printf( "DCF: %d, %02d.%02d.20%02d %02d:%02d - AVR
%02d:%02d:%02d\r",
dcf_day,
dcf_d, dcf_mo, dcf_y, dcf_h, dcf_m,
h, m, s );
}
//
// copy dcf values to internal clock values
//
void set_clock( void ) {
h = dcf_h;
m = dcf_m;
s = 0;
}