Forum: Projekte & Code DCF77 (wieder mal)


von Michael R. (Firma: Brainit GmbH) (fisa)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

Für ein Projekt brauchte ich ein DCF77-Interface, allerdings mit etwas 
anderen Anforderungen, weswegen nix was ich gefunden habe so richtig 
gepasst hat. Daher eben selbst gemacht. ich wills euch nciht 
vorenthalten, und bin für Anregungen und kritik sehr dankbar (AVR ist 
noch etwas Neuland für mich)

Man verzeihe mir die "Arduino"-Restln die noch drinnen sind.

Meine Anforderungen waren:

- DCF-Interface vom Conrad
- kein LCD oder so, sondern möglichst "roh"
- kein Polling, sondern per Pin Change Interrupt
- möglichst wenig Abhängigkeit vom Timer, da dieser (zusammen mit 
demselben Pin Change Interrupt) noch für weitere Signale gebraucht wird 
(im Code aber nciht enthalten)
- Keine Synchronisierung irgendeiner Uhr, da in weiterer Folge nur "ab 
und zu" eine batteriegepufferte Echtzeituhr nachsynchronisiert werden 
soll
- deshalb auch keine Sekundensynchronisation
- deshalb auch wenig bis gar keine Fehlerkorrektur (bis auf 
Spike-Unterdrückung), es reicht wenn alle paar Stunden (oder in der 
Nacht) einmal ein korrektes Signal empfangen wird, und der DS1307 
nachgeführt wird.
- möglichst wenig Arbeit in der ISR

Grundsätzliche Funktionsbeschreibung:

Der Pin Change Interrupt wird verwendet, um die Signale vom 
DCF-Empfänger aufzuzeichnen. Timer 1 läuft "Vollgas" also ohne 
Vorteiler, mit Overflow bei 65536. Das Board läuft mit 16 MHz, sodass 
der overflow mit ~244 Hz erhöht wird, bzw. alle ~4msec. Diese 4msec 
reichen als Auflösung für die DCF-Decodierung, deshalb wird der 
eigentliche Timer-Wert auch gar nicht verwendet (wird aber ausgelesen, 
da später für was anderes benötigt)

In der PCINT2_vect werden die geänderten Bits ausmaskiert, und der 
(momentan einzige) Zweig für das DCF-Bit angesprungen (wird später um 
weitere zweige erweitert)

Je nach steigender oder fallender Flanke wird das Timing ermittelt, zu 
kurze Signale werden als Spike behandelt und ignoriert. Die lange Pause 
des Minutensignals wird verwendet, um a) den Bit-Zähler zurückzusetzen, 
und b) falls vorher ein vollständiger und fehlerfreier Bitstrom 
ermittelt werden konnte, dieser per "valid-Flag" dem Hauptprogramm 
mitgeteilt. kurze pausen werden als 0 oder 1 erkannt und in den 
Bit-Puffer geschrieben. Ein zu langes Signal wird als Fehler vermerkt, 
und der gesamte Frame als fehlerhaft markiert.

Im Hauptprogramm braucht man nur warten bis DCF.valid gesetzt ist, dann 
sollte man (schnell) die Daten verarbeiten, da das nächste erkannte Bit 
die Daten wieder invalidiert (der selbe Puffer wird wiederverwendet bzw. 
überschrieben). Mit DCF_validate sollte man vorher noch diverse 
Plausibilitätskontrollen (Parity, Bereichsprüfungen) durchführen.

Eine kleine Besonderheit stellt das Dekodieren des bitstromes dar: Dies 
erledigt "der Compiler" indem ein entsprechendes großes Bitfeld 
deklariert wird.


So, und nun her mit der Kritik!

lg Michi


Nachtrag: die Timing-Werte vertragen vielleicht noch massiv Optimierung, 
momentan wird davon ausgegangen dass ein overflow alle 4 msec auftritt, 
d.h. ein Wert von 100 entspricht 400 msec....

von holger (Gast)


Lesenswert?

Michael Reinelt schrieb:
> Für ein Projekt brauchte ich ein DCF77-Interface, allerdings mit etwas
> anderen Anforderungen,

