Anleitung: Weichensteuerung mit Klasse

Dann will ich da auch mal meinen Senf zugeben!
Wenn ich mir die hier vorgeschlagenen Pinauswertungen betrachte, stehe ich eindeutig auf:

oder auch auf
if ( not digitalRead(reedPinA) )

Habe mich selber allerdings in meinem Libs für die noch nicht genannte Methode entschieden.
Und zwar:
Alle Zustände, in den Programmen, grundsätzlich mit positiver Logik darzustellen. Das vereinfacht die Arbeit ungemein. Man muss viel weniger Bedingungen im Kopf haben.
Einem Relais, welches anziehen soll, schickt man einfach eine 1 oder true. Völlig unabhängig davon, ob es invertiert, oder auch nicht. Es reicht, wenn das Relais es weiß, dass es invertieren muss.

Combie::Pin::InvInputPin<2> tasterA;
Combie::Pin::InputPin<3> tasterB;

Hier weiß nur der TasterA darum, dass er seinen Pegel invertieren muss.
Also werden auch beide Taster eine 1, bzw. true liefern, wenn sie gedrückt sind.

Das gleiche gibts da auch für Outputs.
Man kann im ganzen Rest des Programms die ganzen Negierungen vergessen, muss sich nicht drum kümmern. Es wird nur an einer Stelle festgelegt. Bei der Definition des Taster oder Relais. Und ist somit auch an einer zentralen Stelle austauschbar.
Evtl ist in setup() noch ein taster.init() durch ein taster.initPullup() zu ersetzen.


Zu dem MoBa Timer...
Ja, ich finde es richtig die eigentliche Weichen Klasse zu verschlanken, in dem man einen gut getesteten Timer wiederverwendet. Das nennt sich übrigens "Aggregation", oder ist zumindest ein Teilgebiet davon.
Wie schon gesagt, ich würde wohl auch noch die Invertierungen aus der Weiche raus ziehen und an IO Klassen delegieren.
Komponenten orientierte Programmierung ?

Übrigens, wenn man sich entscheiden soll/muss, ob man jetzt Vererbung oder Aggregation einsetzt, gibts eine klasse Hilfe:

Wenn sich dieser Satz gut anhört:
"Weiche ist ein Timer", dann sollte/könnte Weiche vom Timer erben.
Also Weiche ein Ableger von Timer werden.

Wenn sich dieser Satz gut anhört:
"Weiche nutzt einen Timer", dann ist Aggregation das Mittel der Wahl.

Wenn einem die Sätze nicht wirklich helfen, dann sollte man das Aggregat bevorzugen. Denn die Kopplung zwischen Elter und Kind ist doch schon arg eng.

Kleinere Komponenten lassen sich leichter überblicken und besser auf Herz und Nieren testen. Sie lassen sich dann nach Belieben kombinieren.

Das exakte Gegenteil davon sind "Gott Klassen", welche versuchen die Weltformel in sich zu vereinigen. z.B. auch "big hairy objects".
Kann manchmal Sinn machen, gerade in der µC Welt, ist aber dennoch ein Instrument mit Nebenwirkungen.

Danke für den Senf!

Um die in meinen Programmen verwenden zu können, müßte ich sie verstehen. Als Teil meiner Anleitungen müßte ich sie dann auch noch erklären können. Leider trifft das nicht zu, weshalb ich Deinen Vorschlag nicht umsetzen werde.

Ob mir Deine Bibliotheken für Anfänger geeignet erscheinen würden, wäre darüberhinaus noch zu diskutieren. Aber dazu müßte ich sie erstmal selbst verstehen.

Hallo,

das ist ja nun eh schon für Fortgeschrittene. :slight_smile:

Wenn ich gedanklich vorgreifen darf. combies Libs bestehen aus spezialisierten Klassen Templates. Soweit okay, gut und feine Sache. Man kann jedoch Klassen Templates und "normale" Klassen nicht mischen. Also man kann kein Objekt eines Klassen Templates in einer normalen Klasse erstellen. Du müßtest deine Klasse auch auf Klassen Template umbauen. Was noch kein Problem wäre. Das Problem ergibt sich dann beim iterieren mittels Array. Das funktioniert mit Klassen Templates erstmal nicht. Jedes Objekt eines Klassen Templates stellt einen eigenen Typ dar. Man müßte von allen Methoden mittels einer Interface Klasse virtuelle Methoden zur Verfügung stellen. Und genau das bläht den Code unheimlich auf, was wiederum den Vorteil von Klassen Templates zu nichte macht. Mit wenigen Methoden mag das noch egal sein. Ich habe das bei meiner µC Hardware-Timer Klasse erneut festgestellt und verglichen. Ich rede wie gesagt bei Nutzung zum iterieren mittels Array.

Du kannst jedoch eine normale Klasse names 'Taster' schreiben der die notwendigen Methoden bereit stellt. Danach dröselst Du deine Gleis Klasse auf. Das meint combie mit Gott Klasse. Deine Klasse 'Gleis' ist Multi Kulti, wenn ich das so sagen darf. Du nimmst mir das hoffentlich nicht übel. :wink:
Also, aus was besteht das Gesamtkunstwerk?
Taster,
Weiche,
Gleis,
Uhr

