[Schrittmotor] Woher kommt DIESES Verhalten?

Hallo Forum!

Ich weiß mal wieder nicht weiter. Ich bastle schon länger an einem Projekt herum, was grundsätzlich auch prima funktioniert … aber manchmal eben nicht.

Zur Erklärung: Ich bewege einen Schlitten mit einem Schrittmotor über eine Kreissäge, um damit Zinkungen zu sägen. Wenn alles fertig ist wird der Schlitten den ganzen Weg zurückgefahren, bis ein induktiver Näherungsschalter ausgelöst wird und die Rückfahrt per Interrupt beendet wird. Dann wird noch ein kleines Stück weitergefahren (um das Spindelspiel loszuwerden) und wieder zurückgefahren. Wenn der Näherungsschalter wieder auslöst, wird die Position nullkalibriert. So funktioniert es auch absolut zuverlässig - einfach geil, hochpräzise.

Aber eben nicht immer. Manchmal zeigt der Motor das Verhalten, was im folgenden Video zu sehen ist (25 MB):

https://nx17140.your-storageshare.de/s/mmcZRExY7bLCasD

Erst fährt der Schlitten 2x korrekt zurück, beim dritten mal knörzt er ordentlich, bewegt sich deutlich langsamer und fährt auch eine unkorrekte Position an, wie an der Messuhr zu sehen ist. Woran könnte das liegen? Ich habe gegoogelt und bin auf das "overshooting"-Problem gestoßen, aber es ist eher das genaue Gegenteil, wie mir scheint.

So, welche Infos könnten benötigt werden?

Ich verwende:

  • Arduino Nano
  • DRV8825 von Ti
  • 18V Spannungsversorgung aus einem BOSCH-Akku (keine Sorge, ist voll)
  • Step-Down-Wandler auf 7V für Arduino VIN, Rest geht in Nema17-Schrittmotor
  • ich benutze die Accesstepper-Library, Herumspielen mit MaxSpeed und Acceleration löst das Problem nicht
  • Das Problem taucht immer nur nach einem Richtungswechsel im Interrupt auf!
  • Das Problem ist nicht sicher reproduzierbar, ca 85% der Fahrten sind absolut i.O.
  • es gibt keine mir bekannten Bugs, wie Hitzeprobleme oder sowas, der Sketch funktioniert auch nach Stunden noch, der Motor wird bei Nichtbenutzung ausgeschaltet

void setup() {

	Serial.begin(9600);

	pinMode(notaus_pin, INPUT_PULLUP);  //  I N T E R R U P T !
  attachInterrupt(digitalPinToInterrupt(notaus_pin), not_aus, CHANGE);  // Not-Aus-Knopf

	/****************************************
			Einstellungen für den Schrittmotor:
	****************************************/
	pinMode(richtung_pin, OUTPUT);  // Richtung
	pinMode(schritt_pin, OUTPUT);   // Schritt
	pinMode(ms0, OUTPUT);           // Microsteps 1
	pinMode(ms1, OUTPUT);           // Microsteps 1
	pinMode(ms2, OUTPUT);           // Microsteps 2
	pinMode(enable, OUTPUT);        // Enable Motor

  digitalWrite(enable, LOW); // LOW = Motor an, HIGH = Motor aus!

	stepper.setMaxSpeed(4800);
	stepper.setAcceleration(12000);
  stepper.runToNewPosition(320 * ms_mm * motorpositionsfaktor);
  stepper.runToNewPosition(-20 * ms_mm * motorpositionsfaktor);
}

void loop() {
}

void not_aus() {

  stepper.setAcceleration(40000);
  
  if (drehrichtung() == 'r'){
    stepper.stop();
    stepper.moveTo(30 * ms_mm * motorpositionsfaktor); // Position wird nie erreicht, weil sie vorher unterbrochen wird!
  }

  if (drehrichtung() == 'l'){
    stepper.setCurrentPosition(0);
    stepper.moveTo(3 * ms_mm * motorpositionsfaktor);
  }

}

Das Video zeigt das fehlerhafte Verhalten, das bei diesem Code auch gezeigt wurde. Der Codeschnipsel wird aber im Video nicht abgespielt.

Also: Wo kann ich anfangen zu suchen - oder was wird noch benötigt?

Danke und Gruß, kuahmelcher.

Dann gehen Schritte verloren, weil der Motor von irgendwas behindert wird. Wenn eine Reduktion der Geschwindigkeit bzw. Beschleunigung nicht hilft, dann muß ein stärkerer Motor (oder Netzteil) her oder die Ursache für die Blockierung muß gefunden und beseitigt werden.

