Linearachse per Schrittmotor mit zwischen Positionen steuern

Hallo zusammen

Bis jetzt habe ich nur 1 Arduino Projekt erfolgreich abgeschlossen und würde gerne etwas weiteres realisieren. Da ich mir den Code meist mittels Internet und circuito.io zusammen bastle um es zu verstehen habe ich ab und an etwas Verständnis Probleme. auch da es meist mehrere lösungsvarianten gibt.

Meine aufgabe ist folgende

Eine Linearachse wird mittels einem schrittmotor auf tastendruck seitlich bewegt. Links und rechts ist die achse begrenzt.
zwischendrinn sind ca 8 schaltnocken welche ein feedback an einen zwischenpositions schalter geben.

als bediener inputs hat es 2 taster (links/rechts)
und 3 positionsschalter (anschlag links/rechts und zwischenposition)
als output's 2 LED's und den stepper driver.

DIe Inputs sind Taster welche für LInks und Rechts stehen.
Die LED's ist das eine Grün für "In position" und das eine rot für "Fährt"
DIe Positionsschalter sind jeweils einer links und einer rechts am ende der achse.
Ein positionierschalter ist auf der linearachse und wird an vorgegebenen "nocken" geschaltet als zwischenposition.

Die Funktion ist nun relativ einfach.
wird die links oder rechts taste gedrückt, fährt der schrittmotor in die jeweilige Richtung bis der zwischenschalter an einem nocken betätigt wird (während der fahrzeit springt die LED grün auf rot)
Die achse wird gestoppt und fährt bei erneutem tastendruck in die entsprechende richtung zum nächsten positions nocken etc. ist die achse am jeweiligen ende, werden die endschalter betätigt und es wird die entsrechende richtung zum losfahren gesperrt.

die einzelnen funktionen kann ich abfragen und ansteuern (stepper via steppermotor.h buttons als pullup etc.) doch wie ich dies in den loop verbauen soll komm ich leider noch auf keinen grünen zweig.

Ohne deinem Streng geheimen Sketch wird schwierig zu helfen :wink:

Mir ist nicht klar, warum Du einen Schrittmotor verwendest, der eigentlich zum Positionieren gedacht ist. Oder umgekehrt, warum verwendest Du Schaltnocken, wenn Du mit einem Schrittmotor positionieren kannst?

Schau Dir mal das Beispiel Stepper_Slider der Bibliothek MobaTools an.

Schrittmotoren zeichnen sich dadurch aus, dass man sie alleine durch die Step-Pulse genau positionieren kann.
Je nachdem wie viele Schritte oder Mikroschritte pro Umdrehung des Schrittmotors gemacht werden kann man dadurch auf Mikrometer genau positionieren. Zumindest theoretisch. Dass es praktisch dann meistens doch keine Mikrometer werden liegt aber nicht am Prinzip des Schrittmotors sondern am mechanischen Spiel des Linearantriebes und Ungleichmäßigkeiten der Antriebswelle.

Zehntelmillimeter ist überhaupt kein Problem.
Dazu genügt es dann zu sagen "fahre an Schrittposition "X"
Man braucht bei einem Schrittmotor nur in einer Situation eine Rückmeldung.
Bei der Referenzfahrt. Dafür genügt ein einzelner Sensor. Alle weiteren Positionen kann man dann als Schrittanzahl relativ zum Referenzpunkt angeben.

Libraries wie die MobaTools machen den ganzen Rest.
Den MobaTools genügt es zu sagen Fahre an Absolutkoordinate "X"
Die Drehrichtung und die Anzahl Schritte wird dann von der library berechnet.

Das funktioniert so lange man den Schrittmotor nur so viel Drehmoment zumutet wie der ohne Schrittverluste schafft. Eine Überlastung schadet dem Motor nicht. Statt sich zu drehen ruckelt er halt nur oder summt ohne sich zu drehen. Wenn der Schrittmotor Schritte verloren hat, dann wird eine neue Referenzfahrt nötig weil das Programm durch das Schritte verlieren den Motor an einer anderen Position wähnt als er tatsächlich ist.

vgs

Ich find die Aufgabe recht klar. Es gibt zwei Endschalter, über die darf nicht hinweg gefahren werden und (einen) Schalter zwischendrin, bei dem das befahren einen Halt auslöst und wenn die Taste dann losgelassen wird, kann in jede Richtung wieder angefahren werden.

