DCF77 Scope

This experiment establishes a DCF77 analysis tool. It is useful for troubleshooting. It can be used for the following purposes.

  1. Determine if the DCF77 module is properly connected to the Arduino
  2. Determine if it is “normal” or “inverted” output
  3. Determine the reception quality
  4. Determine the drift of the Arduino’s local clock

Notice that the drift issue is very important in case you want to use my clock code. The details are described in the phase detection experiment.

In order to do so it emulates a rudimentary scope with your Arduino’s oscillator as a time base. The timebase resolution is fixed to 10 ms and one horizontal sweep equals 100 steps or 1 s. For each 10 ms step it will integrate how many 1 ms steps it sees a high signal and render this as a number (0 is rendered as an underscore and 10 is rendered as X). Thus _ indicates a low signal. X indicates a high signal. Anything in between shows signal transitions / fluctuations.

If you have a Blinkenlight Shield or a Blinkenlighty the output will also be rendered to the LEDs which results in immediate visual feedback. This is very useful during optimization of antenna placements.

Notice that the design is by no means specific to DCF77 thus the sketch can also be used to analyze WWVB or MSF time signals.

Here is 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/


const uint8_t dcf77_analog_sample_pin = 5;
const uint8_t dcf77_sample_pin = A5;  // == D19 for standard Arduinos
const uint8_t dcf77_inverted_samples = 1;
const uint8_t dcf77_analog_samples = 1;

const uint8_t dcf77_monitor_pin = A4; // == D18 for standard Arduinos

const uint8_t lower_output_pin = 2;
const uint8_t upper_output_pin = 17;


void stopTimer0() {
    // ensure that the standard timer interrupts will not
    // mess with msTimer2
    TIMSK0 = 0;
}

ISR(TIMER2_COMPA_vect) {
    process_one_sample();
}

void initTimer2() {
    // Timer 2 CTC mode, prescaler 64
    TCCR2B = (0<<WGM22) | (1<<CS22);
    TCCR2A = (1<<WGM21) | (0<<WGM20);
    
    // 249 + 1 == 250 == 250 000 / 1000 =  (16 000 000 / 64) / 1000
    OCR2A = 249;
    
    // enable Timer 2 interrupts
    TIMSK2 = (1<<OCIE2A);
}

uint8_t sample_input_pin() {
    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);
    return sampled_data;
}

void led_display_signal(const uint8_t sample) {
    static uint8_t ticks_per_cycle = 12;
    static uint8_t rolling_pin = lower_output_pin;
    static uint8_t counter = 0;

    digitalWrite(rolling_pin, sample);
        
    if (counter < ticks_per_cycle) {
        ++counter;
    } else {
        rolling_pin = rolling_pin < upper_output_pin? rolling_pin + 1: lower_output_pin;
        counter = 1;
        // toggle between 12 and 13 to get 12.5 on average
        ticks_per_cycle = 25-ticks_per_cycle;
    }
}

const uint16_t samples_per_second = 1000;
const uint8_t bins                = 100;
const uint8_t samples_per_bin     = samples_per_second / bins;

volatile uint8_t gbin[bins];
boolean samples_pending = false;

void process_one_sample() {
    static uint8_t sbin[bins];
    
    const uint8_t sample = sample_input_pin();
    led_display_signal(sample);

    static uint16_t ticks = 999;  // first pass will init the bins    
    ++ticks;

    if (ticks == 1000) {
        ticks = 0;
        memcpy((void *)gbin, sbin, bins);
        memset(sbin, 0, bins);
        samples_pending = true;     
    }

    sbin[ticks/samples_per_bin] += sample;    
}

void setup() {
    Serial.begin(115200);
    Serial.println();

    pinMode(dcf77_sample_pin, INPUT);
    digitalWrite(dcf77_sample_pin, HIGH);

    pinMode(dcf77_monitor_pin, OUTPUT);
    
    for (uint8_t pin = lower_output_pin; pin <= upper_output_pin; ++pin) {
        pinMode(pin, OUTPUT);
        digitalWrite(pin, LOW);
    }

    initTimer2();
    stopTimer0();
}


int32_t sign(int32_t value) {
    return (value>0) - (value<0);
}

void loop() {
    static int64_t count = 0;
    uint8_t lbin[bins];    
    
    if (samples_pending) {      
        cli();
        memcpy(lbin, (void *)gbin, bins);
        samples_pending = false;
        sei();
       
        ++count;
        // ensure the count values will be aligned to the right
        for (int32_t val=count; val < 100000000; val *= 10) {
            Serial.print(' ');
        }
        Serial.print((int32_t)count);
        Serial.print(", ");       
        for (uint8_t bin=0; bin<bins; ++bin) {
            switch (lbin[bin]) {
                case  0: Serial.print(bin%10? '-': '+'); break;
                case 10: Serial.print('X'); break;
                default: Serial.print(lbin[bin]);
            }
        }
        Serial.println();
     }  
}

Once the code is in the Arduino we get a nice log in the serial monitor. Below are some examples to exhibit how to read it.