...und damit für den Rest der Welt unbrauchbar.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

holger schrieb:
> Michael Reinelt schrieb:
>> Für ein Projekt brauchte ich ein DCF77-Interface, allerdings mit etwas
>> anderen Anforderungen,
>
> ...und damit für den Rest der Welt unbrauchbar.

Warum????

von holger (Gast)


Lesenswert?

Michael Reinelt schrieb:
> holger schrieb:
>> Michael Reinelt schrieb:
>>> Für ein Projekt brauchte ich ein DCF77-Interface, allerdings mit etwas
>>> anderen Anforderungen,
>>
>> ...und damit für den Rest der Welt unbrauchbar.
>
> Warum????

weil Stunden/Minuten in Zehner/Einer getrennt sind und damit nicht 
sinnvoll weiterverwendet werden können.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

holger schrieb:
> weil Stunden/Minuten in Zehner/Einer getrennt sind und damit nicht
> sinnvoll weiterverwendet werden können.

O welch unlösbares Problem für den Rest der Welt...

Hast du dich schon mal mit dem DS1307 auseinandergesetzt?

Der muss absolut unbrauchbar sein, weil der hat Zehner und Einer 
getrennt.

eigenartig, der scheint aber trotzdem recht weit verbreitet zu sein...

im übrigen: von sich selbst auf den Rest der Welt zu schließen, zeugt 
schon von einer gewissen.. ähh.. aja, wir wollten ja höflich sein :-)

von holger (Gast)


Lesenswert?

> Der muss absolut unbrauchbar sein, weil der hat Zehner und Einer getrennt.

die "Timekeeper Registers" sind 8 bit breit und BCD kodiert, da ist 
nichts getrennt.

von Peter D. (peda)


Lesenswert?

Michael Reinelt schrieb:
> - kein Polling, sondern per Pin Change Interrupt

Ich verwende mit Absicht Polling im Timer, da dann Störflanken besser 
unterdrückt werden.
Außerdem senkt das die CPU-Last, wenn das Signal stark gestört ist.

Michael Reinelt schrieb:
> - möglichst wenig Abhängigkeit vom Timer

Ich realisiere das auch. Einen Timer hat man ja immer laufen und da 
bietet sich das Polling z.B. alle 10ms geradezu an.

Michael Reinelt schrieb:
> eine batteriegepufferte Echtzeituhr nachsynchronisiert werden
> soll

Kannst Du bitte mal erklären, warum?
Ich benutzte entweder eine RTC mit Batterie (d.h. T2 am 32kHz Quarz) 
oder DCF77. Aber beides zusammen macht irgendwie keinen Sinn.

Michael Reinelt schrieb:
> Dies
> erledigt "der Compiler" indem ein entsprechendes großes Bitfeld
> deklariert wird.

Würde mich mal interessieren, wie riesig der erzeugte Assemblercode 
dafür ist.
Ich mach das Dekodieren für jedes Bit in "knallharter" Echtzeit direkt 
alle Sekunde. Das ist schön wenig Code und minimalste CPU-Belastung.


Peter

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

holger schrieb:
>> Der muss absolut unbrauchbar sein, weil der hat Zehner und Einer getrennt.
>
> die "Timekeeper Registers" sind 8 bit breit und BCD kodiert, da ist
> nichts getrennt.

Wo ist jetzt der Unterschied zwischen BCD-codiert und getrennt?

Ach vergiss es...

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

@Peter: kann ich mal deinen Code sehen? ohne diskutiert es sich etwas 
schwer...

von Peter D. (peda)


Lesenswert?

Ist schon etwas älter:

Beitrag "DCF77 Uhr in C mit ATtiny26"

Peter

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Danke Peter. Werd ich mir morgen früh reinziehen und dann in epischer 
Breite antworten :-)

von Arno Müller (Gast)


Lesenswert?

Michael Reinelt schrieb:
> Daher eben selbst gemacht.

Und das klassische Beispiel dafür, daß selbst gemacht nicht immer auch 
gut gemacht bedeutet...

