My clock library decoded exccedingly well. However it has one really significant drawback: it consumes quite a lot of resources. One approach to deal with this is to put the clock into a standalone clock module. The only question is how should this module expose the time?
My design rests on the assumption that most existing DCF77 projects will of course look like in the picture below.
My solution is to expose the decoded time in DCF77 format again. You might wonder why this should be useful. After all the decoded signal after the receiver module is already in DCF77 format. Well, the point is again noise. Once the signal gets noisy the receiver will not output a valid DCF77 code. However as long as the signal noise level does not get to bad my clock can still decode it and thus synthesize a valid DCF77 code. Thus the setup would be: connect a DCF77 receiver to the “super filter” and then connect the super filter output to your project’s DCF77 input. Presto you have increased the noise tolerance without any code changes.
The inner workings of the super filter are exhibited in the picture below. It just takes the output of the library and encodes it again as a DCF77 signal.
Before we go into the implementation details lets have a look at the code.
// // www.blinkenlight.net // // Copyright 2014 Udo Klein // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/ #include <dcf77.h> const uint8_t dcf77_analog_sample_pin = 5; const uint8_t dcf77_sample_pin = 19; // A5 const uint8_t dcf77_inverted_samples = 1; const uint8_t dcf77_analog_samples = 1; const uint8_t dcf77_monitor_pin = 18; // A4 const uint8_t dcf77_filtered_pin = 12; const uint8_t dcf77_inverse_filtered_pin = 11; const uint8_t dcf77_second_pulse_pin = 10; const uint8_t dcf77_diff_pin = 7; const uint8_t dcf77_signal_good_indicator_pin = 3; volatile uint16_t ms_counter = 0; volatile DCF77::tick_t tick = DCF77::undefined; uint8_t sample_input_pin() { const uint8_t clock_state = DCF77_Clock::get_clock_state(); const uint8_t sampled_data = dcf77_inverted_samples ^ (dcf77_analog_samples? (analogRead(dcf77_analog_sample_pin) > 200) : digitalRead(dcf77_sample_pin)); digitalWrite(dcf77_monitor_pin, sampled_data); digitalWrite(dcf77_second_pulse_pin, ms_counter < 500 && clock_state>=DCF77_Clock::locked); const uint8_t defensive_output = clock_state<=DCF77_Clock::locked? sampled_data: tick == DCF77::long_tick ? ms_counter < 200: tick == DCF77::short_tick ? ms_counter < 100: tick == DCF77::sync_mark ? 0: // tick == DCF77::undefined --> default handling // allow signal to pass for the first 200ms of each second ms_counter <=200 && sampled_data || // if the clock has valid time data then undefined ticks // are data bits --> first 100ms of signal must be high ms_counter <100; digitalWrite(dcf77_filtered_pin, defensive_output); digitalWrite(dcf77_inverse_filtered_pin, !defensive_output); digitalWrite(dcf77_diff_pin, defensive_output ^ sampled_data); ms_counter+= (ms_counter < 1000); return sampled_data; } void output_handler(const DCF77_Clock::time_t &decoded_time) { // reset ms_counter for 1 Hz ticks ms_counter = 0; // status indicator --> always on if signal is good // blink 3s on 1s off if signal is poor // blink 1s on 3s off if signal is very poor // always off if signal is bad const uint8_t clock_state = DCF77_Clock::get_clock_state(); digitalWrite(dcf77_signal_good_indicator_pin, clock_state >= DCF77_Clock::locked ? 1: clock_state == DCF77_Clock::unlocked? (decoded_time.second.digit.lo & 0x03) != 0: clock_state == DCF77_Clock::free ? (decoded_time.second.digit.lo & 0x03) == 0: 0); // compute output for signal synthesis DCF77::time_data_t now; now.second = BCD::bcd_to_int(decoded_time.second); now.minute = decoded_time.minute; now.hour = decoded_time.hour; now.weekday = decoded_time.weekday; now.day = decoded_time.day; now.month = decoded_time.month; now.year = decoded_time.year; now.uses_summertime = decoded_time.uses_summertime; now.leap_second_scheduled = decoded_time.leap_second_scheduled; now.timezone_change_scheduled = decoded_time.timezone_change_scheduled; DCF77_Encoder::advance_minute(now); tick = DCF77_Encoder::get_current_signal(now); } void setup() { using namespace DCF77_Encoder; Serial.begin(115200); Serial.println(); Serial.println(F("DCF77 Superfilter")); Serial.println(F("(c) Udo Klein 2014")); Serial.println(F("www.blinkenlight.net")); Serial.println(); Serial.print(F("Sample Pin: ")); Serial.println(dcf77_sample_pin); Serial.print(F("Inverted Mode: ")); Serial.println(dcf77_inverted_samples); Serial.print(F("Analog Mode: ")); Serial.println(dcf77_analog_samples); Serial.print(F("Monitor Pin: ")); Serial.println(dcf77_monitor_pin); Serial.print(F("Diff Pin: ")); Serial.println(dcf77_diff_pin); Serial.print(F("Filtered Pin: ")); Serial.println(dcf77_filtered_pin); Serial.print(F("Inverse Filtered Pin:")); Serial.println(dcf77_inverse_filtered_pin); Serial.print(F("Second Pulse Pin: ")); Serial.println(dcf77_second_pulse_pin); Serial.print(F("Signal Good Pin: ")); Serial.println(dcf77_signal_good_indicator_pin); Serial.println(); Serial.println(); Serial.println(F("Initializing...")); Serial.println(); pinMode(dcf77_monitor_pin, OUTPUT); pinMode(dcf77_filtered_pin, OUTPUT); pinMode(dcf77_inverse_filtered_pin, OUTPUT); pinMode(dcf77_diff_pin, OUTPUT); pinMode(dcf77_signal_good_indicator_pin, OUTPUT); pinMode(dcf77_second_pulse_pin, OUTPUT); pinMode(dcf77_sample_pin, INPUT); digitalWrite(dcf77_sample_pin, HIGH); DCF77_Clock::setup(); DCF77_Clock::set_input_provider(sample_input_pin); DCF77_Clock::set_output_handler(output_handler); } void loop() { DCF77_Clock::time_t now; DCF77_Clock::get_current_time(now); if (now.month.val > 0) { Serial.println(); Serial.print(F("Decoded time: ")); DCF77_Clock::print(now); Serial.println(); } DCF77_Clock::debug(); }
As you can see the code is very short. This is because the DCF77 library already contains a DCF77 encoder. Since the encoder is unaware of the Meteo Time protocoll it will deliver “undefined” for those bits. My solution is to route these bits straight from the input to the output. Still I add some denoising for them as well. To be more precise my code will set the first 100 ms and the last 800 ms to the correct output level.
In addition to the plugin replacement the filter will also provide a pulse per second signal which is active while the filter is locked. That is if you see a 1 Hz blink then it filters, otherwise it routes the signal just throuh.
I also added a “diff” output that shows the difference of the synthesized and the received signal. You may notice that it sometimes flickers slightly. This is because the received signal shape is often slightly “wider” than the synthesized signal.
Great Udo just the sort of thing I was after to drive my LCD Master clock without having to recode it. It will also work just as well on my old hard wired logic decoded Master Clock giving it a far higher level of noise tolerance.
Hi Brett, this was exactly the idea. Nice to hear that you find it useful.
Also ich finde die Idee sehr gut.Aber….
ich habe SuperFilter auf einen Arduino mit Quartz geflasht – läuft. Aber: soweit ich das verstehe, steht an Pin 12 das gefilterte Signal an. An diesem Pin erwarte ich dann eigentlich das überareitete Signal. Wenn eine sinnvolle Zeit empfangen wurde, erwarte ich, dass an diesem Pin das passende Zeittelegramm ausgegeben wird. Das ist aber nicht so. Entweder folgt Pin 12 dem Eingangspin oder aber ist dauernd HIGH. Kann ja wohl nicht sein.
Ich dachte, der Sinn wäre, dass unabhängig vom Eingangssignal (nachdem einmal synchronisiert wurde) an Pin 12 eine gültige DCF77 Sequenz ausgegeben wird.
Wenn ich bei meinem Probeaufbau das Originalsignal mit dem Signal an Pin 12 vergleiche, fällt mir kein Vorteil auf.
Was könnte der Fehler sein?
Ohne weitere Details ist das schwierig zu analysieren. Was für ein Board? Welche Einstellungen bei den pins
const uint8_t dcf77_analog_sample_pin = 5;
const uint8_t dcf77_sample_pin = 19; // A5
const uint8_t dcf77_inverted_samples = 1;
const uint8_t dcf77_analog_samples = 1;
Welche Version der Library? D.h. wann wurde die von wo runtergeladen?
Habe umgebaut. Es ist jetzt ein 328 mit Quartz auf einer separaten Platine, RX und TX gehen über ein Arduinoboard ohne 328 an den PC und der SerialMonitor zeigt korrekt an. Nach über einer halben Stunde ist dann “Quality” nicht mehr 0 sondern 2, die LED an Pin 3 (signal_good) geht an und der Monitor zeigt korrektes Datum und Zeit. Jetzt ist auch der Ausgang an Pin 11/12 nicht mehr vollkommen synchron zum Eingang (Der Empfänger liegt relativ dicht neben dem 328 und bei “Quality” 2 beginnt der 328 den Emfang zu stören – läßt sich wohl durch räumliche Trennung beheben.)
Was mich aber vollkommen irritiert: Wenn durch Störungen jetzt “Quality” wieder auf 0 fällt, bleibt die LED an Pin 3 (signal_good) zwar an, aber der Ausgang (Pin 11/12) folgt jetzt wieder direkt dem Eingangssignal (Flackern am Eingang erscheint auch an Pin 11/12). Der Monitor am PC zeigt jedoch weiterhin korrektes Datum und Zeit. Ich habe eigentlich erwartet, dass jetzt bei “Quality” 0 aber vorher korrekt empfangenen Daten, der Ausgang weiterhin die korrekten Daten ausgibt, die Fehler des Eingangsignals also wegfiltert und nicht das fehlerhafte Eingangsignal durchschleift. So wie der Superfilter z.Z. bei mir funktioniert, sehe ich wenig Sinn – also muss irgendetwas bei mir falsch sein.
Nochmal zusammengefasst: Solange “Quality” auf 2 steht, zeigt der Ausgang (Pin 11/12) vollkommen korrektes Verhalten auch bei massiv gestörtem Eingangssignal, fällt “Quality” jedoch auf 0 bleibt LED (signal_good) zwar an aber der Ausgang zeigt das fehlerhafte Eingangssignal – also keineswegs “signal_good”.
Man muss warten können…. Irgendwann erreicht “Quality” Stufe 3. Wenn man dann den Empfänger abklemmt, macht der Superfilter das, was man erwartet: Er gibt weiterhin ein DCF-Telegramm aus. Aber nur, bis “Quality” wieder 0 erreicht hat, und das war schon nach knapp 5 Minuten der Fall. Das ist *sehr* enttäuschend, da der Monitor immer noch korrekte Daten zeigt. Der Superfilter sollte schon in der Lage sein, längere Ausfälle zu überbrücken – soo schlecht ist die “innere Uhr” ja doch nicht.
Hi Uli, das hat verschiedene Aspekte. Zunächst einmal nimmst Du einen schmalbandigen Filter und kaum kommt das Nutzsignal durch nimmst Du das Signal weg. So war das Design nicht gedacht. Laß den Filter doch mal ein paar Tage drin. Dann erhöht die Library die Zeitkonstanten und 30 Minuten ohne Signal sollten überhaupt nicht mehr auffallen.
Auf der anderen Seite hast Du aber schon recht. In der Tat könnte man auch solche Störungen länger überbrücken. Die Frage ist nur: “wie genau?” Ich sehe dazu verschiedene Möglichkeiten:
1) So wie es jetzt ist.
2) Das Signal solange synthetisieren bis nicht mehr garantiert werden kann, daß die Zeitabweichung unter 1/3 Sekunde liegt. Danach zurück auf Variante 1.
3) Das Signal solange synthetisieren bis wieder mal ein sync möglich ist und dann einfach die Zeit springen lassen.
Problem ist: was macht ein Empfänger hinter dem Filter damit? Wenn der Empfänger bisher leidlich getan hat, dann sollte er durch (1) auf jeden Fall besser da stehen weil er häufiger ein fehlerfreies Signal bekommt.
Bei (2) oder (3) bekommt er aber auch dann ein “sauberes” Signal wenn der Filter nur auf dem Quarz läuft. Das Problem tritt auf wenn wieder synchronisiert wird weil dann der Sekundentakt plötzlich springen kann. Darauf sind vermutlich nicht alle Empfänger ausgelegt. Einige aber sicherlich. (Je naiver die Implementation desto eher).
Die Frage ist was nun? Mir fallen dazu drei Möglichkeiten ein:
a) Ich mache das konfigurierbar damit man einstellen kann ob man Variante 1, 2 oder 3 haben will und lasse das per Default auf 1.
b) Wie a aber mit einem anderem Default
c) Ich stelle einfach alle 3 verschiedenen Varrianten auf verschiedenen Pins zur Verfügung.
Im Moment tendiere ich zu b mit dem Default auf Variante 2. Was wäre Deine Meinung dazu?
Hallo!
Zuerst möchte ich mal eines klarstellen: Diese Lib ist super. Programmierung weit über meinem Level. (Dürfte vielen anderen auch so gehen….)
Die Frage ist, für wen oder für welchen Zweck soll diese Lib dienen?
Geht es nur darum, irgendeine Uhr zu betreiben – womöglich noch ohne Sekundenanzeige – dann ist Möglichkeit 3 wohl am sinnvollsten. Wenn die Zeit bei Resynchronisierung um ein/zwei Sekunden springt, merkt das niemand. Vielleicht wäre es in diesem Fall sinnvoll, eine LED “Daten unsicher” zu implementieren.
Will man “sekundengenaue” Zeit, dann muss man sich zwischen Möglichkeit 1 und 2 entscheiden. Möglichkeit 1 ist der Königsweg – aber wenn der Empfang nicht mitmacht, nützt auch der Königsweg wenig. Deshalb tendiere ich ebenfalls zu Möglichkeit 2, bis die Differenz 1/3 Sekunde ist, wird es schon dauern…. Und wenn der Anwender dann wählen kann, welche Möglichkeit er bevorzugt, ist das optimal.
Deine Idee c hat natürlich auch ihren Reiz – ich vermute mal, das dies am meisten Aufwand für Dich bedeutet. Aber wenn ich die Idee richtig verstehe, dann ist das so gedacht, dass an drei verschiedenen Pins die Möglichkeiten 1,2 und 3 parallel ausgegeben werden. Der Anwender muss überhaupt nichts konfigurieren, er muss nur den ihm passenden Pin wählen – komfortabler geht es nicht! Aber: diese Arbeit möchte ich nicht machen müssen 🙂
Das ist alles ziemlich genau gleichviel Aufwand. Letztendlich geht es nur darum eine Entscheidung zu treffen. Das mit den 3 Pins eröffnet die Möglichkeit es falsch anzuschliessen. Deshalb frage ich ja nach Deiner Meinung.
Die Daten unsicher LED ist ja schon da (signal_good_indicator).
Nimm mal bitte die neue Arbeitsversion des Superfilters von hier: https://github.com/udoklein/dcf77_library/blob/b03c0942dabf88581adbae00b32db05b0e5a22e2/examples/Superfilter/Superfilter.ino und teste sie. Sag mir bitte ob Du dir das so vorstellen würdest. Falls Du Dich bei Github registrierst kannst Du Dich auf mein Repository registrieren. Dann bekommst Du sofort mit sobald ich das Teil in den Master Zweig einbaue.
Hallo, habe einige Pins meiner Umgebung angepasst und das Teil 12 Stunden laufen lassen, danach war “quality” auf 23. Dann habe ich den Empfänger abgeklemmt. Es dauerte ziemlich genau eine Stunde, bis “quality” auf Null abgefallen war, dann ging auch der Ausgang “filtered” auf Null und “signal_good” begann zu blinken. “semi_synth.” und “synth.” waren weiter aktiv. Dieser Zustand war auch nach weiteren drei Stunden unverändert – dann habe ich abgebrochen.
So wie das jetzt läuft, finde ich optimal: es stehen drei Ausgänge parallel zur Verfügung, Je nach gewünschter Anwendung wählt man sich den passenden.
Die LED “signal_good” habe ich zuerst falsch interpretiert. Ich dachte, sie bezieht sich auf das Eingangssignal.aber sie gibt wohl die Zuverlässigkeit des Ausgangssignals an – auch OK.
Nochmals vielen Dank für Deine Mühe!
Besten Dank für die Rückmeldung. Bei Gelegentheit nehme ich das dann in den Hauptentwicklungszeig auf. Du bist ja jetzt erst einmal versorgt 🙂
Hallo!
Bis dato hatte ich mich auf den Inhalt des Serial-Monitors verlassen und an den Ausgang des SuperFilters keine Funkuhr angeschlossen. Als ich dies jetzt nachholte, sind die Ergebnisse bei mir so, dass der SuperFilter zum Steuern einer üblichen Funkuhr nicht geeignet ist. Sowohl der originale SuperFilter als auch die neue Arbeitsversion geben an der angeschlossenen Funkuhr die exakte Zeit aus, solange “signal_good” aus ist, der Eingang also einfach auf den Ausgang durchgeschleift wird. Hat der SuperFilter jedoch selbst synchronisiert (= hat selbst korrekte Zeit), dann generiert er das Ausgangssignal selbst. Dazu verwendet er aber (bei mir) fälschlicherweise die aktuelle Zeit, die Funkuhr erwartet in dem DCF-Telegramm aber die kommende Zeit. Dies hat zur Folge, dass die angeschlossene Funkuhr um eine Minute nachgeht.
Zusammengefasst: Der DCF-Encoder verwendet zum Encodieren nicht die Zeit der kommenden Minute.
Bei einem Eigenbau ließe sich dies kompensieren, aber zum Steuern bestehender Uhren ist der SuperFilter so leider nicht geeignet.
Sieht so aus als wärst Du der Erste der das wirklich nutzt. Da habe ich wohl wirklich geschlafen. Ich lege jetzt ein Issue bei Github an: https://github.com/udoklein/dcf77_library/issues/5
Ich denke spätestens Ende nächster Woche ist das behoben. Falls ich Zeit habe noch dieses Wochenende.
Vielen Dank für die Info!
Der Fehler ist behoben. Hat nur eine Zeile gekostet. Bei Github findest Du die Korrektur hier: https://github.com/udoklein/dcf77_library/commit/013a9a63ee38936fd20bd3568d5c915157280a09
Danke!
Läuft perfekt….
(So, after posting this in the wrong language, under the wrong article twice…)
Hi,
thanks for all your work.
One question (for now): As far as I can see, this will work with an Arduino Duemilanove, as it comes with a 16MHz crystal ootb?
Yes, this should work with the Duemilanove.
Hallo,
vielen Dank für die ganze Arbeit die du dir gemacht hast. 🙂
Leider blicke ich nicht ganz bei den Pins durch. Wo genau muss ich meinen DCF77 Empfänger (von Pollin) anschließen? Was muss ich an den vielen anderen Pins anschließen?
Ich benutze einen Arduino Uno (Atmega328p).
const uint8_t dcf77_analog_sample_pin = 5;
const uint8_t dcf77_sample_pin = 19; // A5
const uint8_t dcf77_inverted_samples = 1;
const uint8_t dcf77_analog_samples = 1;
const uint8_t dcf77_monitor_pin = 18; // A4
const uint8_t dcf77_filtered_pin = 12;
const uint8_t dcf77_inverse_filtered_pin = 11;
const uint8_t dcf77_second_pulse_pin = 10;
Ich hoffe du kannst mir bei diesem Problem helfen.
Grüße
https://blog.blinkenlight.net/experiments/dcf77/dcf77-library/ nach unten scrollen “Pin Mappings”. “filtered pin” und “inverse filtered pin” liefern das gefilterte Signal. Der eine “normal” der andere “invertiert”. “Second pulse pin” liefert einen Sekundentakt. Davon abgesehen wird es mit einem original Uno nicht funktionieren. Siehe https://blog.blinkenlight.net/experiments/dcf77/dcf77-library/ runterscrollen zu “Hardware Incompatibilities”.
HI
I am trying to use the super-filter, when i compile it, i get the following errors .
I want to simply have the super filter running on a dedicated 328 between the receiver and a clock.
Have you seen the below before ?
C:\Program Files (x86)\Arduino\libraries\dcf77-development\examples\Superfilter\Superfilter.ino: In function ‘void set_output(uint8_t, uint8_t, uint8_t)’:
Superfilter:68: error: ‘enable’ was not declared in this scope
if (enable) {
^
Superfilter:69: error: ‘threshold’ was not declared in this scope
const uint8_t filtered_output = clock_state < threshold? sampled_data: synthesized_signal;
^
Superfilter:70: error: 'filtered_pin' was not declared in this scope
digitalWrite(filtered_pin, filtered_output);
^
Superfilter:71: error: 'inverted_filtered_pin' was not declared in this scope
digitalWrite(inverted_filtered_pin, !filtered_output);
^
Superfilter:71: error: in argument to unary !
digitalWrite(inverted_filtered_pin, !filtered_output);
^
Superfilter:72: error: 'diff_pin' was not declared in this scope
digitalWrite(diff_pin, filtered_output ^ sampled_data);
^
exit status 1
'enable' was not declared in this scope
Which version are you using?
HI
I am using the below version
https://github.com/udoklein/dcf77/releases/tag/v3.0.0
Im using Arduino IDE version 1.6.8
Sorry for my slow response but I am currently very busy. With 1.6.7 it works but with 1.6.8 and 1.6.9 it fails. Seems 1.6.8 introduced some change. Until I figure out how to fix this I suggest to downgrade to 1.6.7.
Thanks for pointing out this issue.
I found the issue by now. It is caused by the IDE. In my opinion the IDE is buggy. Here is what happens. The IDE will preprocess the sketch before compiling it. In particular version 1.6.7 will not touch the set_output template. The newer versions 1.6.8 and 1.6.9 want to be smarter and mess it up as follows:
#line 66 "/home/udo/Desktop/arduino-1.6.9/libraries/dcf77/examples/Superfilter/Superfilter.ino"
void set_output(uint8_t clock_state, uint8_t sampled_data, uint8_t synthesized_signal)
Of course this will not compile. It replaced the template implementation by a function implementation. I suggest to contact the Arduino IDE experts / create a bug tracker issue.
Hallo,
ich bin vor kurzem auf deine definitiv sehr gute Arbeit gestoßen. Habe den “Superfilter” im Einsatz und der läuft einwandfrei!
Nur kann man den “Superfilter” auch auf einem ATTINY 85-20 PU mit externen 16Mhz Quarz laufen lassen?
Natürlich mit abstrichen in der Funktionalität, damit meine ich eigentlich man müsste die Funktionen (provide_filtered_output , provide_semi_synthesized_output , provide_synthesized_output) frei wählen können.
Benötigt werden eigentlich der 1Hz Ausgang und die aufbereiteten Daten (Ausgang).
Grüße
Der Compiler sagt:
Sketch uses 20390 bytes (71%) of program storage space. Maximum is 28672 bytes.
Global variables use 1121 bytes (43%) of dynamic memory, leaving 1439 bytes for local variables. Maximum is 2560 bytes.
Das Datenblatt sagt zum ATTINY 85: 8k Flash, 512 Bytes SRAM. Das reicht vorne und hinten nicht.
Ich habe mich etwas weiter damit beschäftigt und gesehen, dass in der Library schon einige Mikroprozessoren Verwendung finden können. Dies hatte ich überlesen oder ich hatte in eine ältere Version der Library nachgeschlagen.
Ich danke dir für deine Antwort, die mich selbst zum Nachdenken auffordert.
🙂 Grüße