Endschalter programieren funktioniert nicht

Hallo liebe Arduino Community,

wir haben für ein Schulprojekt einen automatischen Getränke Einschänker gebaut.


Dieser läuft über 2 Nema 17 Motoren. Im Grunde genommen funktioniert alles, allerdings möchten wir nun das Programm über einen Schalter aktivieren. Dafür haben wir uns überlegt, jeweils einen Endschalter unten an den Gewindespindeln anzubringen, sodass die Motoren dann stoppen, soweit der Mitnehmer unten angekommen ist, damit wir immer aus der gleichen Position starten können. Die LEDs sollen ebenfalls angesteuert werden. Die Gelben sollen während des Vorgangs blinken anschließend sollen die grünen LEDs blinken, wenn der Vorgang beendet ist.
So die Theorie...
Die Endschalter sind nun eingebaut und wir haben versucht den Code dementsprechend abzuändern. Wenn die Endschalter gedrückt werden, zeigen sie dies durch eine intigrierte LED, allerdings stoppen daraufhin die Motoren nicht.

An der Hardware kann es eigentlich nicht liegen. Das habe ich mehrmals gecheckt. Trotzdem hier noch einmal ein Bild vom Arduino:

Den aktuellen Code den wir benutzen ist dieser:

#define directionPin1 5
#define stepPin1 4

#define directionPin2 2
#define stepPin2 3

#define endSwitchPin1 6
#define endSwitchPin2 7

// Entprellungsfunktion für Endschalter
bool debounce(int pin) {
  int reading = digitalRead(pin);
  static int previousState = HIGH;  // Statischer Wert für den vorherigen Zustand
  static unsigned long lastDebounceTime = 0;
  unsigned long debounceDelay = 50; // Anpassen, je nach Bedarf

  if (reading != previousState) {
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != previousState) {
      previousState = reading;
      return reading == LOW;
    }
  }

  return false;
}

void setup() {
  pinMode(directionPin1, OUTPUT);
  pinMode(stepPin1, OUTPUT);
  pinMode(directionPin2, OUTPUT);
  pinMode(stepPin2, OUTPUT);
  
  pinMode(endSwitchPin1, INPUT_PULLUP);  // Mit INPUT_PULLUP aktiviert der Endschalter den Pin, wenn er betätigt wird
  pinMode(endSwitchPin2, INPUT_PULLUP);
  // Andere Initialisierungen
}

void loop() {
  bool endSwitch1 = debounce(endSwitchPin1);
  bool endSwitch2 = debounce(endSwitchPin2);

  if (endSwitch1) {
    // Wenn Endschalter 1 aktiviert wird, stoppen Sie Motor 1 und fahren Sie für 2 Sekunden nach oben
    digitalWrite(directionPin1, LOW);
    digitalWrite(stepPin1, LOW);
    digitalWrite(directionPin2, HIGH); // Motor 2 fährt 2 Sekunden nach oben
    unsigned long stopTime = millis() + 2000; // Aktuelle Zeit + 2 Sekunden
    while (millis() < stopTime) {
      digitalWrite(stepPin2, HIGH);
      delayMicroseconds(15);
      digitalWrite(stepPin2, LOW);
      delayMicroseconds(15);
    }
  }

  if (endSwitch2) {
    // Wenn Endschalter 2 aktiviert wird, stoppen Sie Motor 2 und fahren Sie für 2 Sekunden nach oben
    digitalWrite(directionPin2, LOW);
    digitalWrite(stepPin2, LOW);
    digitalWrite(directionPin1, HIGH); // Motor 1 fährt 2 Sekunden nach oben
    unsigned long stopTime = millis() + 2000; // Aktuelle Zeit + 2 Sekunden
    while (millis() < stopTime) {
      digitalWrite(stepPin1, HIGH);
      delayMicroseconds(15);
      digitalWrite(stepPin1, LOW);
      delayMicroseconds(15);
    }
  }

  // Arbeitsabschnitt 1: Motor 2 läuft 30 Sekunden lang nach oben
  digitalWrite(directionPin2, HIGH);
  unsigned long startTime = millis();
  while (millis() - startTime < 30000) { // 30 Sekunden warten
    digitalWrite(stepPin2, HIGH);
    delayMicroseconds(15);
    digitalWrite(stepPin2, LOW);
    delayMicroseconds(15);
  }
  
  // Arbeitsabschnitt 2: Motor 2 und Motor 1 parallel für 30 Sekunden nach oben
  digitalWrite(directionPin2, LOW);
  digitalWrite(directionPin1, HIGH);
  startTime = millis();
  while (millis() - startTime < 30000) {
    digitalWrite(stepPin1, HIGH);
    digitalWrite(stepPin2, HIGH);
    delayMicroseconds(10);
    digitalWrite(stepPin1, LOW);
    digitalWrite(stepPin2, LOW);
    delayMicroseconds(20);
  }
  
  // Arbeitsabschnitt 3: Motor 1 läuft endlos nach unten
  digitalWrite(directionPin1, LOW);
  while (true) { // Endlos
    digitalWrite(stepPin1, HIGH);
    delayMicroseconds(15);
    digitalWrite(stepPin1, LOW);
    delayMicroseconds(15);
  }

}

