Erläuterungen zur Software von OpenDecoder


  • dcc_receive
    Empfang des DCC Signals (interruptgesteuert) und Bereitstellen der empfangenen Nachricht.
  • dcc_decode
    Analyse der empfangenen Nachricht; falls Servicemode oder PoM: Abwicklung der entsprechenden CV-Zugriffe
  • servo_engine
    Frei programmierbarer Servo-Kontroller mit Zeit-Ortskurven.
  • port_engine
    Programmierbarer Kontroller für IO (mit Zustandsautomaten, inkl. Blinken)

Interprozess-Kommunikation

    OpenDecoder arbeitet im kooperativem Multitasking, wobei die zeitkritischen Teile in Interrupts ausgelagert sind. Der Nachrichtenaustausch zwischen diesen Modulen erfolgt mittels Semaphor-Operationen. Diese erfolgen unter gesperrten Interrupts, damit die Operationen sicher sind:
  • Es gibt maximal 8 Semaphor-Bits, diese sind in config.h definiert, z.B. C_Received oder C_Save.
  • Der Sender einer Nachricht ruft z.B. semaphor_set(C_Received) auf. Das zugehörige Semaphorbit wird gesetzt.
  • Der Empfänger dieser Nachricht ruft semaphor_get(C_Received) auf. Das zugehörige Semaphorbit abgefragt und gleichzeitig zurückgesetzt.
    Beispiel:
    if (semaphor_get(C_Received)) analyze_received_message();
  • Zusätzlich gibt es für *Spionagezwecke* noch die Methode semaphor_query(C_Received), mit der das Semaphor abgefragt werden kann, es aber dabei nicht verändert wird.
  • Parallel zu diesen Semaphor-Operationen, welche die jeweiligen Ereignisse mitteilen, werden die Nachrichteninhalte über globale Variablen (z.B. ReceivedAddr) übermittelt. Dies ist nicht ganz sauber, jedoch sind die Zeitbedingungen recht entspannt, so daß kein Mailboxsystem erforderlich ist.

Timingsteuerung

    Um den Decoder leicht auf verschiedene Anwendungsfälle anpassen zu können, ist eine allgemeine Zeitsteuerung für jeden Augangsport vorgesehen. Diese Timingsteuerung benutzt drei Variablen je Ausgangsport:
      Variable Bedeutung
      rest Restdauer des aktuellen Zustands
      ontime Dauer des Einschaltzustands
      offtime Dauer des Auschaltzustands
    Zur Timingsteuerung wird nun in OpenDecoder alle 25ms (TICK_PERIOD) ein Interrupt ausgelöst. Die ISR prüft nun die Restdauer des aktuellen Zustand: ist diese Null, so wird der Ausgang unverändert belassen. Ist die Restdauer größer Null, so wird sie um eins dekrementiert. Wenn dabei Null erreicht wird, dann wird je nach aktuellem Zustand des Ports die "ontime" oder die "offtime" nachgeladen. Wenn nun eine Schaltaktion durchgeführt werden soll, so müssen nur der Anfangszustand des Ports und die Variablen richtig geladen werden, um das gewünschte Verhalten zu erreichen.

    Beispiele:
    • Port soll dauernd ein sein: Port = on, rest = 0;
      Durch "rest=0" wird weiteres Umschalten dieses Ports verhindert.
    • Port soll einen einzelnen Puls mit 0,5s ausgeben: Port = on, rest = 500ms, offtime = 0;
      Der "rest" von 500ms bewirkt ein Umschalten und Nachladen nach 500ms, danach erfolgt keine weitere Umschaltung mehr, da ja "rest" mit 0 nachgeladen wurde.
    • Port mit um 1s verzögertem Puls von 2s Dauer: Port = off, rest = 1000ms, ontime = 2000ms, offtime = 0
    • Wechselblinker:
      Port1 = on, rest = 500ms, ontime = 500ms, offtime = 500ms;
      Port2 = off, rest = 500ms, ontime = 500ms, offtime = 500ms
      Jeweils nach 500ms wird nachgeladen, ontime + offtime ergeben zusammen die Periodendauer des Blinkens.
    • Lauflicht, Baustellenblitzer:
      Port1 = off, rest = 100ms, ontime = 25ms, offtime = 975ms;
      Port2 = off, rest = 150ms, ontime = 25ms, offtime = 975ms;
      Port3 = off, rest = 200ms, ontime = 25ms, offtime = 975ms;
      Port4 = off, rest = 250ms, ontime = 25ms, offtime = 975ms;
      Port5 = off, rest = 300ms, ontime = 25ms, offtime = 975ms;
      Port6 = off, rest = 350ms, ontime = 25ms, offtime = 975ms;
      Port7 = off, rest = 400ms, ontime = 25ms, offtime = 975ms;
      Port8 = off, rest = 450ms, ontime = 25ms, offtime = 975ms;
      Alle 8 Ports haben die gleiche Periodendauer von einer Sekunde, werden allerdings um 50ms versetzt gestartet. Durch die kurze ontime entsteht der Blitzeffekt.
    Die im Sourcefile angebenen Zeitdauern sind immer in µs angegeben. Das gibt mitunter recht große Zahlen (auf Überlauf aufpassen) und erfordert, daß die Zahlen mit einem nachgestelltem "L" als long (d.h. 32 Bit) gekennzeichnet sind. Diese werden beim Übersetzen automatisch auf die notwendigen Einstellwerte der Zeitzähler umgerechnet. TICK_PERIOD gibt hier die kleinste Zeiteinheit vor, 255 mal TICK_PERIOD (=6.3s) ist dann die größte Zeiteinheit. U.a. deshalb ist es wichtig, dass beim Übersetzen die richtige CPU-Frequenz eingestellt wird.