First is a log from a Blinkenlighty with a DCF77 module. As you can see the signal is very clean. You can also see that after ~6500 seconds the phase of the DCF77 signal drifted about 200 ms relative to the local clock. Since DCF77 is 100% accurate by definition this implies that the Blinkenlighty is 200 ms to fast every 6500 seconds. Or with other words the local clock deviates by 200 ms / 6500 s ~ 31 ppm. This is in the typical range for a crystal clock and very suitable for the library. It is actually very suitable for basically every DCF77 decoder.

 340, +---------+---------3XXXXXXXXXXXXXXXXXXX6---------+---------+---------+---------+---------+---------
 341, +---------+---------3XXXXXXXXXX2--------+---------+---------+---------+---------+---------+---------
 342, +---------+---------4XXXXXXXXXXXXXXXXXXXX3--------+---------+---------+---------+---------+---------
 343, +---------+---------3XXXXXXXXXX1--------+---------+---------+---------+---------+---------+---------
 344, +---------+---------2XXXXXXXXXXXXXXXXXXXX2--------+---------+---------+---------+---------+---------
 345, +---------+---------5XXXXXXXXXXXXXXXXXXXX2--------+---------+---------+---------+---------+---------
 346, +---------+---------+XXXXXXXXXX2--------+---------+---------+---------+---------+---------+---------
 347, +---------+---------3XXXXXXXXXX---------+---------+---------+---------+---------+---------+---------
 348, +---------+---------+XXXXXXXXXX2--------+---------+---------+---------+---------+---------+---------
 349, +---------+---------3XXXXXXXXXX8--------+---------+---------+---------+---------+---------+---------
...
6500, +---------+---------+---------+---------4XXXXXXXXXX3--------+---------+---------+---------+---------
6501, +---------+---------+---------+---------4XXXXXXXXXX2--------+---------+---------+---------+---------
6502, +---------+---------+---------+---------4XXXXXXXXXXXXXXXXXXXX---------+---------+---------+---------
6503, +---------+---------+---------+---------2XXXXXXXXXXXXXXXXXXXX---------+---------+---------+---------
6504, +---------+---------+---------+---------3XXXXXXXXXXXXXXXXXXX8---------+---------+---------+---------
6505, +---------+---------+---------+---------2XXXXXXXXXXXXXXXXXXXX2--------+---------+---------+---------
6506, +---------+---------+---------+---------3XXXXXXXXXX1--------+---------+---------+---------+---------
6507, +---------+---------+---------+---------3XXXXXXXXXXXXXXXXXXX9---------+---------+---------+---------
6508, +---------+---------+---------+---------2XXXXXXXXXX2--------+---------+---------+---------+---------
6509, +---------+---------+---------+---------2XXXXXXXXXXXXXXXXXXX9---------+---------+---------+---------
6510, +---------+---------+---------+---------4XXXXXXXXXXXXXXXXXXX9---------+---------+---------+---------
6511, +---------+---------+---------+---------+XXXXXXXXXXXXXXXXXXXX---------+---------+---------+---------
6512, +---------+---------+---------+---------1XXXXXXXXX9---------+---------+---------+---------+---------

The next log shows a period with very poor signal quality. This is the kind of signal quality where almost all DCF77 decoders will fail. My clock has no difficulties to decode this kind of signal. The only noticable impact is that it will take several minutes longer as usual until it acquires a first signal phase lock.

  1, +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------
  2, +-432----1XXXX6-----5XXXXXXXXXXXXXXXXXXXXXXX6-----+---------+3--------+5--------+---------+--X5-----
  3, +------2--XXXXXX8--XXXXXXXXXXXX1--------+---8XX---+------5--+---------+--3-XXXXXXXXXXX311X1---------
  4, +---5XXXXXXX6-------463XXXX66XXX835-6XX-+--17-----+---------+------3X52X1-4-683XXXXXXXXX6XX5----3XX4
  5, +---------+---------5XXXXXXXXX7---------+----6XX75X4--------+--8XXXXXXX---------6---------+XXXXXX3--
  6, +-----2---+518------+XXXXXX7-XXXXXX73XX7+--12-----+---------+-----9---+--6X9-864XX6-------+---------
  7, +8XX1---X-9XX2--7XX2+-------482XXXXX8---+------1911------2X1+---------+16-------+---------+------2XX
  8, XXX2------+-----77-2XXXXXXXXX62XXXXX2---+---------+2-----28-+-2XX8----6XX1---7XXX3-----4X-+53-X8--63
  9, 6X8-------+------X5-6XX2--9XXXX1--------+---------+---3X5-79+-4X6---27+---------+---------+----3----
 10, +-----6XXXXXXXXXX9--92-XXXXXXXX-9XX2----+7XXXXXX6-+---------+-28XX32--+-------16+-9XXXX396XX743-----
 11, +------8XXXXX25-----+57XXXXXXX1X94XXXXXX522-------+--------5+---------+-----6XX24---------+------5--
 12, +-----6XX6+---------7XXXXXXXXX5-XX2-----9--------667----68--+---------+---------4-2XXX--33XXXX1--17-
 13, +-7X-2XX846--------4XXXXXXXXXXXXXXXXXXXXX6--------+---------+---------6X7----3XXX1--------+----88---
 14, +---------+--------3X2-6XXXXXXXXXXXXXXXXXXX2------+---------+---------+---------XXXXX3----+---------
 15, +--8XXXX7-+--2XXXXXXXXXXXXXXXXXXXXXXXXXXX---------+--3XXXX3-+--67-----+-49--8X--+---------+---------
 16, +359XXX9--+--------3XXXXXXXXXXX5--------+-47X-----+-------64+-7-------+---------+----79---+--3------
 17, +----56---+---------+1XXXXXXXXXXXXX--141XX8XX4-89-+---------+--------1XX9-----3XX1-XX-----+--XXXX5--
 18, 5XX12X2---+---------4X9XXXXXXXXX6-------+-6-------+---------+-------7XXXX53-64--+---------+25XXX8-4X
 19, X9--------+--8XXX97XXXXXXXXX5-+---------+---------+--------X73--7XXXXX7--------1+-----71--+6X1------
 20, +-------9XX6----2X8X8XXXXXXXXXXXX-------+99-------+--7X4----+1X5------+7X2------+--9XXX27XXXXXXXXXX6

