Swiss Army Debug Helper

Some may wonder how I test and debug my DCF77 library. The most important tool is the “Swiss Army Debug Helper”. It allows to control the output to the Blinkenlightie’s LEDs as well as to dump important and/or interesting internal parameters of the library.

//
//  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>
#include <avr/eeprom.h>
// do not use 0 as this will interfere with the DCF77 lib's EEPROM_base
const uint16_t EEPROM_base = 0x20;

// which pin the clock module is connected to
const uint8_t dcf77_analog_sample_pin = 5;
const uint8_t dcf77_sample_pin = 19; // A5

const uint8_t dcf77_inverted_samples = 1;

// The Blinkenlighty requires 1 this because the input
// pins are loaded with LEDs. All others should prefer
// setting this to 0 as this reduces interrupt contention.
const uint8_t dcf77_analog_samples = 1;

namespace Phase_Drift_Analysis {
    volatile uint16_t phase = 0;
    volatile uint16_t noise = 0;
}

namespace LED_Display {
    // which pin to use for monitor output
    const uint8_t dcf77_monitor_pin = 18;  // A4

    // which pins to use for monitoring lightshow
    const uint8_t lower_output_pin = 2;
    const uint8_t upper_output_pin = 17;

    int8_t counter = 0;
    uint8_t rolling_pin = lower_output_pin;


    void reset_output_pins() {
        for (uint8_t pin = lower_output_pin; pin <= upper_output_pin; ++pin) {
            digitalWrite(pin, LOW);
        }
    }

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

    void setup() {
        pinMode(dcf77_monitor_pin, OUTPUT);
        setup_output_pins();
    }


    volatile char mode = 'p';
    void set_mode(const char c) {
        Serial.print(F("set LED mode: ")); Serial.println(c);
        if (c != mode) {
            // mode assignment must be before reset in order
            // to avoid race conditions
            mode = c;
            reset_output_pins();
        }
    }
    char get_mode() { return mode; }

    void monitor(const uint8_t sampled_data) {
        digitalWrite(dcf77_monitor_pin, sampled_data);

        switch (mode) {
            case '2':    // 200 ms
            case 't':  { // ticks
                const uint8_t ticks_per_cycle_nominator   = 25;
                const uint8_t ticks_per_cycle_denominator = 2;

                if (rolling_pin <= upper_output_pin) {
                    digitalWrite(rolling_pin, sampled_data);
                }

                counter += ticks_per_cycle_denominator;
                if (counter >= ticks_per_cycle_nominator) {
                    rolling_pin = (rolling_pin < upper_output_pin ||
                                   mode == '2' && rolling_pin <= (1000 * ticks_per_cycle_denominator)/ ticks_per_cycle_nominator)
                                   ? rolling_pin + 1: lower_output_pin;
                    counter -= ticks_per_cycle_nominator;

                    if (mode=='2' && rolling_pin <= upper_output_pin) {
                        digitalWrite(rolling_pin, !sampled_data);
                    }
                }
            }
            break;

            case 'f':  { // flash
                for (uint8_t pin = lower_output_pin; pin <= upper_output_pin; ++pin) {
                    digitalWrite(pin, sampled_data);
                }
            }
            break;
        }
    }

    void output_handler(const DCF77_Clock::time_t &decoded_time) {
        switch (mode) {
            case '2':
            case 't':  // ticks
                rolling_pin = lower_output_pin;
                counter = 0;
                break;

            case 's':  { // seconds
                uint8_t out = decoded_time.second.val;
                uint8_t pin = lower_output_pin + 3;
                for (uint8_t bit=0; bit<8; ++bit) {
                    digitalWrite(pin++, out & 0x1);
                    out >>= 1;

                    if (bit==3) {
                        ++pin;
                    }
                }
                break;
            }
            case 'h': { // hours and minutes
                uint8_t pin = lower_output_pin;

                uint8_t out = decoded_time.minute.val;
                for (uint8_t bit=0; bit<7; ++bit) {
                    digitalWrite(pin++, out & 0x1);
                    out >>= 1;
                }
                ++pin;

                out = decoded_time.hour.val;
                for (uint8_t bit=0; bit<6; ++bit) {
                    digitalWrite(pin++, out & 0x1);
                    out >>= 1;
                }
                break;
            }
            case 'm': { // months and days
                uint8_t pin = lower_output_pin;

                uint8_t out = decoded_time.day.val;
                for (uint8_t bit=0; bit<6; ++bit) {
                    digitalWrite(pin++, out & 0x1);
                    out >>= 1;
                }
                ++pin;

                out = decoded_time.month.val;
                for (uint8_t bit=0; bit<5; ++bit) {
                    digitalWrite(pin++, out & 0x1);
                    out >>= 1;
                }
                break;
            }

            case 'a': { // analyze phase drift
                for (uint8_t bit=0; bit<10; ++bit) {
                    const uint8_t pm10 = Phase_Drift_Analysis::phase % 10;
                    digitalWrite(lower_output_pin+bit, pm10==bit);
                }
                break;
            }

            case 'c': { // calibration state + deviation
                const DCF77_Frequency_Control::calibration_state_t calibration_state = DCF77_Frequency_Control::get_calibration_state();
                int16_t deviation = abs(DCF77_Frequency_Control::get_current_deviation());
                uint8_t pin = lower_output_pin;

                // display calibration state, blink if running unqualified
                digitalWrite(pin++, calibration_state.qualified);
                digitalWrite(pin++, calibration_state.running && !calibration_state.qualified && (decoded_time.second.val & 1));
                digitalWrite(pin++, calibration_state.running);

                // render the absolute deviation in binary
                while (pin < upper_output_pin) {
                    digitalWrite(pin, deviation & 1);
                    deviation >>= 1;
                    ++pin;
                }
            }
        }
    }
}

