Modellbahn-Basteleien Teil2

Fortsetzung der Diskussion von Modellbahn-Basteleien: brauche ich dringend Hilfe. Zuerst die Problembeschreibung, die lang wird.
Für eine Ausstellungsanlage soll ein vollautomatischer Betrieb erfolgen. Nach Start des Nano soll der Zugbetrieb mit Weichenschaltung, Brems- und Anfahrverhalten in den Bahnhöfen und Abstellgleisen erfolgen. Auslöser für die jeweils Folgeaktion sollen Hallschaltkreise sein. Damit ich den Überblick behalte, habe ich eine Ablaufmatrix in Excel erstellt. Die Datei mit integriertem Gleisplan im Anhang:
Matrix_Arduino.pdf (423,4 KB)
Ich hoffe, die Darstellung ist verständlich.
Jetzt zur eigentlichen Problematik: Die Weichen sollen mit einer 16-Kanal-Relaiskarte bedient werden. Auslöser sollen immer der Hall im Ziel (hinter der Zielweiche) sein. Ich brauche also allein für die 16 Weichen 32 Output-Pins und für die 12 Halls 12 Input-Pins. Wie löse ich das Problem grundsätzlich? Ich habe begonnen mich mit Schieberegister 74HC595 und MCP23017 zu beschäftigen. Die müsste ich in Reihe schalten. Wer hat sowas Ähnliches schon gemacht oder kann mir Empfehlungen aussprechen?
Übrigens, die Diskussion aus dem vorstehenden Thema hat gefruchtet, es "funzt" mit all den Tipps von Euch!! Danke schon mal.

Ohne in das pdf geschaut zu haben,
32 Outputs mit 2x PCF8575.
12 Input mit 1x PC8575 mit dann Reserve

Datenblatt

Libs dafür gibt es.

Danke. Gibt es große Unterschiede zum MCP23017? Für meinen Zweck scheinen die auch zu gehen, die habe ich da liegen, und Libs gibts auch dafür.[GitHub - adafruit/Adafruit-MCP23017-Arduino-Library].

Häts gesagt, wäre ich nicht drauf gekommen in meiner Bastelkiste zu schauen was geht :wink:

Der MCP kann auch ganz schnell sein.
Ansonsten heisst es DaBlas vergleichen.

Da gibt es schon reichlich Beiträge zu. Suche einfach mal hier im Forum danach.

Für mich leider nicht, der Ablauf ist aber erstmal nebensächlich.

Sowas wie mcp.digitalWrite(pinXY, HIGH); für MCP23017 finde ich anfängerfreundlicher, als die für ein Schieberegister notwendige Bitschubserei.

Ein Thema mit MCP23017, auch wenn es nicht gut paßt.

klarer Fall:
Nimm den MCP23017.
Einen MCP in einem Bus kannst du genau addressieren und ihm mitteilen was du willst.
Bei einem Shiftregister müsstest du alle Werte rausschicken auch zu jene bei denen du nichts ändern willst.

Bau deinen Sketch vernünftig auf, dann ist es der Programmlogik egal ob du einen echten Pin oder einen Portexpander ansprichst. Mach deine Magnetartikel zu Objekte mit immer den gleichen Interface und lass das Objekt mit der Hardware sprechen.

So, da hammar den Salat. Jetzt muss ich was tun und stelle es dann mal vor. Ich versuche mich erst mal, ahne aber schon Grenzen. Aber ich mache mal los, nehme den MCP und schaue, wie weit ich komme. Danke erst mal.

Das muss ich mir näher anschauen, digital.write ist doch was sehr Vertrautes.

Das ist es!

Für Dich zum Amüsieren: Im Jahr 2014 habe ich mich mal mit 74HC595 zum Thema "das belebte Haus" auseinandergesetzt. Da ich millis() noch nicht kannte, habe ich mir einen Zähler gebaut:

  millisekundenZaehler--;
  delay(1);

Den Rest zeige ich besser nicht :relaxed:

Außerdem eine Bitschubserei:

void bitAusgabe (unsigned int aus, unsigned int maske) {
  // Setzt und haelt latchPin waehrend der Datenuebertragung auf LOW.
  digitalWrite(latchPin, LOW);
  srInhalt &= ~maske;
  srInhalt |= aus & maske;
  // Daten fuer zwei 8-Bit-Register
  for (int j = 15; j >= 0; j--) {
    if (srInhalt & (1 << j)) {
      digitalWrite(dataPin, HIGH);
    }
    else {
      digitalWrite(dataPin, LOW);
    }
    digitalWrite(clockPin, LOW);
    delayMicroseconds(100);
    digitalWrite(clockPin, HIGH);
    delayMicroseconds(100);
  }
  // Setzt latchPin nach erfolgter Datenuebertragung auf HIGH.
  digitalWrite(latchPin, HIGH);
}

Wenn man es kann, kann man sich auch ein HC595.digitalWrite(); selbst basteln. So wie bei ShiftRegister 74HC595 Arduino Library gemacht. Nur der Vollständigkeit halber erwähnt.

Wenn Deine Bastelkiste MCP23017 ausspuckt, dann passen die :slightly_smiling_face:

ich hab mal schnell so ein Weichenmuster zusammengebaut.
entweder schließt die Weichen am Arduino ("DiscreteSwitch") an, oder am MCP ("MCP23017Switch").
Wennst keine Weichen mehr hast, kannst Weichen auch simulieren und auf die Serielle Schnittstelle schicken ("SerialSwitch").

Die Basisklasse "BaseSwitch" kann nichts - ihr fehlt die Hardwareimplementierung.

der Code tut halt nur mal kurz 4 Weichen schalten.
Schaut vieleicht etwas kompliziert aus, ABER es wieder supereinfach, wenn es mal umfangreicher wird. Du müsstest dich halt mal hinsetzen und Zeile für Zeile durchgehen.

// Weichen als Objekte
// discrete pins
// am MCP23017
// nur als Demo für eine Serielle Ausgabe
// https://forum.arduino.cc/t/modellbahn-basteleien-teil2/876508/6

#include "Adafruit_MCP23017.h"

Adafruit_MCP23017 mcpA; // one MCP23017 object

// "empty" class for time managment for a two way switch
class BaseSwitch {
  protected:
    uint32_t previousMillis;
    uint8_t fsmState = 0;
    uint8_t switchState = 0;  // 0 left; 1 right
    uint16_t interval = 500;

  public:
    int setLeft() // seen as "entry" from below - exit at the top:  it doesn't matter if is "left" or "straight" there is always a "lefter" exit on a two way switch
    {
      return set(0);
    }

    int setRight()
    {
      return set(1);
    }

    virtual int set(int newValue) // must be overridden in the implementation
    {
      release();  // this will interrupt any ongoing switching
      switch (newValue)
      {
        case 0 :
          // handle hardware (switch on pin)

          break;
        case 1 :
          // handle hardware (switch on pin)
          break;
      }
      previousMillis = millis();
      fsmState = 1;
      switchState = newValue; // assumption: set to new state when STARTED
      return 0;
    }

    void setInterval(uint16_t newInterval)
    {
      interval = newInterval;
    }

    virtual int release()  // must be overridden in the implementation
    {
      //handle hardware (switch off both pins?)
      fsmState = 0;
      return 0;
    }

    void update()
    {
      if (fsmState != 0)
      {
        if (millis() - previousMillis > interval)
        {
          release();
        }
      }
    }
};

/*
   a class for a switch
   two discrete Arduino pins
*/
class DiscreteSwitch : public BaseSwitch {
  protected:
    const uint8_t leftPin;
    const uint8_t rightPin;
  public:
    DiscreteSwitch(uint8_t leftPin, uint8_t rightPin) :
      BaseSwitch(),
      leftPin{leftPin},
      rightPin{rightPin}
    {}

    void begin()
    {
      pinMode(leftPin, OUTPUT);
      pinMode(rightPin, OUTPUT);
    }