The third example exhibits a log from an Arduino Uno. Notice how the signal phase drifts 200 ms in about 150 s. That is the oscillator is to slow by 200 ms / 150 s ~ 1300 ppm. For a resonator this is actually a pretty good accuracy, most Unos are significantly worse. For my clock library this is not good enough though. It requires an oscillator with 100 ppm or better. With other words: although the signal quality is very good my library would not be able to decode it if it is processing it on an Arduino Uno.

  1, +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------
  2, +---------+---------+---------+---------+---------+----3XXXXXXXXXXXXXXXXXXXX9---+---------+---------
  3, +---------+---------+---------+---------+---------+----2XXXXXXXXXX3---+---------+---------+---------
  4, +---------+---------+---------+---------+---------+----5XXXXXXXXX8----+---------+---------+---------
  5, +---------+---------+---------+---------+---------+----6XXXXXXXXXX4---+---------+---------+---------
  6, +---------+---------+---------+---------+---------+----3XXXXXXXXXX1---+---------+---------+---------
  7, +---------+---------+---------+---------+---------+-----XXXXXXXXXX1---+---------+---------+---------
  8, +---------+---------+---------+---------+---------+----5XXXXXXXXXXXXXXXXXXXX1---+---------+---------
  9, +---------+---------+---------+---------+---------+---2XXXXXXXXXXXXXXXXXXXXX----+---------+---------
 10, +---------+---------+---------+---------+---------+---1XXXXXXXXXXXXXXXXXXXX1----+---------+---------
 11, +---------+---------+---------+---------+---------+---5XXXXXXXXX9-----+---------+---------+---------
 12, +---------+---------+---------+---------+---------+---8XXXXXXXXXXXXXXXXXXX3-----+---------+---------
 13, +---------+---------+---------+---------+---------+---3XXXXXXXXXX2----+---------+---------+---------
 14, +---------+---------+---------+---------+---------+---1XXXXXXXXXXXXXXXXXXX7-----+---------+---------
 15, +---------+---------+---------+---------+---------+---5XXXXXXXXXX2----+---------+---------+---------
 16, +---------+---------+---------+---------+---------+---XXXXXXXXXX4-----+---------+---------+---------
 17, +---------+---------+---------+---------+---------+--5XXXXXXXXXXXX3---+---------+---------+---------
 18, +---------+---------+---------+---------+---------+--5XXXXXXXXXXXXXXXXXXXX------+---------+---------
 19, +---------+---------+---------+---------+---------+---XXXXXXXXX9------+---------+---------+---------
 20, +---------+---------+---------+---------+---------+--3XXXXXXXXXX1-----+---------+---------+---------
 21, +---------+---------+---------+---------+---------+--4XXXXXXXXX9------+---------+---------+---------
 22, +---------+---------+---------+---------+---------+--4XXXXXXXXXXXXXXXXXXX6------+---------+---------
 23, +---------+---------+---------+---------+---------+--8XXXXXXXXX3------+---------+---------+---------
 24, +---------+---------+---------+---------+---------+--9XXXXXXXXXXXXXXXXXXX4------+---------+---------
 25, +---------+---------+---------+---------+---------+-3XXXXXXXXXXXXXXXXXXXX5------+---------+---------
 26, +---------+---------+---------+---------+---------+-3XXXXXXXXX9-------+---------+---------+---------
 27, +---------+---------+---------+---------+---------+--XXXXXXXXXX-------+---------+---------+---------
 28, +---------+---------+---------+---------+---------+-2XXXXXXXXXXXXXXXXXXX5-------+---------+---------
 29, +---------+---------+---------+---------+---------+-3XXXXXXXXX8-------+---------+---------+---------
 30, +---------+---------+---------+---------+---------+-5XXXXXXXXX9-------+---------+---------+---------
 31, +---------+---------+---------+---------+---------+-4XXXXXXXXX6-------+---------+---------+---------
 32, +---------+---------+---------+---------+---------+2XXXXXXXXXX5-------+---------+---------+---------
 33, +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------
 34, +---------+---------+---------+---------+---------+-9XXXXXXXXX--------+---------+---------+---------
 35, +---------+---------+---------+---------+---------+3XXXXXXXXX9--------+---------+---------+---------
 36, +---------+---------+---------+---------+---------+6XXXXXXXXXXXXXXXXXXXX3-------+---------+---------
 37, +---------+---------+---------+---------+---------+7XXXXXXXXX7--------+---------+---------+---------
 38, +---------+---------+---------+---------+---------+6XXXXXXXXX9--------+---------+---------+---------
 39, +---------+---------+---------+---------+---------+6XXXXXXXXX4--------+---------+---------+---------
 40, +---------+---------+---------+---------+---------3XXXXXXXXXX2--------+---------+---------+---------