namespace Scope {
    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];
    volatile boolean samples_pending = false;
    volatile uint32_t count = 0;

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

        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;
            ++count;
        }
        sbin[ticks/samples_per_bin] += sample;
    }

    void print() {
        uint8_t lbin[bins];

        if (samples_pending) {
            cli();
            memcpy(lbin, (void *)gbin, bins);
            samples_pending = false;
            sei();

            // 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();
        }
    }
}

namespace High_Resolution_Scope {
    uint16_t tick = 999;

    void print(const uint8_t sampled_data) {
        ++tick;
        if (tick == 1000) {
            tick = 0;
            Serial.println();
        }

        Serial.print(sampled_data? 'X':
                     (tick % 100)? '-':
                                   '+');
    }
}

namespace Raw {
    void print(const uint8_t sampled_data) {
        Serial.println(sampled_data);
    }
}

namespace Phase_Drift_Analysis {
    using namespace LED_Display;

    volatile uint16_t counter = 1000;
    volatile uint16_t ref_counter = 0;
    volatile uint16_t noise_detector = 0;
    volatile uint16_t noise_ticks = 0;
    volatile uint16_t phase_detector = 0;
    volatile uint8_t  phase_ticks = 0;
    volatile uint16_t sample_count = 0;

    void restart() {
        counter -= 1000;
        ref_counter = 0;
    }

    void process_one_sample(uint8_t sampled_data) {
        ++counter;
        ++ref_counter;

        if (ref_counter == 950) {
            phase_detector = 0;
            phase_ticks = 0;
        }
        if (ref_counter == 300) {
            noise_detector = 0;
            noise_ticks = 0;
        }
        if (ref_counter >= 950 || ref_counter < 50) {
            phase_detector += sampled_data;
            ++phase_ticks;
        }
        if (ref_counter == 50) {
            phase = phase_detector;
            noise = noise_detector;
        }

        if (ref_counter >= 300 && ref_counter < 900) {
            noise_detector += sampled_data;
            ++noise_ticks;
        }
    }

    void debug() {
        Serial.print(Phase_Drift_Analysis::phase);
        Serial.print('/');
        Serial.print(100);
        Serial.print('~');
        Serial.print(Phase_Drift_Analysis::noise);
        Serial.print('/');
        Serial.println(600);
    }
}

namespace Timezone {
    uint8_t days_per_month(const DCF77_Clock::time_t &now) {
        switch (now.month.val) {
            case 0x02:
                // valid till 31.12.2399
                // notice year mod 4 == year & 0x03
                return 28 + ((now.year.val != 0) && ((bcd_to_int(now.year) & 0x03) == 0)? 1: 0);
            case 0x01: case 0x03: case 0x05: case 0x07: case 0x08: case 0x10: case 0x12: return 31;
            case 0x04: case 0x06: case 0x09: case 0x11:                                  return 30;
            default: return 0;
        }
    }

    void adjust(DCF77_Clock::time_t &time, const int8_t offset) {
        // attention: maximum supported offset is +/- 23h

        int8_t hour = BCD::bcd_to_int(time.hour) + offset;

        if (hour > 23) {
            hour -= 24;
            uint8_t day = BCD::bcd_to_int(time.day) + 1;
            if (day > days_per_month(time)) {
                day = 1;
                uint8_t month = BCD::bcd_to_int(time.month);
                ++month;
                if (month > 12) {
                    month = 1;
                    uint8_t year = BCD::bcd_to_int(time.year);
                    ++year;
                    if (year > 99) {
                        year = 0;
                    }
                    time.year = BCD::int_to_bcd(year);
                }
                time.month = BCD::int_to_bcd(month);
            }
            time.day = BCD::int_to_bcd(day);
        }

        if (hour < 0) {
            hour += 24;
            uint8_t day = BCD::bcd_to_int(time.day) - 1;
            if (day < 1) {
                uint8_t month = BCD::bcd_to_int(time.month);
                --month;
                if (month < 1) {
                    month = 12;
                    int8_t year = BCD::bcd_to_int(time.year);
                    --year;
                    if (year < 0) {
                        year = 99;
                    }
                    time.year = BCD::int_to_bcd(year);
                }
                time.month = BCD::int_to_bcd(month);
                day = days_per_month(time);
            }
            time.day = BCD::int_to_bcd(day);
        }

        time.hour = BCD::int_to_bcd(hour);
    }
}

void paddedPrint(BCD::bcd_t n) {
    Serial.print(n.digit.hi);
    Serial.print(n.digit.lo);
}