Das wären für mich alles einzelne Klassen die ich erstellen würde. Am Ende würde ich eine letzte Klasse erstellen, nennen wir sie 'Steuerung', die sich aus dem Baukasten, nichts weiter ist das am Ende, der anderen Klassen bedient und alles verknüpft wie man es benötigt. Alles ohne Vererbung. Die Steuerungsobjekte stopfste am Ende in ein Array und lässt es laufen.

Den Baukasten der Klasse 'MotoTimer' nutzt du ja schon in deiner Klasse 'Gleis'. :wink:

Hallo,

nun habe ich mich doch noch reingehangen und alles aufgedröselt. Was habe ich gemacht? Ich habe die einzelnen Objekte für sich betrachtet beschrieben. Einen Sensor kann man nur abfragen. Eine Weiche kann man nur umschalten. Ein Gleis kann man nur einschalten oder ausschalten. Für die Weiche kam noch eine Schutzfunktion rein. Falls der Sensor durch irgendwas dauerhaft sein Signal liefert, Lok bleibt darauf stehen oder sonstwas, dann hat die Weichenspule eine Auszeit damit sie nicht durchbrennt. Die Weiche bekommt im ungünstigen Fall nur nacheinander Impulse und sollte damit nicht kaputt gehen bzw. länger halten.
Den Timer benötigt aktuell nur die Weiche, also sitzt dieser in der Weiche.
Damit hat man einen Baukasten mit allen Elementen geschaffen.
Zum Schluss verknüpft man seine Sensoren und Aktuatoren miteinander. Dafür dient die Klasse Steuerung. Darin erstellt man alle Objekte von allen und soviel davon wie man benötigt. Wohlgemerkt für einen zusammenhängenden Weiche-Gleisabschnitt. Die Vervielfältigung erledigt später das angelegte Array von der Steuerung. Also hier für einen Funktionseinheit 2 Sensoren, 2 Weichen und einen Gleisumschalter. Bzw. wenn ich das richtig verstehe ist das eine Weiche aber ihre zwei Anschlüsse zum umschalten. That's all. :crazy_face:

Das mag auf den ersten Blick sehr groß aussehen. Hat jedoch den Vorteil das man bei Änderungen sich nur auf ein Objekt konzentrieren kann. Entweder korrigiert man darin die Logik oder erstellt eine weitere Methode die man benötigt.

Funktionstest mit Taster und Leds funktioniert. Es kann jedoch sein das ich die Zuordnung durcheinandergebracht habe. Aber das ist schnell korrigiert, falls es doch passiert sein sollte. Entweder man korrigiert die Pinzuordnung oder man korrigiert die zum Sensor gehörende Weiche.

Nochwas zu combies Lib. combie hat in seinen Libs im Grunde nichts anderes gemacht nur leicht anders. Bsp. seine Pin Lib. Er hat sich zu erst alle Methoden erstellt die man mit einem Pin machen kann. Das ist sein Pin Baukasten. Danach hat er verschiedene Klassen erstellt für genau einen speziellen Zweck. Einen Eingang kann man nur lesen und seinen Pullup schalten. Einen Ausgang kann man schalten und seinen Zustand abfragen. usw. Diese speziellen Klassen stellen dem Nutzer nur die Methoden bereit die dafür freigegeben sind. Das alles hat er in Klassen Templates verpackt. Das ist alles. :stuck_out_tongue_winking_eye:

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.13
  Arduino Mega2560
  05.05.2021
*/

#include <MobaTools.h>  // Kann mittels Arduino-IDE installiert werden. 

class Sensor
{
  private:
    const byte pin;

  public:
    Sensor(byte p) : pin {p}
    {}

    void init (void)     { pinMode(pin, INPUT_PULLUP); }
    bool istAktiv (void) { return !digitalRead(pin); }
};

class Weiche
{
  private:
    const byte pin;
    const uint16_t pulsdauer {300};           // in Millisekunden
    const uint16_t schutzdauer {pulsdauer*3}; // Ruhephase zum Spulenschutz vervielfachen
    MoToTimer impulsEin;
    MoToTimer impulsAus;
    bool aktiv {false};

    void ein (void) { digitalWrite(pin,  LOW); }
    void aus (void) { digitalWrite(pin, HIGH); }
    
  public:
    Weiche(byte p) : pin {p}
    {}

    void init (void) {
      digitalWrite(pin, HIGH);
      pinMode(pin, OUTPUT);
    }
   
    void pulsEin (void) {
      if (!aktiv) {                       // Trigger freigegeben?
        ein();                            // Weiche Impuls ein
        aktiv = true;                     // Retriggerung sperren
        impulsEin.setTime(pulsdauer);     // "Eieruhr" aufziehen für EIN Impuls
      } 
    }
    
    void pulsAus (void) {
      if (impulsEin.expired() ) {
        aus();
        impulsAus.setTime(schutzdauer);   // "Eieruhr" aufziehen für AUS Dauer zum abkühlen
      } 
      if (aktiv && impulsAus.expired() ) {
        aktiv = false;                    // Trigger freigeben
      }         
    } 
};