> Wo ist jetzt der Unterschied zwischen BCD-codiert und getrennt?

Aua. Wenn du das schon fragen musst...

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Arno Müller schrieb:
> Michael Reinelt schrieb:
>> Daher eben selbst gemacht.
>
> Und das klassische Beispiel dafür, daß selbst gemacht nicht immer auch
> gut gemacht bedeutet...

Manno. Kannst du ausser stänkern auch noch was?


>> Wo ist jetzt der Unterschied zwischen BCD-codiert und getrennt?
>
> Aua. Wenn du das schon fragen musst...

Manno. Kannst du ausser stänkern auch noch was?

von Herman (Gast)


Lesenswert?

> Manno. Kannst du ausser stänkern auch noch was?
Scheinbar nicht, denn sonst hätte er was besseres präsentiert.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Hallo Peter,

ich hab mir nun deinen Code angesehen. Sehr elegant!

> Ich verwende mit Absicht Polling im Timer, da dann Störflanken besser
> unterdrückt werden.
> Außerdem senkt das die CPU-Last, wenn das Signal stark gestört ist.

So richtige Störflanken-Unterdrückung hab ich in deinem Code nicht 
gefunden. Das Problem beim Polling ist halt, dass wenn du grad zufällig 
während eines Spikes pollst, du ein falsches Ergebnis kriegst.

ich hab hier am "Arbeitsplatz" tagsüber einen sehr schlechten Empfang: 
WLAN-AP daneben, Mobiltelefon daneben, Notebook daneben, Bluetooth... 
Störungen ohne Ende. Trotzdem krieg ich erstaunlicherweise sehr wenige 
Störflanken rein (allerdings auch kaum ein gültiges Signal). irgendwie 
scheint das DCF-Modul da schon einiges zu unterdrücken...

> Ich realisiere das auch. Einen Timer hat man ja immer laufen und da
> bietet sich das Polling z.B. alle 10ms geradezu an.
Ok, verstehe. wie schon gesagt - sehr elegant :-)

>> eine batteriegepufferte Echtzeituhr nachsynchronisiert werden
>> soll
>
> Kannst Du bitte mal erklären, warum?
> Ich benutzte entweder eine RTC mit Batterie (d.h. T2 am 32kHz Quarz)
> oder DCF77. Aber beides zusammen macht irgendwie keinen Sinn.

Im Endausbau soll das eine Langzeit-Messung/Überwachung meiner Heizung 
werden. Da möchte ich einigermaßen verläßliche Zeitinfo haben, die auch 
batteriegepuffert ist (deswegen der DS1307). Andererseits weiss ich 
nicht wie genau der DS1307 ist (z.B. Abweichung in einem Jahr) außerdem 
gibts da ja noch die Sommerzeit... deshalb das DCF77 zusätzlich. Das 
Ding wird im Keller stehen, da hab ich zwar schlechten Empfang, aber 
erstaunlicherweise krieg ich doch alle ~15 Minuten einen gültigen (und 
korrekten) Frame. Das reicht dicke.

>> Dies erledigt "der Compiler" indem ein entsprechendes großes Bitfeld
>> deklariert wird.
>
> Würde mich mal interessieren, wie riesig der erzeugte Assemblercode
> dafür ist.

Ohne den Assembler-Code jetzt angesehen zu haben: beim Schreiben der 
DCF77-Bits wird das Bitfeld gar nicht verwendet (der alte "union" 
trick), nur beim Auslesen. Hier gehe ich davon aus dass der Compiler das 
sehr gut optimiert, jeweils ein shift und ein &, mehr sollte da nicht 
übrigbleiben.

Was ich in deinem Code jetzt nicht gefunden habe:

- Sommerzeit-Erkennung (sollte aber sehr einfach nachzurüsten sein)

- diverse Plausibilitätsprüfungen (Wertebereiche z.B. für Monat: 1..12)


lg Michi

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:
> Kannst Du bitte mal erklären, warum?
> Ich benutzte entweder eine RTC mit Batterie (d.h. T2 am 32kHz Quarz)
> oder DCF77. Aber beides zusammen macht irgendwie keinen Sinn.