    int set(int newValue) override
    {
      release();  // this will interrupt any ongoing switching
      switch (newValue)
      {
        case 0 :
          digitalWrite(leftPin, HIGH);
          break;
        case 1 :
          digitalWrite(rightPin, HIGH);
          break;
      }
      previousMillis = millis();
      fsmState = 1;
      switchState = newValue; // assumption: set to new State when STARTED
      return 0;
    }

    int release() override
    {
      digitalWrite(leftPin, LOW);
      digitalWrite(rightPin, LOW);
      fsmState = 0;
      return 0;
    }
};

/*
   a class for a switch
   two pins on a MCP23017
*/
class MCP23017Switch : public BaseSwitch {
  protected:
    const Adafruit_MCP23017 &mcp;
    const uint8_t leftPin;
    const uint8_t rightPin;
    static const uint8_t OFF = HIGH;  // assumption: invers logic for the MCP's
    static const uint8_t ON = !OFF; 

  public:
    MCP23017Switch(Adafruit_MCP23017 &existingMcp, uint8_t leftPin, uint8_t rightPin) :
      BaseSwitch(),
      mcp(existingMcp),
      leftPin{leftPin},
      rightPin{rightPin}
    {}

    void begin()
    {
      mcp.pinMode(leftPin, OUTPUT);
      mcp.pinMode(rightPin, OUTPUT);
      mcp.digitalWrite(leftPin, OFF);
      mcp.digitalWrite(rightPin, OFF);
    }

    int set(int newValue) override
    {
      release();  // this will interrupt any ongoing switching
      switch (newValue)
      {
        case 0 :
          mcp.digitalWrite(leftPin, ON);
          break;
        case 1 :
          mcp.digitalWrite(rightPin, OFF);
          break;
      }
      previousMillis = millis();
      fsmState = 1;
      switchState = newValue; // assumption: set to new State when STARTED
      return 0;
    }

    int release() override
    {
      mcp.digitalWrite(leftPin, OFF);
      mcp.digitalWrite(rightPin, OFF);
      fsmState = 0;
      return 0;
    }
};

// this is just a demo implementation - it just print out to Serial
class SerialSwitch : public BaseSwitch {
  public:
    SerialSwitch() : BaseSwitch() {}

    void begin()
    {
      Serial.println(F("begin"));
    }

    int set(int newValue) override
    {
      release();  // this will interrupt any ongoing switching
      switch (newValue)
      {
        case 0 :
          Serial.println(F("switch LEFT to HIGH"));
          break;
        case 1 :
          Serial.println(F("switch RIGHT to HIGH"));
          break;
      }
      previousMillis = millis();
      fsmState = 1;
      switchState = newValue; // assumption: set to new State when STARTED
      return 0;
    }

    int release() override
    {
      Serial.println(F("switch LEFT to LOW"));
      Serial.println(F("switch RIGHT to LOW"));
      fsmState = 0;
      return 0;
    }
};

DiscreteSwitch weicheA(12, 13);
MCP23017Switch weicheM(mcpA, 6, 7);
MCP23017Switch weicheN(mcpA, 8, 9);
SerialSwitch   weicheS;

void setup() {
  Serial.begin(115200);
  Serial.println(F("Weichendemo"));
  Wire.begin();
  mcpA.begin();

  // start your objects:
  weicheA.begin();
  weicheM.begin();
  weicheN.begin();
  weicheS.begin();

  weicheA.setInterval(900);  // if a switch needs a longer pulse
  weicheN.setInterval(800);  // if a switch needs a longer pulse
  
  // do something
  weicheA.setRight();
  weicheM.setLeft();
  weicheN.setLeft();
  weicheS.setRight();
}

void loop() {
  weicheA.update();
  weicheM.update();
  weicheN.update();
  weicheS.update();
  // put your main code here, to run repeatedly:
}

bei mir ist es so, dass die Ausgabe am MCP invertieren soll, daher drehe ich ON/OFF auf Klassenebene um (daher sind die beiden Konstanten static).
Das könnte bei den discreten Pins eventuell auch notwendig sein, aber ich wollte dass der Unterschied sichtbar ist.

edit: minor bug entfernt (einmal virtual zu viel)