char mode = 'd';
void set_mode(const char mode) {
    Serial.print(F("set mode: ")); Serial.println(mode);
    ::mode = mode;
}
char get_mode() { return mode; }

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));

    // computations must be before display code
    Scope::process_one_sample(sampled_data);
    Phase_Drift_Analysis::process_one_sample(sampled_data);

    LED_Display::monitor(sampled_data);


    if (mode == 'r') {
        Raw::print(sampled_data);
    } else
    if (mode == 'S') {
        High_Resolution_Scope::print(sampled_data);
    }

    return sampled_data;
}

void output_handler(const DCF77_Clock::time_t &decoded_time) {
    Phase_Drift_Analysis::restart();
    LED_Display::output_handler(decoded_time);
}

/*
void free_dump() {

    uint8_t *heapptr;
    uint8_t *stackptr;

    stackptr = (uint8_t *)malloc(4);   // use stackptr temporarily
    heapptr = stackptr;                // save value of heap pointer
    free(stackptr);                    // free up the memory again (sets stackptr to 0)
    stackptr =  (uint8_t *)(SP);       // save value of stack pointer


    // print("HP: ");
    Serial.print(F("HP: "));
    Serial.println((int) heapptr, HEX);

    // print("SP: ");
    Serial.print(F("SP: "));
    Serial.println((int) stackptr, HEX);

    // print("Free: ");
    Serial.print(F("Free: "));
    Serial.println((int) stackptr - (int) heapptr, HEX);
    Serial.println();
}
*/

namespace Parser {
    // ID constants to see if EEPROM has already something stored
    const char ID_u = 'u';
    const char ID_k = 'k';

    void persist_to_EEPROM() {
        uint16_t eeprom = EEPROM_base;
        eeprom_write_byte((uint8_t *)(eeprom++), ID_u);
        eeprom_write_byte((uint8_t *)(eeprom++), ID_k);
        eeprom_write_byte((uint8_t *)(eeprom++), ::get_mode());
        eeprom_write_byte((uint8_t *)(eeprom++), LED_Display::get_mode());
        Serial.println(F("modes persisted to eeprom"));
    }

    void restore_from_EEPROM() {
        uint16_t eeprom = EEPROM_base;
        if (eeprom_read_byte((const uint8_t *)(eeprom++)) == ID_u &&
            eeprom_read_byte((const uint8_t *)(eeprom++)) == ID_k) {
            ::set_mode(eeprom_read_byte((const uint8_t *)(eeprom++)));
            LED_Display::set_mode(eeprom_read_byte((const uint8_t *)(eeprom++)));
            Serial.println(F("modes restored from eeprom"));
        }
    }

    void help() {
        Serial.println();
        Serial.println(F("use serial interface to alter settings"));
        Serial.println(F("  L: led output modes"));
        Serial.println(F("    q: quiet"));
        Serial.println(F("    f: flash"));
        Serial.println(F("    t: ticks"));
        Serial.println(F("    2: 200 ms of the signal"));
        Serial.println(F("    s: BCD seconds"));
        Serial.println(F("    h: BCD hours and minutes"));
        Serial.println(F("    m: BCD months and days"));
        Serial.println(F("    a: analyze phase drift"));
        Serial.println(F("    c: calibration state + deviation"));
        Serial.println(F("  D: debug modes"));
        Serial.println(F("    q: quiet"));
        Serial.println(F("    d: debug quality factors"));
        Serial.println(F("    s: scope"));
        Serial.println(F("    S: scope high resolution"));
        Serial.println(F("    a: analyze frequency"));
        Serial.println(F("    b: demodulator bins"));
        Serial.println(F("    B: detailed demodulator bins"));
        Serial.println(F("    r: raw output"));
        Serial.println(F("    c: CET/CEST"));
        Serial.println(F("    u: UTC"));
        Serial.println(F("  *: persist current modes to EEPROM"));
        Serial.println(F("  ~: restore modes from EEPROM"));
        Serial.println();
    }

    void help_on_none_space(const char c) {
        if (c!=' ' && c!='\n' && c!='\r') {
            help();
        }
    }

    // The parser will deliver output in two different ways
    //     1) synchronous as a return value
    //     2) as a side effect to the LED display
    // We are lazy with the command mapping, that is the parser will
    // not map anything. The commands are fed directly from the parser
    // to the consumers. This of course increases coupling.
    void parse() {
        enum mode { waiting=0, led_display_command, debug_output_command};

        static mode parser_mode = waiting;

        if (Serial.available()) {
            const char c = Serial.read();

            switch(c) {
                case '*': persist_to_EEPROM();
                    return;
                case '~': restore_from_EEPROM();
                    return;
                case 'D':
                    parser_mode = debug_output_command;
                    return;
                case 'L':
                    parser_mode = led_display_command;
                    return;
                default:
                    switch (parser_mode) {
                        case led_display_command: {
                            switch (c) {
                                case 'q':  // quiet
                                case 'f':  // flash
                                case 't':  // ticks
                                case '2':  // 200 ms of the signal
                                case 's':  // seconds
                                case 'h':  // hours and minutes
                                case 'm':  // months and days
                                case 'a':  // analyze phase drift
                                case 'c':  // calibration state + deviation
                                    LED_Display::set_mode(c);
                                    return;
                            }
                        }
                                case debug_output_command: {
                                    switch(c) {
                                        case 'q':  // quiet
                                        case 'd':  // debug
                                        case 's':  // scope
                                        case 'S':  // scope
                                        case 'a':  // analyze phase drift
                                        case 'b':  // demodulator bins
                                        case 'B':  // more on demodulator bins
                                        case 'r':  // raw
                                        case 'c':  // CET/CEST
                                        case 'u':  // UTC
                                    ::set_mode(c);
                                    return;
                                    }
                                }
                    }
            }
            help_on_none_space(c);
        }
    }
}

