Schrankensteuerung am Modellbahnübergang mittles Mikrocontroller

Wie kam es zu dem Projekt ?

Ich baue an einer Spur N-Anlage, die ich sehr kompakt ausgelegt habe, damit sie in die Dachboden-Abseite passt. Die Hauptstrecke ist ein zweigleisiger Ring mit einer Länge von (nur) ca. 4,50 m. Ein Zug braucht da gerade mal 20 s, um einmal rumzufahren.

Es sollte nun auch ein beschrankter, automatisch betriebener Bahnübergang an die Hauptstrecke. Dazu hatte ich mir einen Faller-Bausatz 222170 für einen Bahnübergang mit elektrischem Schrankenantrieb besorgt. Faller-Bild

Bei diesem Antrieb erfolgt die Bewegung zum Senken und Heben der Schranken über eine Exzenterscheibe (unterer Totpunkt: Schranken zu, oberer Totpunkt: Schranken offen). Der Getriebe-Synchronmotor, der die Scheibe dreht, kann nur in eine Richtung laufen. Zum Anhalten in der jeweiligen Endstellung, wird ein Kontakt am unteren bzw. oberen Totpunkt über entsprechende Nocken (angegossene Stifte auf der Exzenterscheibe) geöffnet.

Wieso nicht so wie vom Hersteller gedacht?

Der Schaltplan in der Anleitung sieht Kontaktgleise bzw. Reedkontakte und Relais zur Steuerung vor. Entsprechend der langsamen Drehbewegung der Scheibe ist eine Vorlaufzeit von 8 s für die Meldung eines ankommenden Zuges angegeben. Dies bedeutet einen Abstand der Kontakte vor der Schranke von ca. 1,80 m. Ich musste feststellen, dass das auf meiner kleinen Anlage keinen Sinn machen würde. Der Antrieb musste also irgendwie schneller und die Steuerung möglichst effektiv werden. Dazu waren mir die Möglichkeiten der Elektromechanik zu eingeschränkt und die entsprechenden Komponenten auch zu teuer.

Und überhaupt wollte ich für dem Einbau von Kontaktgleisen oder Reedkontakten nicht die bereits fest verlegten und eingeschotterten Schienen wieder rausreissen.

Aber wie dann ?

Wenn es darum geht, eine Steuerung zu realisieren, die flexibel alle Funktionen abdeckt, setzt man heutzutage natürlich einen Microcontroller ein. Wie auch bei anderen Projekten, habe ich das Board Pro Mini (Arduino bzw. kompatible) in der 5V/16MHz-Ausführung genommen. Hier gibt's Näheres zum Einsatz dieser Technik..

Beim Grübeln, wie sich die Zug-Detektion preiswert, dezent und gleisschonend machen ließe, ist mir eine Lösung mit induktiven Sensoren eingefallen, die sich sehr gut und quasi nebenbei mit mit dem Prozessor auswerten lassen. Ich beschreibe das ausführlich auf meiner Seite "Näherungssensor".

Wichtiger Hinweis dazu: Im Gegensatz zu Reed-Kontakten oder Hall-Sensoren braucht dieser Sensor keine Magneten am rollenden Material. Ein bisschen Metall ist auch einfachen Waggons dran. Das genügt.

Sensoren braucht's in jeder Fahrtrichtung, und zwar:

  • Annäherungs-Sensoren auf der ankommenden Seite im ausreichenden Abstand zum Übergang und
  • Durchfahrts-Sensoren auf der abgehenden Seite unmittelbar nach dem Bahnübergang.
Auf meiner (kleinen) Spur N-Anlage befinden sich die Annäherungs-Sensoren auf Grund des Platzmangles nur jeweils ca. 80 cm vor dem Übergang. Auf einer Seite liegt direkt der Bahnhof mit zwei vor dem Übergang zusammenlaufenden Gleisen. D.h. hier braucht es in beiden Gleisen einen Annäherungs-Sensor. Ingesamt kommen also fünf Stellen zusammen.

Ein Sensor besteht aus einer zylindrischen Induktivität mit einem Duchmesser von ca. 6 mm. Er kann in eine entsprechende Bohrung im Gleis eingeklebt werden.

