OpenDCC: Erläuterungen zum Dekodieren von DCC
Prinzip des DCC-Empfangs:
-
Wesentlich für einen Decoder ist die Frage: wie kann man DCC
dekodieren?
- 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
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.
- 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)
- 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.
- 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.
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:
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 |
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.
Recstate | Zustand 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. |
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.
Recstate | Zustand 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. |