void setup() {
    //using namespace DCF77_Encoder;

    Serial.begin(115200);

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

    LED_Display::setup();

    DCF77_Clock::setup();
    DCF77_Clock::set_input_provider(sample_input_pin);
    DCF77_Clock::set_output_handler(output_handler);

    Serial.println();
    Serial.println(F("DCF77 Clock V2.0"));
    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(LED_Display::dcf77_monitor_pin);
    Serial.print(F("Freq. Adjust:   ")); Serial.println(DCF77_1_Khz_Generator::read_adjustment());

    int8_t  adjust_steps;
    int16_t adjust;
    DCF77_Frequency_Control::read_from_eeprom(adjust_steps, adjust);
    Serial.print(F("EE Precision:   ")); Serial.println(adjust_steps);
    Serial.print(F("EE Freq. Adjust:")); Serial.println(adjust);

    Serial.println();

    Parser::help();

    Serial.println();
    Serial.println(F("Initializing..."));
    Serial.println();

    Parser::restore_from_EEPROM();
}

void loop() {
    Parser::parse();

    switch (mode) {
        case 'q': break;
        case 'a': {
            DCF77_Clock::time_t now;
            DCF77_Clock::get_current_time(now);

            DCF77_Frequency_Control::debug();
            Phase_Drift_Analysis::debug();
            //DCF77_Demodulator::debug();

            break;
        }
        case 'b': {
            DCF77_Clock::time_t now;
            DCF77_Clock::get_current_time(now);

            DCF77_Demodulator::debug();
            break;
        }

        case 'B': {
            DCF77_Clock::time_t now;
            DCF77_Clock::get_current_time(now);

            switch (DCF77_Clock::get_clock_state()) {
                case DCF77::useless: Serial.println(F("useless")); break;
                case DCF77::dirty:   Serial.println(F("dirty")); break;
                case DCF77::synced:  Serial.println(F("synced")); break;
                case DCF77::locked:  Serial.println(F("locked")); break;
            }

            DCF77_Demodulator::debug_verbose();
            Serial.println();
            break;
        }


        case 's':
            Scope::print();
            break;
        case 'S': break;
        case 'r': break;

        case 'c': // render CET/CEST
        case 'u': // render UTC
            {
                DCF77_Clock::time_t now;
                DCF77_Clock::get_current_time(now);

                if (now.month.val > 0) {
                    switch (DCF77_Clock::get_clock_state()) {
                        case DCF77::useless: Serial.print(F("useless:")); break;
                        case DCF77::dirty:   Serial.print(F("dirty:  ")); break;
                        case DCF77::synced:  Serial.print(F("synced: ")); break;
                        case DCF77::locked:  Serial.print(F("locked: ")); break;
                    }
                    Serial.print(' ');

                    const int8_t target_timezone_offset =
                        mode == 'c' ?         0:
                        now.uses_summertime? -2:
                                             -1;
                    Timezone::adjust(now, target_timezone_offset);

                    Serial.print(F("20"));
                    paddedPrint(now.year);
                    Serial.print('-');
                    paddedPrint(now.month);
                    Serial.print('-');
                    paddedPrint(now.day);
                    Serial.print(' ');

                    paddedPrint(now.hour);
                    Serial.print(':');
                    paddedPrint(now.minute);
                    Serial.print(':');
                    paddedPrint(now.second);

                    Serial.print(' ');
                    if (mode == 'c') {
                        if (now.uses_summertime) {
                            Serial.println(F("CEST (UTC+2)"));
                        } else {
                            Serial.println(F("CET (UTC+1)"));
                        }
                    } else {
                        Serial.println(F("UTC"));
                    }
                }
                break;
            }

        default: {
            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();
            //DCF77_Second_Decoder::debug();
            DCF77_Local_Clock::debug();
        }
    }
    //free_dump();
}

Startup Message

The first what you will see is some output like below.

DCF77 Clock V2.0
(c) Udo Klein 2014
www.blinkenlight.net

Sample Pin:     19
Inverted Mode:  1
Analog Mode:    1
Monitor Pin:    18
Freq. Adjust:   -518
EE Precision:   1
EE Freq. Adjust:-518


use serial interface to alter settings
  L: led output modes
    q: quiet
    f: flash
    t: ticks
    2: 200 ms of the signal
    s: BCD seconds
    h: BCD hours and minutes
    m: BCD months and days
    a: analyze phase drift
    c: calibration state + deviation
  D: debug modes
    q: quiet
    d: debug quality factors
    s: scope
    S: scope high resolution
    a: analyze frequency
    b: demodulator bins
    B: detailed demodulator bins
    r: raw output
    c: CET/CEST
    u: UTC
  *: persist current modes to EEPROM
  ~: restore modes from EEPROM


