Ansteuerung mehrerer Schrittmotoren

Hallo zusammen.

Vorweg ich bin ein Neuling was das programmieren von µC und Arduinos betrifft.

Eine Verwandte hat mich gebeten für ihr Kind ein Puppentheater zu bauen und ich hab mich jetzt dazu entschieden dieses mit einem Arduino Mega2560, 6 EasyDriver v44 und 6 dazupassenden Schrittmotoren zu bauen.

Beim Programmieren bin ich jetzt aber auf erste Schwierigkeiten gestoßen. Und zwar hab ich einmal 3 Funktionen definiert in denen die Schrittmotoren angesteuert werden. Leider hat sich das Problem ergeben, dass sich immer alle Motoren drehen, bzw. alle drei Funktionen ausgeführt werden, obwohl immer nur eine Funktion nach der anderen abgearbeitet werden sollte.

Ich hoffe ihr könnt mir helfen

int Distance_1_1 = 0;//Record the number of steps motor 1 has taken
int Distance_2_1 = 0;//Record the number of steps motor 2 has taken
int Distance_3_1 = 0;//Record the number of steps motor 3 has taken

void setup() {                
  pinMode(0, OUTPUT);//Direction Motor 1     
  pinMode(1, OUTPUT);//Step Motor 1
  pinMode(2, OUTPUT);//Direction Motor 2
  pinMode(3, OUTPUT);//Step Motor 2
  pinMode(4, OUTPUT);//Direction Motor 3
  pinMode(5, OUTPUT);//Step Motor 3
  digitalWrite(0, LOW);//
  digitalWrite(1, LOW);
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  digitalWrite(4, LOW);
  digitalWrite(5, LOW);
}

void pause(){
  digitalWrite(1, LOW);
  digitalWrite(3, LOW);
  digitalWrite(5, LOW);
  delayMicroseconds(100);
}

void schritt_1(){
  digitalWrite(1, HIGH);
  //digitalWrite(3, HIGH);
  //digitalWrite(5, HIGH);  
  delayMicroseconds(100);  
  digitalWrite(1, LOW); 
  //digitalWrite(3, LOW);
  //digitalWrite(5, LOW);
  delayMicroseconds(100);
  Distance_1_1 = Distance_1_1 + 1;   // record this step
  //Distance_2_1 = Distance_2_1 +1;
  //Distance_3_1 = Distance_3_1 +1;
  // Check to see if we are at the end of our move
  if (Distance_1_1 == 900)
  {
    // We are! Reverse direction (invert DIR signal)
    if (digitalRead(0) == LOW)
    {
      digitalWrite(0, HIGH);
    }
    else
    {
      digitalWrite(0, LOW);
    }
    // Reset our distance back to zero since we're
    // starting a new move
    Distance_1_1 = 0;
    // Now pause for half a second
    delay(500);
  }
  
  //if (Distance_2_1 == 900)
  //{
    // We are! Reverse direction (invert DIR signal)
    //if (digitalRead(2) == LOW)
    //{
      //digitalWrite(2, HIGH);
    //}
    //else
    //{
      //digitalWrite(2, LOW);
    //}
    // Reset our distance back to zero since we're
    // starting a new move
    //Distance_2_1 = 0;
    // Now pause for half a second
    //delay(500);
  //}
  
  //if (Distance_3_1 == 900)
  //{
    // We are! Reverse direction (invert DIR signal)
    //if (digitalRead(4) == LOW)
    //{
      //digitalWrite(4, HIGH);
    //}
    //else
    //{
      //digitalWrite(4, LOW);
    //}
    // Reset our distance back to zero since we're
    // starting a new move
    //Distance_3_1 = 0;
    // Now pause for half a second
    //delay(500);
  //}
}

void schritt_2(){
  //digitalWrite(1, HIGH);
  digitalWrite(3, HIGH);
  //digitalWrite(5, HIGH);
  delayMicroseconds(100);  
  //digitalWrite(1, LOW); 
  digitalWrite(3, LOW);
  //digitalWrite(5, LOW);
  delayMicroseconds(100);
  //Distance_1_1 = Distance_1_1 + 1;   // record this step
  Distance_2_1 = Distance_2_1 +1;
  //Distance_3_1 = Distance_3_1 +1;
  
  // Check to see if we are at the end of our move
  //if (Distance_1_1 == 1800)
  //{
    // We are! Reverse direction (invert DIR signal)
    //if (digitalRead(0) == LOW)
    //{
      //digitalWrite(0, HIGH);
    //}
    //else
    //{
      //digitalWrite(0, LOW);
    //}
    // Reset our distance back to zero since we're
    // starting a new move
    //Distance_1_1 = 0;
    // Now pause for half a second
    //delay(500);
  //}
  
  

  if (Distance_2_1 == 1800)
  {
    // We are! Reverse direction (invert DIR signal)
    if (digitalRead(2) == LOW)
    {
      digitalWrite(2, HIGH);
    }
    else
    {
      digitalWrite(2, LOW);
    }
    // Reset our distance back to zero since we're
    // starting a new move
    Distance_2_1 = 0;
    // Now pause for half a second
    delay(500);
  }
  
  //if (Distance_3_1 == 1800)
  //{
    // We are! Reverse direction (invert DIR signal)
    //if (digitalRead(4) == LOW)
    //{
      //digitalWrite(4, HIGH);
    //}
    //else
    //{
      //digitalWrite(4, LOW);
    //}
    // Reset our distance back to zero since we're
    // starting a new move
    //Distance_3_1 = 0;
    // Now pause for half a second
    //delay(500);
  //}
}