Hallo, es gibt - meines Wissens - keine (mechanische) Behinderung. Der Schlitten läuft beim dritten Mal genauso frei, wie beim ersten und zweiten Mal. Die Behinderung muss in der SOftware zu suchen sein … meine ich.

Oder könnte die Gewindespindel selbst sich irgendwie verkanten …? Aber warum immer im Interrupt und nie woanders?

Danke und Gruß, kuahmelcher

Dann kommt noch Hardware in Betracht, vielleicht heizt sich etwas zu stark auf? Software kann nicht mal so und mal anders laufen, das läßt sich ausschließen. Bis auf:

Ich würde es ohne Interrupt implementieren.

In einer ISR?
Nee...

Soweit mir bekannt ist, wird das so keiner!

1 Like

Kann ich - glaube ich - ausschließen. Der Fehler taucht manchmal direkt am Anfang auf und wird mit der Zeit nicht signifikant mehr …

Hm, aber was könnte der Grund sein? Wenn das ein grundsätzliches Interrupt-Stepper-Problem wäre, dann wäre das Verhalten doch sicher bekannt … Ich meine eigentlich, dass der Interrupt zur höheren Präzision ganz nützlich ist.

Danke und Gruß, kuahmelcher

Die Präzisioh brauchst Du nur beim Zurückfahen zum Endschalter. Und das muß so langsam erfolgen, daß der Motor jederzeit sofort gestoppt werden kann.

Ja, das stimmt. Aber was könnte der Grund sein?
Danke und Gruß, kuahmelcher.

Hat @combie doch schon in #5 gesagt:
In einer ISR nur das Notwendigste tun, also ein Flag setzen, das dann in loop() abgefragt und behandelt wird.

Also funktioniert es NICHT!

Ist es ja auch - deshalb die Hinweise zum Interrupt.
Interrupts sind nicht einfache Funktionen wie alle anderen auch. Interrupts passieren auf Mikroprozessor-Ebene, nicht auf C++ Ebene. Eine C++ Anweisung wird in ( u.U. sehr viele ) Maschinenbefehle zerlegt. Und der Interrupt kann bei jedem der Maschinenbefehle 'zuschlagen'. Da ist eine C++-Anweisung in aller Regel noch nicht komplett ausgeführt. Wenn dann z.B. im Interrupt mit den evtl. inkonsistenten Daten gearbeitet wird rauchts. Und das ist nur ein Problem, dass mit Interrupts auftreten kann. Wenn dann noch in der ISR Funktionen aufgerufen werden, die nie dafür gedacht waren in einer ISR aufgerufen zu werden ...

Wenn man sich nicht wirklich mit Interrupts auskennt ( was da auf Prozessorebene passiert! ) sollte man die FInger davon lassen. Der 'sorglose' Umgang mit Interrupts ist eine vielfach zu beobachtende Quelle für sporadische und schwer zu findende Fehler.
In 99% der Fälle wo hier im Forum Probleme wegen Interrupts nachgefragt werden, sind die Interrupts gar nicht notwendig. Meist ist es ein Problem schlechter und nicht blockadefreier Programmierung im loop().

Codeschnipsel sind wertlos. Zeige einen kompletten, kompilierbaren Sketch, der das Verhalten zeigt

1 Like

Ahh, oh. Das hatte ich anders verstanden. Danke für den Hinweis!
Die Flags zu setzen ist ein Ansatz, dafür brauche ich ein bisschen Zeit.
Gruß, kuahmelcher.

Richtig - deshalb dieser Post! :o)

Ja … könnte schon passen. Am Ende will ich nicht um jeden Preis Interrupts benutzen, sondern, dass meine Zinken "anständig" gesägt werden. Wenn das ohne Interrupts besser geht … aber am liebsten wäre mir, wenn ich das Problem begreifen würde um es in anderen Projekten vermeiden zu können.

Ja, du hast Recht. Ich habe noch einmal ein Minimalbeispiel gebaut, es nach bestem Wissen und Gewissen kommentiert und ein passendes Video vom "fehlerhaften" Verhalten des Motors mit diesem Sketch gedreht:

Schrittmotor Problem 2

Zu sehen ist eine andere Perspektive, die den Näherungsschalter und ein Blech zeigt, das ihn auslöst. Bei der ersten Rückfahrt klappt alles korrekt, bei der zweiten Rückfahrt ist das Geknörze zu hören/sehen.

(Den Hinweis mit den Flags anstatt der ganzen Anweisungen im Interrupt habe ich da aber jetzt noch nicht verwurstet, dazu brauche ich noch ein bisschen Zeit.)

#include <AccelStepper.h>

const byte ms0 = 11;
const byte ms1 = 10;
const byte ms2 = 9;
const byte enable = 12;