Initializing...

set mode: a
set LED mode: c
modes restored from eeprom

This output gives already some interesting details. First things first, so my copyright notice is the first thing that will appear. Then you it outputs the pin settings. These are not the settings of the library. The library has no pin bindings on its own. These are the settings of the debug tool.

In the case above it tells us that it samples on pin 19. That is the DCF77 module has to be connected to pin 19. It also says it uses inverted mode. Inverted mode implies that the module will represent the modulated DCF77 carrier as a logical 0. Most modules will decode DCF77 the other way round. So you may want to use inverted_mode = 0. What you can also see is that I use “analog mode”. This is just another way to read the input pin. This is helpful if the module has no 5V output. If you module has a 5V output then it is just a waste of resources though. If you want to understand the behaviour in exact detail read the source:

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));
 

The next thing we see is the monitor pin. The monitor pin is used to indicate the decoded signal. If you do not use a Blinkenlighty then you may want to set this to 13.

The frequency adjust says -518 and the EEPROM precision says 1. This implies that the last time the frequency adjustment was persisted to EEPROM the crystal was running at 16 000 518 Hz. It also implies that the measurement period was long enough to get a result with a resolution of 1 Hz or better. As the ambient temperature may have changed since the last measurement it does NOT imply that the crystal is running at 16 000 518 Hz now. However we will use this as a reasonable approximation for the current frequency. Hence a correction of -518 Hz is applied.

List of Commands

The next thing is the list of all interaction commands. The commands always consist of two letters. For example “Lq” will “silence” the LED output and “Dq” will “silence” the serial output. You can chain commands, e.g. LqDq will silence both outputs.

The exceptions to the rules are “*” and “~” which can be used to persist the current “command state” to EEPROM or restore it from there. This is mainly useful if the module is supposed to startup in some desired mode without recompiling it everytime.

LED quite – Lq

This sets all LED pins but the monitor pin to low.

LED flash – Lf

This replicates the monitor pin output to all LEDs. Thus they will flash as the signal arrives.

LED ticks – Lt

The LED bar is used like a rudimentary oscilloscope. One sweep of the bar corresponds to 200 ms. If the clock is synced it will trigger at the start of each second. This gives a very nice visualization of the signal. This mode is also very useful for a quick assessment of the received signal quality.

LED 200 ms ticks – L2

The LED bar is used like a rudimentary oscilloscope. One sweep of the bar corresponds to the first 200 ms of a second. The other 800 ms the state will be held. This makes it very simple to grasp if a short tick, a long tick or a minute marker is received.

LED seconds – Ls

Render the current seconds as BCD on the LED bar.

LED hours and minutes – Lh

Render the current hour and minutes as BCD on the LED bar. This is bascially what most people (incorrectly) call a binary clock.

LED months and days – Lm

This mode exists just for completeness. It renders months and days as BCD on the LED bar. A “binary” calendar.

LED analyze phase drift – La

Experimental mode for rendering the current phase with a 1 ms relution (10 times the resolution of the clock library). Since this is experimental I will not go into any details. If you want to find out read the source code.

LED calibration state and deviation – Lc

Render the current calibration state and the deviation that was observed so far. Allows it to see with one glimpse if the clock is synced or not and also if calibration is running or not. If calibration is running but the clock is out of sync it will start to blink. It also renders the the cumulated deviation (in centisecond ticks) in binary.

The video shows the different modes in action. Notice the dirty signal visualized in modes Lt and L2. This is due to the fluroescent video lighting.

Debug quiet – Dq

Do not render any output to the serial console. Helpful if you want to “freeze” the current output.

Debug quality factors – Dd

Each second it will output the decoded time (if any) as well as the leap second and summer/wintertime anouncement flags. Then it will output the statistics for the different decoder stage bins. For the numbers in parentheses the first number is the maximum of the convolution computation. The second number is the largest value below the maximum. A high difference shows a good signal noise ratio. The number behind the double colons shows the “quality” that is computed to assess if the values allow to compute a reasonable phase. “Bigger is better”. Then there are the flag statistics. They just count the collected numbers for the flag. The last value is the prediction match. It counts how good the locally synthesized signal matches the received signal. If this equals 50 the signal has no relevant interference. If it equals 25 or less the signal is completely useless.

This results in output that might look like below.

Decoded time: 14-09-16 2 22:42:40 CEST ..
Quality (p,s,m,h,wd,d,m,y,st,tz,ls,pm): 22 (75608-0:255)(163-0:22)(248-6:34)(252-5:34)(255-2:35)(252-5:34)(255-4:35)(252-3:35)86,43,43,50
Clock state: synced
Tick: 16

Decoded time: 14-09-16 2 22:42:41 CEST ..
Quality (p,s,m,h,wd,d,m,y,st,tz,ls,pm): 22 (75618-0:255)(163-0:22)(248-6:34)(252-5:34)(255-2:35)(252-5:34)(255-4:35)(252-3:35)86,43,43,50
Clock state: synced
Tick: 16