Dimmersteuerung:

    Die Dimmersteuerung für das Überblenden geschieht mit einer interruptgesteuerten achtfachen Pulsweitenmodulation (PWM). Diese ist wie folgt implementiert:
    • Es gibt 60 Helligkeitstufen. Der gewünschte Heilligkeitswert je Port wird in dimm_val hinterlegt.
    • Alle 300us erfolgt ein Interrupt, dieser schaltet den Dimmer um eine Stufe weiter, nach 60 Stufen wird wieder von vorne begonnen. In jeder Stufe wird nun folgende Überprüfung durchgeführt:
        Stufe Aktion
        MIN alle Ports mit einem dimm_val > DIMM_MIN werden eingeschaltet.
        i Ein Port, dessen dimm_val kleiner x ist, wird abgeschaltet.
        MAX Restart und Meldung an den DIMMER (C_Dimmstep).
    • Folge: Alle Ports mit einem dimm_val kleiner DIMM_RANGE_MIN sind dauerhaft aus, alle Ports mit einem dimm_val größer DIMM_RANGE_MAX sind dauerhaft ein. Alle dazwischen liegenden Helligkeitswerte werden mit einer entsprechende PWM ausgegeben.
    • Da die PWM 60 Stufen hat und alle 300us ein Interrupt erfolgt, wird dieser Durchlauf alle 18ms durchgeführt. Dies entspricht einer Refreshrate von 55Hz.
    Beim Restart der PWM wird der DIMMER angestoßen, welcher je nach aktuell eingestellter Rampe und Vezögerung den dimm_val für den nächsten Durchlauf verändert. Z.B. kann durch langsames incrementieren des dimm_val ein Port aufgedimmt werden.

    Die Rampe und Verzögerung wird für die normalen Signale mit der Funktion set_new_light_val eingestellt; dieser Funktion braucht man nur das Bitfeld der neuen Zustände zu übergeben, eine Anpassung an verschiedene Signalbilder sollte daher leicht möglich sein.

Der Weg zum eigenen Decoder

    Nach Installieren der Tools (AVR Studio, WinAVR und Ponyprog) wird AVR-Studio geöffnet und ein neues Project angelegt. Für dieses wird als Simulationsumgebung AVR-Simulator und als Prozessor ATtiny2313 ausgewählt. Für OpenDecoder V2 entsprechend Atmega8515.

    Nun wird unter Project->Configuration Options die Taktrate und Optimierungshinweise für den Compiler eingestellt (man beachte besonders -Os = optimize for size):

    Hinweis: AVR Studio erwartet die Eingabe der Taktrate als Zahl (ohne UL), stellt die eingegebene Zahl dann jedoch mit angehängtem UL (=unsigned long) dar.

    Das Orginalfile wird dann mit rechter Maus auf den Reiter Source-File und "add existing source file" zum Project hinzugefügt und nun wird zuerst mal ohne das Sourcefile zu verändern übersetzt. Damit kann man kontrollieren, ob die Umgebung richtig eingerichtet ist.