Ich habe euch mal auf Youtube das Video hochgeladen, dass wir an unseren Lehrer geschickt haben, wo wir unseren Zwischenstand präsentieren sollten: https://youtu.be/xUmr4goPGu0?si=K3TnULo2gh92X1OE
Nur zur Info, hier wurde das Programm nicht über den Schalter aktiviert, sondern über den Computer und zusätzlich haben wir die LEDs an eine Batterie angeschlossen, um die Wirkung zu erzeugen, dass diese eine Funktion haben :wink:

Ich würde mich sehr über eure Ratschläge freuen, da dies mein erstes Arduino Projekt ist und ich gewisse Grundkenntnisse habe, diese allerdings nicht mehr ausreichen dafür.

Liebe Grüße,
Justus

Please use English in the English language forum sections.

sorry, wrong section

Könnten Sie bitte ein Schaltbild der Verdrahtung und eine Erklärung zur Verbindung der Endschalter bereitstellen?


Es gibt ein Problem in der Funktion bool debounce(int pin), da die statische Variable, die den alten Zustand speichert, nur für eine Schaltfläche vorgesehen ist, während Sie sie für beide verwenden

  bool endSwitch1 = debounce(endSwitchPin1);
  bool endSwitch2 = debounce(endSwitchPin2);

Können Sie anstelle eine Drittanbieterbibliothek verwenden?
Toggle von @dlloyd.

Ich habe mal kurz drauf geschaut.
Du möchtest einen endlichen Automaten bauen.

Kannst Du mal in Worten beschreiben, was da wann passiert?
Ich habe nicht ganz verstanden, was die ganzen Zeiten da machen und wo sich welche Endschalter für welche Funktion befinden.

Was ich beim schnellen scrollen gesehen habe:

Soetwas fällt Euch unabdingbar auf die Füße.
Ihr rechnet in die Zukunft. Bei einem Überlauf trifft die darauf aufbauende Bedingung immer zu....

Und das sorgt dafür, das in der Zeit nichts anderes ausgeführt wird.
Vergiss jede Form von Schleifen.

Aber beschreib mal, dann bekommt man das auch hin...

Das ist leider eine häufige Fehlbeurteilung :wink:

Für die zielgerichtete Programmierung einer solchen Maschine ist es immer hilfreich, den erwarteten Ablauf aufzuschreiben (oder als Flussdiagramm darzustellen). Hier mal als Text

  • Nach dem Einschalten

    • Variable und Hardware initialisieren
    • Prüfen: Sind die Halter in der Grundstellung (unten)?
    • Wenn nein: Halter in Grundstellung fahren
  • Danach sind folgende Zustände denkbar

    a) Maschine steht in unterer Stellung
    b) Flasche (und Glas?) wird/werden zum Füllen angehoben
    c) Flasche (und Glas?) in oberer Stellung zum Abschließen des Befüllens
    d) Glas und Flasche absenken

Die Maschine kann zu jedem Zeitpunkt nur in einem der vier Zustände sein.

Wie kommt sie nun aber von einem in den nächsten Zustand?

Hierzu legt man nun Übergangsbedingungen fest:

  1. Von a) -> b) vermutlich am einfachsten durch einen Tastendruck, da es keinen Sensor dafür gibt, ob Glas/Flasche korrekt eingesetzt sind

  2. Von b) zu c) durch Abfrage des oberen Endschalters

  3. Von c) zu d) nach einer Wartezeit in oberer Stellung, die sicherstellt, dass die Flüssigkeit komplett im Glas gelandet ist (empirisch ermitteln, ggf. mit einem kleinen Sicherheitszuschlag)

  4. Wenn d) abgeschlossen ist (also der untere Endschalter zuschlägt), geht man automatisch in den Zustand a) zurück

Und dann beginnt alles von vorne.

Grafisch in etwa so :