Wenn Taste "nach links" geschlossen, Antrieb nach links und Taste "nach rechts" blockieren.
Wenn Endschalter links geschlossen und Taste links geschlossen, Antrieb halt (Und Taste nach rechts noch immer blockiert)
Wenn Endschalter links geschlossen und Taste "nach links" geöffnet gebe Taste "Nach rechts frei und blockiere Taste "nach Links".
Wenn Taste "nach Rechts" gedrückt, blockiere Taste "nach links" und fahre Antrieb nach rechts.
(Wenn Taste "nach rechts" losgelassen, stoppe Antrieb und gebe beide Tasten frei)
Wenn Nockenschalter erreicht, Antrieb halt und Sperre setzen
Wenn Taste "naxh rechts" losgelassen Sperre aufheben und beide Taster freigeben.
Jetzt kannst Du von dort aus wieder nach links oder rechts und das Spiel beginnt von vorn.....

Wenn ich das richtig verstanden habe, ist das garnicht so schlimm....

Hallo
Mit dem Kochrezept von my_xy_projekt sollte das einfach anzurühren sein.
Ich würde mit der Funktion zum Steuern des Motors beginnen und testen.

void motorCtrl (int control); 

sorry, wurde familiär unterbrochen. werde ihn noch posten

ich verwende einen schrittmotor weil er A: vorhanden ist (inkl controller) B:warscheindlich einfacher mit Rampe angesteuert werden könnte (ist anfangs nicht erforderlich) und C:Evtl die steuerung noch weiter entwickelt wird wo der stepper von vorteil wäre.

die schaltnocken verwende ich da die abstände nicht gleich sind und verstellbar sein müssen (klemmhebel auf, schaltnocken verstellen, klemmhebel zu)

// Include Libraries
#include "Arduino.h"
#include "LED.h"
#include "Button.h"
#include "StepperMotor.h"


// Pin Definitions
#define LEDG_PIN_VIN	5   // LED In Position
#define LEDR_PIN_VIN	6   // LED Achse Fährt
#define MICROSWITCH_1_PIN_COM	2   // Endschalter Links
#define MICROSWITCH_2_PIN_COM	3   // Schalter Zwischenposition
#define MICROSWITCH_3_PIN_COM	4   // Endschalter Rechts
#define PUSHBUTTONMOMENTARY_1_PIN_2	7   // Bedienschalter Links fahren
#define PUSHBUTTONMOMENTARY_2_PIN_2	8   // Bedienschalter Rechts fahren
#define STEPPER_PIN_STEP	10    // Ausgang Stepper Driver Step
#define STEPPER_PIN_DIR	9   // Auscgang Stepper Driver Dir



// Global variables and defines
#define stepperDelayTime  1000    // Geschwindigkeit Stepper
// object initialization
LED ledG(LEDG_PIN_VIN);
LED ledR(LEDR_PIN_VIN);
Button microSwitch_1(MICROSWITCH_1_PIN_COM);
Button microSwitch_2(MICROSWITCH_2_PIN_COM);
Button microSwitch_3(MICROSWITCH_3_PIN_COM);
Button pushButtonMomentary_1(PUSHBUTTONMOMENTARY_1_PIN_2);
Button pushButtonMomentary_2(PUSHBUTTONMOMENTARY_2_PIN_2);
StepperMotor stepper(STEPPER_PIN_STEP,STEPPER_PIN_DIR);



// Void Setup, Start
void setup() 
{
    Serial.begin(9600);   // Serial Monitor für debugging
    while (!Serial) ; 
    Serial.println("Serial Verbindung OK");
    
    microSwitch_1.init();
    microSwitch_2.init();
    microSwitch_3.init();
    pushButtonMomentary_1.init();
    pushButtonMomentary_2.init();
    stepper.enable();   // enable stepper motor
    // set stepper motor speed by changing the delay value, the higher the delay the slower the motor will turn
    stepper.setStepDelay(stepperDelayTime);
    
}

hier mal mein Anfangsteil bis zum loop.

den loop überarbeite ich gerade und stell ihn dann auch rein.