Vorbereitung der Bohrung

Vor dem Bohren sollte man den ggf. vorhandenen Schotter (Quarzsand) mit dem Diamantschleifer abgetragen, damit der Bohrer nicht stumpf wird. Es macht auch Sinn die Schwellen in der Größe des späteren Lochs wegzuschleifen, damit diese beim Bohren nicht weggegerissen werden.


Bohrung im Gleis

Nun kann man bohren.


Einkleben des Sensors

Den Sensor klebt man mit ein paar Tropfen Sekundenkleber bündig mit der Schienenoberkante ein. Dazu drückt man ihn am besten von unten gegen einen aufgelegten Anschlag.


Sensor im Gleis

Wieder Schotter rings um den Sensor, fertig! Schaut doch gut aus. O.k. man kann's noch schöner machen. Aber jedenfalls muss man da nichts verstecken.

Die Bilder zeigen übrigens einen der beiden Sensoren für ankommende Züge im Bahnhofsbereich.


Kann man den Antrieb schneller machen ?

Da schaut's auf den ersten Blick schlecht aus. Der Synchronmotor läuft halt nun mal so schnell, wie es die 50 Hz aus dem Trafo vorgeben,oder?

Aber nein, wir haben doch den Controller! Der kann jede Frequenz erzeugen, wenn auch der Einfachheit halber nur als Rechteck. Eine H-Brücke hinten dran, die ausreichend Strom treiben kann und schon hat man zwei Fliegen mit einer Klappe geschlagen: Ein-/Ausschalten des Motors und Geschwindigkeits-Boost.

O.k., jetzt erst mal der Schaltplan

Schaltplan
Schaltplan Schrankensteuerung

Die Stromversorgung der Schaltung erfolgt über den Stiftverbinder J0. Die gleichgerichtete und gesiebte Spannung aus einem üblichen Eisenbahntrafo kann verwendet werden. Die 5 V Für die Versorung des Microcontrollers (VCC ) werden mit einem 7805-Spannungsregler eingestellt. Für den Motor, d.h. für die Betriebsspannung der Leistungs-OPs, mit denen die H-Brücke realisiert ist, wird die Eingangsspannung direkt weitergeführt. Mit C1 (4,7 μF, 35V) erfolgt eine lokale Glättung dieser Spannung.

An die Analog-Eingänge A0...A5 des Controllers werden über die Stiftverbinder J1...J6 die induktiven Sensoren angeschlossen. Bei den Pro Mini-kompatiblen Boards sind meist auch noch die Analog-Pins A6 und A7 herausgeführt, so dass an A6 ein weiterer Sensor angeschlossen werden kann. Mehr geht aber nicht, da A7 nur als Eingang verwendbar ist.

Die Controller-Pins D2 und D3 werden als Ausgänge zur Ansteuerung der Leistungs-OPs des L272-Bausteins verwendet. Die Signale gehen an die invertierenden Eingänge. Für die nicht-invertierenden Eingänge der beiden OPs wird mit dem Spannungsteiler R9/R10 eine Referenz erzeugt, die zwischen den Logikpegeln des Microcontrollers liegt. Da die OPs ohne Gegenkopplung betrieben werden, werden ihre Ausgänge, entsprechend der anliegenden Logikpegel entweder voll auf Masse oder voll auf die Versorgungsspannung gezogen. Bei komplementärer Ansteuerung der Controller-Ausgänge D2, D3 ergibt sich am Motor eine rechteckige Wechselspannung, die nahezu die gleiche Amplidude wie die ursprüngliche Spannung aus dem Trafo hat, nur dass die Frequenz nun variabel ist. D1...D4 sind Freilaufdioden, die gebraucht werden, um Spannungsspitzen beim Umschalten zu unterdrücken. Gewöhnliche Gleichrichterdioden, z.B. 1N4004, tun es hier.

Der Dritte, etwas abgesetzte, Pin J10 wird mit dem oben erwähnten Nocken-Kontakt der Mechanik verbunden.

Motorstecker Dieses Bild zeigt den Stecker an J9/J10, wie ich ihn realisiert habe. D.h. verdrehsicher durch die Lücke, die durch einen Batzen Schmelzkleber verfüllt ist.


