Dieser Artikel beschreibt den Aufbau einer einfachen DCF77 Funkuhr, die das Funksignal aus Frankfurt mit Hilfe einer kleinen Schaltung dekodiert.
Dieser Artikel baut auf das Projekt "Anschluß eines LCD Display" auf. In diesem Projekt wurde eine
Schaltung aufgebaut, mit deren Hilfe ein LCD-Display Text anzeigt. Nun nutzen wir dieses Display zur
Anzeige der von DCF77 gelesenen Uhrzeit.
Um das Signal einfach zu dekodieren, bedienen wir uns einer kleinen Platine von Conrad Elektronik
 |
Diese Uhrplatine vereinfacht den Aufbau wesentlich. Alles was zu tun ist,
ist die Uhr mit der Versorgungsspannung von 5 Volt zu verbinden und eine
Pullup-Widerstand (16k) auf Klemme 4 zu führen. Von dort bekommen wir
von nun an einen saubern TTL Takt, der das Zeitsignal beschreibt. |
DCF77 ist ein Zeitsignal und wird auf der Normalfrequenz 77,5 kHz als Zeitinformation in kodierter
Form ausgestrahlt. Die Sendeleistung beträgt etwa 50 kW bei einer ausgestrahlten Leistung von etwa
30 kW in einer Reichweite von ca. 2000 km im Umkreis um Frankfurt/Main bzw. Mainflingen.
Das DCF77 Signal sendet pro Sekunde einen Impuls. Ist dieser Impuls 0,1 Sekunden lang, wurde eine NULL
gesendet. Ist das Signal hingegen 0,2 Sekunden lang, wurde eine EINS gesendet. So können also pro Minute
59 BIT gesendet werden. Das letzte Bit wird nicht verwendet und markiert eine "schweige Sekunde", um den
Funkuhren die Synchronisation auf die volle Minute zu erleichtern. Im ganzen baut sich das Signal also wie
folgt auf :
| Dieser Kreis zeigt sehr schön, zu welchen Sekunden welches Bit zu
erwarten ist. Wir haben in unserer Beispielprogrammierung lediglich Stunde,
Minute und den Tag ausgewertet. So bleibt ein wenig Paltz für eigene
Entwicklungen ;-) |
 |