Soll der Motor exakt am Schaltnocken stehen bleiben?
Wenn ja dann nützt das mit der Rampe gar nichts. Die Steuerung bekommt das Signal "Schalter durch Nocke betätigt" und soll dann sofort stehen bleiben = keine Rampe herunterfahren sondern Start-Stop-Betrieb.

Sonst musst du das Herunterfahren der Rampe mit einkalkulieren in dem Sinne, dass der Schaltnocken betätigt wird und dann das Rampe herunterfahren beginnt und deine Linear-Achse dann an der richtigen Position steht wenn der Motor Drehzahl null erreicht hat. Nach dem herunterfahren der Rampe.

Man kann das Automatisieren in dem du nach dem Verstellen der Schaltnocken eine Referenzfahrt machst die alle Schaltnocken einbezieht. Ob das eine sinnvolle Variante ist hängt davon ab wie deine Abläufe insgesamt sind.

Dazu müsstest du mal einen Überblick über dein Projekt geben.
Wenn du den Überblick gegeben hast, dann ist die Wahrscheinlichkeit für andere, noch besser funktionierende Lösungen ziemlich hoch.
Wissen kann man das erst wenn du den Überblick gegeben hast.
vgs

Ja der Motor soll exakt auf dem Schaltnocken stehen bleiben. Der einfachheit halber anfangs auch ohne rampenbetrieb vom motor.

Der schaltnocken kann ich aber später auch in form einer schaltrampe umkonstruieren so das wenn der geschaltet wird, der motor mittels rampe bremst und dann in der mitte der schaltrampe stehen bleibt. positioniergenauigkeit solte ca 1mm sein was ich denke einfach gehen sollte.

zur Projektübersicht.

es handelt sich um eine 3,5m lange arbeitsfläche mit ca 9 positionen. die positionen sind wie schon erwähnt durch die mechanische verstellung der schaltnocken schnell anpassbar. durch die taster links und rechts kann die nächste oder vorgehende arbeitsposition angefahren werden. am jeweiligen ende wie schon erwähnt muss die richtungstaste welche zur überfahrt der achse führen würde, blockiert werden.

Die Achse wird mittels eines Schrittmotores (VEXTA PH299-E4) angetrieben. Der ist grösse NEMA 34 und hat genug power, wird mit 24V und 4 A betrieben.

Ein referenzfahren möchte ich bewusst nicht einbauen da das system stromlos sein sollte und bei betrieb nach einschalten möglichst schnell einsatzbereit sein sollte, ohne Ref. fahrt.

danke aber für deine guten inputs

Dann beschränkt sich die Betriebsart auf:
Wenn Endschalter nicht betätigt fahre link/rechts bis Schaltnocke betätigt wird.
Wenn Schaltnocke betätigt wird halte an.

Wenn das später auf mit Rampe umgestellt wird, dann würde ich zwei Schaltnocken einsetzen
oder einen Schaltnocken mit einer Rampe so dass zwei Schalter nacheinander geschaltet werden.

Der erste Schalter initiiert die Bremsrampe um so weit herunterzubremsen, dass mit dem Schalten des zweiten Schalters abrupt gestoppt werden kann.

Oder ein einzelner Schaltnocken wird so benutzt:
Wenn der Schalter betätigt wurde bremse herunter was zu einem Überfahren des Schaltnockens führt. Schalter ist nicht mehr betätigt. Dann mit kleiner Geschwindigkeit zurückfahren bis der Schalter wieder geschaltet wird.
Wegen der kleineren Geschwindigkeit beim Zurückfahren kann man dann abrupt anhalten.

Man würde dann von den Fähigkeiten einer Schrittmotorsteuerung nur die Fähigkeit
halte abrupt an und halte diese Position nutzen.

vgs

Dann beschränkt sich die Betriebsart auf:
Wenn Endschalter nicht betätigt fahre link/rechts bis Schaltnocke betätigt wird.
Wenn Schaltnocke betätigt wird halte an.

Genau

Das mit den zwei schaltnocken ist eine gute idee und ist für mich kein problem um mechanisch umzusetzen. dann würde zum schaltnocken einfach eine rampe dazukommen wo die geschwindigkeit gedrosselt würde.

Die überfahr variante wäre möglich, aber meiner meinung nach nicht so das wonach ich suche.

Leider habe ich an einigen orten gelesen das accelstepper nicht geeignet sei für kontinuierliche bewegung.
empfohlen wird continuousstepper

