Schrittmotor verlangsamt sich wegen Serieller Abfrage

Hallo zusammen ich bin neu in diesem Forum habe aber schon öfters mit Arduino gearbeitet.
Angefangen bei meinem 3D Drucker und aktuell jetzt in meiner Weiterbildung zum Techniker Automation in der Schweiz.

Aktuell arbeiten wir an einem Sammel und Sortier Roboter.

Dieser soll grüne und rote Klötze finden aufsammeln sortieren und dann an einem bestimmten Ort ablegen. Wir stecken mitten in der Programmierung. Für das auffinden der Klötze benutzen wir die Pixy2. Das funktioniert soweit auch gut. Jedoch habe ich ein Problem wo ich gerne schöner gelöst hätte.

Problem: Der Roboter fährt aktuell geradeaus bis die Pixy einen Klotz erkennt. Durch die erkannte Position von Pixy2 wird dann x und y offset errechnet. Anhand dem x Offset dreht sich der Robi entweder links oder rechts. Und genau hier ist das Problem. Beim drehen möchte ich ständig abfragen wo sich der Offset punkt befindet und dementsprechend zum richtigen Zeitpunkt anhalten. Deswegen frage ich in diesem Loop die Position immer wieder ab. Da jedoch die Pixy2 über die Serielle Schnittelle arbeitet( korriegieren wenn das nicht stimmt bin nicht zu 100 %sicher) verlangsamen sich die Schrittmotoren dermassen dass es uns einfach zu lange dauert.

Hier mein Code:

Mir ist bewusst das Serialprint auch seine Zeit benötigt. Aber nur das auskommentieren von pixy.ccc.getBlocks hilft mir hier weiter. Aber ohne dies dreht sich der Robi einfach weiter.

void pixxy() {
  int i;
  int32_t XOffset, YOffset;
  // Finde Blöcke!
  pixy.ccc.getBlocks();

  // Wenn es Blöcke hat dann anfahren und aufnehmen.
  if (pixy.ccc.numBlocks)
  {
    XOffset = (int32_t)pixy.frameWidth / 2 - (int32_t)pixy.ccc.blocks[0].m_x;
    YOffset = (int32_t)pixy.ccc.blocks[0].m_y - (int32_t)pixy.frameHeight / 2;
    Serial.print("X Offset ");
    Serial.println(XOffset);
    Serial.print("Y Offset ");
    Serial.println(YOffset);
    Serial.print("Breite ");
    Serial.println(pixy.ccc.blocks[0].m_width);
    Serial.print("Höhe ");
    Serial.println(pixy.ccc.blocks[0].m_height);
    Serial.print("Index ");
    Serial.println(pixy.ccc.blocks[0].m_index);
    delay(Pause_m);
    Serial.println(pixy.ccc.numBlocks);
    if (XOffset  > 0)
    {
      Serial.print("zu weit links ");
      Serial.print(" ");
      delay(Pause_m);
      while (XOffset > 0)
      {
        pixy.ccc.getBlocks();
        XOffset = (int32_t)pixy.frameWidth / 2 - (int32_t)pixy.ccc.blocks[0].m_x;
        YOffset = (int32_t)pixy.ccc.blocks[0].m_y - (int32_t)pixy.frameHeight / 2;
        Serial.print("X Offset ");
        Serial.println(XOffset);
        for (int i = 0; i <= 1; i++)
        {
          Linksdrehung();
        }

      }

    }
    else
    {
      Serial.print("zu weit rechts");
      Serial.print(" ");
      delay(Pause_m);

      while (XOffset < 0)
      {
        pixy.ccc.getBlocks();
        XOffset = (int32_t)pixy.frameWidth / 2 - (int32_t)pixy.ccc.blocks[0].m_x;
        YOffset = (int32_t)pixy.ccc.blocks[0].m_y - (int32_t)pixy.frameHeight / 2;
     
          Rechtsdrehung();
       
        //Serial.print("zu weit rechts ausgeführt ");
        //Serial.print(" ");
      }
      while (digitalRead(ko_gr1) == HIGH)
      {
        Serial.print("again");
        Vorwaerts_pixy();
        Serial.print("Vorwärts am ausführen ");
        Serial.print(" ");
      }

    }
  }
  else {
    Serial.print("NOT DETECTET");
    delay(Pause_m);
    while (!pixy.ccc.numBlocks)

    {
      pixy.ccc.getBlocks();
      for (int i = 0; i <= 1000; i++) {
        Vorwaerts();
      }
    }
  }
}

Ich habe jetzt stundenlang gesucht. Wahrscheinlich fehlen mir einfach die richtigen Begriffe um das zu finden was ich suche. Ich habe diverse verschiedene Sachen in dem Code wo ich einfach ausprobiert habe um mehr rauszufinden und halt zu testen. Das meiste kriegt man ja wirklich mit google und ein bisschen schlechten english irgendwie zusammengebastelt. Aber hier komme ich einfach nicht mehr weiter.