...
188, +---------+---------+---------+5XXXXXXXXXXXXXXXXXXXX1-------+---------+---------+---------+---------
189, +---------+---------+---------+8XXXXXXXXXXXXXXXXXXXX--------+---------+---------+---------+---------
190, +---------+---------+---------+8XXXXXXXXXXXXXXXXXXX8--------+---------+---------+---------+---------
191, +---------+---------+---------3XXXXXXXXXX2--------+---------+---------+---------+---------+---------
192, +---------+---------+---------1XXXXXXXXXXXXXXXXXXX8---------+---------+---------+---------+---------
193, +---------+---------+---------4XXXXXXXXXX7--------+---------+---------+---------+---------+---------
194, +---------+---------+---------+8XXXXXXXXXXXXXXXXXXX---------+---------+---------+---------+---------
195, +---------+---------+---------3XXXXXXXXX7---------+---------+---------+---------+---------+---------
196, +---------+---------+---------7XXXXXXXXX9---------+---------+---------+---------+---------+---------
197, +---------+---------+--------1XXXXXXXXXX2---------+---------+---------+---------+---------+---------
198, +---------+---------+---------9XXXXXXXXXXXXXXXXXXX2---------+---------+---------+---------+---------
199, +---------+---------+---------8XXXXXXXXX5---------+---------+---------+---------+---------+---------
200, +---------+---------+--------2XXXXXXXXXX4---------+---------+---------+---------+---------+---------
201, +---------+---------+---------8XXXXXXXXX4---------+---------+---------+---------+---------+---------
202, +---------+---------+--------2XXXXXXXXXXXXXXXXXXXX2---------+---------+---------+---------+---------
203, +---------+---------+--------7XXXXXXXXX2+---------+---------+---------+---------+---------+---------
204, +---------+---------+--------9XXXXXXXXXXXXXXXXXXX7+---------+---------+---------+---------+---------

As a final example have a look at the next log. The 100 ms and 200 ms modulations of the DCF77 signal are clearly visible but they show as “low” instead of “high”. The signal is useful but inverted. Thus set the inverted_samples constant to 1 if it was 0 or to 0 if it was 1 while you created the log.

159, XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX9XXXXX4------+---------+-5XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
160, XXXXXXXXXXXXXXXXXXX8XXXXXXXXXXXXXXXXXXXXXX4-------+-8XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
161, XXXXXXXXXXX7-9XXXXXXXXXXXXXXXXXXXXXXXXXXX7--------+--8XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
162, XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX8--------+---------+-2XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
163, XXXXXXXXXXXXXXXXXXX69XXXXXXXXXXXXXXXXXXXX4--------+-4XXXXXXXXX7XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
164, XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX2-------+-5XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
165, XXXXXXXXXXXXXXXXXXXXXX3XXXXXXXXXXXXXXXXXX7--------+---------+1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
166, XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX---------+---------+4XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
167, XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX7--------+---------+7XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