Decoded time: 14-09-16 2 22:42:42 CEST ..
Quality (p,s,m,h,wd,d,m,y,st,tz,ls,pm): 22 (75609-0:255)(163-0:22)(248-6:34)(252-5:34)(255-2:35)(246-0:34)(255-4:35)(252-3:35)86,43,43,50
Clock state: synced
Tick: 16

Of course while the clock syncs this may look more like the output below. In this example the last 4 decoder states are already almost good. However their rated quality is 0 and thus the clock does not yet lock to the signal. On the other hand the first decoder stage (the 1 Hz phase lock) is already perfectly well locked (quality maxed out at 255). Typically the phase lock is always easiest to achieved and here you can see it.

Quality (p,s,m,h,wd,d,m,y,st,tz,ls,pm): 0 (6617-0:255)(40-2:7)(32-24:1)(21-15:1)(9-6:0)(18-15:0)(15-12:0)(12-9:0)8,4,4,255
Clock state: useless
Tick: 5990
Quality (p,s,m,h,wd,d,m,y,st,tz,ls,pm): 0 (6638-0:255)(40-2:7)(32-24:1)(28-20:2)(9-6:0)(18-15:0)(15-12:0)(12-9:0)8,4,4,255
Clock state: useless
Tick: 6990
Quality (p,s,m,h,wd,d,m,y,st,tz,ls,pm): 0 (6659-0:255)(40-2:7)(32-24:1)(28-20:2)(9-6:0)(18-15:0)(15-12:0)(12-9:0)8,4,4,255
Clock state: useless
Tick: 7990

Debug scope – Ds

This emulates a simple logic analyzer / oscilloscope on the serial port. Since the code samples at 1 kHz it would be possible to render 1000 ticks per line. But because most monitors are not wide enough I render only 100 ticks per line. In order to still see I render 10 ms per tick. The output is the number of times a logical one was sampled per 10 ms. 0 is represented as either – or + and 10 is represented by an X. Below is some sample output of 5 seconds of a clean signal.

       10, +---------XXXXXXXXXX5---------+---------+---------+---------+---------+---------+---------+---------
       11, +---------9XXXXXXXXX6---------+---------+---------+---------+---------+---------+---------+---------
       12, +---------XXXXXXXXXXXXXXXXXXXX3---------+---------+---------+---------+---------+---------+---------
       13, +---------XXXXXXXXXX6---------+---------+---------+---------+---------+---------+---------+---------
       14, +---------XXXXXXXXXX5---------+---------+---------+---------+---------+---------+---------+---------
       15, +---------XXXXXXXXXXXXXXXXXXXX4---------+---------+---------+---------+---------+---------+---------

Debug Scope high resolution – DS

Similar as above but now with 1 ms per tick. I find this not that useful but it might become helpful with some suitable post processing.

Debug analyze frequency – Da

This mode allows to monitor the auto tune process. In the example below the “confirmend precision” is 0 Hz. This implies that the frequency adjustment was read from EEPROM and not yet verified. You can also see that autotune is running “@” and the clock is currently synced “+”. The current frequency adjustment is -518 Hz (so the crystal should be running at somewhere close to 16 000 518 Hz). It also shows that the phase lock drifted so far by 0 centiseconds. Also we see that the current calibration run is active for something around 10 minutes. Take notice how the centiseconds mod 60000 wrap around after ten minutes.

Below the auto tune stuff there are also some more esoteric numbers that belong to the experimental 1 ms resolution phase detection. This is irrelevant for the clock library but informative for low level analysis. If you want to figure this out please look into the source code.

confirmed_precision ?? adjustment, deviation, elapsed
0 Hz @+ , -518 Hz, 0 ticks, 9 min, 59901 cs mod 60000
53/100~0/600
confirmed_precision ?? adjustment, deviation, elapsed
0 Hz @+ , -518 Hz, 0 ticks, 10 min, 1 cs mod 60000
55/100~0/600
confirmed_precision ?? adjustment, deviation, elapsed
0 Hz @+ , -518 Hz, 0 ticks, 10 min, 101 cs mod 60000
53/100~0/600

Debug demodulator bins – Db

This gives some insight into the current state of the phase demodulator bins – the first and most important decoder stage. It allows to infer if it correctly locks to the phase and how good the signal quality is. Notice the bins between the “|” characters. These represent the first and second 100 ms of the second. The first 100 ms should always have very high values. Everything outside these 200 ms should ideally be 0. Below is a protocoll during startup at optimal conditions.

Phase: 29 Tick: 13 Quality: 2560-0 Max Index: 14 Quality Factor: 230
>,0,0,0,0,0,0,0,0,0,0,0,0,0,0|80,80,80,80,80,80,80,80,80,80|0,0,0,0,0,0,0,0,0,0|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Phase: 29 Tick: 13 Quality: 2590-0 Max Index: 14 Quality Factor: 232
>,0,0,0,0,0,0,0,0,0,0,0,0,0,0|81,81,81,81,81,81,81,81,81,81|1,1,1,1,1,1,1,1,1,1|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Phase: 29 Tick: 13 Quality: 2620-0 Max Index: 14 Quality Factor: 235
>,0,0,0,0,0,0,0,0,0,0,0,0,0,0|82,82,82,82,82,82,82,82,82,82|2,2,2,2,2,2,2,2,2,2|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Phase: 29 Tick: 13 Quality: 2630-0 Max Index: 14 Quality Factor: 236
>,0,0,0,0,0,0,0,0,0,0,0,0,0,0|83,83,83,83,83,83,83,83,83,83|1,1,1,1,1,1,1,1,1,1|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