Wäre sehr dankbar für eure Inputs.

Liebe Grüsse

Sui aus der Schweiz

Hallo, mir ist beim Lesen des Codeschnipsels die Funktion delay(Pause_m) aufgefallen. Dieser Funkrionsaufruf blockiert die weitere Codebearbeitung für Pause_m*millisekunden.

tkkt92: Problem: Der Roboter fährt aktuell geradeaus bis die Pixy einen Klotz erkennt. Durch die erkannte Position von Pixy2 wird dann x und y offset errechnet. Anhand dem x Offset dreht sich der Robi entweder links oder rechts. Und genau hier ist das Problem. Beim drehen möchte ich ständig abfragen wo sich der Offset punkt befindet und dementsprechend zum richtigen Zeitpunkt anhalten. Deswegen frage ich in diesem Loop die Position immer wieder ab.

Grüezi! Mein Problem: Ich kenn nicht das, was Du hast und muss raten.

Dein Problem: delay(Pause_m);

Gib den Code so wieder, das er nachvollziehbar ist und alles das beinhaltet, was man wissen muss um adäquat darauf reagieren zu können.

Ui seid ihr schnell^^ Ungewohnt aber schön :slight_smile:

ich stell hier mal meinen kompletten Code rein. Es sind mehrere Reiter damit ich es für mich übersichtlicher habe.

Das mit dem delay(Pause_m) habe ich mir zuerst auch gedacht jedoch befindet der Robby sich in dieser Zeit wo das passiert in einer While Schleife. Egal jetzt ob nach linksdrehend oder rechtdrehend. Sobald ich effektiv das abfragen der Pixy ausschalte dreht er sich in normaler Geschwindigkeit. Delay pause m hatte ich bereits schon ausgeklammert. Hatte jedoch nicht die Wirkung.

Das Problem ist mir ,glaube ich, bewust und kann es auch nachvollziehen. Das ständige Abfragen der Position kostet Zeit weil die Pixy auf meinem Arduino 2560 board am ICSP angeschlossen ist. Soviel ich gelesen habe ist dies eine Serielle Schnittstelle. Das ständige abfragen kostet zeit und genau das soll anscheinend den Schrittmotor daran hindern die gewollte Geschwindigkeit zu erreichen.

Dies ist zumindest meine Vermutung.

Aber ich denke hier ist bestimmt jemand dabei wo meinen Fehler findet und mir das besser erklären kann bzw sogar einen Lösungsvorschlag hat. Ich würde mir nicht die ZEit für ein Thread nehmen wenn ich nicht shcon 6 Stunden gesucht hätte.

Liebe Grüsse
Sui

PS: der code war zu lang lade es im Anhang hoch

Robi.zip (4.98 KB)

Der Aufruf von

pixy.ccc.getBlocks();

wird halt einfach zu lange dauern. ICSP ist die SPI-Schnittstelle. Das ist zwar eine serielle Schnittstelle, aber eigentlich ziemlich flott. Aber die Laufzeit der Methode dürfte ja nicht nur von der Übertragungszeit abhängen.

Deine Speed ist 2000 Steps/Sec, also ca. 0,5ms / Step. Zur Ausführung eines Steps braucht die AccelStepper ca 0,2ms, bei 2 Motoren also 0,4ms. Da bleibt für dein getBlocks() nicht mehr viel Zeit, wenn es die Schrittausführung nicht verlangsamen soll.

Musst das getBlocks wirklich zwischen jeden einzelnen Step aufrufen? Du könntest das ja auch alle 10 Steps machen. Allerdings wird es dann bei jedem 10. Step einen kurzen Ruckler geben.

Mit welcher Baudrate arbeitet dein Serial.print()? Das sieht man leider auch nicht in dem Codeschnipsel.

Baudrate für Serial.print() liegt bei 152000. Muss man dies überhaupt in den Code einbinden?
Weil wenn ich Serial montior am PC öffne kann ich ja die Baudrate auswählen.

Nein ich muss es natürlich nicht jeden Step abgefragt haben. Jedoch ist mir aktuell noch was unklar. An diesen Lösungsansatz hatte ich schon gedacht. Jedoch verstehe ich die der Schrittmotoren über Accelstepper nicht ganz.
Ich probiere dies mal zu erläutern:

      while (XOffset < 0)
      {
        pixy.ccc.getBlocks();
        XOffset = (int32_t)pixy.frameWidth / 2 - (int32_t)pixy.ccc.blocks[0].m_x;
        YOffset = (int32_t)pixy.ccc.blocks[0].m_y - (int32_t)pixy.frameHeight / 2;
     
          Rechtsdrehung();
       
        //Serial.print("zu weit rechts ausgeführt ");
        //Serial.print(" ");
      }