https://github.com/bblanchon/ArduinoContinuousStepper

so wie ich das verstehe sollte das auch eine rampen funktion beinhalten (für später)

ich habe nun mein angefangenes design mit accelstepper verworfen und beginne neu mit continuousstepper...

Ich denke, das dann Dein Design schon falsch war.
Normalerweise wäre alles wiederzuverwenden, nur eben für den Antrieb die Funktion zu ändern.

Ich hatte heute vormittag mal angefangen und gehofft, Deinen loop() zu sehen.
Willst Du sehen, wie ich das gehandhabt hätte? Dann tippe ich noch was halbwegs fertig.

okay, ich tue mir schwer. was ich bis jetzt hingekriegt habe, jedoch mittels neu programmieren.
Bin trotzdem mal auf accelstepper geblieben.

arduino startet, led grün leuchtet.
sobald ich taster drücke (habe mal nur einen eingebaut auf pin 2) geht er auf rot und motor dreht sich um 100 schritte.

anschliessend wieder auf grün und wartet neu auf eingabe.
die einzelnen funktionen habe ich z.t. jedoch an den verknüpfungen habe ich probleme mit dem verständniss.

Wie ich es genau lösen sollte komm ich gerade nicht weiter. meine idee ist immer eine kleine schrittweite zu fahren und den zwischenschaltnocken zu überprüfen. dies solange loopen bis ein eingang anliegt ?!? kein plan.

bin offen für ideen die ich evtl begreife.

#include <AccelStepper.h>

AccelStepper stepper(1, 10, 9); // 1 = Driver Mode, Step = pin 4, Direction = pin 3

int ledGreen = 12; // Set Green LED Pin 12

int ledRed = 11; // Set Red LED Pin 11

void setup()

{

stepper.setMaxSpeed(20000.0);// Set the motor speed (RPMs):

stepper.setAcceleration(20000.0);// Set the motor acceleration (RPMs):

const byte buttonPin = 2; // Set Button Pin 2

pinMode(buttonPin, INPUT_PULLUP); // Define Button Pin 2

pinMode(ledGreen, OUTPUT); // Set Pinmode LEDGreen

pinMode(ledRed, OUTPUT); // Set Pinmode LEDRed

}

void loop() {

digitalWrite(ledGreen, HIGH);

digitalWrite(ledRed, LOW);

stepper.setCurrentPosition(0);

delay(500);

const byte buttonPin = 2;

while (digitalRead(buttonPin) == LOW);

digitalWrite(ledGreen, LOW);

digitalWrite(ledRed, HIGH);

delay(500);

stepper.runToNewPosition(100);

delay(500);

}

Die Idee ist zielführend.
Ich habe das auch so als Grundgerüst vorgesehen.
Wenn das Ganze ohne blockieren geschrieben wird, kannst Du mit jedem Umlauf auf alle Button reagieren.

Ich habe die Button komplett ohne Libs gemacht und werd mal noch ein paar Kommentare schreiben. Das sieht dann ein wenig anders aus, als das wa Du hast, aber vielleicht hilft es dann...

Bei mir etwas anders.
Ich habe mich an Deine Pinbelegung gehalten, wenn ich nichts übersehen habe.
Es ist (noch) kein Motor integriert. Nur die Funktion zur Ansteuerung.
Dafür sollen auf dem seriellen Monitor entsprechende Ausgaben zu sehen sein.

Die Button sollen entsprechend auslösen.
Die LED's müssten richtig sein.
Da ich weder einen arduino noch einen Antrieb habe, ist das auf gut Glück gemacht.
Die Button schalten nach GND.
Wenn die Tasten nicht oder nicht richtig auslösen, dann habe ich irgendwas übersehen.

// Forensketch Antrieb steuern mit Wiederanfahrmöglichkeit
// https://forum.arduino.cc/t/linearachse-per-schrittmotor-mit-zwischen-positionen-steuern/1052842
/*
   Was der Sketch machen soll:
   Nur die Button abfragen
   Ausgeben, was aufgrund des jeweils ausgelösten Button passieren soll
   Es ist noch kein Motor integriert

   ungetestet - kompiliert fehlerfrei.
*/

#include <Streaming.h>     // https://github.com/janelia-arduino/Streaming