Debug detailed demodulator Bins – DB

This mode gives all the output of Db plus the results of the filter convolution. Notice the value of 7035 for tick 13. This is the maximum value. The larger the difference of this to the second highest value the better the phase lock. As noise increases the maximum gets flatter and consequently phase jitter will increase.

Notice that this output contains artefacts (starting at bin 31) that are caused by the interrupt handling. Or with other words: the output has to be interpreted cum grano salis. It is not inteded for anything but debug purposes. If you do not understand why this happens just ignore it. The effect will never happen during the ISR that computes the phase lock. The effect is caused by evaluating the bins outside the ISR.

dirty
Phase: 29 Tick: 13 Quality: 7030-0 Max Index: 14 Quality Factor: 64
>,0,0,0,0,0,0,0,0,0,0,0,0,0,0|15F,15F,15F,15F,15F,15F,15F,15F,15F,15F|1,1,1,1,1,1,1,1,1,1|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
max_index, max, index, integral
0, 2108, 0, 2459
1, 2459, 1, 2810
2, 2810, 2, 3161
3, 3161, 3, 3512
4, 3512, 4, 3865
5, 3865, 5, 4218
6, 4218, 6, 4571
7, 4571, 7, 4923
8, 4923, 8, 5275
9, 5275, 9, 5627
10, 5627, 10, 5979
11, 5979, 11, 6331
12, 6331, 12, 6683
13, 6683, 13, 7035
14, 7035, 14, 6332
14, 7035, 15, 5629
14, 7035, 16, 4926
14, 7035, 17, 4223
14, 7035, 18, 3520
14, 7035, 19, 2819
14, 7035, 20, 2118
14, 7035, 21, 1417
14, 7035, 22, 716
14, 7035, 23, 15
14, 7035, 24, 13
14, 7035, 25, 11
14, 7035, 26, 9
14, 7035, 27, 7
14, 7035, 28, 5
14, 7035, 29, 3
14, 7035, 30, 1
14, 7035, 31, 4294967295
32, 4294967295, 32, 4294967293
32, 4294967295, 33, 4294967291
32, 4294967295, 34, 4294967291
32, 4294967295, 35, 4294967291
32, 4294967295, 36, 4294967291
32, 4294967295, 37, 4294967291
32, 4294967295, 38, 4294967291
32, 4294967295, 39, 4294967291
32, 4294967295, 40, 4294967291
32, 4294967295, 41, 4294967291
32, 4294967295, 42, 4294967291
32, 4294967295, 43, 4294967291
32, 4294967295, 44, 4294967291
32, 4294967295, 45, 4294967291
32, 4294967295, 46, 4294967291
32, 4294967295, 47, 4294967291
32, 4294967295, 48, 4294967291
32, 4294967295, 49, 4294967291
32, 4294967295, 50, 4294967291
32, 4294967295, 51, 4294967291
32, 4294967295, 52, 4294967291
32, 4294967295, 53, 4294967291
32, 4294967295, 54, 4294967291
32, 4294967295, 55, 4294967291
32, 4294967295, 56, 4294967291
32, 4294967295, 57, 4294967291
32, 4294967295, 58, 4294967291
32, 4294967295, 59, 4294967291
32, 4294967295, 60, 4294967291
32, 4294967295, 61, 4294967291
32, 4294967295, 62, 4294967291
32, 4294967295, 63, 4294967291
32, 4294967295, 64, 4294967291
32, 4294967295, 65, 4294967291
32, 4294967295, 66, 4294967291
32, 4294967295, 67, 4294967291
32, 4294967295, 68, 4294967291
32, 4294967295, 69, 4294967291
32, 4294967295, 70, 4294967291
32, 4294967295, 71, 4294967291
32, 4294967295, 72, 4294967291
32, 4294967295, 73, 4294967291
32, 4294967295, 74, 4294967291
32, 4294967295, 75, 4294967291
32, 4294967295, 76, 4294967291
32, 4294967295, 77, 4294967291
32, 4294967295, 78, 4294967291
32, 4294967295, 79, 4294967291
32, 4294967295, 80, 4294967291
32, 4294967295, 81, 4294967291
32, 4294967295, 82, 4294967291
32, 4294967295, 83, 4294967291
32, 4294967295, 84, 4294967291
32, 4294967295, 85, 4294967291
32, 4294967295, 86, 4294967291
32, 4294967295, 87, 4294967291
32, 4294967295, 88, 4294967291
32, 4294967295, 89, 4294967291
32, 4294967295, 90, 4294967291
32, 4294967295, 91, 4294967291
32, 4294967295, 92, 4294967291
32, 4294967295, 93, 4294967291
32, 4294967295, 94, 347
32, 4294967295, 95, 699
32, 4294967295, 96, 1051
32, 4294967295, 97, 1403
32, 4294967295, 98, 1755
32, 4294967295, 99, 2107
noise_index, noise_max: 52, 0