Wer sich jetzt noch genauer Informieren möchte, kann sich bei http://www.heret.de/funkuhr/index.htm
bestens informieren. Es gibt sicherlich noch eine Vielzahl anderer Seiten, aber diese hat mir am meisten
weitergeholfen. Um nun endlich zu beginnen schließen wir die Klemme vier der DCF Platine an den Interrupt1
Eingang des Atmel. Da dieses Signal durch einen Pullup (16k) zwischen 5V und 0V schwankt erhalten wir in
Folge immer einen Interrupt, sobald eine Sekunde beginnt. Der Interrupt muss demnach auf eine Steigende
Flanke reagieren. Tritt nun ein Interrupt auf, lassen wir einen Zähler loslaufen, dessen Hochzählen erst
durch das Wegfallen des DCF Signal beendet wird.
So messen wir also die Zeit, in der der Impuls angelegen hat. Alles was nun noch zu tun ist, ist diesen Zähler
zu "kallibrieren". Wir bestimmen wieviele Inkrements durchgeführt wurde, wenn eine NULL gesendet wird und wieviele
bei einer EINS. Genau den Mittelwert nehmen wir als Kriterium, ob eine NULL oder EINS gesendet wurde. Zusätzlich
simulieren wir in einem Softwareinterrupt ein Uhrwerk, welche Minuten / Sekunden / Stunden selbständig zählt.
Das Uhrwerk misst eine Sekunde, indem wir auch dort einen Zähler stetig erhöhen. Hat er 15600 erreicht, ist eine
Sekunde vergangen. So tickt die Uhr auch dann weiter, wenn durch Empfangschwierigkeiten das DCF Signal nicht sauber
eliefert wird.
Ihr könnt das nun zu eine Projekt in CodeVision zusammenfügen und ausprobieren. Das war wieder nicht sehr schwer
und macht ein riesen Spass .... oder ?
Ich werde in Kürze ein komplettes Projektfile hier noch hinzufügen ...
Viel Spass beim Probieren und Spielen.
Bernhard
|
Begleitende Codebeispiele und Erklärungen
Die Software verwendet zwei Zählervariablen. Die Variable g_count zählt das normale
Uhrwerk und g_count2 wird zum Stoppen der DCF Signallänge genutzt. Die Struktur dcf
speichert die gelesenen Werte. Alle Variablen sind global definiert :
unsigned int g_count=0; // Dieser Wert wird vom Timer 15000mal in der Sekunde
erhöht ( ms zählen )
unsigned int g_count2=0; // Dieser Wert wird vom Timer erhöht und ermöglicht
dem INT1 ein Timing
struct dcf { // DCF Signal dekodiert
unsigned char sec;
unsigned char minute;
unsigned char stunde;
unsigned char tag;
unsigned char wochentag;
unsigned char monat;
unsigned char jahr;
unsigned char insync; //0=Kein Sync gefunden 1=Sync gefunden 2=Zeit ist ok
}g_dcf;
// SyncModes der DCF Uhr
#define NOSYNC 0
#define PRESYNC 1
#define INSYNC 2
interrupt [TIM0_OVF] void timer0_ovf_isr(void) {
static unsigned char val=3,i;
static unsigned char test[12];
// Uhrwerk
g_count++;
g_count2++;
//************* Inbuild Uhr weiterzählen ********************
// halbe Frequenz (15625) plus Korrektur wegen vorlaufen (2)
// 15627 Tics=1 Sekuden bei 8 MHZ
if(g_count==15600) {
g_zeit[2]++;
g_count=0;
}
// Wenn Sekunde 60 erreicht hat Minute erhöhen
if(g_zeit[2]==60) {
g_zeit[1]++;
g_zeit[2]=0;
}
// Wenn Minute 60 erreicht hat Stunde erhöhen
if(g_zeit[1]==60) {
g_zeit[0]++;
g_zeit[1]=0;
}
// Stundenüberlauf beachten = 1 Tag
if(g_zeit[0]==24) {
g_zeit[0]=0;
}
// Uhrwerk ende
//************* DCF Uhr dekodieren ********************
// Steht ein Signal kürzer als 0,1 Sekunden an wurde ein 0 gesendet andernfalls
eine 1
if(PIND.3==0) { // DCF Signal liegt an
if(g_count2<2500) val=0;
if(g_count2>2500) val=1;
gcountsave=g_count2;
} else { // DCF Signal fertig nun dekodieren
if(val!=3 && g_dcf.insync!=NOSYNC && gcountsave>1000) {
// Val ist <3 wenn der Wert noch nicht verarbeitet wurde
// und die Uhr mit DCF in Sync ist
// und es wurde kein flattern festgestellt (gcount<1000)
if(g_dcf.sec==5) {
g_dcf.minute=0; // Minute initialisiern für Neuempfang
g_dcf.stunde=0; // Stunde initialisiern für Neuempfang
g_dcf.wochentag=0; // Stunde initialisiern für Neuempfang
}
//zwischen der 21-28 Sekuden wird die Minute gesendet
if(g_dcf.sec>=22 && g_dcf.sec<=28) {
g_dcf.minute>>=1; // Bit schieben
g_dcf.minute|=val*64; // Bits von oben einfügen hinzufügen
}
if(g_dcf.sec==29) {
if(checkPBit(g_dcf.minute,val)==0) {
// Prüfe ob das PBit zum Wert passt
g_dcf.minute=bcd2bin(g_dcf.minute); // Zeit umrechnen
} else {
g_dcf.insync=NOSYNC; // Fehler festgestellt
}
}
//zwischen der 29-35 Sekunde wird die Stunde gesendet
if(g_dcf.sec>=30 && g_dcf.sec<=35) {
g_dcf.stunde>>=1; // Bit schieben
g_dcf.stunde|=val*64; // Bits von oben hinzufügen
}
if(g_dcf.sec==36) {
g_dcf.stunde>>=1;
// zusätzlich ein Bit schieben, da Stunde nur 6 Bit lang ist
// Die Funktion bcd2bin würde aber sonst nicht funktionieren
if(checkPBit(g_dcf.stunde,val)==0) { // Prüfe ob das PBit
// zum Wert passt
g_dcf.stunde=bcd2bin(g_dcf.stunde); // Zeit umrechnen
} else {
g_dcf.insync=NOSYNC; // Fehler festgestellt
}
}
//zwischen der 42-44 Sekunde wird der Wochentag gesendet
if(g_dcf.sec>=43 && g_dcf.sec<=45) {
g_dcf.wochentag>>=1; // Bit schieben
g_dcf.wochentag|=val*4; // Bits von oben hinzufügen
}
if(g_dcf.sec==46) {
g_dcf.wochentag=bcd2bin(g_dcf.wochentag);
// alt:Eins abziehen, da Sonntag = 7 wir aber ein TextArray ab 0
// führen
// neu: das Frei pos=0 hat nicht mehr notwendig
}
val=3; // Wert wurde verarbeitet
}
// Irgend ein Fehler ist passiert alles zurücksetzen
if(g_dcf.sec==50 && g_dcf.insync==NOSYNC) {
g_dcf.stunde=g_dcf.minute=g_dcf.wochentag=99;
} else {
// Warum wurde das Signal nicht ausgewertet ? Hier Debug ausgaben
;
}
// External Interrupt 1 service routine
// Wird durch DCF Uhr angesteuert
// Hier wird die Uhrezit zentral gesteuert
interrupt [EXT_INT1] void ext_int1_isr(void)
{
// Feststellen ob der Prozessorcounter länger als ein Sekunde nicht
// zurückgesetzt wurde
// Zeitlücke zwischen 58 und 60 Sekunde erkannt. Sekunde = 0
if(g_count2>28000) {
if(g_dcf.insync < PRESYNC) {
g_dcf.insync=PRESYNC;
// In Sync / Freigabe zum Dekodieren der DCF Zeit
g_lastdcf_count=3;
} else {
// Zeit übernehmen falls keine Fehler entstanden sind
// Fehler wird mit Minute und Stunde =0 deklariert
if(g_dcf.minute>60 || g_dcf.stunde>24) {
g_dcf.insync=NOSYNC; // Fehler erkannt zurück in No-Sync Modus
//g_dcf.minute=g_dcf.stunde=0;
} else {
g_zeit[2]=1;
// Sekunde gleich als erstes korrigieren +1, damit Uhr sich nicht selbst
weiterstellt
g_lastdcf[--g_lastdcf_count]=(int)g_dcf.stunde*100+g_dcf.minute;
// Wenn drei Werte gelesen worden sind, muss überprüft werden, ob diese in
Reihe liegen.
if(g_lastdcf_count==0) {
if (g_lastdcf[0]-1==g_lastdcf[1] && g_lastdcf[1]-1==g_lastdcf[2]) {
g_zeit[0]=g_dcf.stunde;
g_zeit[1]=g_dcf.minute;
g_zeit[3]=g_dcf.wochentag;
printf("INT1: Uhrzeit im System gesetzt");
}
g_lastdcf_count=3;
}
g_count=0;
g_dcf.insync=INSYNC; // Uhr ist Fehlerfrei in Sync
}
}
g_dcf.sec=0;
}
if(g_count2>10000) { //Normales DCF Sekunden signal erkannt --> Sekunde erhöhen
g_count2=0;
g_dcf.sec++;
}
}
// Diese Funktion überprüft, ob das Prüfbit zu dem Empfangenen Wert passt
// Das Prüfbit muss die Anzahl der Einsen im byte auf eine gerade Zahl ergänzen
unsigned char checkPBit (unsigned char data,unsigned char pb) {
unsigned char t=1,i=0,p=0;
for (i=0;i<8;i++) {
if((data&t)==t)
if(p==0) p=1; else p=0;
t<<=1;
}
if (pb==p) return 0; // Prüfbit stimmt - OK antworten
else return 1;
}
Nach einem Minutendurchlauf hat sich nun die Struktur g_dcf gefüllt. Die Uhrzeit
liegt vor und kann weiter verarbeitet werden.
Wie man im Code sehen kann, wird der externe Interrupt nur zum Anstarten der
Messung verwendet und zum bestimmen des SYNC Modes. Diesen Begriff habe ich
eingeführt, um festzustellen, ob die Tastlücke schon erkannt wurde. Nur so kann
die Zeitbestimmung sinnvoll von statten gehen. Der Software ist immer bekannt,
welche AtomSekunde gerade gesendet wird. Desweiteren ist es für eine Software so
leichter den Zustand auszuwerten. In der Hauptschleife des Programms kann man nun
schön den Zustand der Uhr und die gelesene Zeit auswerten.
void w_Disp(char *a,char *b) {
lcd_gotoxy(0,0);
lcd_puts(a);
lcd_gotoxy(0,1);
lcd_puts(b);
}
//Variablen
int altsekunde;
int g_zeit[3];
while (1) {
// Schreibe Uhrzeit
if(g_zeit[2]!=altsekunde) {
// Zeit u. Wochentag anzeigen mit :Sekunden blinken durch AND Verknüpfung
if(g_zeit[2]&1)
sprintf(zeit,"%02u:%02u %2s
",g_zeit[0],g_zeit[1],g_tag[g_zeit[3]]);
else
sprintf(zeit,"%02u %02u %2s
",g_zeit[0],g_zeit[1],g_tag[g_zeit[3]]);
switch (g_dcf.insync) { // Falls die DCF Uhr noch keinen Sync hat
dieses angeben
case INSYNC:
sprintf(temp,"bereit ");
break;
case PRESYNC:
sprintf(temp,"bereit S");
//sprintf(temp,"%02u:%02u%02uS",g_dcf.stunde,g_dcf.minute,g_dcf.sec);
break;
case NOSYNC:
sprintf(temp,"bereit X");
break;
}
w_Disp(zeit,temp);
altsekunde=g_zeit[2];
}
|