Super Filter and Synthesizer

Probably you have read my super filter design. It aims to augment projects starting from a setup like pictured below.

The idea is to leverage my clock library to provide a super filter.

Inside this filter uses the library and feeds its results into a DCF77 synthesizer.

Pretty simple and obvious once the hard work of implementing such a library is done. However one of my readers complained that the result performed significantly worse than the library. This appears surprising at first glance but it was actually a design choice which might by argued about. Before this issue can be resolved you need to understand the different internal states of the clock library. These states were introduced with the local clock and are described at this experiment in greater detail.

The point is that the library has an internal state engine that follows the diagram below.

Clock States

Depending on the internal state the library will have more or less accurate time. Synced is most accurate, then comes locked, unlocked and free. Dirty might be accurate but useless is almost never accurate. The point was that I choose to only synthesize the signal if the internal state was “locked” or “synced”. In all other cases I was just passing the signal as it was received. This was to guarantee that the filter would not create “leaps” in its output (except wrongly placed 1s leaps for leap seconds). However the library keeps pretty accurate time even in free mode, especially if it was able to auto tune for a week or so. So the expectation was that even when the signal gets lost the synthesizer should still provide usable output. The impact of this is that the clock might leap an arbitrary amount of time once it resyncs. OK, I am exagerating. It would probably leap less than 1s for each week it was runnning “free”. Still this might be unacceptable for some projects.

I see some reasons why leaps may be unacceptable. One reason are “alarm triggers.” I would suggest to change those to “alarm states” like in my “Electro Mechanical” Time Switch experiment. However this might be no option for an existing project. Another reason are projects that will somehow use DCF77 for calibration purposes and rely on the knowledge of signal loss to switch to their internal clock.

On the other hand there may be projects that use just “naive sync with DCF77”. For those synthesizing the time signal even in “free” state would be an improvement.

Since I can not know what a project really would need my new super filter will provide just all options. It provides three different outputs.

    1. Filtered Output – This is the only option of the original Super Filter – If the clock is at least in state “locked” the output is synthesized and thus 100% clean. The phase will be spot on but it might leap for 1s at unexpected times if can not properly decode the leap second anouncement bit while a leap second is actually scheduled. In less than “locked” states it will pass the signal through. This is the most defensive modes which provides least filtering. If your DCF77 signal is not total crap it may be more than sufficient.
    2. Semi Synthesized Output – This is almost the same as above. However it keeps synthesizing in “unlocked” mode. That is it may allow the phase of the second ticks to drift up to 0.3 s before it starts to pass the signal just through. It is a middle ground. I have no clue if someone will need it but it was almost no additional effort to implement this.
    3. Synthesized Output – This is the most agressive filter mode. After its initial sync it will synthesize no matter what the internal state is. This is probably what some of my readers had in mind when they read about the super filter. However beware: if you do not evaluate the diff pin you will never know that it may be running “free” and thus drifting out of sync slowly. Of course if the signal is reasonable every once in a while it will resync though.

If you look into the code you will notice that it is almost the same as before. Only the pin setup and output handling basically trippled. There are also options to disable some of the output modes. I strongly recommend to disable the undesired modes because unless the signal is really really poor the behaviour of the modes is exactly the same.

//
//  www.blinkenlight.net
//
//  Copyright 2015 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 bool provide_filtered_output = true;
const uint8_t dcf77_filtered_pin          = 12;
const uint8_t dcf77_inverted_filtered_pin = 11;
const uint8_t dcf77_filter_diff_pin       = 7;

const bool provide_semi_synthesized_output = true;
const uint8_t dcf77_semi_synthesized_pin          = 16;
const uint8_t dcf77_inverted_semi_synthesized_pin = 15;
const uint8_t dcf77_semi_synthesized_diff_pin     = 14;

const bool provide_synthesized_output = true;
const uint8_t dcf77_synthesized_pin          = 6;
const uint8_t dcf77_inverted_synthesized_pin = 5;
const uint8_t dcf77_synthesized_diff_pin     = 4;

const uint8_t dcf77_second_pulse_pin = 10;


const uint8_t dcf77_signal_good_indicator_pin = 3;

volatile uint16_t ms_counter = 0;
volatile DCF77::tick_t tick = DCF77::undefined;


template <bool enable, uint8_t threshold,
          uint8_t filtered_pin, uint8_t inverted_filtered_pin, uint8_t diff_pin>