Jetzt liegt es erstmal an Dir/Euch zu prüfen, ob das tatsächlich Dein/Euer gewünschter Ablauf ist, oder ggf. Deine/Eure Vorstellung mal so darzustellen. Häufig merkt man dann erst, an was man noch nicht gedacht hat oder wo im gedachten Ablauf Lücken oder Fehler zu finden sind.

Wenn man hier eine klare Vorstellung hat, lohnt es sich die einzelnen Anteile nach und nach zu realisieren und am Ende alles zu verbinden ("zu integrieren").

Viel Erfolg!
ec2021

3 Likes

Daß delay() den Arduino blockiert wirst Du bereits gehört haben. Damit das nicht passiert wurde Dir geraten millis() zu verwenden. Auch immer noch richtig.
Du hast aber mit millis() delay() emuliert.

Dieses Konstrukt ist gleich blockierend wie delay()
Die While schleife läuft solange bis die Zeit abgelaufen ist. Da in der Schlaife kein Endschalter abgefragt wird kann der Motor auch nicht stoppen.

Das ist so wie Du zu Deinem Freund sagst er soll Dich auf dem Händy anrufen sobald er mit den hausaufgaben fertig ist, daß Du ihn besuchen kannst und dann ohne Händy außer Haus gehen. :wink: :wink:

Grüße Uwe

Habe mir Deine Beschreibung zusammen mit dem Code nochmal genauer angeschaut:

Ihr wollt anscheinend die Endschalter nur in der Beladestellung einsetzen? Korrekt?

Da Ihr Schrittmotoren verwendet, ist das grundsätzlich ok.

  • Man benutzt den Endschalter dann als "Indexgeber", über den man eine definierte Position ermittelt. In diesem Fall "unten".
  • Danach bietet es sich aber an, die für die Aufgabe erforderliche Anzahl von Schritten und nicht die Zeit als Massstab für das Erreichen der Endposition zu nehmen: Also die Schrittanzahl nach oben mitzählen und nach n Schritten aufhören.
  • Da man den Weg nach unten beendet, wenn man den Endschalter wieder erreicht hat, wird das System bei jedem Vorgang automatisch wieder auf die untere Endposition kalibriert.

Hier mal eine angepasste Grafik:

Sehe ich das richtig?

Wenn ja, könnte es so aussehen:

Sketch
/*
    Forum: https://forum.arduino.cc/t/endschalter-programieren-funktioniert-nicht/1180400
    Wokwi: https://wokwi.com/projects/379139190355998721
*/



constexpr unsigned long SCHRITTZEIT =   20;
constexpr unsigned long ABWARTEZEIT = 5000;
constexpr int         FUELLSCHRITTE =  100;
constexpr byte directionPin1 = 5;
constexpr byte stepPin1      = 4;
constexpr byte directionPin2 = 2;
constexpr byte stepPin2      = 3;
constexpr byte glasEndSchalter  = 6;
constexpr byte flaschenEndSchalter  = 7;
constexpr byte startTaster  = 8;
constexpr byte grueneLED   = 9;
constexpr byte roteLED    = 10;


enum Zustaende {BELADESTELLUNG, UMFUELLEN, ABWARTEN, ZURUECKFAHREN};
Zustaende zustand = ZURUECKFAHREN;


/*
  Bei den folgenden Funktionen debouncen wir ausschliesslich das Schliessen der Endschalter,
  da uns das Öffnen nicht interessiert

*/
boolean glasUnten() {
  static unsigned long zuletztLOW;
  byte state = digitalRead(glasEndSchalter);
  if (state ==  LOW) {
    zuletztLOW = millis();
    return true;
  };
  if (state == HIGH && millis()-zuletztLOW > 100){
    return false;
  } 
}

boolean flascheUnten() {
  static unsigned long zuletztLOW;
  byte state = digitalRead(flaschenEndSchalter);
  if (state ==  LOW) {
    zuletztLOW = millis();
    return true;
  };
  if (state == HIGH && millis()-zuletztLOW > 100){
    return false;
  } 
}


void setup() {
  Serial.begin(115200);
  digitalWrite(roteLED,LOW);
  pinMode(roteLED, OUTPUT);
  digitalWrite(grueneLED,LOW);
  pinMode(grueneLED, OUTPUT);
  pinMode(directionPin1, OUTPUT);
  pinMode(stepPin1, OUTPUT);
  pinMode(directionPin2, OUTPUT);
  pinMode(stepPin2, OUTPUT);
  pinMode(glasEndSchalter, INPUT_PULLUP);
  pinMode(flaschenEndSchalter, INPUT_PULLUP);
  pinMode(startTaster, INPUT_PULLUP);
  motor1NachUnten();
  motor2NachUnten();
  Serial.println(glasUnten());
  Serial.println(flascheUnten());
}

