OpenDCC: Erläuterungen zum Dekodieren von DCC

Prinzip des DCC-Empfangs:

    Wesentlich für einen Decoder ist die Frage: wie kann man DCC dekodieren?
    Bei DCC handelt es sich um im Prinzip um FM: eine "1" wird mit zwei kurzen Pulsen (nominal je 58µs), eine "0" mit zwei langen Pulsen (je 116µs) kodiert. Ein Decoder muß Puls zwischen 52µs und 64µs als "1" interpretieren, Pulse zwischen 90µs und 12ms als "0".

    Folgende Möglichkeiten zur Dekodierung ergeben sich daraus:
    1. Messen der Pulsdauern und Vergleich dieser Pulsdauern mit den Grenzen, die in der NMRA vorgegeben sind.
      • Realisierungsmöglichkeit 1: Die Pulsdauer könnte zum Beispiel mit dem Capture-Mode der Timer auf dem AVR gemessen werden.
      • Realisierungsmöglichkeit 2: Das Eingangssignal wird mit einer Softwareroutine abgetastet.
      • Bewertung: gute Erfassung des Signals (natürlich abhängig von der Taktrate der Abtastung), bei der Realisierung mit Capture-Mode allerdings auch große Störanfälligkeit - kurze Signalfehler und Reflexionen können die Messung durcheinander bringen.
        Der Capture-Mode verursacht geringe CPU-Last.
        Besser ist eine Abtastung (z.B. mit 100kHz) mit nachgeschalteter Filterung - hier lassen sich ungünstige Empfangsbedingungen (z.B. kurze Signalunterbrecher wegen verschmutzter Gleise) kompensieren.
        Berechnung der notwendigen Abtastrate
        A = max. Samples bei Erkennung 1: 64µs / Period + 1
        B = min. Samples bei Erkennung 0: 90µs / Period
        Bedingung, um 0/1 zu unterscheiden: A < B
        Ergebnis (aus Delta): Period < 13µs

        Jetzt muß zusätzlich noch die Bedingung "N = natürliche Zahl" an einer der beiden Grenzen berücksichtigt werden. Dabei können sich bei beiden Grenzen ähnliche Fraktionalteile ergeben, so daß sich jenseits obiger Bedingung weitere Period-Werte finden lassen, welche eine eindeutige Unterscheidung erlauben.

        Hier ist zu sehen, dass bei Abtastintervallen bis zu 15µs immer eine eindeutige Unterscheidung 0-1 möglich ist. Im Bereich 17-18us und bei 22us ergeben sich Fenster, die auch eine Unterscheidung erlauben. Bei kleinen Abtastintervallen wird die Differenz immer größer, was die Sicherheit erhöht.
      • Filter:
        Ein deutliche Verbesserung des Empfangs speziell bei mobilen Empfängern (wo ja der Rad-Schiene-Kontakt eine erhebliche Unsicherheit darstellt) kann mit einem passenden Tiefpassfilter erreicht werden, dessen Impulsantwort etwa die Breite einer halben "Eins" haben sollte.
        Das Tiefpaßfilter sollte zur Erhöhung der Störsicherheit wegen der Symmetrie von DCC eine ungerade Länge haben, also z.B. 3, 5 oder 7 lang sein. Die Integrationsdauer sollte nicht größer als die minimale Halbperiode einer "1" (etwa 50us) sein. Es ergeben sich daher folgende Alternativen:
          Filterlänge 3, Period ~ 17µs
          Filterlänge 5, Period < 11µs
          Filterlänge 7, Period < 7µs
        Nachfolgend ist die Wirkung eines solchen Filters (L=5) bei stark gestörtem Eingangssignal dargestellt:

        Ergebnis: Kurze Signal-Unterbrechungen werden weggefiltert.
        Die Abtastmethode kann für BiDi (RailCom) eine zu grosse zeitliche Unsicherheit bedeuten und ist zumindest in der 22µs-Variante nicht brauchbar.
    2. Einstellen einer mittleren Pulsdauer zwischen 1 und 0 und gezieltes Abtasten des Eingangssignals.
      • Realisierungsmöglichkeit: Das DCC-Signal startet (z.B. durch Flankentriggerung) den Abtaster. Dieser tastet nach 1,5 Pulsdauern einer "1" das Signal ab und erkennt so, ob schnelle Pegelwechsel (die Polarität hat sich geändert) oder langsame Pegelwechsel (die Polaritiät hat sich nicht geändert) anliegen. Das ist realisierbar mit den Interrupts und Timern des AVR. Vorteil: geringer Softwareaufwand.
      • Bewertung: relativ sichere Auswertung (Messung in Augenmitte), allerdings erfolgt nur eine Signalbewertung. Ist diese gestört, so wird ein falsches Bit erkannt (aber dafür hat DCC ja eine Checksum :-) Wenn der Abtaster nicht sauber realisiert ist (z.B. weil der erste Interrupt, welcher das Abtastintervall startet, erst verzögert bearbeitet wird oder weil die Schaltverzögerungen für Vor- und Rückflanke des Eingangsignals nicht gleich sind), kann es auch Fehlfunktionen kommen.
        Für RailCom ist zusätzlich eine Flankenerkennung erforderlich, um den zeitlichen Bezug für die Rücksendung sicherzustellen (dies ist aber möglich)
    3. Direkte Flankentriggerung, Zeitvergleich
      • Realisierungsmöglichkeit: Das DCC-Signal generiert einen Interrupt, darin wird die aktuelle Zeit (zirkular) genommen und mit der vorherigen Zeit verglichen. Die ISR erkennt so, ob schnelle oder langsame Pegelwechsel anliegen. Vorteil: geringer Softwareaufwand.
      • Bewertung: relativ einfach, entscheidungssicher. Probleme bei Reflexionen und Störungen.
        Für RailCom gut geeignet, da ja bereits ein harter Flankenbezug vorliegt.
    4. FM-Demodulation z.B. durch gleitende DFT oder Korrelation
      • Realisierungsmöglichkeit: Abtasten des Eingangssingals z.B. mit 14.5us (das ist 0,125 * Gesamtdauer einer "1"). Diese 8 Werte werden nun mit den entsprechenden Pulsfolgen für "1" bzw. "0" korreliert und je nach Ausschlag des Korrelators erfolgt die Decodierung.
      • Bewertung: Sichere Dekodierung auch bei massiv gestörten Signalen, erfordert jedoch *etwas* Rechenleistung ;-) . Für railcom weniger geeignet.
        Mit so einem Algorithmus wird mit einer Kanone auf Spatzen geschossen, zumindest wenn es um Signaldecoder geht; bei Lokdecodern, wo es durch den Rad-Schiene Kontakt und durch den Motor zu Störungen kommt, könnte es durchaus sinnvoll sein.
    In OpenDecoder (V.1) wird zur Zeit die Methode 2 verwendet. In OpenDecoder (V.2) wird auch die Methode 2 verwendet, jedoch kann alternativ Methode 1 (Abtastung und Filterung) gewählt werden.