void set_output(uint8_t clock_state, uint8_t sampled_data, uint8_t synthesized_signal)
{
    if (enable) {
        const uint8_t filtered_output = clock_state < threshold? sampled_data: synthesized_signal;
        digitalWrite(filtered_pin, filtered_output);
        digitalWrite(inverted_filtered_pin, !filtered_output);
        digitalWrite(diff_pin, filtered_output ^ sampled_data);
    }
}

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 synthesized_signal =
        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;

    set_output<provide_filtered_output, DCF77_Clock::locked,
              dcf77_filtered_pin, dcf77_inverted_filtered_pin, dcf77_filter_diff_pin>
              (clock_state, sampled_data, synthesized_signal);

    set_output<provide_semi_synthesized_output, DCF77_Clock::unlocked,
               dcf77_semi_synthesized_pin, dcf77_inverted_semi_synthesized_pin, dcf77_semi_synthesized_diff_pin>
               (clock_state, sampled_data, synthesized_signal);

    set_output<provide_synthesized_output, DCF77_Clock::free,
               dcf77_synthesized_pin, dcf77_inverted_synthesized_pin,  dcf77_synthesized_diff_pin>
               (clock_state, sampled_data, synthesized_signal);


    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;

    now.undefined_minute_output                         = false;
    now.undefined_uses_summertime_output                = false;
    now.undefined_abnormal_transmitter_operation_output = false;
    now.undefined_timezone_change_scheduled_output      = false;

    DCF77_Encoder::advance_minute(now);

    tick = DCF77_Encoder::get_current_signal(now);
}

void setup_serial() {
    Serial.begin(115200);
}

void output_splash_screen() {
    using namespace DCF77_Encoder;

    Serial.println();
    Serial.println(F("DCF77 Superfilter 1.1.0"));
    Serial.println(F("(c) 2015 Udo Klein"));
    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.println();

    if (provide_filtered_output) {
        Serial.println(F("Filtered Output"));
        Serial.print(F("    Filtered Pin:         ")); Serial.println(dcf77_filtered_pin);
        Serial.print(F("    Diff Pin:             ")); Serial.println(dcf77_filter_diff_pin);
        Serial.print(F("    Inverse Filtered Pin: ")); Serial.println(dcf77_inverted_filtered_pin);
        Serial.println();
    }

    if (provide_semi_synthesized_output) {
        Serial.println(F("Semi Synthesized Output"));
        Serial.print(F("    Filtered Pin:         ")); Serial.println(dcf77_semi_synthesized_pin);
        Serial.print(F("    Diff Pin:             ")); Serial.println(dcf77_semi_synthesized_diff_pin);
        Serial.print(F("    Inverse Filtered Pin: ")); Serial.println(dcf77_inverted_semi_synthesized_pin);
        Serial.println();
    }

    if (provide_synthesized_output) {
        Serial.println(F("Synthesized Output"));
        Serial.print(F("    Filtered Pin:         ")); Serial.println(dcf77_synthesized_pin);
        Serial.print(F("    Diff Pin:             ")); Serial.println(dcf77_synthesized_diff_pin);
        Serial.print(F("    Inverse Filtered Pin: ")); Serial.println(dcf77_inverted_synthesized_pin);
        Serial.println();
    }

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

void setup_pins() {
    if (provide_filtered_output) {
        pinMode(dcf77_filtered_pin, OUTPUT);
        pinMode(dcf77_filter_diff_pin, OUTPUT);
        pinMode(dcf77_inverted_filtered_pin, OUTPUT);
    }

    if (provide_semi_synthesized_output) {
        pinMode(dcf77_semi_synthesized_pin, OUTPUT);
        pinMode(dcf77_semi_synthesized_diff_pin, OUTPUT);
        pinMode(dcf77_inverted_semi_synthesized_pin, OUTPUT);
    }

    if (provide_synthesized_output) {
        pinMode(dcf77_synthesized_pin, OUTPUT);
        pinMode(dcf77_synthesized_diff_pin, OUTPUT);
        pinMode(dcf77_inverted_synthesized_pin, OUTPUT);
    }

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



void setup_clock() {
    DCF77_Clock::setup();
    DCF77_Clock::set_input_provider(sample_input_pin);
    DCF77_Clock::set_output_handler(output_handler);
}

void setup() {
    setup_serial();
    output_splash_screen();
    setup_pins();
    setup_clock();
}

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

If you look at the code there is almost nothing new compared to the old super filter. Of course the output handling basically trippled as it now provides 3 outputs. The only noteworthy thing is the template function set_output. Notice that the if statement is NOT evaluated at run time but at compile time. As a consequence the compiler will optimize the function away for the modes that are not in use.

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