Hallo Freunde,
habe mich etwas herangetastet. Erst mal einfach mit Hallis Led schalten, dann 4 Relais (für 2 Weichen). Das geht erstmal, grundlegend den MCP verstanden. Jetzt kommt aber ein Riesenproblem, die Motorsteuerung. Ich habe dazu wieder den L298N Motortreiber benutzt. habe ich ja schon mit Erfolg und eurer Hilfe direkt am Nano getan, Beschleunigen, bremsen, Halt. Da ich die mcp23017.h verwende, gehe ich mit analogWrite ins Aus. Kennt die nicht. DigitalWrite kann aber nicht hoch/runterzählen. Mir fehlt jetzt eine zündende Idee. Mit dem folgenden Sketch springt der Motor an, aber eben gleich auf max. V, logo. Digitalwrite macht an oder aus. Habe diese Zeilen als Kommentar drin. Also kurz zusammengefasst, wie kriege ich über den MCP einen DC-Motor zum Beschleunigen/Bremsen? Kann ich überhaupt analoge Signale ausgeben?

[code]
#include <Wire.h>
#include <Adafruit_MCP23017.h>

Adafruit_MCP23017 mcp1; // den 1. MCP erstellen


const uint8_t addr1 = 0; // Adresse 0x20 / A0 A1, A2 auf Ground

void setup() {
  mcp1.begin(addr1);      // Starte MCP 1

  mcp1.pinMode(7, INPUT); // Hall 1 Input
  mcp1.pinMode(6, INPUT); // Hall 2 Input
  //mcp1.pullUp(7, HIGH);  // Activate Internal Pull-Up Resistor
  //mcp1.pullUp(6, HIGH);  // Activate Internal Pull-Up Resistor



  mcp1.pinMode(8, OUTPUT); //Kontoll-LED 1
  mcp1.pinMode(9, OUTPUT); // Kontoll-LED 2
  mcp1.pinMode(10, OUTPUT); // Relais 1 bis 4
  mcp1.pinMode(11, OUTPUT);
  mcp1.pinMode(12, OUTPUT);
  mcp1.pinMode(13, OUTPUT);
  mcp1.pinMode(5, OUTPUT); //Pins vom L298N ENA
  mcp1.pinMode(4, OUTPUT);  //IN1
  mcp1.pinMode(3, OUTPUT);  //IN2
  


} // Ende Setup