Motorstecker Farben Hier sieht man die Farbbelegung der Anschlussleitungen. Bei dem verwendeten Faller-Bausatz sind gelb und grün die Motoranschlüsse. Der Nocken-Kontakt liegt zwischen dem grünen und dem blauen Draht. Die meiste Zeit ist also das Potential auf grün und blau identisch. Nur wenn der Kontakt offen ist, ist blau hochohmig.

Die Unterbrechung am oberen Totpunkt wird für den zyklischen Reset der inkrementellen Positionsbestimmung benutzt. Die untere Nocke, d.h. dieser angegossene Stift auf der Exzenterscheibe, muss leider abgeschnitten werden, um Eindeutigkeit zu garantieren..

Um festzustellen, ob der Nocken-Kontakt geschlossen oder geöffnet ist, muss der Microcontroller prüfen, ob dieser Motorspannung führt oder hochohmig ist. Die Kombination R11, R12, D5 setzt dafür die Motorspannung auf Logikpegel um bzw. liefert 0 V an den Eingang D8, wenn der Kontakt offen ist.

Zu guter Letzt wäre da noch die Ansteuerung eventueller Lichter zu erwähnen. Ich habe mir Andreaskreuze mit Blinklichtern, also mit kleinen roten LEDs, dazugekauft. Auch die, vielleicht authentischeren, gelb/roten Lichtsignale (Ampeln) sind denkbar. Der Anschluss der Lichter erfolgt über J8. Die Controller-Ausgänge D4 und D5 treiben über die Vorwiderstände R13 und R14 die entsprechenden Leuchtdioden getrennt nach linker und rechter Straßenseite bzw. nach gelbem und grünem Licht. Die im Schaltplan angegebenen Vorwiderstände sind für je eine LED ausgelegt, die damit ca. 3 mA erhält. Habt Ihr mehrere Lichter zu treiben, weil Ihr z.B. beide Seiten des Übergangs bestückt habt, so müsst Ihr weitere Verbinder mit eigenen Vorwiderständen hinzufügen.

Was muss die Controller-Software also alles machen?

Der Microcontroller muss dazu folgende Aufgaben erledigen:
  1. Betrieb der Sensoren und Signalverarbeitung,
  2. Feststellung des Zugverkehrs-Status,
  3. Entscheidung über Schrankenbewegung,
  4. Erzeugung der Motor-Wechselspannung und Abfrage des Nocken-Kontakts zur Feststellung des oberen Totpunktes,
  5. Betrieb eventueller Warnlichter oder Ampeln.

Den kompletten Arduino-Sketch dazu könnt Ihr hier runterladen. Der Sketch gliedert sich in Deklarationen, Initialisierung (setup()), Hauptprogramm (loop()) und Interrupt-Routinen für Timer 2 (ISR(TIMER2_COMPA_vect, ...) und den Komparator ISR(ANALOG_COMP_vect)). Im Hauptprogramm passieren die Steuerentscheidungen, d.h. die Aufgaben 2) und 3). In der Timer-Interrupt-Routine wird alles gemacht was sauber zyklisch ablaufen soll, nämlich die Signalerfassung und -verarbeitung, die Motoransteuerung mit Abfrage des Nocken-Kontakts und das Blinken der Lichter. Im Folgenden erläutere ich jeweils auszugweise die Code-Schnipsel mit denen die oben beschriebenen Aufgaben erledigt werden. Der Rest sollte sich von selbst erklären.

1) Betrieb der Sensoren und Signalverarbeitung:
Diese Aufgaben werden in der Timer-Interrupt-Routine ISR(TIMER2_COMPA_vect, ...) zyklisch mit einer Rate von 100 Hz ausgeführt Der Betrieb der Sensoren ist an anderer Stelle ausführlich beschrieben. Hier will ich nur auf die spezielle Signalverarbeitung für die Zug-Detektion eingehen. Die Scrollbox zeigt dazu den Abschnitt der Timer-Interrupt-Routine, in dem der Zählwert des abgefragten Sensors gefiltert wird und die Indikatoren für eine Zugbewegung daraus abgeleitet werden:

--- Auszug Arduino-Sketch Schrankensteuerung Signalverarbeitung ---
    ...
    ...
    // smooth measurement by 1st order filter
    m_smooth = measurement[channel];
    m_smooth += min(comparator_count, ZERO_MEASUREMENT); // addd new result, counts above ZERO_MEASUREMENT (due to HF disturbances) are cut off
    m_smooth >>= 1;

    // differentiate measurement and smooth it by 1st order filter
    measurement_delta[channel] -= (measurement_delta[channel] >> 6);  // subtract "forgetting" portion from old value, shifting by 6 means 1/64 portion
    measurement_delta[channel] += (abs(measurement[channel] - m_smooth) << 8); // add fluctuation multiplied by 256

    measurement[channel] = m_smooth;

    motion_detected[channel]    = ((measurement_delta[channel] >> 8)         > MOTION_DETECTION_THRESHOLD);
    if (motion_detected[channel]) time_extension[channel] = SIGNAL_HOLD;
    else if (time_extension[channel] > 0) time_extension[channel]--;
    ...
    ...

Der Code beginnt mit dem Umspeichern des alten Messwertes measurement[] nach m_smooth. Der neue Messwert (in comparator_count) wird, nachdem er nach oben auf den maximal möglichen Zählwert ZERO_MEASUREMENT begrenzt wurde, dazu addiert. Anschließend wird um eins nach rechts geschiftet, also durch zwei geteilt. In m_smooth steht also dann der Mittelwert aus dem bisherigen Messwert und dem neuesten Zählwert. Das beseitigt schon mal einen Gutteil eventuellen Bit-Klapperns (häufiges Hin- und Herspringen um eine Stufe). Die Begrenzung des aktuell hinzu addierten Wertes unterdrückt Ausreisser, die durch Störeinstreuungen, z.B. durch kurzzeitige Unterbrechungen beim Scheine-Lokomotiv-Kontakt, auftreten können.

Anschließend wird measurement_delta[], d.h. ein Wert für die Betragsänderungen von m_smooth, gebildet. Eine mit einer Zeitkonstante von ca. 0,6 s verzögerte Anpassung (ein 1/64 pro 10 ms Zeitschritt) an die momentanten Änderungen sorgt dafür, dass die Lücken zwischen den Achsen überbrückt werden und measurement_delta[] somit als verlässlicher Indikator für Zugbewegungen verwendet werden kann. Die Multiplikation mit 256 (Shift um 8 nach links) bei der Speicherung ist aus Gründen der numerischen Genauigkeit nötig.

Die bool'sche Variable motion_detected[], die im Hauptprogramm letztendlich zum Einsatz kommt, wird unter Berücksichtigung der Schwelle MOTION_DETECTION_THRESHOLD gebildet. Je niedriger diese Schwelle gesetzt wird, desto später fällt das motion_detected-Flag ab, nachdem sich am Sensor nichts mehr rührt. Ein zeitlicher Nachlauf dieser Variablen, der im Hauptprogramm ebenfalls gebraucht wird, wird mit time_extension[] realisiert. SIGNAL_HOLD habe ich in meiner Implementierung auf 600 gesetzt, d.h. time_extension[] geht 6 s nach der abfallenden Flanke von motion_detected[] auf Null. Es ist die maximal angenommene Zeit zwischen Annäherung und Durchfahrt.

2) Feststellung des Zugverkehrs-Status:
Diese Aufgabe wird asynchron zur Messwerterfassung in der Endlosschleife des Hauptprogramms ca. alle 0,1 s ausgeführt. Auf der Basis der oben beschriebenen Indikatoren (von den Sensoren beider Fahrtrichtungen) und der vorherigen Situation muss festgestellt werden, ob gerade (mindestens) ein Zug in Anfahrt ist, der Zug (die Züge) bereits durchgefahren ist (sind) oder ob gerade gar kein Verkehr zu beachten ist. Das klingt einfach, kann aber bei zwei Fahrtrichtungen bereits unübersichtlich werden. Da die bisherige Situation (Zustand) eine Rolle spielt, bietet sich die Programmierung mit Zustandsautomaten an. Das sind die beiden switch-Befehle für je eine Fahrtrichtung mit den Zustandsvariablen train_on_inner_ring und train_on_outer_ring:

--- Auszug Arduino-Sketch Schrankensteuerung Erkennungslogik Zugverkehr ---
  ...
  ...
  switch(train_on_inner_ring) {
    case NO_TRAFFIC:
      if (motion_detected[SENSOR_INNER_RING_GOING])         train_on_inner_ring = LEAVING;
      if (motion_detected[SENSOR_INNER_RING_COMMING])       train_on_inner_ring = APPROACHING;
      break;
    case APPROACHING:
      if ((motion_detected[SENSOR_INNER_RING_GOING]) ||
          (time_extension[SENSOR_INNER_RING_COMMING] == 0)) train_on_inner_ring = LEAVING;
      break;
    case LEAVING:
      if (! motion_detected[SENSOR_INNER_RING_GOING])       train_on_inner_ring = NO_TRAFFIC;
      break;
  }
  switch(train_on_outer_ring) {
    case NO_TRAFFIC:
      if (motion_detected[SENSOR_OUTER_RING_GOING]) train_on_outer_ring = LEAVING;
      if (motion_detected[SENSOR_OUTER_RING_COMMING_R] || motion_detected[SENSOR_OUTER_RING_COMMING_L]) train_on_outer_ring = APPROACHING;
      break;
    case APPROACHING:
      if ((motion_detected[SENSOR_OUTER_RING_GOING]) ||
          ((time_extension[SENSOR_OUTER_RING_COMMING_R] == 0) && (time_extension[SENSOR_OUTER_RING_COMMING_L] == 0))) train_on_outer_ring = LEAVING;
      break;
    case LEAVING:
      if (! motion_detected[SENSOR_OUTER_RING_GOING]) train_on_outer_ring = NO_TRAFFIC;
      break;
  }

  some_train_approaching = (train_on_inner_ring == APPROACHING) || (train_on_outer_ring == APPROACHING);
  some_train_leaving = (train_on_inner_ring == LEAVING) || (train_on_outer_ring == LEAVING);
  ...
  ...

Diese Zustandsvariablen können die Werte:

  • NO_TRAFFIC (es ist gerade nichts los),
  • APPROACHING (es wurde ein Zug in Anfahrt erkannt) und
  • LEAVING (der anfahrende Zug hat den Bahnübergang erreicht, aber noch nicht vollkommen passiert)
annehmen. Je nach momentanem Zustand gelten andere Bedingungen für einen Zustandswechsel (siehe Code). Die Flags motion_detected der einzelnen Sensoren sind dabei die wesentlichen Eingangsgrößen.

Die Zustände der einzelnen Fahrtrichtungen werden letztendlich zu den Flags some_train_approaching (mindestens ein Zug ist in Anfahrt) und some_train_leaving (mindestens ein Zug ist dabei den Übergang zu passieren) verknüpft.

3) Entscheidung über Schrankenbewegung:
Ein weiterer switch-Befehl (Zustandsautomat) mit dem Argument state_of_gate entscheidet auf Basis der o.g. Flags und der aktuellen Position motor_position , ob der Antriebsmotor laufen soll und, wenn ja, wie schnell:

--- Auszug Arduino-Sketch Schrankensteuerung Zustandsautomat Schrankenbewegung ---
    ...
    ...
  switch(state_of_gate) {
    case OPENING:
      if (motor_position >= GATE_DOWN_POSITION) { // if not yet over upper dead centre
        if (some_train_leaving || some_train_approaching) { // if closing request comes in while opening
          if ((motor_position < GATE_DOWN_POSITION+180) || (! some_train_approaching)) // if not so wide open or no train approaching ...
            Motor_Off();                       // ... just interrupt movement
          else {  // direct shortcut to CLOSING, gate will open completely and close again immediately
            state_of_gate = CLOSING;
            motor_position %= 2; // reset motor position
            Motor_On(MAX_BOOST); // motor stays on or is started, going extra fast
          }  // urgent closing request
        } // if closing request comes in while opening
        else // continue opening
          Motor_On(NORMAL_BOOST); // motor stays on or is started, going with normal speed
      } // if not yet over upper dead centre
      else { // already beyond upper dead centre, i.e. motor position was reset in "ISR(TIMER2_COMPA_vect, ...)"
        if (motor_position >= EXTRA_MOVEMENT) {   // "OPENING" state is changed to "OPEN" after wrap around of counter (i.e. reaching further position beyond dead centre)
          state_of_gate = OPEN;
          Motor_Off();
        }
        else
          Motor_On(MAX_BOOST); // motor stays on or is started, going faster finally
      }
      break;
    case OPEN:
      if (some_train_approaching) { // start closing gate
        state_of_gate = CLOSING;
        Motor_On(NORMAL_BOOST); // close with normal speed
      }
      break;
    case CLOSING:
      if (motor_position >= GATE_DOWN_POSITION) {   // "CLOSING" state is changed to "CLOSED"  after reaching according position
        state_of_gate = CLOSED;
        Motor_Off();
      }
      break;
    case CLOSED:
      if (! (some_train_approaching || some_train_leaving)) { // start opening gate
        state_of_gate = OPENING;
        Motor_On(NORMAL_BOOST); // open with normal speed
      }
      break;
  }

  flashing_on = some_train_leaving || some_train_approaching; // switching the lights is done in  "ISR(TIMER2_COMPA_vect,  ...)"
    ...
    ...

Die Zustandsvariable state_of_gate kann die Werte:

  • OPEN (Schranken sind in einer Position bereits etwas über dem oberen Totpunkt, Motor ist aus),
  • CLOSING (Motor läuft, Schrankenbewegung nach unten)
  • CLOSED (Schranken sind am unteren Totpunkt, Motor ist aus) und
  • OPENING (Motor läuft oder ist vorübergehend angehalten, Schrankenbewegung nach oben)
annehmen.

motor_position wird durch Mitzählen bei der Erzeugung der Motor-Wechselspannung ermittelt (siehe weiter unten). Das Argument des Untergrogramms Motor_On() gibt an, um wieviel höher die Motorspeisung über den 50 Hz liegen soll. NORMAL_BOOST ergibt 80 Hz. Bei MAX_BOOST werden 114 Hz erzeugt, der Motor läuft also mehr als doppelt so schnell! Viel höher geht es aber nicht, da bei zu hoher Frequenz der Motor nicht mehr mitmacht, also stehen bleibt.

Was und wann unter welchen Bedingungen gemacht wird, ist im Code zu sehen. Speziell im Zustand OPENING gibt es mehrere wenn-dann-Entscheidungen. Das liegt an folgenden Situationen, die vor allem bei kompakten Anlagen, d.h. bei kurzen Annäherungs- und Öffnungsverzögerungszeiten, auftreten:

Sind die Schranken nämlich gerade wieder am Aufgehen und es kommt just ein Zug auf dem Gegengleis, dann muss die Mechanik zunächst über den oberen Totpunkt (komplett offen) laufen, um dann erst wieder schließen zu können. Das muss möglichst schnell gehen, um zu vermeiden, dass bei Durchfahrt dieses Gegenzuges die Schranken noch offen sind. Dafür kommt MAX_BOOST bei einem direkt eingeleiteten Schließvorgang zum Einsatz. Sind die Schranken bei Annäherung des Gegenzuges aber erst gerinfügig geöffnet, wird der Antrieb lediglich vorübergehend angehalten.

Strebt man eine kurze Verzögerungszeit für das Wiederöffnen der Schranke an, kommt es bei langsamen Zügen mit langen Waggons vor, dass das motion_detected-Flag zwischen den Drehgestellen der Waggons, zeitweise schon wieder abfällt und somit kurzzeitig von einer erfolgten Durchfahrt des Zuges ausgegangen wird. Die Schranken sind dann bereits ein Stück offen, wenn weitere Radsätze vorbeikommen, also erkannt wird, dass der Zug doch noch nicht durch ist. In dem Fall bleibt die Logik zwar weiterhin bei OPENING, der Antrieb wird aber vorübergehend angehalten.

Am Ende der Öffnungsphase werden die Schranken in eine Bereitschaftsposition gefahren, die ein Stück (EXTRA_MOVEMENT) über dem Totpunkt liegt. Der nächste Schließvorgang kann damit schneller erfolgen.