class Gleis
{
  private:
    const byte relais;

  public:
    Gleis(byte p) : relais {p}
    {}

    void init (void) {
      digitalWrite(relais, HIGH);
      pinMode(relais, OUTPUT);
    }

    void stromGleisAbzweig (void) { digitalWrite(relais, LOW);  }
    void stromGleisGerade  (void) { digitalWrite(relais, HIGH); }
};

class Steuerung
{
  private:  // Die folgenden Objekte sind nur innerhalb der Klasse zugänglich. Am Anfang einer Klasse überflüssig.
    // Objekt Deklarationen             
    Sensor sensorAbzweig;  
    Sensor sensorGerade; 
    Gleis gleis;            
    Weiche weicheAbzweig;
    Weiche weicheGerade;
       
  public:
  /*               SensorAbzweig
                   |       SensorGerade
                   |       |       RelaisGleisumschaltung 
                   |       |       |       ImpulsRelaisWeicheGerade 
                   |       |       |       |       ImpulsRelaisWeicheAbzweig  
                   |       |       |       |       |                      */
    Steuerung(byte a, byte b, byte c, byte d, byte e):
      // Initialisierungsliste, Objektreihenfolge wie Deklaration
      sensorAbzweig{a},
      sensorGerade{b},
      gleis {c},
      weicheAbzweig{d},
      weicheGerade{e}
    {}
   
    void init(void)
    {
      sensorAbzweig.init();
      sensorGerade.init();
      gleis.init();
      weicheAbzweig.init();
      weicheGerade.init();
    }

    void control(void)
    {
      if (sensorGerade.istAktiv() ) {
        gleis.stromGleisAbzweig();      // 'Gleis Abzweig' bestromt, 'Gleis Gerade' stromlos
        weicheAbzweig.pulsEin();        // eichen Umschaltpuls starten auf 'Gleis Abzweig' ein   
      }
      
      if (sensorAbzweig.istAktiv() ) {
        gleis.stromGleisGerade();       // 'Gleis Gerade' bestromt, 'Gleis Abzweig' stromlos 
        weicheGerade.pulsEin();         // Weichen Umschaltpuls starten auf Gleis Gerade ein
        
      }

      // muss immer aufgerufen werden können
      weicheAbzweig.pulsAus();         // Weiche Umschaltpuls auf 'Abzweig Gerade' aus
      weicheGerade.pulsAus();          // Weiche Umschaltpuls auf 'Gleis Gerade' aus
    }
};

Steuerung steuerung[]
{
/*  SensorAbzweig
    |   SensorGerade
    |   |   RelaisGleisumschaltung 
    |   |   |   ImpulsRelaisWeicheGerade 
    |   |   |   |   ImpulsRelaisWeicheAbzweig  
    |   |   |   |   |                        */
  {12, 11,  8,  7 , 6},  // 1. Weiche, Gleis 1-2
  {10,  9,  5,  4 , 3},  // 2. Weiche, Gleis 3-4
}; 
/* 
  { // 1. Weiche
    12, //Zugerkennung Reedkontakt Gleis 1
    11, //Zugerkennung Reedkontakt Gleis 2
     8,  //Relais für Stromumschaltung Gleis 1 zu Gleis 2
     7,  //Relais für Weiche geradeaus
     6   //Relais für Weiche abbiegend, LEDgelb
  },
  { // 2. Weiche
    10, //Zugerkennung Reedkontakt Gleis 3
     9,  //Zugerkennung Reedkontakt Gleis 4
     5,  //Relais für Stromumschaltung Gleis 3 zu Gleis 4
     4,  //Relais für Weiche geradeaus
     3   //Relais für Weiche abbiegend
  }
*/

void setup(void)
{
  for (Steuerung &s : steuerung) s.init();
}

void loop(void)
{
  for (Steuerung &s : steuerung) s.control();
}

Ah, jetzt beginne ich zu verstehen, was Du gestern gemeint hast.

Vielen Dank, ein Gewinn für dieses Thema!

Hallo,

wenn es hilft freut mich das. Man muss sich an alles erstmal gewöhnen bzw. umgewöhnen. Das ist mir schon klar. Ging bei mir auch nicht von heute auf morgen. :wink: Der Vorteil ist wie gesagt, man legt die elektrischen Eigenschaften in seinen Objekt Methoden einmal fest. Ob negiert oder nicht negiert usw. und gibt der Methode einen Aussagekräftigen Namen. Diese betrachtet das reine Objekt und nicht mehr die elektrische Eigenschaft die sie enthält. Das ist die Abstraktion. Man löst sich von den speziellen Einzelheiten. Ob die Spule nun Low oder High aktiv ist spielt dann keine Rolle mehr. Man ruft einfach die Methode "einschalten" auf. Wie die das macht hat man vorher in dieser festgelegt bzw. kann das genau dort leichter wieder ändern ohne auf einen Rattenschwanz von Nebenwirkungen Rücksicht nehmen zu müssen.

Ja, genau so, das ist Aggregation!

Das verstehe ich unter:
--> Komponenten orientierte Programmierung
Für jede Teilaufgabe eine Komponente/Modul/Klasse erschaffen, und diese dann schlau kombinieren.