void schritt_3(){
  digitalWrite(5, HIGH);
  delayMicroseconds(100);
  digitalWrite(5, LOW);
  delayMicroseconds(100);
  Distance_3_1 = Distance_3_1 +1;
  
  // Check to see if we are at the end of our move
  if (Distance_3_1 == 3600)
  {
    // We are! Reverse direction (invert DIR signal)
    if (digitalRead(4) == LOW)
    {
      digitalWrite(4, HIGH);
    }
    else
    {
      digitalWrite(4, LOW);
    }
    // Reset our distance back to zero since we're
    // starting a new move
    Distance_3_1 = 0;
    // Now pause for half a second
    delay(500);
  }
}

void loop() {
    schritt_1();
    //delayMicroseconds(100);
    pause();
    schritt_2();
    pause();
    schritt_3();
    pause();
    //delayMicroseconds(100);
}

schritt_1() , _2() und _3() laufen ja auch gleichzeitig.

Auch wenn du delayMicroseconds(100); wieder aktivierst, wird sich die Verzögerung von 0,0001 sec nicht wirklich bemerkbar machen.

Grundsätzlich gibt es zwei Möglichkeiten 1. schritt_1() hängt so lange bis der Schritt fertig ist. (Woran wird das erkannt ?)

  1. in loop() wird ermittelt, welcher Schritt gerade dran ist, und nur diese Funktion aufgerufen. schritt_n() und loop() sind sofort fertig, das ganze System kann bei Bedarf mehrere Sachen gleichzeitig machen ( z.B. Taster abfragen )

Danke für die schnelle Antwort

Grundsätzlich wollte ich es eh so machen wie du in deiner zweiten Möglichkeit gemeint hast.

Das die kleine einen Taster betätigt, das Programm gestartet wird und die loop Funktion, welche dann die einzelnen Schritt-Funktionen aufruft und abarbeitet. Aber wie programmiere ich die loop Funktion so, dass sie die einzelnen schritte nach einander abarbeitet und nicht gleichzeitig.
Mit einem delay, passiert ja nicht wirklich was, außer das sich die motoren langsamer drehen.

Grundsätzlich wollte ich es eh so machen wie du in deiner zweiten Möglichkeit gemeint hast.

Sehr schön.

Du brauchst eine Variable, die den Zustand merkt.

byte status; // 0 = Ruhe, 1 = Schritt_1 , 2 = Schritt_2 ,  ... , 99 = zurück auf Grundstellung 

void setup()
{
   pinMode(TASTER_PIN, INPUT_PULLUP);
}

void loop()
{
   switch (status)
   {
     case 0: 
        if (digitalRead(TASTER_PIN, LOW)) // Taster schaltet auf GND 
        {
            delay(10); // Taster prellen abwarten
            status = 1;
        }
        break;
    case 1:
       step_1();  // diese Funktion schaltet [i]status[/i] weiter, wenn sie fertig ist
       break;
    ...
   }
}

Dann ist die Frage, wie man das ganze einfach aus jeder Situation wieder in eine definierte Grundstellung kriegt Statt deine Distance-Zähler nie zu restten, besser Endschalter vorsehen ?

Aber da bin ich nun wirklich kein Experte.

Danke.

Eine Frage: Wenn ich deinen Code jetzt in mein Programm einbaue wird die switch-case-Anweisung ausgeführt. Aber wenn ich mehrere Schritte habe die ich hintereinander ausführen will hab ich aber wieder die selbe Situation wie vorher. Oder kann ich in der Switch-Case-Anweisung nach jedem Case die variable "status" vor dem brake erhöhen und so zum nächsten case springen?

Code sähe so aus:

switch (status)
   {
     case 0: 
        if (digitalRead(TASTER_PIN, LOW)) // Taster schaltet auf GND 
        {
            delay(10); // Taster prellen abwarten
            status = 1;
        }
        break;
    case 1:
       step_1();  // diese Funktion schaltet [i]status[/i] weiter, wenn sie fertig ist
       status++;
       break;
    ...

Bezüglich der Ausgangsstellung hab ich schon etwas vorbereitet.

Ich habe vor ein magnetisches Klebeband an der Nylonschnur zu befestigen und dieses an einem Reedkontakt (Magnetschalter) vorbeiführen. Dadurch kann ich am Anfang eine vordefinierte Position erreichen und dann das Programm starten.

Nein, da war ich nicht klar genug:

Der Begriff "Programm" und der Zusammenhang mit loop() ist wohl auch etwas gewöhnungsbedürftig:

loop() (und alle darin aufgerufenen Unter-Funktionen) sollte immer nur eine kurze Aktion machen, oder im Normalfall gar nichts, und sofort beenden, damit alles gleich wieder von vorne losgeht. Dadurch erreicht man, dass der Arduino immer sofort auf äussere Ereignisse reagieren kann.

z.B.

void step_1()
{
  // macht bei jedem Aufruf nur einen Motor-Schritt 
  digitalWrite(1, HIGH);
  delay(1);
  digitalWrite(1, LOW);
  Distance_1_1++ ;   // record this step
  if ( Distance_1_1 >= TARGET1_1)
       init_step_2(); // nächsten Programm-Schritt vorbereiten
}

void init_step_2()
{
     Distance_1_2 = 0;
     digitalWrite(2, LOW); // Richtung
     status = 2;
}

Alternativ kann auch step_1() einen Rückgabewert liefern, der dem Aufrufer ( loop ) sagt, dass das Ziel jetzt erreicht ist:

boolean step_1()
{
  // macht bei jedem Aufruf nur einen Motor-Schritt 
  digitalWrite(1, HIGH);
  delay(1);
  digitalWrite(1, LOW);
  Distance_1_1++ ;   // record this step
  return ( Distance_1_1 >= TARGET1_1);  // true = step_1 fertig
}

void loop()
{
  ...
  switch (status) {
  
   ...  
   case 1:
   if ( step_1() == true )
     init_step_2();
   break;

   case 2:  
   if ( step_2() == true )
     init_step_3();
   break;

   }

}

Dank der break; - Abweisung wird im switch immer nur einer der cases durchlaufen, und status wird nur geändert, wenn ein Programmschritt fertig ist ( d.h. in step_1() wurde TARGET1_1 erreicht )

Klarer oder noch unklarer jetzt ;)

Okay. Jetzt hab ich's auch verstanden. Vielen Dank.

Noch ein Tip: Ich würde Pin 0/1 nicht verwenden, die für Serielle Kommunikation vorgesehen sind. Kann man immer gut für Debugging Meldungen und Tests verwenden. Damit arbeitet auch der Bootloader.

Dein 2560 hat ja genug andere Pins.

Danke für den Tipp. Diesbezüglich hätte ich gleich eine weiter Frage

Kann ich eigentlich eines der Kabel für Direction und Step auch auf einen anderen PIN hängen außer auf PWM? Bei Direction sollte das ja eigentlich kein Problem sein, da das eigentlich nur eine Boolsche Variable ist, die ich nur auf 0 oder 1 setze. Bin mir aber nicht sicher darum hab ich zurzeit noch alle 6 Motoren mit ihren zwei Kabel auf PWM-Pins hängen.

In deinem Beispiel hast du 3 Schritt-Motoren mit den Pins 0 .. 5 und nutzt kein PWM. Solange du die Steps weiter selbst generierst, brauchst du kein PWM und kannst jeden Pin für digitalWrite verwenden.( besser nicht die Pins 0 / 1 und evtl. auch die SPI Pins nicht )

Eine Frage hätte ich noch:

1. Ich bin jetzt dabei dem Code ähnlich zu deinem Beispielcode zu implementieren, dabei ist aber eine Frage aufgekommen

boolean step_1()
{
  // macht bei jedem Aufruf nur einen Motor-Schritt 
  digitalWrite(1, HIGH);
  delay(1);
  digitalWrite(1, LOW);
  Distance_1_1++ ;   // record this step
  return ( Distance_1_1 >= TARGET1_1);  // true = step_1 fertig
}

void loop()
{
  ...
  switch (status) {
  
   ...  
   case 1:
   if ( step_1() == true )
     init_step_2();
   break;


   }

}

DU ruftst hier im switch-case nachdem der Taster gedrückt wurde die Funktion step_1 auf indem du step_1() sagst. Danach mit rufst du aber die funktion step_2 mit init_step_2 auf. Ist das init_ der Befehl zum Aufruf der Funktion, oder heißt in dem Beispiel die Funktion init_step_2

Falls ich deine Frage richtig verstanden habe,hilft dies vielleicht:

init_step_2() ist eine andere Funktion als [b]step_2()[/b]

( Ein Unterstrich ist ein normaler Buchstabe, init_step_2 ist ein Name der anders ist als step_2 )

Nur wenn die Funktion step_1() true zurückliefert, wird init_step_2() aufgerufen. init_step_2() sollte status verändern und alles nötige initialisieren, damit beim nächsten Mal der switch zu case 2: verzweigt.

[b]break;[/b] bewirkt übrigens, dass danach nichts mehr aus dem switch Block ausgeführt wird, falls das nicht klar war. Aber loop() kommt ja gleich wieder dran.