void loop() {
  // Hall 1 schaltet Relais 1 und 3
    if (mcp1.digitalRead(7) == HIGH)
  {
   /*
   for (int i = 0; i <= 240; i = 20)
    {
      mcp1.digitalWrite(4, HIGH);
      mcp1.digitalWrite(3, LOW);   // Motor  beginnt zu rotieren
      mcp1.digitalWrite(5, i); // Motor beschleunigt
      delay(500);
    }
    */
    mcp1.digitalWrite(8, LOW);
    mcp1.digitalWrite(10, HIGH);
    mcp1.digitalWrite(12, HIGH);
  }
  else
  {
    mcp1.digitalWrite(8, HIGH);
    mcp1.digitalWrite(10, LOW);
    mcp1.digitalWrite(12, LOW);
  }
  // Hall 2 schaltet Relais 2 und 4
  if (mcp1.digitalRead(6) == HIGH)
  {
    mcp1.digitalWrite(9, LOW);
    mcp1.digitalWrite(11, HIGH);
    mcp1.digitalWrite(13, HIGH);
  }
  else
  {
    mcp1.digitalWrite(9, HIGH);
    mcp1.digitalWrite(11, LOW);
    mcp1.digitalWrite(13, LOW);
  }
} // End loop
[/code]
Und hier noch der Fritzing-Plan
![mcp23017_2Led_2Hall_4Relais_1Motor_Steckplatine|558x500](upload://aCHCMp3wSCwIZuY8dvDEHZK1wtu.jpeg)
Danke schon mal.

man nehme einen IC der PWM kann und werde glücklich.

Zwei Beispiele:
ein PCA9685 oder
bei einem 3.3 V Target ein SX1509

In Deinem Beitrag fehlt was:


Schön zu lesen!

Die Bezeichnung von analogWrite ist irreführend, da ein digitales PWM-Signal ausgegeben wird. Eigentlich müßte es daher pwmWrite heißen.

Grundsätzlich geht auch PWM mit MCP23017, ist ja nur ein und aus:

// getestet mit UNO
#include <Wire.h>
#include <Adafruit_MCP23017.h>

Adafruit_MCP23017 mcp1; // den 1. MCP erstellen

const uint8_t addr1 = 0; // Adresse 0x20 / A0 A1, A2 auf Ground
const uint8_t pwmPin = 3;

void setup() {
  mcp1.begin(&Wire);      // Starte MCP 1
  Wire.setClock(800000); // beschleunigt den I2C-Takt von 100 auf 800 kHz

  mcp1.pinMode(pwmPin, OUTPUT);  // PWM
} // Ende Setup

void loop() {
  static bool pwmStatus;
  mcp1.digitalWrite(pwmPin, pwmStatus);
  pwmStatus = !pwmStatus;
} // End loop

Die PWM-Frequenz beträgt 2,5 kHz.

grafik

Jede weitere Aktivität in loop reduziert und beeinflußt die PWM-Frequenz, weshalb ich MCP23017 nicht für PWM empfehlen möchte, denn da gibt es einfacher zu programmierende Lösungen.

Kurz und pragmatisch, wie immer. Aber ja, den PCA habe ich da und auch schon mit 8 Servos mal getestet. Das der auch mit DC-Motoren geht, hätte ich wahrscheinlich nie probiert, muss ich dann mal machen, wäre die ideale Lösung.
Danke

Hallo agmue,
verstehe ich das auf die Schnelle richtig: pwmPin=3 wird dann der "Steuerpin", den ich auf ENA vom L298 lege? Und das Loop habe ich nicht auseinanderklamüsert. static bool weist nur im loop der Variablen pwmStatus etwas zu. Das geht an pwmPin (3) und wird dann umgekehrt mit !pwmStatus. Aber was hat denn mit bool pwmstatus diese Variable für einen Wert, 0 oder 1??? Denn mit der letzten Zeile soll ja wohl das PWM-Signal erzeugt werden, oder??? Also ein
bool pwmStatus=false wäre mir noch klar. Wieder schlimm, diese Fragen, ich weiß

Ja.

Ja, eine lokale Variable, die am Ende von loop aber nicht gelöscht wird.

Bei static bool pwmStatus = true; ist true der Startwert beim ersten Durchlauf von loop. false ist Standard.

Ja, 0 oder 1 bzw. false oder true. Bei pwmStatus = !pwmStatus; invertiert das Ausrufezeichen den Zustand.

Mit mcp1.digitalWrite(pwmPin, pwmStatus); erzeuge ich mein eigenes PWM-Signal.

Klarer?

Hallo agmue,
dann hatte ich das ja soweit richtig verstanden. Unklar war mir, dass false der Standard ist und deshalb keine Zuweisung nötig ist. Aber wie nun weiter verfahren mit dem PWM-Signal. Ich habe jetzt 2,5 Khz als Frequenz. Wie binde ich das denn in den DC-Motor (L298) ein, so dass er beschleunigt/bremst? Ich brauche ja auch noch einen Auslöser, also Hall für den Beschleunigungs/Bremsvorgang. Fragen über Fragen

Das sagt dir aber jedes C und C++ Grundlagen Buch, seit ca 30 bis 40 Jahren

Die Regel:
Wenn eine automatische Initialisierung erfolgt, und das ist bei globalen und statischen lokalen Variablen immer der Fall, dann wird mit einem 0 äquivalenten Wert initialisiert.

Es gibt nur eine Ausnahme(beim gcc): Wenn man die Variable absichtlich in der .noinit Section ablegt.

Danke für den Hinweis auf meine Unkenntnis. Ist aber wirklich nicht nötig, weiß selbst, wie dumm ich bin. Wenn ich vor 30-40 Jahren mit Grundlagen C begonnen hätte, ja dann... Für mich galt immer die Regel: Initialisieren heißt konkreten Wert zuweisen, ist im Code auch besser lesbar, auch nach Wochen noch verständlich!!