volatile unsigned long alteZeit = 0, piepen_ende;
volatile byte entprellZeit = 200;

AccelStepper stepper = AccelStepper(1, 8, 7);

const byte ins_pin = 3;  // Induktiver Näherungsschalter!

int ms_mm; // Microschritte pro Millimeter

void setup() {

  /****************************************
      Interrupt:
  ****************************************/
  pinMode(ins_pin, INPUT_PULLUP);  //  I N T E R R U P T !
  attachInterrupt(digitalPinToInterrupt(ins_pin), ISR_naeherungsschalter, CHANGE);

  /****************************************
  		Einstellungen für den Schrittmotor:
  ****************************************/
  pinMode(7, OUTPUT);       // Richtung
  pinMode(6, OUTPUT);       // Schritt
  pinMode(ms0, OUTPUT);     // Microsteps 1
  pinMode(ms1, OUTPUT);     // Microsteps 2
  pinMode(ms2, OUTPUT);     // Microsteps 3
  pinMode(enable, OUTPUT);  // Enable Motor
  digitalWrite(enable, LOW); // LOW = Motor an, HIGH = Motor aus!
  ms(4); // Auswahl der Microschritte
  stepper.setMaxSpeed(2800);
  stepper.setAcceleration(6000);
}

void loop() {
  
  stepper.runToNewPosition(10 * ms_mm * -1 );
  stepper.runToNewPosition(-10 * ms_mm * -1 );
}

void ISR_naeherungsschalter() {
  if ((millis() - alteZeit) > entprellZeit) {  // innerhalb der entprell Zeit keine Aktion
    if (drehrichtung() == 'r') {
      stepper.stop();
      stepper.moveTo(30 * ms_mm * -1 ); // Position wird nie erreicht, weil sie vorher unterbrochen wird!
    }
    if (drehrichtung() == 'l') {  
      /* ISR wird erneut ausgeführt, wenn der Schlitten 
      den Näherungsschalter wieder "verlässt":        */
      stepper.setCurrentPosition(0);      // Nullkalibrierung
      stepper.moveTo(2 * ms_mm * -1 );    // 2 mm weiter zu Position OHNE Spindelspiel
    }
  }
}

char drehrichtung() {
  char drehrichtung;
  if (stepper.distanceToGo() < 0) {
    drehrichtung = 'l';
  } else {
    drehrichtung = 'r';
  }
  return drehrichtung;
}

void ms(byte microsteps_per_rotation) {
  switch (microsteps_per_rotation) {
    case 1:  // Full Step
      digitalWrite(ms0, LOW);			
      digitalWrite(ms1, LOW);			
      digitalWrite(ms2, LOW);
      ms_mm = 50;
      break;
    case 2:  // 1/2 Step
      digitalWrite(ms0, HIGH);		
      digitalWrite(ms1, LOW);			
      digitalWrite(ms2, LOW);
      ms_mm = 100;
      break;
    case 4:  // 1/4 Step
      digitalWrite(ms0, LOW);			
      digitalWrite(ms1, HIGH);		
      digitalWrite(ms2, LOW);
      ms_mm = 200;
      break;
    case 8:  // 1/8 Step
      digitalWrite(ms0, HIGH);		
      digitalWrite(ms1, HIGH);		
      digitalWrite(ms2, LOW);
      ms_mm = 400;
      break;
    case 16:  // 1/16 Step
      digitalWrite(ms0, LOW);			
      digitalWrite(ms1, LOW);			
      digitalWrite(ms2, HIGH);
      ms_mm = 800;
      break;
    case 32:  // 1/32 Step
      digitalWrite(ms0, HIGH);		
      digitalWrite(ms1, LOW);			
      digitalWrite(ms2, HIGH);
      break;
  }
}

Danke und Gruß, kuahmelcher

Pollen gehört in loop(), nicht in eine ISR.

Hallo,
so wird das nichts. Ich hatte dir doch geschrieben, dass Du eine ISR nicht wie eine 'normale' Funktion betrachten kannst. Was Du da alles treibst, kann man in einer ISR nicht machen. Deine ISR kann die Accelstepper mitten in einer dort abgearbeiteten Funktion unterbrechen, und dann rufst Du in der ISR wieder eine AccelStepper Funktion auf. Das funktioniert nicht!!!!! Da kommen sich Sketchablauf und ISR in die Quere. Sowas führt zu ganz üblen sporadischen Fehlern.
Zum Pollen hat dir @DrDiettrich ja schon geschrieben.
Lies dir meinen Post oben nochmal genau durch - und dann lass das mit den ISR erstmal sein. Du brauchst für deine Funktionalität keine ISR - Du musst deinen loop() nur blockadefrei programmieren - also auch keine blockierenden Accelstepper Funktionen verwenden.