So solange XOffset kleiner als 0 ist soll der Robi jetzt “einmal” die position der gefundenen Blöcke abfragen. Danach Den X und X Offset berechnen. Dann soll die Rechtsdrehung ausgeführt werden.

void Rechtsdrehung(){
 Motor_L_Rad.moveTo(-dreh_steps);
    if (Motor_L_Rad.distanceToGo() == 0)
  Motor_L_Rad.setCurrentPosition(0);
   Motor_L_Rad.run();

 Motor_R_Rad.moveTo(-dreh_steps);
    if (Motor_R_Rad.distanceToGo() == 0)
  Motor_R_Rad.setCurrentPosition(0);
   Motor_R_Rad.run();
}

-dreh_steps ist mit 200 Steps definiert. Wieso fragt er nach jedem Step ab? Normalerweise müsste er doch jetzt zuerst die 200 Steps ausführen und dann hat er diesen Void beendet und fängt die While Schleife von anfang wieder an?

Da wäre ich froh um eine Erklärung. Mir ist klar dass er jeden Step die Pixy abfragt aber warum er dass jetzt macht ist mir unklar.

Mein Ziel ist es eigentlich je nach dem wo der Klotz sich befindet die notwendigen Schritte auszurechnen und mit dem Errechneten Wert anzufahren damit die Position nicht ständig abgefragt wird. Jedoch ohne get blocks in der While schleife aktualisiert sich der Wert nicht.

Ich hoffe ich habe es verständlich erklärt.

Danke euch

Es ist auch nirgends ersichtlich wie die serielle Kommunikation eigentlich abläuft. Die Baudrate ist da nur eine Sache. Wie der Anfang und das Ende erkannt werden ist viel wichtiger. Naiv kann man das so machen dass man auf den Anfang wartet und wenn x Millisekunden nichts mehr ankommt hat man das Ende. Dadurch kostet natürlich jeder Aufruf Zeit. Unter Umständen viel mehr als die eigentliche Datenübertragung dauert.

Man kann serielle Kommunikation auch nicht-blockierend implementieren. Das hängt aber stark davon ab wie die Daten aussehen. Und der Rest des Programms ist ja auch nicht frei von Verzögerungen und Blockierungen

tkkt92: Baudrate für Serial.print() liegt bei 152000. Muss man dies überhaupt in den Code einbinden? Weil wenn ich Serial montior am PC öffne kann ich ja die Baudrate auswählen.

Da stellst Du aber nur die Baudrate des seriellen Monitors ein. Damit sich Monitor und Arduino verstehen, müssen beide mit der gleichen Baudrate arbeiten, sonst reden die 'aneinader vorbei'.

tkkt92: Normalerweise müsste er doch jetzt zuerst die 200 Steps ausführen und dann hat er diesen Void beendet und fängt die While Schleife von anfang wieder an?

Nein. Die run-Methode der Accelstepper ist nicht blockierend. Die prüft, ob zum aktuellen Zeitpunkt ein Step ausgeführt werden muss. Wenn ja erzeugt sie den entsprechend Impuls, wenn nein macht sie nichts, und kehrt sofort zurück. ( Ürigens nennt sich sowas wie dein 'Rechtsdrehung' eine [u]Funktion[/u] . Das void davor bedeutet nur, dass sie keinen Wert zurückliefert. Da könnte gegebenenfalls auch ein entsprechender Datentyp stehen ). Es macht deshalb auch eigentlich keinen Sinn ein moveTo() und ein run() so hintereinander zu schreiben. Um die mit moveTo() angegebene Zielposition zu erreichen, bedarf es normalerweise vieler run() Aufrufe. Wenn die Funktion 'Rechtsdrehung' erst zurückkehren soll, wenn die komplette Drehung ausgeführt ist, brauchst Du innerhalb der Funktion eine while-Schleife und nicht außenrum. Dann hast Du aber eine blockierende Funktion, was normalerweise nicht der Sinn des Einsatzes von AccelStepper ist.

P.S. Fast alle deine Funktionen zu Stepperansteuerung unterscheiden sich eigentlich nur durch die Zahl der auszuführenden Schritte. Da könntest Du auch eine Funktion schreiben, der Du die Schritte als Parameter übergibst.

Danke für die Inputs. Ich werde mir dies mal Schritt für Schritt durcharbeiten. Ja ich bin noch ein ziemlicher neuling :). Das mit dem moveTo() und run() habe ich aus einer Vorlage aus Accelstepper Bibliothek.

Füge ich hier noch an.

Das mit der Ansteuerung der Stepper ist mir bewust. Ich wollte sie am Schluss so wie du gesagt hast in eine einzige Funktion zusammenführe. Mir ging es aktuell darum die Drehrichtung schnell und einfach wechseln zu können.

