Bau einer DCF77 Uhr

Erstellt am 26.03.2002 15:34:00Klicks [11942]

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];
		}