Realisierung des DCC-Empfang (nach Methode 2):

    Das anliegende DCC-Signal triggert den Interrupt 0 des AVR. In der Interrupt Service Routine zu diesem Interrupt wird nur der Timer 1 gestartet.
    Dieser Timer 1 ist wie folgt programmiert:
      Programmierung Timer 1
      Mode CTC Clear Timer on Compare Match: bei Erreichen des Zählwertes CompA wird ein Interrupt ausgelöst und der Timer wieder auf 0 zurückgestellt.
      CompA 87µs Vergleichswert für CTC-Mode
    Wenn der Timer 1 nun seinen Vergleichswert erreicht hat, dann wird der Interrupt TIMER1_COMPA ausgelöst. In dieser Interruptroutine (ISR) wird zuerst der DCC-Eingang eingelesen und dann der Timer gestoppt und zurückgesetzt.
    Nun wird das eingelesene Bit ausgewertet. Hierzu benutzt die ISR eine Statusvariable Recstate. Recstate beschreibt, an welcher Stelle im DCC-Protokoll sich die ISR befindet.
      RecstateZustand der ISR
      WF_PREAMBLE warten auf den vollständigen Empfang der Präambel (mind. 10 mal "1")
      WF_LEAD0 warten auf die erste "0", welche den Beginn einer Nachricht anzeigt.
      WF_BYTE warten auf den vollständigen Empfang eines Bytes. In diesem Zustand wird für je 8 Bit verblieben.
      WF_TRAILER warten auf das abschließende Trennbit eines Bytes.
      Ist dieses "0", so folgt ein weiteres Byte - der State geht wieder auf WF_BYTE.
      Ist dieses Trennbit "1", so ist eine komplette Nachricht empfangen.
    Wenn eine komplette Nachricht empfangen wurde, dann wird das Flag C_received gesetzt, welches dann das Hauptprogramm veranlaßt, diese DCC-Message zu prüfen und auszuwerten.

Realisierung des DCC-Empfang (nach Methode 1):

    Der Timer wird so programmiert, dass alle 10us ein Interrupt ausgelöst wird. In der Interrupt Service Routine zu diesem Interrupt wird das anliegende DCC-Signal eingelesen und in ein Tiefpaßfilter der Länge 5 eingeschoben.

    Der Ausgangs des Filters kann nun Werte von 0 bis 5 annehmen, wobei 0,1,2 mehrheitlich ein anliegendes "LOW" anzeigen, die Werte 3,4,5 zeigen ein "HIGH".
    Wechselt der Filterausgang die Polarität, so ist ein Nulldurchgang des DCC-Signals erkannt. Man kann entweder nur einen Polaritätwechsel auswerten oder wie hier beide Richtungen. Ist die Zeit seit den letzten Polaritätswechsel <70us, so wurde die Hälfte einer DCC-"1" erkannt.
    Dieses so erkannte Halbbit wird nun ausgewertet. Hierzu benutzt die ISR eine Statusvariable Recstate. Recstate beschreibt, an welcher Stelle im DCC-Protokoll sich die ISR befindet.
      RecstateZustand der ISR
      WF_PREAMBLE warten auf den vollständigen Empfang der Präambel (mind. 20 mal "Halb-1")
      WF_LEAD0 warten auf die erste "0", welche den Beginn einer Nachricht anzeigt.
      WF_SECOND_HALF warten auf das zugehörige zweite Halbbit. Dieses muß den gleichen Wert wie das erste Halbbit haben.
      WF_BYTE warten auf den vollständigen Empfang eines Bytes. In diesem Zustand wird für je 8 Bit verblieben, wobei nach jedem Bit der Zustand WF_SECOND_HALF zusätzlich aktiviert wird, um das zweite Halbbit zu kontrollieren.
      WF_TRAILER warten auf das abschließende Trennbit eines Bytes.
      Ist dieses "0", so folgt ein weiteres Byte - der State geht wieder auf WF_BYTE+WF_SECOND_HALF.
      Ist dieses Trennbit "1", so ist eine komplette Nachricht empfangen.
    Wenn eine komplette Nachricht empfangen wurde, dann wird das Semaphor C_received gesetzt, welches dann das Hauptprogramm veranlaßt, diese DCC-Message zu prüfen und auszuwerten.