void loop() {
  Maschine();
}

void Maschine() {
  static unsigned long letzterSchritt;
  static int schritteNachOben;
  switch (zustand) {
    case  BELADESTELLUNG:
      if (fuellVorgangStarten()) {
        zustand = UMFUELLEN;
        schritteNachOben = 0;
        Serial.println("Umfüllvorgang gestartet");
        letzterSchritt = 0;
        nichtEingabeBereit();
      }
      break;
    case  UMFUELLEN:
      if (millis() - letzterSchritt > SCHRITTZEIT) {
        einSchrittMotor1();
        einSchrittMotor2();
        schritteNachOben++;
        letzterSchritt = millis();
      }
      if (schritteNachOben >= FUELLSCHRITTE) {
        zustand = ABWARTEN;
        letzterSchritt = millis();
        Serial.println("Abwarten");
      }
      break;
    case  ABWARTEN:
      if (millis() - letzterSchritt > ABWARTEZEIT) {
        motor1NachUnten();
        motor2NachUnten();
        zustand = ZURUECKFAHREN;
        Serial.println("Zurückfahren");
      }
      break;
    case  ZURUECKFAHREN:
      if (glasUnten() && flascheUnten()) {
        motor1NachOben();
        motor2NachOben();
        zustand = BELADESTELLUNG;
        Serial.println("Beladestellung erreicht");
        eingabeBereit();
      }
      if (millis() - letzterSchritt > SCHRITTZEIT) {
        letzterSchritt = millis();
        if (!glasUnten()) {
          einSchrittMotor1();
        }
        if (!flascheUnten()) {
          einSchrittMotor2();
        }
      }
      break;
    default:
      Serial.println("Unbekannter Zustand!?");
      break;
  }
}

void  eingabeBereit(){
     digitalWrite(roteLED, LOW) ;
     digitalWrite(grueneLED, HIGH) ;
}

void  nichtEingabeBereit(){
     digitalWrite(roteLED, HIGH) ;
     digitalWrite(grueneLED, LOW) ;
}

// Hier benötigen wir kein Debouncing, da wir auf die erste Signalflanke reagieren
// und danach die Taste sehr lange nicht mehr abfragen ...
boolean fuellVorgangStarten() {
  byte state = digitalRead(startTaster);
  return !state;
}


void einSchrittMotor1() {
  einSchrittBei(stepPin1);
}

void einSchrittMotor2() {
  einSchrittBei(stepPin2);
}



void einSchrittBei(byte stepPin) {
  digitalWrite(stepPin, HIGH);
  delayMicroseconds(15);
  digitalWrite(stepPin, LOW);
  delayMicroseconds(15);
}

void motor1NachOben() {
  richtungAendern(true, directionPin1);
}

void motor1NachUnten() {
  richtungAendern(false, directionPin1);
}

void motor2NachOben() {
  richtungAendern(true, directionPin2);
}

void motor2NachUnten() {
  richtungAendern(false, directionPin2);
}

void richtungAendern(boolean Oben, byte dirPin) {
  if (Oben) {
    digitalWrite(dirPin, LOW);
  } else {
    digitalWrite(dirPin, HIGH);
  }
}

P.S.: Mein Sketch erzeugt nicht den Ablauf, wie er Eurem Video zu entnehmen ist, er enthält aber das grundsätzliche Gerüst, um diesen zu programmieren. Das ist für Euch (hoffentlich) auch interessanter, als eine fertige Lösung zu bekommen...

Hier hast du einen Grund gefunden, warum statische Variablen in Funktionen böse sind.
endSwitchPin1 beeinflusst die Wirkung von endSwitchPin2 und umgekehrt.

Hier wundert mich nicht, dass der Motor weiter läuft.

➜ cf #4...

Die ganze Debounce-Funktion für den Endschalter ist witzlos. Um einen Schalter zu entprellen, reicht es, ihn einfach nicht so oft zu lesen ( Leseinterval > Prellzeit). Das kann man am Anfang von loop mit einer Millis() Abfrage vor dem Lesen für alle Schalter erschlagen.
Bei den Endschaltern is sowieso die Frage, ob ein entprellen sinnvoll ist. Sobald sie das erstemal ansprechen wird der Motor gestoppt. Das Prellen danach ist eigentlich uninteressant.

Ganz schlimm ist der von @Combie als zweites angesprochene Punkt. @justuswie schreibt es ja sogar noch als Kommentar dazu:

Naja, und das tut er dann auch - und mit sonstigem Programmablauf/Schalterabfragen oder sonst was wars das dann... Da läuft kein loop() als loop mehr, und da wird auch kein Endschalter mehr abgefragt - einfach nur Motor läuft und läuft und läuft ....

Was das prellen betrifft wirst du beim rein fahren, in die Endstellung, recht haben.
Beim Rausfahren müsste ich erst mal neu nachdenken.

Aber !
Das Entprellen unterdrückt auch kurze Störspikes.
Und genau das, das kann auch dann wieder von Nutzen sein.

#define directionPin1 5
#define stepPin1 4

#define directionPin2 2
#define stepPin2 3

#define endSwitchPin1 6
#define endSwitchPin2 7


void setup() {
  pinMode(directionPin1, OUTPUT);
  pinMode(stepPin1, OUTPUT);
  pinMode(directionPin2, OUTPUT);
  pinMode(stepPin2, OUTPUT);
  
  pinMode(endSwitchPin1, INPUT_PULLUP);  // wenn der Endschalter betätigt wird gibt es LOW
  pinMode(endSwitchPin2, INPUT_PULLUP);
  // Andere Initialisierungen
}

void loop() {
 
  // Arbeitsabschnitt 1: Motor 2 läuft 30 Sekunden lang nach oben
  digitalWrite(directionPin2, HIGH);
  unsigned long startTime = millis();
  while (millis() - startTime < 30000) { // 30 Sekunden warten                            //normalerweise bei Schrittmotoren zählt man die Schritte, nicht die Zeit
    digitalWrite(stepPin2, HIGH);
    delayMicroseconds(15);
    digitalWrite(stepPin2, LOW);
    delayMicroseconds(15);
  }
  
  // Arbeitsabschnitt 2: Motor 2 und Motor 1 parallel für 30 Sekunden nach oben
  digitalWrite(directionPin2, LOW);
  digitalWrite(directionPin1, HIGH);
  startTime = millis();
  while (millis() - startTime < 30000) {
    digitalWrite(stepPin1, HIGH);
    if(!digitalRead(endSwitchPin2))digitalWrite(stepPin2, HIGH);
    delayMicroseconds(15);
    digitalWrite(stepPin1, LOW);
    digitalWrite(stepPin2, LOW);
    delayMicroseconds(15);
  }
  
  // Arbeitsabschnitt 3: Motor 1 läuft  nach unten
  digitalWrite(directionPin1, LOW);
  while (digitalRead(endSwitchPin2)) { // 
    digitalWrite(stepPin1, HIGH);
    delayMicroseconds(15);
    digitalWrite(stepPin1, LOW);
    delayMicroseconds(15);
  }
while(1);
}

Sollte ein Endschalter nicht eigentlich die Stromzufuhr in Fahrtrichtung unterbrechen? Ansonsten ist das kein sicherer Endschalter...wenn das Programm versagt, dann spult der einfach weiter und weiter. Wenn die Stromzufuhr unterbrochen wird, kann zwangsweise nur noch in die andere Richtung gefahren werden. Klar muss das Softwaremässig auch erfasst werden, aber harwareseitig ist es so nicht möglich einen Crash zu fabrizieren.

Bei Schrittmotoren nicht möglich.
Denn damit würdest du dir auch die andere Richtung versaubeuteln.

ist das so? Ich kenne mich mit Schrittmotoren nicht aus....ich dachte die hätten plus und minus getrennt für beide Laufrichtungen nach dem Treiber. Lasse mich da aber gerne belehren.

Ich will dich gar nicht belehren.

  1. Nein!
  2. Wenn du da unterbrichst, stirbt der Treiber.

Man sollte unterscheiden nach

  • Endpositionsgeber und
  • Notausschalter

Der Notausschalter ist so zu montieren, dass er rechtzeitig zuschlägt, um Schäden zu vermeiden, wobei der Endpositionsgeber bereits ausreichend vorher das Erreichen der gewünschten Position signalisiert.

Wenn Notaus zuschlägt, braucht es zumeist aus gutem Grund einen manuellen Eingriff...

Benutzt man eine Schrittmotortreiberplatine, gibt es dort in der Regel einen Enable-Pin, den man für die Notausschaltung verwenden kann.

2 Likes

am Exponat gib es bereits zwei Schalter, ein davon ist wie ich vermute Stromzufuhr - bzw Stromunterbrecher.

Das sind zwei Positionsendschalter, die zwei unterschiedliche Aufgaben haben.