Ja, da kommen viele kleine Klassen bei rum. Ist aber kaum ein Nachteil, da man sie häufig wieder verwenden kann. Und so schön einzeln testen.

Hallo,

Danke, dann habe ich alles richtig gemacht. :wink:

Das Ganze sieht doch schon sehr aufgeräumt aus :sunglasses: .
Vielleicht aber noch ein paar kleine Anmerkungen von meiner Seite zu den Timern :wink: .

  • Da die Zeiten für das Anziehen der Relais und die Mindestzeit für das AUS dazwischen prinzipiell nie gleichzeitig laufen können, reicht dafür ein Timer aus. Das macht bei ein, zwei Weichen nicht viel an Ressourcen aus, aber wenn es wirklich mal um viele Weichen geht, wäre es gut ein wenig auf einen sparsamen 'Fußabdruck' im RAM zu achten.
  • Vielleicht auch noch eine Benerkung zur 'expired()' Methode: Im Gegensatz zu 'running()', das einfach den statischen Zustand des Timers ( läuft/läuft nicht) zurückgibt, ist expired() ein Ereignis. Ähnlich wie bei einem Taster mit Flankenerkennung das Loslassen. Das wird auch nur einmal beim Loslassen wahr, und natürlcih nur, wenn vorher auch gedrückt wurde.
    Auch expired() wird nur einmal unmittelbar nach dem Ablauf des Timers wahr. Und damit natürlich auch nur, wenn es vorher ein setTime() gegeben hat. Zusätzliche Abfragen von Flags kann man sich da deshalb in aller Regel sparen.

P.S. Es gibt natürlich immer verschiedene Varianten und Herangehensweisen. Für mich ist aber das Stellen in Richtung 'Gerade' oder in Richtung 'Abzweig' ein eindeutiges Merkmal, was zur Weiche gehört. Das sollte auch nie beides gleichzeitig aktiv sein können. Deshalb würde ich das mit in die Klasse 'weiche' aufnehmen. D.h. ich habe eine Weiche, und die kann ich auf Gerade oder Abzweig stellen. Wie sie das macht, bleibt in der Klasse verborgen.
Ist aber nur meine unmaßgebliche Meinung azu :wink: .

Hallo,

anmaßgebliche Meinung ist etwas untertrieben. :wink: Das mit nur einer Weiche und 2 Schaltstellungen stimmt schon, sollte man ändern. Ansonsten kann man mit mehr Weichen schnell durcheinander kommen. Mit dem nur einen Timer verwenden ist auch eine Idee. Ich überlege mir mal was. Danke für die Hinweise. :+1:

Hallo,

also mit einem Timer komme ich derzeit aus, nur die Sicherheitsfeatures benötigen 2 eigene Flags.

Ich muss aber nachfragen zur Umschaltung der Gleisbestromung.
Welches Gleis wird unter Strom gesetzt und warum wenn die Weiche umschaltet?
Wenn ich den Ablauf richtig verstehe, dann kommt die Lok ja schon angefahren, hat demzufolge schon Strom, fährt auf die Weiche zu, löst dabei den Readkontakt aus und schaltet passend die Weiche um.
Wäre für mich derzeit eine reine automatische Weichensteuerung.
Wer steuert das Gleis im Vorfeld damit die Lok losfährt?

Ich hatte es so verstanden, daß sich der Reedkontakt eher am Ende des Gleises in der Halteposition des Zuges befindet.

═══Relais1 (Gleisstrom1)═══Reedkontakt1═══╗
═══Relais1 (Gleisstrom2)═══Reedkontakt2═══╩Weiche1═══ ←

  1. Zug fährt von rechts über die Weiche auf Gleis 1.
  2. Am Gleisende löst der Reedkontakt aus.
  3. Einfahrweiche schaltet um.
  4. Gleis 1 wird stromlos, weshalb der Zug stoppt.
  5. Gleis 2 erhält Strom, der dort wartende Zug fährt ab.

Für mich auch.

Ein Zug fährt in den Schattenbahnhof, ein anderer verläßt ihn, das bringt etwas mehr Abwechslung. Mit einer Gleisharfe kann man das mittels Zufall oder nach Fahrplan für viele Züge so machen. Zusammen mit einer Blocksteuerung ergibt sich ein automatische Betrieb.

Im halbautomatischen Betrieb wird die Hauptfahrstrecke über einen Schattenbahnhof mit Zügen versorgt und der Fahrdienstleiter bedient die Gleise im Bahnhof, wo er Güterzüge auf dem Durchfahrtsgleis passieren läßt und Personenzüge am Bahnsteig halten.

Dieser Punkt wurde im Ursprungsthema ausdrücklich ausgeklammert.

Ich gehe davon aus, ein Zug wartet, der andere ist unterwegs und die Weiche steht Richtung leerem Gleis.

So hatte ich das auch verstanden. Der Zug stellt mit dem Reedkontakt nicht seinen eigenen Fahrweg, sondern sichert sich sozusagen nach hinten ab, damit ihm kein anderer ins 'Hinterteil' fährt.