4) Erzeugung der Motor-Wechselspannung und Abfrage des Nocken-Kontakts:
Der Code dazu befindet sich in der Timer-Interrupt-Routine:

--- Auszug Arduino-Sketch Schrankensteuerung Motorbetrieb ---
    ...
    ...
  /* motor control */
  if (chopping_count++ >= (THE_100HZ_INTERVAL-chopping_boost)) {
    if (motor_on) {
      if ((! PIN_IS_HIGH(PINB, PINB0)) && (! PIN_IS_HIGH(PORTD, PORTD3))) motor_position %= 2; // reset motor position
      if (motor_position % 2 == 0) {
        SET_LOW(PORTD, PORTD2);
        SET_HIGH(PORTD, PORTD3);
      }
      else {
        SET_LOW(PORTD, PORTD3);
        SET_HIGH(PORTD, PORTD2);
      }
      motor_position++;
      if ((chopping_boost < max_boost) && ((motor_position % 8) == 0)) chopping_boost++;
    }
    else {
      SET_LOW(PORTD, PORTD2);
      SET_LOW(PORTD, PORTD3);
    }
    chopping_count = 0;
  }  // clock for motor, i.e. 100 Hz + boost
    ...
    ...

Die gezeigte Codesequenz wird, wie auch die gesamte Interrupt-Routine, mit 3,2 kHZ aufgerufen. Über den Zähler chopping_count und den Vergleich mit THE_100HZ_INTERVAL-chopping_boost erfolgt ein Herunterteilen auf eine Rate von 100 Hz plus X (konkret verwendete Werte siehe oben). Entsprechend dem Flag motor_on, das vom Hauptprogramm gesetzt wird, werden die beiden Motorausgänge D2/D3 alternierend geschaltet oder zu Null gesetzt. Nebenbei wird motor_position mitgezählt. Der aktuelle Stand des Antriebes, ausgehend vom Reset am oberen Totpunkt, ist damit exakt bestimmt. chopping_boost wird bei Motorstart vom Hauptprogramm zunächst auf Null gesetzt und hier langsam auf max_boost hochgezählt.

Die Abfrage des Nocken-Kontakts (Pin B0) erfolgt vor dem Schalten der Motorpins. Wenn festgestellt wird, dass B0 nicht auf high steht, obwohl D3 low ist (der zugehörige Motorpin ist über die Negierung des Verstärkers dann high), wird der Reset von motor_position durchgeführt.

5) Blinken der Warnlichter:
Dieser Code findet sich am Ende Timer-Interrupt-Routine:

--- Auszug Arduino-Sketch Schrankensteuerung Blinken der Warnlichter ---
    ...
    ...
//#define TEST  // if defined the lights are used to give internal information about state
#ifndef TEST
  /* flashing lights */
  if ((flashing_on) || (flashing_phase > 0)) { // if flashing requested or cycle not completed
    if (flashing_phase < FLASHING_INTERVAL/2) {
      SET_HIGH(PORTD, PORTD5);  // Pin 5, this flashing light initially on
      SET_LOW(PORTD, PORTD4);   // Pin 4, opposite flashing light initially dark
    }
    else {
      SET_LOW(PORTD, PORTD5);   // Pin 5, this flashing light now dark
      SET_HIGH(PORTD, PORTD4);  // Pin 4, opposite flashing light now on
    }
    if (flashing_phase++ >= FLASHING_INTERVAL) flashing_phase = 0;
  }  // flashing on
  else {  //lights dark
    SET_LOW(PORTD, PORTD4); // both lights dark
    SET_LOW(PORTD, PORTD5);
  }
#endif

#ifdef TEST
// using the lights to give internal information about state
if (flashing_phase == 0) {
  switch(state_of_gate) {
    case CLOSING:  // left light blinking
      SET_LOW(PORTD, PORTD5);
      TOGGLE(PORTD, PORTD4));
      break;
    case CLOSED:   // left light on
      SET_LOW(PORTD, PORTD5);
      SET_HIGH(PORTD, PORTD4));
      break;
    case OPENING:  // right light blinking
      TOGGLE(PORTD, PORTD5);
      SET_LOW(PORTD, PORTD4));
      break;
    case OPEN:      // right light on
      SET_HIGH(PORTD, PORTD5);
      SET_LOW(PORTD, PORTD4));
      break;
  }
}
if (flashing_phase++ >= 1000) flashing_phase = 0;
#endif
    ...
    ...

