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

entwicklung dienstleistung beratung
blog excerpts
|< < > >|


Der Blog / Excerpts

Willkommen bei Albrecht Weinerts Blog.

Beiträge


  • Javas nicht kompatible Dateisperre

    Frame4J

    Java auf dem Pi

    Bei der Arbeit an einem Projekt über Prozessein- und -ausgabe mit Java auf Raspberry Pi entstanden Java-Applikationen die Sperrdateien .. .. Mehr lesen



  • DCF77 mit dem Pi dekodieren

    Im Beitrag “Zeitsynchronisation im lokalen Netz” beschrieb ich die Nutzung von DCF77-Empfängern als Quelle der (Atom-) Standardzeit der PTB. Die Wahl von AM-Empfängermodulen wurde ebenso behandelt wie ihr Anschluss an einen µ-Controller wie den Raspberry Pi.

    Hier nun geht es um die Dekodierung des AM-Signals mit C-Software auf einem Pi.

    Die Natur des AM-Signals

    Zu Beginn einer jeden Sekunde einer Minute mit Ausnahme der letzten wird die Amplitude für 100 oder 200 ms auf 15% abgesenkt. Für den Rest der einen bzw. zwei Sekunden wird die volle Amplitude (100%) gesendet.

    Der Digitalausgang von AM-Empfängermodulen ist üblicherweisegutes DCF77 signal
      •   HI (1, true)  für die Zeit mit 15% Amplitude   und
      •   LO (0, false) für den Rest der Modulationsperiode von 1 bzw. 2s.
    Aber es gibt es auch umgekehrt – was die Software mit einer Option, à la - - invert  handhaben sollte.

    Für die Modulationsdauer gilt
      •   200ms bedeutet true   und
      •   100ms bedeutet false.

    Es gilt also, folgende Informationen zu erlangen und zu dekodieren
      a)   ein 58-Bit-Telegram in jeder Minute   und
      b)   die Startzeiten der Modulationsperioden.

    a): Das Telegramm beginnt mit 14 Bit kommerzieller Geheiminformation. Die übrigen relevanten 44 Bits enthalten alle Zeit- und Datumsinformationen. Der Kode ist sehr einfach und gut publiziert.

    b): Die Zeit sollte so exakt wie möglich erfasst werden – am besten als ein µs-Zeitstempel mit 20..50µs Genauigkeit. Mit einem solchen Zeitstempel und der zugehörigen Zeit können wir die Systemzeit setzen bzw. korrigieren oder Zeit­signale bzw. -antworten generieren – also DCF77 als redundante oder auch einzige Zeit­quelle nutzen.

    So kommt es letztendlich auf das genaue Erfassen der Startzeiten an.

    Erfassen (sampling) des AM-Signals

    Drei Wege sind denkbar
      A)   Eingabe des Empfängersignals im 1ms-Zyklus,
      B)   eine Interrupt-Prozedur für beide Signalflanken oder
      C)   das Ausnutzen der Fähigkeiten von pigpiod.

    Mit einem Laufzeitsystem oder Framework mit SPS-artigen Zyklen, wie in Raspberry for remote services (see publications) beschrieben und in allen unseren µC-Steuerungsprojekten eingesetzt, ist der Ansatz A) möglich. Andererseits möchten wir letztlich DCF77 als Ersatz von oder Redundanz zu NTP einsetzen. Hier muss die Genauigkeit und ggf. die Synchronität um mindestens eine Größenordnung besser sein als der jeweils schnellste Zyklus. Diesen zur Zeit­er­fassung einzu­setzen verbietet sich im Allgemeinen. (Für eine DCF77-Uhr für Menschen würde es genügen.)

    Eine Sequenz von zeitgestempelten Ereignissen für die spätere Auswertung in einem eigenen thread aufzuzeichnen ist per se ein guter Ansatz. Und ein Interrupt-Handler (B) könnte dies. Die Raspberry-Prozessoren haben ein Interrupt-System, das Flanken-Interrupts für jeden GPIO bietet. Die Handhabung aber ist nicht einfach und die enthaltende (organisierende) Anwendung braucht sudo-Privilegien.

    Ein eingängliche, verständliche C-Lösung mit Pi-GPIO-Interrupts ist kaum zu finden. Einige Bibliotheken oder Frameworks verwenden einen schnellen Abtast-thread + interthread/interprocess-Kommunikation – und nennen das Ganze “interrupt”.
    Nun, der Ansatz ist OK. Es ist ein bisschen A) “in schneller” und das, was C) perfekt und nahtlos unterstützt. Es als Interrupt zu verkaufen, ist jedoch Etikettenschwindel.

    Ich bin ein großer Fan der pigpiod library von Joan N.N. Siehe die betreffenden Kapitel und die Literaturliste in der oben erwähnten Publikation. Wenig überraschend nutze ich auch hier die Fähigkeiten einer Bibliothek, die ich eh auf jedem Pi einsetze.

    Erfassen der Modulationsflanken mit pigpiod

    pigpiod ist wie gesagt unser bevorzugter Ansatz für IO mit dem Raspberry und auch der einzige den wir hier für echte Steuerungsanwendungen nutzen. pigpiod definiert einen Server oder daemon, der alles initialisiert und (nur) die verwendeten GPIOs mit all ihren möglichen Funktionen steuert. Dieser Server muss mit sudo gestartet werden und kann dann unbegrenzt laufen. Auf allen Raspberry Pis, wo wir dies installierten, erledigen wir den Start ab Einschalten mit einem (sudo) crontab-Eintrag:

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

    Die Option -s 10 lässt pigpiod in einem 10µs-Zyklus, statt default 5µs, laufen. Für alle unsere bisherigen Anwendungen waren 20µs Verzögerung bzw. Genauigkeit von Signalen und Zeitstempeln ausreichend. (Die schnellste Einstellung wäre 1µs.)

    Programme, die (process) IO machen, kommunizieren einfach mit dem daemon über
      •   socket (wie im Projekt GPIO mit Java,
      •   pipe (hier nie genutzt) oder
      •   mit einem Satz von C-Funktionen (die das socket interface nutzen).

    Bei pigpiod kann man eine callback-Funktion für die Flanke(n) eines Eingangs-GPIO setzen:

    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
    

    Das ist semantisch ganz nahe an Interrupts. Aber hierbei vermeiden wir sämtliche Komplikationen (und Gefahren) von Interrupts und bekommen einen 32 Bit Zeistempel für jede Flanke mit 1µs Auflösung und etwa 15µs Genauigkeit (mit -s 10, s.o.) als 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)
    

    Die gezeigte call back-Funktion erzeugt für jede Modulationsperiode eine Aufzeichnung (record) dfc77actRecPer vom Typ dcf77recPerData_t und tut ihn in einen FiFo (ring buffer).
    Die Struktur (struct) sieht so aus (Auszug mit gekürzten Kommentaren):

    /** 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;
    

    Mit
      •   der Startzeit .tic, idealerweise zu jeder “Atom-“ Sekunde,
      •   der Periode .per, idealerweise entweder 1s oder 2s, und
      •   der Modulationszeit .tim, idealerweise entweder 100ms oder 200ms,
    aufgezeichnet über eine Minute haben wir alles um die DCF77-Zeit zu dekodieren und zu nutzen.

    Anmerkung zum Diskriminieren und Dekodieren

    Mit der Kette von records – einer pro Modulationsperiode, gestört oder korrekt – ist der erste Schritt die Werte .tim und .per. zu diskriminieren.
    Wir definieren hierzu eine Struktur für (uint32_t) Wertbereiche:

    /** 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;
    

    Für die Modulationsperiode (.per) und die Modulationszeit (.tim) definieren wir damit jeweils fünf Bereiche, davon zwei gute und drei schlechte (darunter, undefiniert dazwischen und darüber):

    /*  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'}    };
    

    Abhängig von den eingesetzten AM-Empfängern, ihrer Qualität, ggf. zusätzlichen Filtern (nicht empfohlen) usw. können weitere Sätze mit anderen Grenzen bereitgestellt werden. Die Array-Länge ist aber offensichtlich immer 5. Die ermöglicht eine optimale Driskriminator-Funktion, die den zum Wert passenden Eintrag liefert:

    /*  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)
    

    Nach der Diskrimierung von Modulationszeit (.tim) und -periode (.per) gibt es vier mögliche gute Ergebnisse:
      •   F.S  false . Sekunden-Tick
      •   T.S  true  . Sekunden-Tick
      •   F.M  false . Minuten-End-Tick (2 s)
      •   T.M  false . Minuten-End-Tick (2 s)
    Alle anderen Kombinationen sind schlecht bzw. gestört. Ohne Störungen haben wir eine Kette logischer Werte (true oder false), denen wir Indizes bzw. Sekunden-Nummern von 0 bis 58 zuordnen und dekodieren können. Und idealerweise würde jeder jeder Zeitstempel (.tic) einer Modulationsperiode eine “echte- Atom-“ Sekunde markieren.

    Aber leider sehen wir Ausfälle Störungen aller Arten, die auch schon mal mehr als 59 Modulationsperioden pro Minute vortäuschen. Mit guten Empfängern und geeigneten Antennenstandorten sind Störungen recht selten – aber dennoch, sie passieren.

    Störungen mit pigpiod filtern

    Die gewöhnliche Störung bei AM-Rundfunk sind kurze – oft unter 1 ms – Störungen entweder als Signalausfall oder Störsignale anderer Sender oder elektrischer/elektronischer Geräte. Im Fall von DCF77 täuschen letztere i.a. anstelle der 15% Modulation volles Signal vor. Neben kurzen Spikes kommen auch länger anhaltende Störungen (40 ms und mehr) vor.

    Kurze Spikes bei binären Signalen können mit einfachen “de-bouncing” Algorithmen ausgefiltert werden, die alle Signalwechsel unterhalb einer minimalen Dauer ignorieren. Und, dankenswerterweise, bietet pigpiod genau diese Fähigkeit für Eingänge, die man mit call back-Funktionen überwacht.

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

    Mit dem Einsatz dieser glitch-Filter kommt der Aufruf der call back-Funktion natürlich später, aber pigpiod setzt den Zeitstempel – wie nicht anders zu erwarten – immer noch korrekt.

    Der Wert für den Spike- bzw. glitch-Filter kann zwischen 0 (kein Filter) und 30.000 µs gesetzt werden. Nach vielen Experimenten (über Wochen) halten wir ihn unter 10ms.
        dcf77onPi --glitch 9999 >> logs/dcf77test32cAnG.log &

    Es zeigte sich, dass höhere Werte gelegentlich über viele Sekunden hinweg ausfiltern. Also könnte man fälschlicher zur Diagnose “Gar kein Empfang” oder “Empfänger Aus” kommen.

    Zusätzliches Filter für Modulationsstörungen

    Der zusätzliche Nutzen von 30ms (das Maximum) Filterzeit verglichen mit den bevorzugten 10 ms ist bei guten Empfängern marginal. Und außerdem könnte beispielsweise die Verkürzung einer Modulationsperiode unter z.B. 400ms durch einen Spike mit dem glitch-Filter von pigpiod sowieso nicht ausgefiltert erden. Genau so etwas kommt durchaus vor und führt dann meistens zu zwei Perioden, wo eine sein sollte. Ohne Zusatzmaßnahmen ist dann alles Indizieren und Dekodieren für die betroffene Minute verloren.

    Ein recht einfaches zusätzliches Filter (Software) hiergegen ist es, das erste Vorkommen von ?.b zurückzuhalten und diese (zu kurze) Periode in korrekter Weise zur nächsten hinzu zu addieren. Hiermit können die meisten solchen Fälle repariert werden. Der Log-Auszug (Fr, 29.01.2021 14:16) zeigt eine Minute, die man ansonsten in Sekunde 41 verloren hätte:

    ###        .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.
    

    Diese “gemäßigt intelligente” Filter rettet die meisten solcher Fälle (welche mit guten Empfängern eh schon selten sind). Es kommt vor, dass dieser Algorithmus versagt, wo das Addieren der gestörten Periode zur vorangehenden geholfen hätte. Nun wäre es möglich auch das noch zu implementieren. Aber nach unseren Beobachtungen ist das weder der Mühe noch der verminderten Lesbarkeit des Programms wert.

    Schlussbemerkung

    Das Empfangen und Dekodieren von DCF77 haben wir auf einem Pi mit preiswerten AM-Empfängermodulen implementiert. Mit dem Empfangs-Tick und dem zugehörigen Paar “call back-Tick + call back-Systemzeit” haben wir die Werte zur Korrektur bzw. dem Setzen der Systemzeit beieinander. Somit haben wir eine (redundante) Zeitquelle, die NTP ersetzen oder ergänzen kann. Damit kann man auch verteilte Systeme in einem abgeschlossenen Netz synchronisieren oder dort einen (zusätzlichen) NTP-Server bereitsetellen.

    Der ungekürzten Kode findet sich im SVN-Repository weinert-automation.de/svn/.

    Literatur

    Weinert, Albrecht,  “Zeitsynchronisation im lokalen Netz”,  2021 post

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

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

    PTB  “DCF77”  2004 German survey .. .. Mehr lesen



  • Jekyll-Tricks

    Meine Schlussbemerkungen in Weg von Typo3 zeigen, dass ich ein engagierter Befür­worter der statischen Generierung von Web-Bereichen bin. Diese sind schneller als jedes CMS und fühlen sich auch auf preiswerten Servern wohl, die kaum mehr als Apache .. .. Mehr lesen



  • Zeitsynchronisation mit NTP & DCF77

    In einem LAN sollten alle Maschinen die selbe Zeit haben – egal ob Server, PC oder kleiner Controller, wie z.B. ein Pi für Echt­zeit­steuerung, wie in “Raspberry for remote services” (siehe .. .. Mehr lesen



  • getopt_long akzeptiert Abkürzungen

    Gestern (13.10.2020) verdeutlichte ich eine eine Programmoption von --DCF auf --DCF77 und musste feststellen, dass die vorherige immer noch ging. Die C-Bibliotheksfunktion getopt_long akzeptiert also .. .. Mehr lesen



  • Deutsches Wörterbuch für Eclipse

    Wer nicht nur Englisch schreibt sondern auch viele deutsche Texte in JavaDoc- oder Doxygen-Kommentaren .. .. Mehr lesen



  • 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. .. .. Mehr lesen



  • Weg von Typo3

    Typo3 ist ein weit verbreitetes sehr mächtiges content management system (CMS). Es umfasst Nutzer­ver­waltung, front end .. .. Mehr lesen



  • Raspberry Pi Ein- und Ausgabe mit Java

    Frame4J

    Pi IO und Prozesssteuerung mit C

    Bei sehr kleinen Linux-Rechnern, wie einem Raspberry Pi, ist die Sprache der Wahl C — besonders bei Prozesssteueranwendungen, welche z.B. beim Pi die GPIO .. .. Mehr lesen



  • 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. .. .. Mehr lesen



  • Ein serieller Bootloader für weAut_01, ArduinoMega &c

    Dies ist ein Entwicklungsbericht über einen seriellen Bootlader für ATmega1284P, ATmega328P und ATmega2560 .. .. Mehr lesen



  • Make Subversion keep mTime

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



  • 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. .. .. Mehr lesen



  • 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 .. .. Mehr lesen



  • 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 .. .. Mehr lesen



  • 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 .. .. Mehr lesen



  • 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 .. .. Mehr lesen



  • Start mit Wordpress

    Dieser Blog war bis April 2019 bei 1&1 gehostet und verwendete dort WordPress. Hier finden Sie ein paar Anmerkungen zum Einsatz und Erfahrungen. .. .. Mehr lesen



Copyright   ©   2020  Albrecht Weinert          E-Mail (webmaster)
Stand:  2 (06.10.2020)        [en]