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.
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
I am currently very busy. Still I would give you the information that you need. What part of the output are you interested in?
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 😉
Thanks
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
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
Hi
I’m very excited by all this and am looking forward to the potential. I wanted to take things slowly just so I could understand everything.
I have a DCF77 hooked up and it seems to be picking up a signal. I wanted to hook this up to the scope but failed to come up with anything from my Arduino uno to the serial monitor. On this I decided to start with the Swiss army debugger but after loading in the sketch I get a tone of errors so I failed before I started.
Please advise.
This is not enough information to give you any hint on how to resolve this. Please post your errors. You may also want to read and follow this guide.
I updated to the latest version of the Arduino IDE. and this sorted out the errors.
I have a couple of questions that might put me on the right track but I save this until tomorrow. Thanks in advance.
Hi Udo,
I’m using your library with the conrad module and Conrad C-Control open Uno,(arduino clone) that *seems* to have a real crystal. When running at a breadboard setup I got A signal within 3 minutes every time, but after build into a box with some hardware I never get a lock. I did some experimenting with the dcf module and the DCF77_scope and find out that I only get a good signal when my body is close to the antenna (!?). I tried the swiss army knife debug tool with the same settings as the scope, and with Ds option, but is doesn’t give any readings at all while the scope does. I checked the settings a couple of times over again but without result.
I could really use your advice how to proceed.
Kind regards,
Gerard.
output of army knife tool:
Compiled: Fri Jul 27 17:39:04 2018
Architecture: AVR
Compiler Version: 4.9.2
DCF77 Library Version: 3.2.8
CPU Frequency: 16000000
Phase_lock_resolution [ticks per second]: 100
Quality Factor Sync Threshold: 2
Has stable ambient temperature: 1
Sample Pin: 3
Sample Pin Mode: 0
Inverted Mode: 0
Analog Mode: 0
Monitor Led: 18
EE Precision: 0.0000 ppm
EE Freq. Adjust: 0.0000 ppm
Freq. Adjust: 0.0000 ppm
Hi Gerad,
the Uno (and probably also the clones) do have a crystal. However it is used for serial communication and NOT as the controller clock. Hence this is most probably already a reason for your setup to not work as desired. WIth regard to the logs: I need more than just the boilerplate of the swiss amry debug helper. I need at least 1 hour worth of log data for further analysis.
Kind regards, Udo
Do recent version of the library show the example output for Db command? May be it is my fault but I don see the initial statistics. (Phase: 29 Tick: 13 Quality: 2560-0 Max Index: 14 Quality Factor: 230
>)
The code defaults to mode = d
https://github.com/udoklein/dcf77/blob/035ae2a1c7a5409e88062013200dd829bddce397/examples/Swiss_Army_Debug_Helper/Swiss_Army_Debug_Helper.ino#L440
So it should start with output of the quality factors
https://github.com/udoklein/dcf77/blame/035ae2a1c7a5409e88062013200dd829bddce397/examples/Swiss_Army_Debug_Helper/Swiss_Army_Debug_Helper.ino#L545
However the debug helper can persist its settings to EEPROM and will read them during setup.
https://github.com/udoklein/dcf77/blame/035ae2a1c7a5409e88062013200dd829bddce397/examples/Swiss_Army_Debug_Helper/Swiss_Army_Debug_Helper.ino#L735
I suggest to persist your desired default to the EEPROM.
https://github.com/udoklein/dcf77/blame/035ae2a1c7a5409e88062013200dd829bddce397/examples/Swiss_Army_Debug_Helper/Swiss_Army_Debug_Helper.ino#L557
Hope this helps.
Thank you.
You might fancy this little program