//#define DEBUG            // wenn einkommentiert gibts zusätzliche Ausgaben
#ifdef DEBUG
#define DBG_PRINTLN(...) Serial << __VA_ARGS__ << endl
#else
#define DBG_PRINTLN(...)
#endif

enum richtung {links, rechts, halt = 255};     // Richtungen für Auswahl und Antrieb
uint8_t driveDir = richtung::halt;             // Vorbelegung
enum farbe {rot, gruen};                       // für LED
constexpr uint8_t dirNums = 2;                 // Es gibt nur zwei Bewegungsrichtungen
constexpr uint8_t camNums = 1;                 // Anzahl Nockenschalter
constexpr uint8_t ledNums = 2;                 // ROT / GRUEN
constexpr uint8_t endSwitch[dirNums] = {2, 4}; // PIN(Endschalter): LINKS, RECHTS
constexpr uint8_t camSwitch[camNums] = {3};    // PIN(Nocken):
constexpr uint8_t dirSwitch[dirNums] = {7, 8}; // PIN(Taster): LINKS, RECHTS
constexpr uint8_t ledPin[ledNums] = {6, 5};    // PIN(Led): ROT, GRUEN
constexpr uint32_t bounceTime = 40;            // zum entprellen

uint32_t lastEndTime[dirNums] = {0};           // Merker letzte Auslösezeiten
uint32_t lastDirTime[dirNums] = {0};
uint32_t lastCamTime[camNums] = {0};

bool isEndSwitch[dirNums] = {false, false};    // Merker Buttonzustände
bool isCamSwitch[dirNums] = {false, false};
bool isDirSwitch[dirNums] = {false, false};
bool lockDirSwitch[dirNums] = {false, false};  // Merker Sperre für Richtungstaster
bool lockCamSwitch = false;                    // Merker für ausgelösten Nockenschalter (Es braucht nur einen)

void setup()
{
  Serial.begin(115200);
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2650)) // https://github.com/arduino/Arduino/issues/10764
  delay(300);
#endif
  Serial << (F("\r\nStart...\r\n")) << endl;
  DBG_PRINTLN(__FILE__);
  DBG_PRINTLN( __TIMESTAMP__);
  for (byte b = 0; b < dirNums; b++)
  {
    pinMode(endSwitch[b], INPUT_PULLUP);
    pinMode(dirSwitch[b], INPUT_PULLUP);
  }
  for (byte b = 0; b < camNums; b++)
  { pinMode(camSwitch[b], INPUT_PULLUP); }
  for (byte b = 0; b < ledNums; b++)
  { pinMode(ledPin[b], OUTPUT); }
}

void loop()
{
  readEndSwitch();
  readDirSwitch();
  readCamSwitch();
  driveLogic();
  actor(driveDir);
  ampel(driveDir);
  debugAusgabe(driveDir);
}


void driveLogic()
{
  byte gegenRichtung = richtung::rechts;         // Vorbelegung
  for (byte b = 0; b < dirNums; b++)             // Abruf der Richtungen
  {
    if (b == gegenRichtung)                      // Wenn ...
      gegenRichtung = richtung::links;           // ... Änderung der Gegenrichtung
    if (isDirSwitch[b])                          // Taste (Richtung) gedrückt? ...
    {
      lockDirSwitch[gegenRichtung] = true;       // ... Tastensperre (gegenrichtung) setzen ...
      if (isEndSwitch[b])                        // ... Endschalter (Richtung) ausgelöst? ...
      {
        driveDir = richtung::halt;               // ... Merker setzen ...
        DBG_PRINTLN (F("Endschalter ") << b << F("angefahren"));
        return;                                  // ... und Schluß hier
      }
      if (!lockDirSwitch[b])                     // ... Taste (Richtung) nicht gesperrt? ...
      {
        driveDir = b;                            // ... Richtung vorbelegen ...
        bool cam = false;                        // ... Merker vorbereiten
        for (byte c = 0; c < camNums; c++)       // ... Nocken abfragen ...
        {
          if (isCamSwitch[b])                    // ... (irgendein) Nocken ist ausgelöst? ...
            cam = true;                          // ... Merker setzen
        }
        if (cam)                                 // (irgendein) Nocken hat ausgelöst? ...
        {
          if (!lockCamSwitch)                    // ... Nockensperre nicht gesetzt? ...
          {
            lockDirSwitch[b] = true;             // ... Taste (Richtung) sperren ...
            lockCamSwitch = true;                // ... Nockensperre setzen ...
            driveDir = richtung::halt;           // ...
            DBG_PRINTLN(F("Nockenschalter") << b << F("ausgelöst"));
          }
        }
        else                                     // Kein Nocken ausgelöst? ...
          lockCamSwitch = false;                 // ... Nockensperre löschen
      }
      else
        lockDirSwitch[b] = false;
    }
  }
}