Das ist übrigens eine sinnlose Kombination in der Accelstepper: stepper.stop() hält den Stepper nicht sofort an, sondern definiert nur eine neue Zielposition, die mit der aktuell eingestellten Beschleunigung ( die gilt ja auch fürs Bremsen) gerade noch erreicht werden kann.
stepper.moveTo überschreibt die Zielposition dann gleich wieder mit einer anderen.

P.S. Du könntest Dir vielleicht auch meine MobaTools anschauen. Dort gibt es für die Stepperansteuerung gar keine blockierenden Funktionen. Da kannst Du deinen Näherungsschalter im loop() bequem abfragen, und den Stepper entsprechend ansteuern.
Die verwenden für die Schritterzeugung zwar auch Interrupts, aber da funktioniert das :wink:
Die Schrittgeschwindigkeit musst Du da vermutlich etwas 'tunen', aber bei nur einem Stepper ist das kein Problem. Eine Dokumentation dazu gibt's auch - sogar in deutsch :wink: .

Hallo MicroBahner, ja Danke für die Hinweise. Ich muss den ganzen Sketch umbauen. Aber ich werde erst mal damit anfangen mir selbst eine Anleitung für das Konzept von blockierenden und nicht blockierenden Befehlen der accelstepper Library zu schreiben.

Danke auch für deine Entwicklungsarbeit in der MobaTools-Library, die ich aber erst anschauen werde, wenn ich die accelstepper-Library so einigermaßen verstanden habe.

Wenn ich darf schreibe ich noch einmal ein paar konkrete Fragen, aber ich muss erst den Sketch umbauen.

Danke und Gruß, kuahmelcher

Auch dafür Danke, werde ich beachten!
Gruß, kuahmelcher

Dafür ist das Forum ja da :smile: :smile: :wink:

Accelstepper ist entweder blockierend
oder
du musst für loop() in jedem einzelnen Durchlauf garantieren dass loop() schneller durchlaufen wird als die Schrittauslösung für den Schrittmotor.

Dieses langsamer laufen könnte davon ausgelöst sein, dass du bei einem einzigen Schrittimpuls das timing um ein paar Mikrosekunden verschoben ist und der Schrittmotor deswegen aus dem Tritt gerät.

Die MobaTools machen wirklich in den angebenen Grenzen, die Schrittimpulse zum richtigen Zeitpunkt.

Das einzige was die MobaTools aus dem Tritt bringen könnte wäre böses Schindluder treiben mit zusätzlichen interrupts bei denen dieser zusätzliche Interrupt zu lange zum abarbeiten braucht.

Also du hast jetzt die Wahl zwischen Zeit brauchen um das Accelstepper-Problem im Detail zu verstehen. Das ist schon nützlich weil du dann ja mehr übers programmieren weißt.
oder
gleich auf die MobaTools umsteigen.

Hallo liebe Helfende,

ich habe jetzt den ganzen Sketch umgebaut und möchte kurz Ergebnisse berichten - falls jemand mal ähnliche Probleme hat.

Also - das Problem war ursprünglich, dass der angesteuerte Schrittmotor in ca 15% der Fälle beim Homing "geknörzt" und keine exakte Position angefahren hat.

Um es kurz zu machen: Die Ursache wurde hier sehr schnell benannt, nämlich dass die ISR überstrapaziert wurde und viele verschiedene Befehle enthielt. Sie darf aber nur wenig Code enthalten, am Besten nur ein Flag, was gesetzt wird. Ich habe den Code dementsprechend jetzt radikal umgebaut:

void not_aus() {
  /****************************************************************
   * I N T E R R U P T S E R V I C E R O U T I N E !
   * **************************************************************
   * Im Interrupt dürfen nur kurze Schalter gesetzt werden, damit 
   * das Timing des Prozessors nicht gestört wird. Dann funktioniert
   * die Accelstepper-Library nicht mehr störungsfrei.
  *****************************************************************/
  interruptmarker = 1;
}
void loop(){
   if (interruptmarker != 0){
      // Umfangreicher Code, der bisher in der ISR stand
   }
}

Was soll ich sagen? Kaum macht man alles richtig - funktioniert's! ;o)

Eine zweite Korrektur bezog sich auf die Verwendung der Funktionen in der Accelstepper-Library. Es wurden blockierende und nicht blockierende Funktionen eingesetzt, die sich teilweise gegenseitig störten. Dies war allerdings kosmetisch, weil der Hauptfehler durch die erste Korrektur bereits beseitigt wurde. Trotzdem lohnt eine grundsätzliche Auseinandersetzung mit der Library und ihrer Konzept.

Danke an alle, die geholfen haben,
Gruß, kuahmelcher.