Letztendlich sollte die Weiche so immer auf ein freies Gleis zeigen.

Hallo,

alles klar. Der Zug schaltet hinter sich die Weiche um.

Ich hatte bis jetzt im Kopf das der Zug sich seine Weiche die vor ihm liegt passend umschaltet das er raus kann. Das wäre dann ein Thema für eine zweite Steuerungsklasse mit der Bedienung aus dem gleichen Baukasten Sensor und Weiche.

Ich mach mal weiter ...

Nochwas. Vielleicht bin ich jetzt zu pingelig. Kann es sein das der Zug auf dem Readkontakt stehen bleibt? Damit gebe es ein Dauersignal. Das würde zwar meine Sicherheitsschaltung zum Schutz der Weichenspule abfedern, aber das sollte man vielleicht wissen ob der Zug noch ein Stück weiter fährt, also runter vom Kontakt oder nicht. Vielleicht kann michvog noch dazu etwas sagen,

Bin nicht sicher, ob er hier mitliest, das Thema hat sich ja schon etwas spezieller entwickelt.

Egal, ob dies praktisch möglich ist, würde ich eine zeitlich begrenzte gegenseitige Verriegelung vorsehen. Daher hatte ich ursprüglich auch mehr Schritte, dies aber der Übersichtlichkeit wegen vereinfacht. Aber wenn Du schon wühlst, dann gerne auch gleich richtig :slightly_smiling_face:

Hallo,

hab mir folgendes überlegt, es funktioniert wie folgt. Irgendein Sensor wird aktiv und der Puls auf die Weiche wird entsprechend aktiviert. Dadurch wird die Ansteuerung auf den anderen Weichenpin automatisch gesperrt. Beide Weichenspulen können nicht zeitgleich mit einem Puls beaufschlagt werden. Das regelt das Flag pulsAktiv.

Ab jetzt läuft die Zeit pulsdauer runter. Wenn diese abgelaufen ist, wird die Zeit der 'ruhephase' aktiv. Das regelt das Flag pauseAktiv.

Da ich nur einen Timer verwenden durfte :joy:, wird dieser eine Timer immer mit der benötigten Zeitspanne neu gestartet und geschaut ob diese Zeit abgelaufen ist.

Erst wenn auch die Ruhephase vorbei ist kann die Weiche erneut angesteuert werden. Damit wäre die Weichenspule schon einmal vorm durchbrennen geschützt. Das heißt jedoch auch, wenn der Sensor dauerhaft ein Signal liefert bekommt die Weiche permanent ein PWM Signal.

   pulsAktiv       pauseAktiv
   __________
  |          |____________________
   pulsdauer       ruhephase

  |<----------------------------->|
              Periode

Die Ruhephase kann man der Zugfrequenz anpassen. ruhephase habe ich der Lesbarkeit wegen als Variable belassen. Ansonsten den Faktor direkt einsetzen.

Jetzt ist die Frage, soll das nicht lieber komplett verriegelt werden? Sollte Dauer PWM vermieden werden? PWM würde wiederum gegen leicht hängende Weichen helfen. Ich hätte Hunderte für und wieder im Kopf.
Oder überlassen wir das dem Nutzer für seine spezielle Zugsteuerung?
Letztlich kommt es auf den Einsatzzweck an. Ich weiß es nicht. Auf der anderen Seite kann man hier nicht alles zeigen was man sich ausdenken könnte.

Ich persönlich würde bestimmt mehrere Sensoren im Gleis verbauen. Einer zum Weiche schalten und der Nächste stoppt den Zug. Aber das kommt sicherlich auch wieder auf den Gleisplan an und was man wie steuern möchte.

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.13
  Arduino Mega2560
  06.05.2021
  https://forum.arduino.cc/t/anleitung-weichensteuerung-mit-klasse/856934/25
*/

#include <MobaTools.h>  // Kann mittels Arduino-IDE installiert werden. 

class Sensor
{
  private:
    const byte pin;

  public:
    Sensor(byte p) : pin {p}
    {}

    void init (void)     { pinMode(pin, INPUT_PULLUP); }
    bool istAktiv (void) { return !digitalRead(pin); }
};

class Weiche
{
  private:
    const byte pinGerade;
    const byte pinAbzweig;
    const uint16_t pulsdauer {300};           // in Millisekunden
    const uint16_t ruhephase {pulsdauer*7};   // Ruhephase zum Spulenschutz vervielfachen
    MoToTimer impuls;
    bool pulsAktiv {false};
    bool pauseAktiv {false};

    void ein (const byte pin) { digitalWrite(pin,  LOW); }
    void aus (const byte pin) { digitalWrite(pin, HIGH); }
    
  public:
    Weiche(byte ger, byte abz) : pinGerade {ger}, pinAbzweig {abz}
    {}

    void init (void) {
      digitalWrite(pinGerade, HIGH);
      digitalWrite(pinAbzweig, HIGH);
      pinMode(pinGerade, OUTPUT);
      pinMode(pinAbzweig, OUTPUT);
    }