Debug raw output – Dr

This mode delivers the raw data as a stream of zeroes and ones separated by newlines. This is useful for computerized logging or post processing.

Debug cet/cest – Dc

This is pretty obvious. The output looks like below. During summertime / wintertime transition it will change the timezone between CEST and CET as needed.

synced:  2014-09-18 18:12:07 CEST (UTC+2)
synced:  2014-09-18 18:12:08 CEST (UTC+2)
synced:  2014-09-18 18:12:09 CEST (UTC+2)

Debug utc – Du

This is UTC (sometimes also referred to UTC+0 or GMT). To be formally correct this is UTC(PTB) with some small offset depending on your distance to DCF77. Below is some sample output.

synced:  2014-09-18 16:12:10 UTC
synced:  2014-09-18 16:12:11 UTC
synced:  2014-09-18 16:12:12 UTC

Persist output settings to EEPROM – *

This can be used to persist the current output settings to EEPROM. After the next reset the current settings will then be restored from EEPROM.

Restore output settings from EEPROM – ~

This overwrites the current settings with the value read from EEPROM. This can be used to determine what is currently stored in EEPROM.

Advertisements

9 Responses to Swiss Army Debug Helper

  1. Meo says:

    Hi Udo,

    Thanks for all this.
    At https://blog.blinkenlight.net/experiments/dcf77/the-clock/, you explained how to interpret the output of the debugger. In your release V3.0, you have changed the output a bit and I do not recognize some of the values anymore.
    Can you please redo the explanation?

    Regards,
    Meo

  2. Meo says:

    I ran your code on a Pro Mini 5V with crystal. Below is the last part of the output.
    I think you explained the meaning of the values behind p through Y more or less, but I don’t understand the values between parentheses at the the start of the record after 16.03.23. Nor do I understand the 4 values at the end.

    What is your opinion on the quality of the signal?

    Regards,
    Meo

    Decoded time: 16-03-23 3 15:07:02 CET ..
    16.03.23(3,3)15:08:01 MEZ 0,0 2 p(7404-0:255) s(130-27:14) m(48-30:3) h(56-36:3) wd(30-20:1) D(50-42:1) M(30-20:1) Y(30-30:1) 18,10,7,50
    Clock state: synced
    Tick: 14

    Decoded time: 16-03-23 3 15:07:03 CET ..
    16.03.23(3,3)15:08:02 MEZ 0,0 2 p(7396-0:255) s(130-27:14) m(48-30:3) h(56-36:3) wd(30-20:1) D(50-42:1) M(30-20:1) Y(30-30:1) 18,10,7,50
    Clock state: synced
    Tick: 14

    Decoded time: 16-03-23 3 15:07:04 CET ..
    16.03.23(3,3)15:08:03 MEZ 0,0 2 p(7396-0:255) s(130-27:14) m(48-30:3) h(56-36:3) wd(30-20:1) D(50-42:1) M(30-20:1) Y(30-30:1) 18,10,7,50
    Clock state: synced
    Tick: 14

    • The most important number is the last one. It is the “prediction match”. It indicates how many of the received bits (per minute) match the local synthesized values. The maximum is 50. So you have perfect signal quality.The other two values give some info on the “quality” of the timezone and leap second bit. Usually they do not give a lot of insight into the signal quality. If you see 50 at the end the signal is perfect. If you see 30 it is poor but still usable, if it is around 25 it is useless crap. If it is 0 your receiver got inverted during operation 😉

  3. John Prentice says:

    I suspect the V3.0 library has a problem with the MSF60 option.

    Here is my output for a PV DCF77 module:
    http://pastebin.com/QzFSkcDw

    When I put #define msf60 at the head of the Swiss Army tool source and connect the high sensitivity MSF60 board from PV I get:
    http://pastebin.com/vwz1kLNL

    The Second decoder just does not seem to see anything useful. I have tried reading the code but so far don’t understand it well enough to see the trouble.

    Any suggestions would be most welcome.

    John Prentice (UK – in the Midlands)

    • Hi John,
      if you look at github. Which branch / version are you talking about?
      Best regards, Udo

      • John Prentice says:

        The code is from the development branch which I got to by the first link on:

        https://blog.blinkenlight.net/2015/08/01/dcf77-library-release-3-0-0/

        The last commit to the header file was: 0650dd756f1ed14c2ff597da656713f071bd642f
        by Gavin commented as adding MSF support which is why I hoped it was switched on. There are certainly plenty on #ifdefs MSF60 in the .cpp code.

        Now that you point to the GitHub graph I realize I don’t understand it 😦 Master never seems to get merges from Develop. I suspect the meaning is quite different from the diagram on Bitbucket and other Git graphical tools as it is showing ownership.

        Best wishes

        John

      • Well, the development branch is for developers. There is no guarantee whatsoever that it works. I alloed Gavin’s code because he confirmed that it works. However I do not officially support it. So far it is considered experimental. I suggest you contact Gavin on this issue.

        Best regards, Udo

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s