Auch dieser Teil wird 3200 mal pro Sekunde aufgerufen. Hier wird mit dem Zähler und dem Vergleichswert FLASHING_INTERVAL heruntergeteilt. Mir einem Wert von 4000 für FLASHING_INTERVAL ist ein Blinkzyklus, bei dem die halbe Zeit D5 und die andere Zeit D6 high ist, etwas über eine Sekunde lang. Ob überhaupt geblinkt wird, wird mit flashing_on aus dem Hauptprogramm gesteuert. Fällt flashing_on auf false, so wird der aktuelle Blinkzyklus noch zu Ende gebracht.

Für die Testphase: Definiert man den Compiler-Switch TEST, so werden die Lichter zur Anzeige der Steuerungszustände verwendet.

Das Ganze auf Lochstreifenplatte

Um diese Anleitung abzurunden, habe ich keine Mühe gescheut und die Schaltung in fritzing auf Lochstreifenplatte entworfen. Ihr könnt die entsprechende fritzing-Datei hier herunterladen. Die Leiterplattenansicht braucht Ihr nicht zu beachten. Ich habe da nur mal das Auto-Routing drüber laufen lassen.

Für den Nachbau genügt es aber eigentlich, wenn Ihr Euch die fogenden Bilder runterholt (mit der linken Maustaste groß anzeigen lassen, dann mit der rechten Maustaste draufgehen und "Grafik kopieren" wählen):

Lochstreifenplate
Hier sieht man die Lochstreifenplatte mit den Bauteilen. Die orangenen Drahtbrücken sind am besten mit Kupferlackdraht zu machen. Mit ihnen wird das Microcontroller-Board angeschlossen. Sie müssen nicht genau so gelegt werden, wie es gezeichnet ist. Man lötet sie von oben am Microcontroller-Board an, führt sie durch ein passendes Loch auf die Unterseite und lötet sie dort an.


Lochstreifenplate unten
Hier die Leiterplatte von der Unterseite. Schwarze Schraffuren sind da, wo Ihr die Streifen durchtrennen müsst.

Die grau abgebildeten Drahtbrücken könnt Ihr mit blankem Draht machen. Bei schwarz und rot braucht Ihr isolierten Draht.


Ich hoffe, Ihr könnt mit dieser Anleitung etwas anfangen. Viel Erfolg !

Und hier die automatische Schranke im Betrieb:

In schneller Abfolge rauschen die Züge von beiden Seiten durch. Die Annäherungs-Sensoren befinden sich auf der Anlage ungefähr da, wo im Video der linke bzw. rechte Bildrand ist. Das Gebrumme, das zu hören ist, ist die, über Lautsprecher hörbar gemachte, Motor-Wechselspannung. Man hört die oben erwähnten Frequenzen, inklusive Hochlaufen aus 50 Hz. Das normale Schließen und Öffnen erfolgt mit 80 Hz. Nach dem oberen Totpunkt bis zum Erreichen der Bereitschaftsposition wird noch kurz auf die schnellere Frequenz, also 114 Hz hochgeschaltet. Im Video sieht man auch folgende spezielle Situationen:

  • Bei 0:13 (min:sec) fällt das motion_detected-Signal zwischen den Drehgestellen des langsamen Zuges bereits vorübergehend ab. Die Schranken öffnen sich stückchenweise etwas. Das ist dem Kompromiss, schnelles Wiederöffnen gegen sichere Durchfahrterkennung geschuldet.
  • Bei 1:25 kommt von rechts ein Gegenzug am Anfang der Öffnungsphase. Die Schranken sind erst ein kleines Stück offen. Der Antrieb wird angehalten, was in dieser Situation am besten ist.
  • Bei 1:39 kann man einen direkt eingeleiteten Schließvorgang mit 114 Hz (MAX_BOOST) beobachten, da bei Eintreffen des Gegenzuges die Schranken ohnehin schon fast ganz offen sind.

Wollt Ihr einen Kommentar loswerden oder habt Ihr eine Frage? Schickt mir eine Mail