    void zuGerade (void) {
      if (!pulsAktiv) {                   // gegenseitige Verriegelung aktiv?
        impuls.setTime(pulsdauer);        // Timer setzen und Puls starten
        ein(pinGerade);
        pulsAktiv = true;                 // Retriggerung verhindern, sich selbst und gegenseitig verriegeln
      } 
    }

    void zuAbzweig (void) {
      if (!pulsAktiv) {                   // gegenseitige Verriegelung aktiv?
        impuls.setTime(pulsdauer);        // Timer setzen und Puls starten
        ein(pinAbzweig);
        pulsAktiv = true;                 // Retriggerung verhindern, sich selbst und gegenseitig verriegeln
      }  
    }

    void pulsControl (void)
    {
      // wenn Pulsdauer vorbei wird Ruhephase aktiv
      if (pulsAktiv && !pauseAktiv && !impuls.running() ) // Pulsdauer abgelaufen?
      {
        aus(pinGerade);
        aus(pinAbzweig);
        impuls.setTime(ruhephase);        // Timer setzen für AUS Dauer zum abkühlen
        pauseAktiv = true;                // Puls-Ruhephase zum Spulenschutz aktivieren
      }

      // erst wenn Ruhephase abgelaufen ist, kann die Weiche erneut angesteuert werden
      if (pulsAktiv && pauseAktiv && !impuls.running() )
      {
        pulsAktiv = false;
        pauseAktiv = false;
      }
    } 
};

class Gleis
{
  private:
    const byte relais;

  public:
    Gleis(byte p) : relais {p}
    {}

    void init (void) {
      digitalWrite(relais, HIGH);
      pinMode(relais, OUTPUT);
    }

    void stromGleisAbzweig (void) { digitalWrite(relais, LOW);  }
    void stromGleisGerade  (void) { digitalWrite(relais, HIGH); }
};

class Steuerung
{
  private:  // Die folgenden Objekte sind nur innerhalb der Klasse zugänglich. Am Anfang einer Klasse überflüssig.
    // Objekt Deklarationen  
    Sensor sensorGerade;            
    Sensor sensorAbzweig;  
    Gleis gleis;            
    Weiche weiche;
       
  public:
  /*               SensorGerade
                   |       SensorAbzweig
                   |       |       RelaisGleisumschaltung 
                   |       |       |       ImpulsRelaisWeicheGerade 
                   |       |       |       |       ImpulsRelaisWeicheAbzweig  
                   |       |       |       |       |                      */
    Steuerung(byte a, byte b, byte c, byte d, byte e):
      // Initialisierungsliste, Objektreihenfolge wie Deklaration
      sensorGerade{a},
      sensorAbzweig{b},
      gleis {c},
      weiche{d, e}
    {}
   
    void init(void)
    {
      sensorGerade.init();
      sensorAbzweig.init();
      gleis.init();
      weiche.init();
    }

    void control(void)
    {
      if (sensorGerade.istAktiv() ) {
        gleis.stromGleisAbzweig();      // 'Gleis Abzweig' bestromt, 'Gleis Gerade' stromlos
        weiche.zuAbzweig();             // Weichen Umschaltpuls starten auf 'Gleis Abzweig' ein   
      }
      
      if (sensorAbzweig.istAktiv() ) {
        gleis.stromGleisGerade();       // 'Gleis Gerade' bestromt, 'Gleis Abzweig' stromlos 
        weiche.zuGerade();              // Weichen Umschaltpuls starten auf Gleis Gerade ein
      }

      // muss immer aufgerufen werden können
      weiche.pulsControl();             // Weichen Puls Überwachung
    }
};

Steuerung steuerung[]
{
/*  SensorGerade
    |   SensorAbzweig
    |   |   RelaisGleisumschaltung 
    |   |   |   ImpulsRelaisWeicheGerade 
    |   |   |   |   ImpulsRelaisWeicheAbzweig  
    |   |   |   |   |                      */
  { 2,  3, 28, 29 ,30},  // 1. Weiche, Gleis 1-2
  { 4,  5, 31, 32 ,33},  // 2. Weiche, Gleis 3-4
}; 


void setup(void)
{ 
  for (Steuerung &s : steuerung) s.init();
}

void loop(void)
{
  for (Steuerung &s : steuerung) s.control();
}

Meinungen? Korrekturen?
Pinzuordnung ist in der Initialisierung in der Reihenfolge gerade gezogen. Immer zuerst Gerade dann Abzweig. Falls das jemanden stört.

Hallo,

ich wollte nochmal optimieren, dabei stoße ich auf ein Problem mit running(). Der Gedanke ist, running() lokal in eine bool zu speichern, da es dort ständig aufgerufen wird, das spart einen Aufruf und beim kompilieren paar Bytes Flash. Allerdings machen die Weichenpulse damit was sie wollen, es gibt keinen Zusammenhang mit meinen Zeiten. Also Debugausgaben eingebaut. Hier sehe ich das der Rückgabewert von running() permanent zwischen 0 und 1 wechselt, sobald ich einen Taster (simuliert Sensor) drücke oder gedrückt hatte. running() wechselt nicht mehr seinen Status wenn der Timer abgelaufen ist.

