• 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
Prof. Dr.-Ing. Albrecht Weinert

development service consulting
blog excerpts
|< < > >|

The blog / excerpts

Welcome at Albrecht’s blog.


  • DCF77 decoding on Pi

    In the post “Time synchronisation in local nets” I described the uses of DCF77 receivers to get the standard time from PTB’s atomic clocks. The choice of AM receiver modules was commented on as well as their connection to a µController like a Raspberry Pi.

    Here we discuss the decoding of the DCF77 AM signal by C software on a Raspberry Pi.

    The nature of the AM signal

    At the begin of all seconds of a minute but the last one the amplitude is reduced to 15% for either 100 or 200 ms. For the rest of the one second or two second period the full (100%) amplitude is transmitted.

    The AM receivers’ modules digital output is usuallya good DCF77 signal
      •   HI (1, true)  for the 15% modulation time   and
      •   LO (0, false) for the rest of the 1 or 2 s period.
    But it can be the other way round – what the software should handle by, e.g. an option - - invert.

    For the modulation time
      •   200ms means true   and
      •   100ms means false.

    Hence, the information we must acquire and decode is
      a)   an 58 bit telegram in every minute   and
      b)   the start time of the modulation period.

    a): The telegram starts with 14 bits of secret commercial information. The remaining 44 relevant bits contain all time and date information. The code is simple and well published.

    b): The time should be captured as exactly as possible, best as µs time stamp with 20..50µs accuracy. With the time stamp of the modulation flank and their DCF77 time we get the correction respectively setting of our system time – for the use of DCF77 as our system’s redundant or only time source.

    So in the end, it’s all about getting the time (time stamps) of the modulation flanks.

    Sampling the AM signal

    We can think of three approaches
      A)   read the receiver signal in the 1 ms cycle,
      B)   have an interrupt handler for both signal flanks or
      C)   exploit the abilities of pigpiod.

    Having a runtime or framework providing PLC like cycles, as described in Raspberry for remote services (see publications) and used in most of our controller projects, approach A is feasible. On the other hand, when wanting DCF77 as substitute or redundancy for NTP the sync would have to be at least one order of magnitude better than the fastest cycle’s timing: a reason to exclude A).

    Generating sequence of time stamped events to be handled later in in an other thread or process is per se a good approach. And an interrupt handler (B) could do this. Raspberry processors have an interrupt system allowing flank interrupts for any GPIO. But the handling is complicated and the application requires sudo privileges.

    A comprehensible C solution with GPIO interrupts is hardly found. Some libraries or frameworks employ a fast sampling thread + interthread/interprocess communication – and call it “interrupt”.
    Well the approach is OK. It’s what what A) does too slow (what could be mended by an extra asynchronous fast cycle) and what C) (see below) supports perfectly. But selling it as interrupt is label fraud.

    Well I’m an aficionado of Joan N.N.’s pigpiod library. See the chapter in question and the literature list in above mentioned publication. So, not surprisingly, I exploit special abilities of a library used anyway.

    Capturing modulation flanks with pigpiod

    As said, pigpiod is our preferred approach to Raspberry IO and the only one we use for real control applications. It defines a server or daemon which does all initialisations and has control over all functions of the GPIOs used. This server has to be started with sudo to run forever in background. On all Raspberry Pis where we installed it we start it at boot by a (sudo) crontab entry:

    @reboot  /usr/local/bin/pigpiod  -s 10

    The -s 10 means pigpiod running ia 10µs cycle instead of a default 5µs one. For all our applications sofar a 20µs delay, time stamp and signal generation accuracy was sufficient. (The fastest would be 1µs.)

    Programs doing (process) IO just communicate with the daemon by
      •   socket (as in the GPIO with Java project),
      •   pipe (never used here) or by
      •   a set of C functions.

    pigpiod allows to set a callback function for the flank(s) of an input pin:

    set_mode(thePi, dcfGpio, PI_INPUT);             // make dcfGpio input
    if (dcfPUD <=PI_PUD_UP) // Raspberry Pi's pull up is sufficient for open
       set_pull_up_down(thePi, dcfGpio, dcfPUD);  // collector output stages
    dcf77callbackID = callback(thePi,            // register dcf77receiveRec
       dcfGpio, EITHER_EDGE, &dcf77receiveRec); // as callback function

    Semantically this is not so far from interrupts. But we avoid all interrupt complications (and dangers) and get 32 bit system time stamp for every signal flank in 1µs resolution and about 15µs accuracy as extra.

    /*  The actual respectively last modulation period data received. */
    dcf77recPerData_t dfc77actRecPer;
    /*  DCF77 receive recorder.
     *  This is a pigpiod callback function for .... (in other file)
    void dcf77receiveRec(int pi, unsigned gpio, unsigned level, uint32_t tick){
      uint32_t const dif = tick - dfc77actRecPer.tic;
      dcf77lastLevel = dcfInvert ? !level : level; // handle AM receiver polarity
      if (dcf77lastLevel) { // in 15% modulation, i.e end of last period
        dfc77actRecPer.per = dif; // calculate duration
        dfc77ringBrecPer[dfc77ringBrecWInd] = dfc77actRecPer; // put in FiFo
        ++dfc77ringBrecWInd;  // signal FiFo write respectively period end
        dfc77actRecPer.tic = tick;  // start of new period
      } else {  // full (100%) signal 
        memcpy(dfc77actRecPer.sysClk, lastSysClk, 12); // log time for current
        memcpy(lastSysClk, stmp23 + 11, 12);  // log time text for next period
        dfc77actRecPer.cbTic = get_current_tick(pi);  // call back tick
        dfc77actRecPer.tim = dif;
        dfc77actRecPer.sysClk[12] = dfc77actRecPer.sysClk[13] = '\0';
    } // dcf77receiveRec(int, 2*unsigned, uint32_t)

    For every modulation period the callback function shown fills a record of dfc77actRecPer of type dcf77recPerData_t and fills it in a FiFo (ring buffer).
    The structure is (excerpt with comments shortened):

    /** Data for one received DCF77 AM period. */
    typedef struct {
    /** The piogpiod time tick in µs. */
      uint32_t tic;
    /** The system time as text hh:mm:ss.mmm at period start for logging convenience.
     *  It is the time when the call back function for start of 15% AM is called.
      char sysClk[14]; // text provided by rasProject_01 framework 
    /** The system tick at second's start in µs.
     *  It is the time of entering the call back function for 15% AM.
     *  By the pigpiod event to callback entry delay cbTic should be later than tic. By
     *  their difference an apparent system-DCF77 time difference has to be shortened. 
      uint32_t cbTic;
    /** The 15% modulation's duration in µs. */
      uint32_t tim; // 100ms: FALSE, 200ms: TRUE
    /** The period's duration in µs. */
      uint32_t per; // 1s: second's end, 2s: minute's end
    } dcf77recPerData_t;

      •   start time .tic, ideally every real atomic second,
      •   period .per, ideally either 1s or 2s, and
      •   modulation time .tim, ideally either 100ms or 200ms,
    recorded over a minute we got all needed to decode and use DCF77 time.

    Remarks on discriminating and decoding

    With the chain of records – one for each modulation period, spoiled or correct – the first step is to discriminate the values .tim and .per.
    We define a stucture for a (uint32_t) value range:

    /** Values for discrimination of duration. */
    typedef struct {
    /** Index.
     *  The lowest value in a chain (array or enum) shall get the index 0. */
      unsigned i;
    /** Value.
     *  This is the upper bound value of the range. <br />
     *  The highest value in a chain (array or enum) is implied as the maximum
     *  possible / measurable value. */
      uint32_t v;
    /** Name.
     *  This is a short (5 character max.) name of the discrimination range. */
      char n[6];
    /** Charakter.
     *  This is a recognisable character for the discrimination range, unique
     *  for the complete chain. It may be used for (narrow) logs instead of .n */
      char c;
    } durDiscrPointData_t;

    For modulation period (.per) and modulation time (.tim) we define five ranges each two of them good and three bad (below, in beetween and above):

    /*  Discrimination values for the 15% modulation time. */
    durDiscrPointData_t timDiscH[5] = {
        {0,  60000, "spike", 's'},  // 0 .. 59.9 ms modulation spike
        {1, 128999, "false", 'F'},  
        {2, 130000, "undef", 'u'},
        {3, 228000, "true",  'T'},
        {4, MXUI32, "error", 'e'}      };
    /*  Discrimination values for the modulation period. */
    durDiscrPointData_t perDiscH[5] = {
        {0,  960000, "below", 'b'},
        {1, 1040000, "secTk", 'S'}, // seconds tick
        {2, 1960000, "undef", 'u'},
        {3, 2040000, "minTk", 'M'}, // end minute tick (last two seconds)
        {4,  MXUI32, "error", 'e'}    };

    Depending on the receivers, their quality and perhaps filters extra sets with other values are provided. In any case, obviously, the array length is 5. So one optimal discriminating function getting the array and a value returning the entry:

    /*  Discriminating a value.
     *  @param table discrimination table of length 5. With other lengths the
     *               function will fail. Must not be null.
     *  @param value the number to be discriminated
     *  @return  the lowest table entry with value < table[i].v
    durDiscrPointData_t * disc5(durDiscrPointData_t table[], uint32_t const value){
      if (value < table[1].v) {
        if (value < table[0].v) return & table[0];
        return & table[1];
      if (value < table[3].v) {
        if (value < table[2].v) return & table[2];
        return & table[3];
      return & table[4]; // Note: table[4].v (MXUI32 above) is never used
    } // disc5(durDiscrPointData_t const *, uint32_t const)

    When discrimination both the modulation time (.tim) and the period (.per) we get four good outcomes:
      •   F.S  false . second tick
      •   T.S  true  . second tick
      •   F.M  false . end minute tick (2 seconds)
      •   T.M  false . end minute tick (2 seconds)
    All other combinations are bad. Without bad ones we have a chain of true and false only, we can put indices respectively second numbers 0 .. 58 on and we can decode. And ideally each time stamp .tic of a modulation period would mark a “real” atomic second.

    But, alas, we get outages and spikes of all sorts leading to over 100 instead of 59 modulation periods per minute. With good receiver modules and antenna positioning you get very few disturbance. But yet, they occur.

    Filtering spikes with pigpiod

    On common sort of (AM) disturbances are short – often sub millisecond – spikes either as outage of the full modulation signal or as bursts appearing as full signal instead of 15% amplitude. Both disturbances can get in the range of 40 ms and more.

    Spikes could be wiped away with a simple de-bouncing algorithm ignoring all signal changes below a minimal duration. And, fortunately, pigpiod offers this feature for any input captured by call back function.

       if (dcfGlitch > 30000) dcfGlitch = 0;
       set_glitch_filter(thePi, dcfGpio, dcfGlitch);

    With glitch filtering the signal change call back, of course, would come later, but the time stamp is still correct.

    The spike or glitch filter value can be in the range of 0 (no filter) to 30000 µs. After many experiments (over weeks) we keep it below 10ms.
        dcf77onPi --glitch 9999 >> logs/dcf77test32cAnG.log &

    It emerged that higher values might filter out many seconds periods in sequence. Hence, one might detect “no reception at all” respectively “receiver off” wrongly.

    An extra filter for modulation disturbances

    The extra gain of a 30ms (maximum) glitch/spike filter compared with 10 ms is marginal with good receivers. And anyway, the shortening of a period below e.g. 400ms by a spike could not be handled by pigpiod’s glitch filter. These cases do occur and in most cases they lead to two periods where one should be. Without extra measures all counting and decoding hence on ist lost for the rest of the minute.

    A relatively simple extra filter therefore is holding back the first occurrence of ?.b and add this period correctly to next. By this most of those cases could be repaired. The log (Fr, 29.01.2021 14:16) shows a minute otherwise spoiled in second 41:

    ###        .tic       .tim    sec dis    .per  system time  -cbck decode
    DCF77 1.839.060.809   184080  36: T.S 1000492  14:17:35.138 -.185
    DCF77 1.840.061.301    84361  37: F.S  998913  14:17:36.238 -. 85
    DCF77 1.841.060.214    80060  38: F.S  998333  14:17:37.138 -. 80
    DCF77 1.842.058.547   186510  39: T.S 1004452  14:17:38.133 -.187
    DCF77 1.843.062.999    79390  40: F.S 1002533  14:17:39.238 -. 80
    DCF77 1.844.065.532    28300  41: s#b   53930  14:17:40.135 ignor= // spiky |
    DCF77 1.844.119.462   174560  41: T=S  994862  14:17:41.087 -. 29 29.mm. // v
    DCF77 1.845.060.394   180051  42: T.S 1004343  14:17:41.232 -.180
    DCF77 1.846.064.737    79360  43: F.S  996692  14:17:42.233 -. 79
    DCF77 1.847.061.429   181361  44: T.S 1003933  14:17:43.136 -.181 Day:Fr
    DCF77 1.848.065.362   175890  45: T.S  994952  14:17:44.235 -.176
    DCF77 1.849.060.314    82120  46: F.S 1003043  14:17:45.234 -. 82
    DCF77 1.850.063.357    74190  47: F.S  997652  14:17:46.134 -. 75
    DCF77 1.851.061.009    82901  48: F.S 1002913  14:17:47.131 -. 83
    DCF77 1.852.063.922    78590  49: F.S  997822  14:17:48.136 -. 79 dd.01.

    This moderately “intelligent” filter rescues most of those cases (which are quite seldom with good receiver modules). There are some samples where this algorithm failed while adding the spiky period to the one before would have saved that case. Well, implementing that is feasible but according to our data not worth the effort – and the reduced readability of the program.

    Final remarks

    On a Pi we implemented receiving and decoding DCF77 with inexpensive AM receiver modules. With the reception tick and the “call back tick + call back system time” pair we have all data to correct or set the system time. Hence, we have a (redundant) time source to replace or substitute NTP. It can also synchronise distributed systems in a closed network and or provide a NTP server there.

    The full code can be found in the SVN repository weinert-automation.de/svn/.


    Weinert, Albrecht,  “Time synchronisation in local nets”,  2021 post

    Weinert, Albrecht,  “Raspberry for remote services”,   2018 – 2020 publication

    N.N, Joan,  “pigpio Daemon”  2020 documentation

    PTB  “DCF77”  2004 German survey .. .. Read more

  • Jekyll tricks

    As became clear from my closing remarks in Dispose of Typo3 I am a strong proponent of static web sites. They run faster than any CMS and do well on inexpensive servers featuring little more than an Apache .. .. Read more

  • Sync time by NTP & DCF77

    In a LAN one should have the same time on all machines – be them servers, workstations or small controllers, like Pis for real time control as described, e.g., in “Raspberry for remote services” (see .. .. Read more

  • getopt_long accepts abbreviation

    Today (13.10.2020) I discovered getopt_long accepting abbreviations of long options. If your struct option (getopt.h) defines an option cons it will be triggered by --cons --con --co .. .. Read more

  • Scann QR code with Pi

    One of our realtime process control project with Raspberry Pi and C shall be enhanced with a QR and bar code scanner for access and booking. The ID cards in question would contain only numbers. In hindsight, regarding the scanner chosen, this is sheer luck. .. .. Read more

  • A comprehensive German dictionary for Eclipse

    If your HTML, XML and JavaDoc or Doxygen is to some extend German (and not just English) Eclipse’s spell checker .. .. Read more

  • Out of Typo3

    Typo3 is a powerful content management system (CMS) in wide spread use. It encompasses user administration, front end .. .. Read more

  • Java's incompatible file lock


    Java on the Pi

    Working on a project dealing with process IO with Java on a Raspberry Pi I got Java applications that must compete for a lock .. .. Read more

  • Raspberry Pi IO with Java


    Pi IO and process control with C

    The natural language for a small Linux controller, like a Raspberry Pi, is C — especially when it comes to process control using Pi’s GPIO .. .. Read more

  • Sampling stack usage on ATmegas with GCC software

    Embedded systems using AVR ATmega controllers are usually (best) implemented in pure C with a (WIN-avr) GCC toolchain. .. .. Read more

  • A serial bootloader for weAut_01, ArduinoMega and akin

    This is an AVR ATmega development report on a serial bootloader for ATmega1284P, ATmega328P and ATmega2560 .. .. Read more

  • Make Subversion keep mTime

    Subversion (SVN) is a good versioning system and in many fields still the standard.
    Nevertheless, it has one big deficiency: .. .. Read more

  • A first character range filter with XSLT 1.0

    This is a tip about implementing a first character range filter with pure XSLT 1.0. .. .. Read more

  • Oracle schließt Kenai

    Seit Ende 2008 bot SUN mit der Plattform Kenai Entwicklern von Java open source Projekten eine weltweit zugängliche Versionsverwaltung (mit .. .. Read more

  • Frame4J a Java – Framework

    Frame4J This infrastructure project facilitates the building of

    • robust, comfortable, nationalisable and reliable applications and tools for
    • stand-alone use as well as distributed web-applications.

    The main intended area of use are development, administration and process .. .. Read more

  • Windows verweigert .exe

    Das Phänomen

    Windows lässt den Admin ein Programm nicht starten.

    Beim Starten einer .exe (mit Doppelklick) sagt Ihnen Windows 2003 enterprise .. .. Read more

  • winmail.dat – Ärger

    Das Phänomen

    Ein mail-Anhang winmail.dat kommt ohne Wissen der Absender gelegentlich zustande, wenn Microsoft-Office-Dokumente (direkt oder indirekt) mit Microsoft-Outlook .. .. Read more

  • Start with Wordpress

    Until April 2019 this Blog was hosted at 1&1 and used WordPress there. Please find here some remarks on usage and experiences. .. .. Read more

Copyright   ©   2020  Albrecht Weinert          E-Mail (webmaster)
Revision: 2 (10/06/2020)        [de]