Noch einmal wegen Baudrate. Muss ich denn nun was einstellen? Weil die kommunikation funktioniert. Wenn ich das richtig verstanden habe ist die Baudrate richtig weil ich jeztt kommunizieren kann. Dann gehe ich davon aus dass die Standart Baudrate ohne definierung bei 115200 liegt. (152000 war verschrieben).

Theoretisch müsste ich also das fahren in ein While schleife setzten und z.b. While (position stepper x) wiederholen lassen.

Ich teste mal ein paar sachen aus und melde mich dann nochmals.

Gruss

Sui

// Bounce.pde
// -*- mode: C++ -*-
//
// Make a single stepper bounce from one limit to another
//
// Copyright (C) 2012 Mike McCauley
// $Id: Random.pde,v 1.1 2011/01/05 01:51:01 mikem Exp mikem $

#include <AccelStepper.h>

// Define a stepper and the pins it will use
AccelStepper stepper; // Defaults to AccelStepper::FULL4WIRE (4 pins) on 2, 3, 4, 5

void setup()
{  
  // Change these to suit your stepper if you want
  stepper.setMaxSpeed(100);
  stepper.setAcceleration(20);
  stepper.moveTo(500);
}

void loop()
{
    // If at the end of travel go to the other end
    if (stepper.distanceToGo() == 0)
      stepper.moveTo(-stepper.currentPosition());

    stepper.run();
}

Das Beispiel macht es richtig. Da wird das moveTo() nur jeweils 1x aufgerufen, wenn die Zielposition erreicht ist und dann der neue Wert gesetzt. Die run()-Methode wird dagegen bei jedem loop-Durchlauf aufgerufen, unabhängig vom moveTo().

Das mit der Baudrate hängt auch vom Board ab. Bei Boards mit einer nativen USB-Schnittstelle ist es in der Tat egal - auf beiden Seiten. Da läuft es immer mit dem USB-Tempo.

Alles klar ich hab mir das mal zu herzen genommen.

Aktuell habe ich eine Lösung gefunden womit ich meine Schrittmotoren laufen lassen kann die Geschwindigkeit dabei steuern kann und gleichzeitig den X Offset rauslesen kann. Zwar muss ich bei dieser Variante auf das geschmeidige Beschleunigen verzichten jedoch funktioniert es wenigstens.

Ich bin dafür weg von Accelstepper und löse es so:

 while (XOffset < 0)
      {
        pixy.ccc.getBlocks();
        XOffset = (int32_t)pixy.frameWidth / 2 - (int32_t)pixy.ccc.blocks[0].m_x;
        YOffset = (int32_t)pixy.ccc.blocks[0].m_y - (int32_t)pixy.frameHeight / 2;
     
    digitalWrite(55, HIGH);
    digitalWrite(38, LOW);
    tone(54,Geschw_dreh);
       
        Serial.print("X Offset ");
        Serial.println(XOffset);

Nun es funktioniert. Jedoch ist das klug? Weiss ich nicht. Es gibt soweit ich es in englischen Threads gelesen habe weitere möglichkeiten Stepper anzusteuern und gleichzeitig andere Sachen machen zu können ohne dass eines der Beiden geblockt wird. Jedoch kenne ich mich dafür zu wenig aus und an manchen stellen happert einfach das Englisch :/.

Falls Ihr eine elegantere Lösung habt oder andere Lösungsvorschläge her damit. Ich möchte nur die Möglichkeiten kennen lernen. So funktioniert es ja aktuell für mich.

Gruss sui

Du könntest mal meine MobaTools probieren

Interessant. Ich musste schmerzhaft feststellen dass man mit Tone nur ein Pin gleichzeitig ansteuern kann. Schlussendlich bin ich jetzt beim integrierten Hardware Timer hängen geblieben. Ich probiere damit noch eine funktionierende Lösung zu basteln.

Kann ich mit MobaTools mehrere Schrittmotoren unbeeinflusst vom restlichen Programm über die Hardware Timer laufen lassen? Wenn ja probiere ich diese gerne aus.

Gruss sui

tkkt92: Kann ich mit MobaTools mehrere Schrittmotoren unbeeinflusst vom restlichen Programm über die Hardware Timer laufen lassen? Wenn ja probiere ich diese gerne aus.

Mit den MobaTools kannst Du bis zu 6 Schrittmotore unabhängig voneinander ansteuern. Die verwenden dazu auf UNO/Nano den Timer 1, auf Mega oder Leonardo/Micro den Timer 3. Für alle wird nur 1 Timer verwendet. Die Stepimpulse werden in Timerinterrupts erzeugt.

Ach sehr schön. Gibt es doch schon Lösungen. Danke sehr für deinen Input. Ich werde es mal austesten und mitteilen ob es bei mir soweit funktioniert.

Liebe Grüsse

Sui