Wo ist jetzt der Logikfehler?

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.13
  Arduino Mega2560
  06.05.2021
  https://forum.arduino.cc/t/anleitung-weichensteuerung-mit-klasse/856934/25
*/

#include <MobaTools.h>  // Kann mittels Arduino-IDE installiert werden. 

enum class state : byte {NON, ACTIV};   // Serielle aktivieren
const state debug = state::ACTIV;

class Sensor
{
  private:
    const byte pin;

  public:
    Sensor(byte p) : pin {p}
    {}

    void init (void)     { pinMode(pin, INPUT_PULLUP); }
    bool istAktiv (void) { return !digitalRead(pin); }
};

class Weiche
{
  private:
    const byte pinGerade;
    const byte pinAbzweig;
    const uint16_t pulsdauer {900};           // in Millisekunden
    const uint16_t ruhephase {pulsdauer*3};   // Ruhephase zum Spulenschutz vervielfachen
    MoToTimer timer;
    bool pulsAktiv {false};
    bool pauseAktiv {false};

    void ein (const byte pin) { digitalWrite(pin,  LOW); }
    void aus (const byte pin) { digitalWrite(pin, HIGH); }
    
  public:
    Weiche(byte ger, byte abz) : pinGerade {ger}, pinAbzweig {abz}
    {}

    void init (void) {
      digitalWrite(pinGerade, HIGH);
      digitalWrite(pinAbzweig, HIGH);
      pinMode(pinGerade, OUTPUT);
      pinMode(pinAbzweig, OUTPUT);
    }

    void zuGerade (void)
    {
      if (!pulsAktiv)                    // gegenseitige Verriegelung aktiv?
      {
        timer.setTime(pulsdauer);        // Timer setzen für EIN Impuls
        ein(pinGerade);
        pulsAktiv = true; 
        if (debug == state::ACTIV) { Serial.println("pulsAktiv Gerade"); } 
      } 
    }

    void zuAbzweig (void)
    {
      if (!pulsAktiv)                    // gegenseitige Verriegelung aktiv?
      {
        timer.setTime(pulsdauer);        // Timer setzen für EIN Impuls
        ein(pinAbzweig);
        pulsAktiv = true; 
        if (debug == state::ACTIV) { Serial.println("pulsAktiv Abzweig"); } 
      }  
    }

    void pulsAusControl (void)
    {
      const bool isTimeOver = !timer.running();
      if (debug == state::ACTIV) { Serial.print("isTimeOver "); Serial.println(isTimeOver); } 
      
      if (pulsAktiv && !pauseAktiv && isTimeOver ) // Pulsdauer abgelaufen?
      {
        aus(pinGerade);
        aus(pinAbzweig);
        timer.setTime(ruhephase);        // Timer setzen für AUS Dauer zum abkühlen
        pauseAktiv = true;               // Puls-Ruhephase zum Spulenschutz aktivieren
        if (debug == state::ACTIV) { Serial.println("Pulspause läuft"); } 
      }

      if (pulsAktiv && pauseAktiv && isTimeOver )
      {
        pulsAktiv = false;
        pauseAktiv = false;
        if (debug == state::ACTIV) { Serial.println("Trigger aktiviert"); } 
      }
    }
};

class Gleis
{
  private:
    const byte relais;

  public:
    Gleis(byte p) : relais {p}
    {}

    void init (void) {
      digitalWrite(relais, HIGH);
      pinMode(relais, OUTPUT);
    }

    void stromGleisAbzweig (void) { digitalWrite(relais, LOW);  }
    void stromGleisGerade  (void) { digitalWrite(relais, HIGH); }
};

class Steuerung
{
  private:  // Die folgenden Objekte sind nur innerhalb der Klasse zugänglich. Am Anfang einer Klasse überflüssig.
    // Objekt Deklarationen  
    Sensor sensorGerade;            
    Sensor sensorAbzweig;  
    Gleis gleis;            
    Weiche weiche;
       
  public:
  /*               SensorGerade
                   |       SensorAbzweig
                   |       |       RelaisGleisumschaltung 
                   |       |       |       ImpulsRelaisWeicheGerade 
                   |       |       |       |       ImpulsRelaisWeicheAbzweig  
                   |       |       |       |       |                      */
    Steuerung(byte a, byte b, byte c, byte d, byte e):
      // Initialisierungsliste, Objektreihenfolge wie Deklaration
      sensorGerade{a},
      sensorAbzweig{b},
      gleis {c},
      weiche{d, e}
    {}
   
    void init(void)
    {
      sensorGerade.init();
      sensorAbzweig.init();
      gleis.init();
      weiche.init();
    }

    void control(void)
    {
      if (sensorGerade.istAktiv() ) {
        gleis.stromGleisAbzweig();      // 'Gleis Abzweig' bestromt, 'Gleis Gerade' stromlos
        weiche.zuAbzweig();               // Weichen Umschaltpuls starten auf 'Gleis Abzweig' ein   
      }
      
      if (sensorAbzweig.istAktiv() ) {
        gleis.stromGleisGerade();       // 'Gleis Gerade' bestromt, 'Gleis Abzweig' stromlos 
        weiche.zuGerade();                // Weichen Umschaltpuls starten auf Gleis Gerade ein
      }

      // muss immer aufgerufen werden können
      weiche.pulsAusControl();          // Weichen Puls Überwachung
    }
};