Die meisten (batteriebetriebenen) Funkuhren machen das ebenso, einfach 
um Strom zu sparen, meist wird dann in der Nacht synchronisiert da dort 
auch die wenigsten Störungen vorhanden sind.

von karsten b. (Gast)


Lesenswert?

Hallo Michael,

super Projekt :) Ich denke, dass das Projekt durchaus für den Rest der 
Welt brauchbar ist. Das kritische ist das Herausfiltern von Störpulsen 
und die korrekte Erkennung von '0' und '1'. Die Auswertung ist nun nicht 
weiter schwierig. Das Datums- und Zeitformat kann wohl jeder für sich 
anpassen.
Echtzeituhr ist bei mir auch vorhanden und wird zyklisch mit dem DCF 
abgeglichen. Das zuverlässigste Zeitsignal liefert nun mal die DCF77, 
von der Sommer- und Winterzeitumstellung mal abgesehen. Also eine 
durchaus sinnvolle Kombination.

Ich habe auch ein Projekt mit ähnlichen Anforderungen. Mein ATxmega hat 
keinen externen Quarz und läuft mit dem (ungenauen) internen 2MHz 
Oszillator.

Von dem Oversampling bin ich auch wieder weg, da auch Störpulse 
statistisch gesehen erfasst werden und zu einem falschen Ergebnis führen 
können.
Die genaueste Lösung ist wohl über den Pin-Change IRQ die Pulsbreite 
auszuwerten und zu kurze Pulse zu verwerfen. Geht besser als das 
Oversampling. Machen Dir die Pulsbreiten Probleme, die größer als die 
Spikepulsbreite und kleiner als die minimale Pulsbreite für eine '0' 
liegen? Wie fängst Du kurze Unterbrechungen der '0' und '1' Pulse ab?

Meine Lösung sieht Deiner sehr ähnlich. Meine Software wartet auf zwei 
aufeinanderfolgende gültige Pulse. Daraus erkenne ich, ob die 
Starbedingung für eine neue Minute vorliegt oder ob der Sekundenpuls 
(von steigender Flanke zu steigender Flanke) im Rahmen der 
Messgenauigkeit zuverlässig war
Mein Probleme liegen allerdings darin,
- dass die Pulse vom Pollin-Modul in der Breite teilweise stark 
variieren
- der intern verwendeten Oszillator relativ ungenau ist und damit der 
10ms-Basistimer auch nicht so genau ist (bei mir 9,7ms statt 10ms)

Um die Flanken müssen dann schon große Toleranzbereiche gelegt werden, 
damit der Algorithmus funktioniert.
Meine Überlegungen gehen noch dahin, ob es vielleicht sinnvoll ist die 
Pulsbreite mit den Capture-Compare IRQ auszumessen. Damit würde der 
10ms-Timer nicht mehr benötigt. Ob das Vorteile hat, werde ich nochmal 
ausprobieren.

Karsten

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

karsten b. schrieb:
> Machen Dir die Pulsbreiten Probleme, die größer als die
> Spikepulsbreite und kleiner als die minimale Pulsbreite für eine '0'
> liegen?

Die behandle ich als "Spike"

> Wie fängst Du kurze Unterbrechungen der '0' und '1' Pulse ab?
Führt entweder zu einer falschen Erkennung, oder wird als Spike 
ignoriert, wenn die Zeit ausserhalb der gültigen grenzen liegt

karsten b. schrieb:
> Um die Flanken müssen dann schon große Toleranzbereiche gelegt werden,
> damit der Algorithmus funktioniert.

Das mach ich auch so.

Generell würd ich den Code so nicht mehr schreiben, sondern samplen. Und 
vor allem: Die Idee mit dem riesigen Bitfeld ist, wie Peter Danegger 
schon richtig erkannt hat, nicht so gut. Das Auswerten und die 
Plausibilitätsprüfung sind aufgrund der vielen bit-Operationen sehr 
aufwändig.

Damals habe ich peters Code nciht verstanden, heute würde ich seinen als 
basis verwenden.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.