void readCamSwitch()
{
  for (byte b = 0; b < camNums; b++)
  {
    readSwitch(camSwitch[b], lastCamTime[b], isCamSwitch[b]);
  }
}

void readDirSwitch()
{
  for (byte b = 0; b < dirNums; b++)
  {
    readSwitch(dirSwitch[b], lastDirTime[b], isDirSwitch[b]);
  }
}

void readEndSwitch()
{
  for (byte b = 0; b < dirNums; b++)
  {
    readSwitch(camSwitch[b], lastCamTime[b], isCamSwitch[b]);
  }
}

void readSwitch(const byte pin, uint32_t &lastTime, bool &buttonState)
{
  if (!digitalRead(pin))                      // Ausgelöst?
  {
    lastTime = millis();                      // Auslösezeit merken
    buttonState = true;                       // Status setzen
  }
  else if (millis() - lastTime > bounceTime)  // Nicht ausgelöst und Prellzeit abgelaufen
  {
    buttonState = false;                      // Status zurücksetzen
  }
}

void actor(const byte dir)                    // In diese Funktion kommt später die Ansteuerung des Antrieb!
{
  switch (dir)
  {
    case richtung::links:
      break;
    case richtung::rechts:
      break;
    case richtung::halt:
      break;
  }
}

void ampel(const byte dir)
{
  dir == richtung::halt ?                      // Nicht in Bewegung? ...
  digitalWrite(ledPin[farbe::gruen], HIGH) :   // ... dann gruen an
  digitalWrite(ledPin[farbe::gruen], LOW);     // ... sonst gruen aus
  digitalWrite(ledPin[farbe::rot], !digitalRead(ledPin[farbe::gruen])); // rot ist negiert gruen
}

void debugAusgabe(const byte dir)
{
  static byte lastDir = richtung::halt;
  if (dir == lastDir)
    return;
  Serial << F("Antrieb -> ");
  switch (dir)
  {
    case richtung::links:
      Serial << F("links");
      break;
    case richtung::rechts:
      Serial << F("rechts");
      break;
    case richtung::halt:
      Serial << F("HALT");
      break;
  }
  Serial.println();
}

Du hast doch gar keine kontinuierliche Bewegung im Sinne das der Motor stundenlang in eine Richtung drehen würde. Nach spätestens 3,5m kommt der Endhalt.
Wegen der dazwischenliegenden Schaltnocken ist die Bewegung noch früher zu Ende.
Dauerbewegung heißt mehr als 2 Milliarden Schrittimpulse. Das ist die Anzahl die eine signed long integer-variable maximal speichern kann.
Du kannst im Prinzip bei jedem Halt den Nullpunkt neu setzen.

Continious stepper ist was für Uhren die Jahrelang immer in die gleiche Richtung drehen oder für Maschinen die 16 Stunden am Stück bei hoher Drehzahl und Mikroschrittauflösung laufen.

vgs

...krass

sieht ja, etwas anders aus :slight_smile: Ich denke meine nicht funktionierende primitive ruckelvariante hat nicht ganz so viele "Features"

-Hab die Libs bei mir noch angepasst, streaming.h hatte ich noch nicht.
-den serial monitor habe ich noch auf 9600 angepasst und die arduino mega definition deaktiviert da ich einen uno verwende.
-serial monitor sieht nun gut aus und ich sehe was -> geht auf Start.
-taste links und rechts startet den antrieb in jeweilige richtung
-led springt von grün auf rot.
-endswitches oder Nockenschalter sprechen leider noch nicht an.

ich lese mich jetzt da mal ein da ich viele Funktionen noch nicht mal kenne und die logik einigermassen verstehen möchte. für mich sehr komplex