Steuerung steuerung[]
{
/*  SensorGerade
    |   SensorAbzweig
    |   |   RelaisGleisumschaltung 
    |   |   |   ImpulsRelaisWeicheGerade 
    |   |   |   |   ImpulsRelaisWeicheAbzweig  
    |   |   |   |   |                      */
  { 2,  3, 28, 29 ,30},  // 1. Weiche, Gleis 1-2
  { 4,  5, 31, 32 ,33},  // 2. Weiche, Gleis 3-4
}; 


void setup(void)
{
  if (debug == state::ACTIV)
  {
    Serial.begin(250000);
    Serial.println("\nSTART ### ### ###");
  }  
  for (Steuerung &s : steuerung) s.init();
}

void loop(void)
{
  for (Steuerung &s : steuerung) s.control();
}

Ich weiss nicht, was in der lib so drin steht, kann mich aber an einen quote erinnern, den microbahner mir gegeben hat.

Vielleicht ein Anstoss. Vielleicht auch total daneben.

Hallo,

ich hatte #30 so verstanden das ich running() statt expired() verwenden soll, was auch erstmal funktionierte und sogar noch Bytes einspart.
MoToTimer

Ich habs raus. Fehler lag bei mir. Der Ablauf hatte sich doch geändert, was für mich nicht gleich ersichtlich war. Dadurch das ich den Timerstatus nur einmal am Anfang abgefragt hatte gab es folgenden Effekt.

Der Timer wurde mittels Sensor gestartet.
pulseAktiv wird true
pauseAktiv noch! false

Nachdem die Pulsdauer vorüber war wird das erste IF in pulsControl gültig.
pulseAktiv ist schon true
pauseAktiv wird true
und der Timer erneut gestartet

und jetzt kommts, dass nächste IF wird ja sofort danach abgefragt und gültig.
Warum wird das gleich gültig?
Weil ich den Timer mittels running oder expired nicht erneut dazwischen abfrage.
Der Timer wurde wieder aktiviert aber ich habe dessen Status danach nicht erneut abgefragt.
Demzufolge ist der Timer für den Code noch abgelaufen und das letzte IF wird sofort gültig.

Nur den Effekt mit dem ständigen running() Timerstatuswechsel kann ich mir noch nicht erklären. Der passt mir noch nicht ins Bild.

[class Weiche
{
  private:
    const byte pinGerade;
    const byte pinAbzweig;
    const uint16_t pulsdauer {900};           // in Millisekunden
    const uint16_t ruhephase {pulsdauer*2};   // Ruhephase zum Spulenschutz vervielfachen
    MoToTimer timer;
    bool pulsAktiv {false};
    bool pauseAktiv {false};

    void ein (const byte pin) { digitalWrite(pin,  LOW); }
    void aus (const byte pin) { digitalWrite(pin, HIGH); }
    
  public:
    Weiche(byte ger, byte abz) : pinGerade {ger}, pinAbzweig {abz}
    {}

    void init (void) {
      digitalWrite(pinGerade, HIGH);
      digitalWrite(pinAbzweig, HIGH);
      pinMode(pinGerade, OUTPUT);
      pinMode(pinAbzweig, OUTPUT);
    }

    void zuGerade (void)
    {
      if (!pulsAktiv)                    // gegenseitige Verriegelung aktiv?
      {
        timer.setTime(pulsdauer);        // Timer setzen für EIN Impuls
        ein(pinGerade);
        pulsAktiv = true; 
        if (debug == state::ACTIV) { Serial.println("pulsAktiv Gerade"); } 
      } 
    }

    void zuAbzweig (void)
    {
      if (!pulsAktiv)                    // gegenseitige Verriegelung aktiv?
      {
        timer.setTime(pulsdauer);        // Timer setzen für EIN Impuls
        ein(pinAbzweig);
        pulsAktiv = true; 
        if (debug == state::ACTIV) { Serial.println("pulsAktiv Abzweig"); } 
      }  
    }

    void pulsControl (void)
    {  
      Serial.print("isTimeOver "); Serial.println(!timer.running()); } 
      
      if (pulsAktiv && !pauseAktiv && !timer.running() ) // Pulsdauer abgelaufen?
      {
        aus(pinGerade);
        aus(pinAbzweig);
        timer.setTime(ruhephase);        // Timer setzen für AUS Dauer zum abkühlen
        pauseAktiv = true;               // Puls-Ruhephase zum Spulenschutz aktivieren
        Serial.println("Pulspause läuft"); } 
      }

      if (pulsAktiv && pauseAktiv && !timer.running() )
      {
        pulsAktiv = false;
        pauseAktiv = false;
        Serial.println("Trigger wieder aktiviert"); } 
      }
    }
};
1 Like

Ok.. Ja sowas ist natürlich "böse" - aber dafür muss man sich in die libs reinwühlen.
Herzlichen Glühstrumpf zu dieser Aktion.

Ich bekomme heute endlich Hardware - dann mach ich mit dem Dude weiter....
BG