31 Responses to DCF77 Scope

  1. David says:

    Hi,
    thank you for this utility however I have a couple of questions:
    1: which pin do I connect the signal pin from the DCF77 to on the Arduino? A5, 5 or D19?
    2. with only one signal pin on the DCF77 – what is the difference between “sample_pin” and “analog_sample_pin”?
    3. What are these and what/where do I connect them?
    const uint8_t lower_output_pin = 2;
    const uint8_t upper_output_pin = 17;
    4. my serial monitor displayed page upon page of :
    5555555555555555555555555555555555555555555555555555555555555555555555555
    5555555555555555555555555555555555555555555555555555555555555555555555555
    5555555555555555555555555555555555555555555555555555555555555555555555555
    5555555555555555555555555555555555555555555555555555555555555555555555555
    is this good or bad?
    Thanks
    David

  2. David says:

    Woah !!
    Serial monitor suddenly gave me this:
    X3——-7XXXX1–1XXXX8X9799–+——-9X5–6XX8-7XXXX9-3X6XXXX1-8XXXX-+——21-+—-7XXXXXXX8—2XX
    102, X8—92-3XXXXXXX3—+9X5–7XX7+———+——88-+———+———+———+——–3+——–X
    103, X5——–+———+81—-8–+-9X4—-9XXXXX74-9XXXXXXX4—+2XX81XX754—9XX4–+——-7XXXXXX8XXXX
    104, XXXXX88XXXXXXXXX2-3-+—-95-2X9XXXXX9845X37XXXXXXXX1——–+———+95——-+———+———
    105, +——–5XX4-29—-96——–+39——-+—-3XXXXX9XXXXXXXX7-5XXXX8–5XXX7XX7–+–5XXX9–+4X——-
    106, +-2XX5XXXXX9XXX4XX9XXXXX34XXXXXXXXXX11XXXXXXXXXX9XXXXXX—–+-4X35-18-+-3X7—–+———+–2X9XX7X
    107, X3—3X3–+———+-28——+——4XXX3——–+9XX66XX9XXXXXXXXX8-7X6—-3XXXXX9——+4XXXXXX7-
    108, 2———+———+———+———+———+———13——–+–6XXX2–+-4——-+—–68–
    109, +———+1X8XXXXXXXX1——-+——-9X7——9XXXXXX8–7XXXXXX-1XX97+—-XXXX4+5X9——1–XXX22–
    110, +4X9XXXXXXXX1—4—+—5X1—+-3XXXXXXXXXXXX–X5-+-5XXXXXXX3—39-6XXXXXXXXXX9XXXXXX2—-+———
    111, +-92——+—-2XXXX+———+—–XXXXXXX9-471–+–XXXXX72XXX1-1—3XXXXX4129XXX9XXX22X-2-3XXXXX9X
    112, X5XXXX4—+2X38XX57XXXXX397XX7+4XX7—79+-29XXXX9-+—–389XXXXX4—–8X2–4XXXXX98–7X3–+—-5–8X
    113, XXXXXXXXX1+—2XXXXXXXX3-3XXX5+—-9XXXX9XXXX9–5XXXXXXXXX32+-1-6XXXXXXXXXXX8-3XXXX1—-27+—-9XXXX
    114, XX7—3–87—–7XX-3XXXXXX895+—-2XX9XXX5XXX—-+–98-68XXX———3XX7—5XX2XXXXXXX5-27—–21-
    115, +——–89XX7——+———+——9XXXXXXXXXX7XX8—-6X9XXXXXXXX3–5XXXX52XXX28XXX6—-+———
    116, +-3XX4—-+–3XXXX1-+——–68—6X—-+———+-4XXXXXXXXX9——-+-99XXXX9X7-5XX6—-+4XXXXXXXX
    117, XXXX85—-+1XX462-6857X5-566X-+-2XXXXX9XXXXX3-XX6-+4X6——5X6——-+3XXX99XXXX51XXXXXXX44XX5–3XX
    118, X7——–+——1XX2—–496-+—-5829XX8——–+-9XXXXXXXX–6XXXXX1+–2XX6XX8368XXX9—+–3XXXXXX
    119, XX474—-9XXXXXXXXXXXXXXXXXXX3+———+——42-31–48XXXXXX3—3—+—–6XXX6X4——-+—–3—
    120, +——X6XXXXXXX3—+———+———+—-89–3XXXXXXXXXX4231XX74–3——-9XXXXXX-X4–2–4—-2-
    121, 6XXXX2—-+–1–4XXXXX99X94—39XX8-2XXX+-12-1XXXXX46XXXXXXXXXX85XXXXXXXXXXX7—+———+—7—–
    122, +—–76X-+———+———+-3XX—3-+—3XXXXXXXXXXXXXXXX8X—6XXX5–8XXXXXXXXXX2—-6XXXXXXXXXX
    123, XXXXX9XXXXXXX9—1XX7—-9XX9XX5——9XXXXXXXXXXXXXXX9XXXXX69XXXXXX9X5-3X4–21-+6——–+–5XXXXXX
    124, X6-3XXXX7-+4XXXXX58947XXXXXXXX+8XXX4XXX8+3——-3XX25XXXXXXXX42X9XX9XXXXX—89XXXXXXX31X1+——-5X
    125, X92–89XX9X—–5XXXXX4—-9XXX3–1XXX9XXXXXXX3XXXX——2XXX3–1XXX2-1XXXXX9—+——95-+—–3-3X
    126, XXXXXXXXX9+——-

    is this good or bad?
    Thanks
    David

  3. David says:

    Thank you so much – all very helpful;
    Would you know – does the Arduino mini pro have an on-board crystal? I am needing a small foot-print and so don’t want to use a due if I don’t have to.
    Many thanks
    Keep up the very good work
    Much love
    David

  4. Frank says:

    DCF77 scope doesn’t compile (IDE 1.8.5):
    ‘lt’ was not declared in this scope ..
    TCCR2B = (0<<WGM22) | (1<<CS22); ..

    Thanks for your interesting blog,
    frank

  5. Frank says:

    .. ah, sorry figured it out (html cut & paste ) 😉

  6. Martin says:

    Hi,

    Seriell Monitor does gave me this output, is this ok?

    1, +———+———+———+———+———+———+———+———+———+———
    2, +———+———+———+——–XXXXXXXXXXXXXXXXXXXX8+———+———+———+———
    3, +———+———+———+———+1XXXXXXXX6———+———+———+———+———
    4, +———+———+———+——–1XXXXXXXXXX2———+———+———+———+———
    5, +———+———+———+———7XXXXXXXXX8———+———+———+———+———
    6, +———+———+———+———6XXXXXXXXXXXXXXXXXXX1———+———+———+———
    7, +———+———+———+——–29XXXXXXXXXXX——–+———+———+———+———
    8, +———+———+———+——–1XXXXXXXXXX4———+———+———+———+———
    9, +———+———+———+———4XXXXXXXXXX4——–+———+———+———+———
    10, +———+———+———+———1XXXXXXXXXXX6——-+———+———+———+———
    11, +———+———+———+———+9XXXXXXXXXX7——-+———+———+———+———
    12, +———+———+———+———+8XXXXXXXXXXXXXXXXXXXX1——-+———+———+———
    13, +———+———+———+———9XXXXXXXXXXX——–+———+———+———+———
    14, +———+———+———+———+5XXXXXXXXX9——–+———+———+———+———
    15, +———+———+———+———+8XXXXXXXXXXXXXXXXXXXX8——-+———+———+———
    16, +———+———+———+———+-9XXXXXXXXXXXXXXXXXXX8——-+———+———+———
    17, +———+———+———+———+3XXXXXXXXXX7——-+———+———+———+———
    18, +———+———+———+———+3XXXXXXXXXXXXXXXXXXX9——–+———+———+———
    19, +———+———+———+———+1XXXXXXXXXXX——-+———+———+———+———
    20, +———+———+———+———+-7XXXXXXXXXX6——+———+———+———+———
    21, +———+———+———+———+—-1XXXXXXX4——+———+———+———+———
    22, +———+———+———+———+–9XXXXXXXXXXXXXXXXXXX9——+———+———+———
    23, +———+———+———+———+–4XXXXXXXXXXXXXXXXXXXX6—–+———+———+———
    24, +———+——-12+———+———+–8XXXXXXXXXXXXXXXXXXXX6—–+———+———+———
    25, +———+———+———+———+–5XXXXXXXXXXXXXXXXXXXX3—–+———+———+———
    26, +———+———+———+———+-2XXXXXXXXXXX9—–+———+———+———+———
    27, +———+———+———+———+–5XXXXXXXXXXXXXXXXXXXX——+———+———+———
    28, +———+———+———+———+–7XXXXXXXXXXX8—-+———+———+———+———
    29, +———+———+———+———+—2XXXXXXXXXXXX8–+———+———+———+———
    30, +———+———+———+———+—7XXXXXXXXXX—–+———+———+———+———
    31, +———+———+———+———+—9XXXXXXXXXXXXXXXXXXXX4—-+———+———+———
    32, +———+———+———+———+———+———+———+———+———+———
    33, +———+———+———+———+—4XXXXXXXXX7—–+———+———+———+———
    34, +———+———+———+———+—-8XXXXXXXXXX6—+———+———+———+———
    35, +———+———+———+———+—-7XXXXXXXXXX2—+———+———+———+———
    36, +———+———+———+———+—-6XXXXXXXXXX6—+———+———+———+———
    37, +———+———+———+———+—-3XXXXXXXXXX8—+———+———+———+———
    38, +———+———+———+———+—-5XXXXXXXXXX4—+———+———+———+———
    39, +———+———+———+———+—-1XXXXXXXXXXXXXXXXXXXX9—+———+———+———
    40, +———+———+———+———+—–5XXXXXXXXXXXXXXXXXXX9—+———+———+———

  7. Michael Haardt says:

    I like the visualization of the signal. Having ported this to Linux with a serial DCF77 clock, the recovered bit stream shows some noise, just enough to let a naive implementation fail for sure (334 is a good example):

    329, +———+———+———+–1XXXXX9+———+———+———+———+———+———
    330, +———+———+———+——-6XXXXXXXXXXXXX4——-+———+———+———+———
    331, +———+———+———+–7XXXXXXX3——–+———+———+———+———+———
    332, +———+———+———+4XXXXXXXXX6——–+———+———+———+———+———
    333, +———+———+———+-7XXXXXXXXX3——-+———+———+———+———+———
    334, +———+———+———+2XXXXX8–+–2XXXXX8+———+———+———+———+———
    335, +———+———+———+1XXXXXXXXXXXXXXXXX9+———+———+———+———+———
    336, +———+———+———+-6XXXXXXXXXXXXXXXXX4———+———+———+———+———
    337, +———+———+———+-2XXXXXXX8———+———+———+———+———+———
    338, +———+———+———+1XXXXXXXXX9——–+———+———+———+———+———

    Porting the actual library might be worth the efforts!

    • If you have a strong enough machine to run Linux then NTP is the way to go. Or NTP + DCF77. With Linux there are always two issues.

      1. A lot of Linux boxes does not have a jitter free local scheduling which messes with my algorithm.
      2. Not every Linux box has direct (=jitter free) IO.

      On the other hand typical NTPD implementations already account for that and are also able to combine several clock sources. You can find lots of projects that leverage e.g. the Raspi’s direct kernel IO pin access to have direct NTPD access to a ppm signal. You may want to lookup and before you invest any effort to port this to linux.

      In particular if you say Linux you also imply that you are going for network connectivity. This also implies that you can place the server anywhere you want and most probably with wall power. This implies that GPS is most probably a much better choice anyway.

      If you still believe this would be worth the effort then go ahead and port it. After all this is open source. IMHO it will not be worth the effort.

  8. Michael Haardt says:

    For multiple time sources, GPS and DCF77 would be nice, but DCF77 does not work, because the NTP rawdcf driver is way too simple for even slight noise. That’s why I am interested in your code. 🙂

    The data I showed is the result of mapping the received serial data bitwise (50 baud = 20 ms per bit) to the bins like you do with the sampled digital input. Given that not even a single datagram was received properly, I was surprised that the signal actually looks pretty good. It just needs some noise correction. The visualization of dcf77-scope is by far better than any other test program I saw so far.

    It could be spiced up with the pulse correlation maximum to get an idea of the jitter. I checked the phase detection experiment, but have to admit I don’t get how it works. What you call “integral” appears to be the cross correlation of the received bits with the sum of a short and long pulse, so much I understood, and I think that’s pretty smart, because it saves having to perform two cross correlations. The following “noise” part that looks so similar makes me wonder.

    • You can use the Meinberg Emulator or the Superfilter. Both will resolve this issue. Of course it requires an additional Arduino in between. However given the price tag of the hardware vs. the development effort vs. the small audience that would want such a thing this is not worth my time. As I already said: this is open source. If you really want to have it go ahead and implement it.

  9. Michael Haardt says:

    Interesting projects, thanks for the pointer! They certainly can provide very good quality, yet I wonder how far I could get with the hardware I have. Perhaps it suffices just with better code?

    I added the phase detection to the serial Linux version of dcf77-scope (for now excluding the noise code) and enjoyed to see how smart the approach is implemented and how well it works. Even in the presence of heavy noise, the phase is detected correctly and very stable. The jitter from Linux is there, but averaging deals with that, because the system clock is stable enough. That exceeds everything I ever saw from a simple serial clock.

    The comment on the sliding average size was not quite clear to me. Did I understand this right?

    The phase detection uses a 1s cyclic sliding average of sampled data to decrease noise. The interval is restricted by the local clock drift, which must not exceed the time taken by one bin, which is 10 ms:

    30 ppm * N seconds < 10 ms
    N seconds < 0.01 s / 0.00003 = 333, so you used 300 to stay below?

    I think I also understood why the code uses convolution instead of correlation: It shifts the sum of the short and long pulse to the right, not to the left, which does not matter as long as you only want to get the maximum, correct?

    I'll have to study how the pulses are detected. What a cool project. 🙂

    • Yes, I need 300 to stay below the threshold.

      With regards to the clock stability of Linux I have some doubts. Of course the clock may be stable. The question is if the interrupt scheduler can deliver suitable timing guarantees.

      With regards to convolution vs. correlation: mathematically these are almost equivalent. However convolution has much nicer algebraic properties. E.g. it is associative. Also its Fourier Transform behaves nicer. This is why I prefer convolution.

  10. Tim says:

    I’ve tried to run the Scope sketch but get error
    }
    ^
    ‘for (uint8_t bin=0; bin&1t;bins; ++bin {

    exit status ‘1t’ was not decared in this scope?
    anyone help newbe? do i need a library with this
    using Arduino uno?

  11. Tim says:

    Thank you for your quick reply, and for the excellent work you do, it loaded after using your link without any errors in the sketch it says include Ardunino.h, but cannot find this library, and though it didn’t need a library. However the serial monitor gives me a serial of unrecognised characters? And not in the form of your examples? Is it because I didn’t find and include Arduino.h?

    • Tim says:

      Sorry now working had baud rate wrong and not set to 115200, thanks again for all your excellent info formation ….Bestwishes.

  12. Ivan Burvenich says:

    Hi! I made a trace with “DCF77 Scope” today on my almost finished nixie clock build and the result shows a decent signal, very little noise. That’s good. But I observe a little drift to the right: about 260 ms per hour:
    17:37:44.264 ->
    17:37:44.264 -> 1, +———+———+———+———+———+———+———+———+———+———
    17:37:45.285 -> 2, +———+———+———+—-5XXXXXXXXXXXXXXXXXXX5—-+———+———+———+———
    17:37:46.259 -> 3, +———+———+———+—-9XXXXXXXXXX—-+———+———+———+———+———
    17:37:47.285 -> 4, +———+———+———+—-9XXXXXXXXX6—-+———+———+———+———+———
    17:37:48.281 -> 5, +———+———+———+—3XXXXXXXXXX8—-+———+———+———+———+———
    17:37:49.261 -> 6, +———+———+———+—-8XXXXXXXXXX9—+———+———+———+———+———
    17:37:50.256 -> 7, +———+———+———+—4XXXXXXXXXXXXXXXXXXXX7—-+———+———+———+———
    17:37:51.257 -> 8, +———+———+———+—-XXXXXXXXXX9—-+———+———+———+———+———
    17:37:52.265 -> 9, +———+———+———+—3XXXXXXXXXX4—-+———+———+———+———+———
    17:37:53.269 -> 10, +———+———+———+—-2XXXXXXXXX9—-+———+———+———+———+———
    17:37:54.277 -> 11, +———+———+———+—1XXXXXXXXXX6—-+———+———+———+———+———
    17:37:55.263 -> 12, +———+———+———+—1XXXXXXXXXXX—-+———+———+———+———+———
    17:37:56.271 -> 13, +———+———+———+—-7XXXXXXXXX8—-+———+———+———+———+———
    17:37:57.279 -> 14, +———+———+———+—-3XXXXXXXXXXXXXXXXXXX1—-+———+———+———+———
    17:37:58.287 -> 15, +———+———+———+—2XXXXXXXXXX3—-+———+———+———+———+———
    17:37:59.259 -> 16, +———+———+———+—-3XXXXXXXXXX1—+———+———+———+———+———
    17:38:00.267 -> 17, +———+———+———+—-9XXXXXXXXXXXXXXXXXXXX—-+———+———+———+———
    17:38:01.254 -> 18, +———+———+———+———+———+———+———+———+———+———
    17:38:02.262 -> 19, +———+———+———+—-8XXXXXXXX9—–+———+———+———+———+———
    17:38:03.271 -> 20, +———+———+———+—-5XXXXXXXXXXXXXXXXXXX5—-+———+———+———+———

    18:38:03.961 -> 3621, +———+———+———+———+———+———+5XXXXXXXXX4——–+———+———
    18:38:04.964 -> 3622, +———+———+———+———+———+———+5XXXXXXXXX6——–+———+———
    18:38:05.943 -> 3623, +———+———+———+———+———+———+3XXXXXXXXXX6——-+———+———
    18:38:06.945 -> 3624, +———+———+———+———+———+———+-9XXXXXXXXX5——-+———+———
    18:38:07.953 -> 3625, +———+———+———+———+———+———+4XXXXXXXXXXXXXXXXXXXX3——-+———
    18:38:08.961 -> 3626, +———+———+———+———+———+———+1XXXXXXXXXX——–+———+———
    18:38:09.933 -> 3627, +———+———+———+———+———+———+7XXXXXXXXXX——–+———+———
    18:38:10.941 -> 3628, +———+———+———+———+———+———+-6XXXXXXXXX1——-+———+———
    18:38:11.949 -> 3629, +———+———+———+———+———+———+4XXXXXXXXXX9——-+———+———
    18:38:12.931 -> 3630, +———+———+———+—-1—-+———+———+-7XXXXXXXXXXXXXXXXXXX——–+———
    18:38:13.954 -> 3631, +———+———+———+———+———+———+4XXXXXXXXXX1——-+———+———
    18:38:14.941 -> 3632, +———+———+———+———+———+———+4XXXXXXXXXXXXXXXXXXXX——–+———
    18:38:15.945 -> 3633, +———+———+———+———+———+———+3XXXXXXXXXXX——-+———+———
    18:38:16.953 -> 3634, +———+———+———+———+———+———+3XXXXXXXXXX1——-+———+———
    18:38:17.961 -> 3635, +———+———+———+———+———+———+1XXXXXXXXXXX1——+———+———
    18:38:18.955 -> 3636, +———+———+———+———+———+———+1XXXXXXXXXXXXXXXXXXXX2——-+———
    18:38:19.954 -> 3637, +———+———+———+———+———+———+-9XXXXXXXXX——–+———+———
    18:38:20.942 -> 3638, +———+———+———+———+———+———+4XXXXXXXXXX5——-+———+———
    18:38:21.950 -> 3639, +———+———+———+———+———+———+1XXXXXXXXXXXXXXXXXXXX1——-+———
    18:38:22.936 -> 3640, +———+———+———+———+———+———+-6XXXXXXXXXXXXXXXXXXXXX2—–+———

    This is about 72 ppm. I thought I had a 16 Mhz crystal on the Arduino board (mega 2560 pro – made in China), but this result shows clearly that it is not or a very bad crystal (should be <= 30 ppm). I observed a drift of about 6 seconds per day on the clock and that made me think that the 'crystal' is off limits. Sadly, it is …
    Do you agree with my conclusion?
    If yes, I know what to do: install crystal (<= 30 ppm) and matching capacitors.
    If you do not agree: what other things can be wrong and what can I do?

    • This crystal is not the best but it should do. The proper way to analyze this is to run the Swiss Army Debug Helper and see if the library will lock to the signal.

      • Ivan Burvenich says:

        I ran the Swiss Army Debug Helper too and yes, the library syncs without any problem. But after 24 hours I see a time difference of 6 seconds, due to the drift. The drift is for some reason not compensated.

        • If it properly locks to the signal, what is the issue? If you want me to have a look at it I need: first 1000 seconds + 200 seconds of the Swiss Army Debug Helper Output. Inclusive the “boilerplate” output at the start.

          • Ivan Burvenich says:

            I think that generating an output will not help in this case because my clock behaves differently when it is connected to a computer via the USB port. I’m still struggling with that issue and not your problem anyway.
            But can you explain me how the library acts in the states: ‘Synced’ and ‘Locked’. Is it correct that in the state ‘Locked’ the library does not rely on the DCF77 data? However in state ‘Synced’ the DCF77 data is considered. Can that explain why my clock sometimes not compensates the drift?
            ie. yesterday my clock jumped 3 seconds backward to be back on track with DCF77 (I inserted some test to detect this event). That is good. (case: state = Synced ?)
            The problem is that this mechanism is sometimes working. ie. 2 weeks ago my clock ran 24 seconds ahead of DCF77 after 4 days. That’s not good, of course. (case: state = Locked ?)
            If you can clarify me the way the library acts on it’s different states, that would already help me a lot.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.