Mehrere Servos synchron ansteuern

Du mußt die Servos nicht gleichzeitig sondern kontrolliert steuern.
Das nennt man Kinematik.
Für Hexapods braucht man das auch.
Grüße Uwe

Hätte jetzt eine Variante bei der man den Servos einfach eine Durchführungszeit übergibt, und die Servos teilen sich dann die Zeit selber auf.

Schaut dann optisch so aus als würde die Bewegung zu gleichen Zeit geschehen ... tut es natürlich nicht.

/*
   move two servos combined
   https://forum.arduino.cc/t/mehrere-servos-synchron-ansteuern/1030870/20

   dynamic

   by noiasca
   2022-09-12
*/

#include <Servo.h>

constexpr uint8_t openPin = 4;     // GPIO for a movement
constexpr uint8_t closePin = 3;    // GPIO for another movement
constexpr uint8_t servoAPin = 8;   // GPIO for Servo A
constexpr uint8_t servoBPin = 9;   // GPIO for Servo B

// make your own class for two servos
class SmoothServo
{
  protected:
    uint16_t target {90};              // target angle
    uint16_t current {90};             // current angle
    uint8_t interval {1};              // delay time
    uint32_t previousMillis {0};       // last movement
    uint16_t targetTime {500};         // how long as the servo time for the movement
    uint32_t previousMillisStart{0};   // last start with set

  public:
    Servo servo;

    void begin(const byte pin)
    {
      servo.attach(pin);
      servo.write(target);   // bring the servo to a defined angle
    }

    void set(uint16_t target, int16_t targetTime = 500)
    {
      this->target = target;
      this->targetTime = targetTime;
      previousMillisStart = millis();
    }

    void update(uint32_t currentMillis = millis())
    {
      if (currentMillis - previousMillis > interval)  // slow down the servos
      {
        previousMillis = currentMillis;
        if (target != current)
        {
          uint32_t passedTime = currentMillis - previousMillisStart;
          if (passedTime < targetTime)
          {
            uint32_t remainingTime = targetTime - passedTime;
            int diff = target - current;
            diff = abs(diff);
            interval = remainingTime / diff;
          }
          else
          {
            interval = 1;
          }
          if (target < current)
          {
            current = current - 1;
          }
          else if (target > current)
          {
            current = current + 1;
          }
          servo.write(current);
        }
      }

    }
};

SmoothServo smoothServoA;  // create a servo object
SmoothServo smoothServoB;

void setup()
{
  Serial.begin(115200);
  smoothServoA.begin(servoAPin);          // start the servo object
  smoothServoB.begin(servoBPin);
  pinMode(openPin, INPUT_PULLUP);
  pinMode(closePin, INPUT_PULLUP);
}

void doorOpen()
{
  Serial.println(F("move"));
  smoothServoA.set(0);
  smoothServoB.set(45);
  //groupA.servoA.write(90); // hardcoded write
}

void doorClose()
{
  Serial.println(F("move in a different speed"));
  smoothServoA.set(180, 2000);    // angle, milliseconds for movement
  smoothServoB.set(135, 2000);
}

void loop()
{
  // read buttons, sensors...
  if (digitalRead(openPin) == LOW) doorOpen();
  if (digitalRead(closePin) == LOW) doorClose();

  // call all servos
  uint32_t currentMillis = millis();
  smoothServoA.update(currentMillis);  // call the update method in loop
  smoothServoB.update(currentMillis);
}

Hallo combie

Vielen Dank für die Ohrfeige :see_no_evil: . Ich habe in der Tat nicht das Datenblatt "durchgelesen" bzw. übersetzt, hätte ich tun sollen. Man sucht bekanntlich nach dem einfachsten Weg der da wäre; Tante G fragen, Videos und Tutorials schauen bzw. lesen und alles was in englischer Sprache irgendwo steht schlicht zu ignorieren.

Jedenfalls danke ich Dir für den Hinweis, auch wenn ich bisher keinen Sketch gefunden habe in dem das Beschriebene umgesetzt wurde ("ermöglichen die gleichzeitige Adressierung von Gerätegruppen in beliebigen Kombinationen")

Vielleicht kannst Du mir noch ein Beispiel für eine Mehrfachadressierung zukommen lassen, dafür wäre ich Dir sehr dankbar. (Und nein, eine Ohrfeige reicht völlig aus :rofl: )

Also nochmals danke!

Hallo noiasca!

Vielen herzliche Dank für Deinen Sketch! Der Ansatz - das die Servos sich die Zeit "selber" aufteilen - finde ich perfekt. Das bedeutet, wenn ich das richtig verstanden habe, das ich unabhängig wie weit sich ein Servo bei einer gewünschten Zielposition bewegen muss, sich der andere Servo der "zeitgleich" einen anderen, z.B. längeren Weg hätte, die Taktung so einteilen das beide Servos "zeitgleich" ihre Zielposition erreichen, unabhängig ihres Weges, ja ??

Diese Ansatz ist echt ungaublich wertvoll da ich dadurch eine synchrone Bewegung bekommen kann z.B. wenn beide Arme angehoben werden sollen. Sehr wertvoller Gedanke.

Wenn ich mir Deinen Sketch so anschaue, so ist er doch sehr komplex und ich sehe großes Code-Wirrwar auf mich zukommen :see_no_evil: . Aber der Gedanke dahinter ist echt super. Ich denke wenn man die Ist-Position der Servos ausliest, die Differnzen zur Soll-Position ermittelt und dann die Schrittweite zum Zielpunkt berechnet (also mit Deinem Ansatz die Schritte im richtigen Verhältnis zueinander) berechnen lässt, sollte das Ganze so funktionieren.

Also auch Dir herzlichen Dank für Deinen Lösungsansatz und den Sketch!

Ja. siehst im Simulator. Der Servo mit dem kurzem Weg braucht für die Bewegung gleich lange wie der andere Servo. Zwischendurch hatte ich auch eine Version mit einer Klasse und zwei Servos, aber das hat mich dann zu sehr auf "zwei Servos" eingeschränkt. So wie es jetzt ist klappts auch für einen, zwei, drei oder mehr Servos, einfach hintereinander mit der gleichen Stellzeit starten.
Ein weiterer "Vorteil" ergibt sich aus meiner Sicht: man kann jeder Bewegung mitgeben wie schnell sie ablaufen soll. Ich finde von der Parametrierung her ist das sehr übersichtlich. entweder nur der Winkel - dann soll das alles in 500ms erledigt sein, oder wenn das zu schnell ist, halt schreiben wie lange es dauern soll.

na hoffentlich nicht. Es gibt eine Klasse die dafür sorgt dass ein Servo sich in einer zu übergebenen Zeit sich einen übergebenen Winkel bewegt. Das wird nicht mehr.
Weitere Servos sind nur mehr eine Variable für den Pin, das Objekt anlegen, im Setup eine Zeile und in den jeweiligen Funktionen eine Zeile mehr.

ich schlage vor, du schaust dir alle 3 Beispiele noch mal an, beginnend mit der verlinkten HP, dann den ersten Woki Link, dann den #22.

Das war ein "Wink mit einer Latte", an der ein ganzer Zaun hängt und noch ein paar Hütten

Ich finde auch deine Rechtfertigung recht witzig, mit der du begründest, dass ich doch bitte deine Arbeit machen soll. Dabei hilft dir das überhaupt nicht, wenn ich verstehen lerne, wie man das zu tun hat. Bist es doch du, der es zum Einsatz bringen will.

Ich würde mal ganz stark vermuten, dass im Datenblatt bis ins Detail vorgekaut wird, wie man das tut. Aus meiner pragmatischen/naiven Sicht, wäre der nächste Schritt, das mal genau so auszuprobieren!

Natürlich darfst du dann daraus eine Library machen, mit ausführlichen Beispielen, und dann hier vorstellen.
Die schaue ich mir gerne an.

Hallo Tommy56.

Auch Dir danke ich für Deinen Beitrag.
Du hast natürlich Recht, nur ist die Verwendung der richtigen Bibliothek im Augenblick nicht so sehr von Relevanz. Ich denke die Adressierung inklusive der entsprechenden Bibliotheken ist bei allen Arduinos ähnlich und final werden mehrere Boards zum Einsatz kommen. Schlussendlich wird es darauf hinaus laufen das mehrere ESP32 ihren Dienst tun (jeder mit eigener "Zuständigkeit" für bestimmte Regionen) die zentral über den "Haupt"rechner angesteuert werden. Du kannst dir das so vorstellen das der ESP32 für den Arm alle Bewegungen (und die damit verbundenen Positionsberechnungen) gespeichert hat und auf einen Zentralbefehl, zum Beispiel "Arm heben" den entsprechenden Loop ausführt. Das minimiert die Belastung für den "Hauptrechner". Der gesamte Programm-Ablauf wird eine sehr große Abfrageschleife (hören, sehen, eigene Position, Position der Körperteile, Position der Körperteile zueinander ... usw. ) werden und aus diesem Grund werden die Aufgaben geteilt werden müssen.

Aber auch Dir herzlichen Dank für Deinen Beitrag!

Du hast schon Recht wenn Du sagst dass der Lernerfolg ausbliebe wenn Du meine Arbeit machen sollst. So war das auch nicht gedacht. Aber wie Du vielleicht anhand der anderen Beiträge/Beispiele siehst, ist keine wirkliche synchrone Bewegung realisiert sondern eine in einem Loop verpackte schrittweise und abwechselnde Ansteuerung der Servos.

Aber ich werde mir natürlich zu Herzen nehmen was Du mir damit sagen möchtest und werde mir das Datenblatt genauer anschauen. Vielen Dank für Deinen Beitrag!

Hallo derspitz,
schau doch mal nach der ServoEasing lib. von Armin Joachimsmeyer. Die sollte genau das tun was du suchst.
Langsames Anlaufen und Abbremsen von Bewegungen, gleichzeitigen oder zeitversetztes ansteuern von Servos, Geschwindigkeit regulieren u.s.w.
Ich nutze diese lib. fuer Animaronik und finde es einfach nur klasse, was man damit machen kann.

Hallo Ninian,

auch Dir danke ich herzlich für Deinen Beitrag. Im Augenblick muss ich allerdings das Projekt etwas zurücksetzen da ich beruflich bedingt sehr wenig Zeit habe. Ich werde mir diese Bibliothek anschauen denn wenn Du Dich mit Animatronik befasst, gehe ich stark davon aus dass Du ähnliche Anforderungen nutzt wie ich sie brauche.

Viel Glück bei Deinen Vorhaben und nochmals danke!

Das ganze sieht sehr sehr gut aus, wenn mir jetzt noch jemand erklären könnte wie ich das ganze mit HC-SR04 Ultrasonic verwirklichen kann wäre ich sehr dankbar :slight_smile:

was genau soll der HC auslösen?

das bei anhäherung die servos in die eine und bei entfernung in die andere richtung drehen

Sozusagen anstelle der Buttons :wink:

na dann schließ mal den HC an und bring ihn dazu die aktuelle Entfernung auf der Seriellen auszugeben.

Den HC habe ich schon in einer Separaten Schaltung mal zum laufen gebracht ....
Hintergrund der Sache, es soll ein "Modell Tor" geöffnet und geschlossen werden.

Der hc soll erkennen ob sich das Modell im inneren der Garage befindet oder nicht, und somit das Tor schließt wenn das Modell in inneren ist.

Hier ist mal der code wie es jetzt ist .....

imho braucht es dann mehr Logik.

  • Nur wenn offen ist, soll auf das einfahrende Modell reagiert werden.
  • Ich würde noch etwas warten bis das Tor wirklich schließt.
  • Man braucht einen manuellen Öffnen Button um das Tor aufzumachen.

Dafür eignet sich eine finite state machine.

/*
   move two servos combined


   based on:
   https://forum.arduino.cc/t/mehrere-servos-synchron-ansteuern/1030870/50

   Variant with a HC ultrasonic Sensor as external Trigger
   for delayed CLOSE and
   imideately OPEN

   by noiasca
   2023-02-18
*/

#include <Servo.h>

constexpr uint8_t trigPin = 23;  // ESP32 pin GIOP23 connected to Ultrasonic Sensor's TRIG pin
constexpr uint8_t echoPin = 22;  // ESP32 pin GIOP22 connected to Ultrasonic Sensor's ECHO pin
constexpr uint8_t distanceThreshold = 50; // centimeters

constexpr uint8_t openPin = 12;     // GPIO for a movement
constexpr uint8_t closePin = 14;    // GPIO for another movement
constexpr uint8_t servoAPin = 26;   // GPIO for Servo A
constexpr uint8_t servoBPin = 25;   // GPIO for Servo B

constexpr uint8_t ledRPin = 21;
constexpr uint8_t ledYPin = 19;
constexpr uint8_t ledGPin = 18;

// make your own class for two servos
class SmoothServo {
  protected:
    uint16_t target {90};              // target angle
    uint16_t current {90};             // current angle
    uint8_t interval {1};              // delay time
    uint32_t previousMillis {0};       // last movement
    uint16_t targetTime {500};         // how long has the servo time for the movement
    uint32_t previousMillisStart{0};   // last start with set

  public:
    Servo servo;

    void begin(const byte pin) {
      servo.attach(pin);
      servo.write(target);   // bring the servo to a defined angle
    }

    void set(uint16_t target, uint16_t targetTime = 500) {
      this->target = target;
      this->targetTime = targetTime;
      previousMillisStart = millis();
    }

    void update(uint32_t currentMillis = millis()) {
      if (currentMillis - previousMillis > interval) { // slow down the servos
        previousMillis = currentMillis;
        if (target != current) {
          uint32_t passedTime = currentMillis - previousMillisStart;
          if (passedTime < targetTime) {
            uint32_t remainingTime = targetTime - passedTime;
            int diff = target - current;
            diff = abs(diff);
            interval = remainingTime / diff;
          }
          else {
            interval = 0;
            // could also be used to force the servo in the target position
          }
          if (target < current) {
            current = current - 1;
          }
          else if (target > current) {
            current = current + 1;
          }
          servo.write(current);
        }
      }
    }
};

SmoothServo smoothServoA;  // create a servo object
SmoothServo smoothServoB;

void setup() {
  Serial.begin(115200);
  pinMode(trigPin, OUTPUT); // set ESP32 pin to output mode
  pinMode(echoPin, INPUT);  // set ESP32 pin to input mode
  smoothServoA.begin(servoAPin);          // start the servo object
  smoothServoB.begin(servoBPin);
  pinMode(openPin, INPUT_PULLUP);
  pinMode(closePin, INPUT_PULLUP);

  pinMode(ledRPin, OUTPUT);
  pinMode(ledYPin, OUTPUT);
  pinMode(ledGPin, OUTPUT);
}

void red() {
  digitalWrite(ledRPin, HIGH);
  digitalWrite(ledYPin, LOW);
  digitalWrite(ledGPin, LOW);
}

void yellow() {
  digitalWrite(ledRPin, LOW);
  digitalWrite(ledYPin, HIGH);
  digitalWrite(ledGPin, LOW);
}

void green() {
  digitalWrite(ledRPin, LOW);
  digitalWrite(ledYPin, LOW);
  digitalWrite(ledGPin, HIGH);
}

void doorOpen() {
  Serial.println(F("move open"));
  smoothServoA.set(90, 1000);
  smoothServoB.set(90, 1000);
  //smoothServoA.servo.write(90); // hardcoded write
}

void doorClose() {
  Serial.println(F("move close in a different speed"));
  smoothServoA.set(180, 500);    // angle, milliseconds for movement
  smoothServoB.set(115, 500);
}

float getDistance() {
  // variables will change:
  float duration_us, distance_cm;
  // generate 10-microsecond pulse to TRIG pin
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  // measure duration of pulse from ECHO pin
  duration_us = pulseIn(echoPin, HIGH);
  // calculate the distance
  distance_cm = 0.017 * duration_us;

  // read buttons, sensors...
  if (digitalRead(openPin) == LOW) doorOpen();
  if (digitalRead(closePin) == LOW) doorClose();

  // call all servos
  uint32_t currentMillis = millis();
  smoothServoA.update(currentMillis);  // call the update method in loop
  smoothServoB.update(currentMillis);

  // print the value to Serial Monitor
  Serial.print("distance: ");
  Serial.print(distance_cm);
  Serial.println(" cm");
  return distance_cm;
}

// not used - just as an example how to avoid a dirty delay
void handleHC() {
  static uint32_t previousMillis = 0;
  if (millis() - previousMillis > 500) {
    previousMillis = millis();
    float distance = getDistance();
  }
}

// read buttons, sensors... within a finite state machine
void fsm() {
  enum State {OPEN, TRIGGER, CLOSE};
  static State state;
  static uint32_t previousMillis = 0;

  switch (state) {
    case State::OPEN:
      if (millis() - previousMillis > 500) {
        previousMillis = millis();
        float distance = getDistance();
        if (distance < distanceThreshold) {
          state = TRIGGER;
          yellow();
          Serial.println(F("door will close in short time"));
        }
      }
      if (digitalRead(closePin) == LOW) {
        doorClose();
        red();
        Serial.println(F("door will close imidiately"));
        state = CLOSE;
      }
      break;
    case State::TRIGGER:
      if (millis() - previousMillis > 1000) {
        previousMillis = millis();
        state = CLOSE;
        Serial.println(F("door closing"));
        doorClose();
        red();
      }
      break;
    case State::CLOSE:
      if (millis() - previousMillis > 500) {
        previousMillis = millis();
        float distance = getDistance();
        if (distance > distanceThreshold) {
          state = OPEN;
          doorOpen();
          green();
          Serial.println(F("door will open now"));
        }
      }
      if (digitalRead(openPin) == LOW) {
        Serial.println(F("door will open"));
        state = OPEN;
        doorOpen();
        green();
      }
      break;
  }
}

void loop() {
  fsm();
  // call all servos
  uint32_t currentMillis = millis();
  smoothServoA.update(currentMillis);  // call the update method in loop
  smoothServoB.update(currentMillis);
}

natürlich könnte man das noch schöner machen, und phasen für "OPENING" und "CLOSING" einführen, damit man während der Torbewegung z.B. die Taster als Nothalt verwenden kann oder nachdem der Trigger ausgelöst hat eine LED Blinken lassen oder ähnliches.

Aber das Prinzip soll klar sein.

Wow mega.... Vielem lieben dank.

Aber es soll natürlich auch darauf reagiert werde wenn das Modell in der Garage ist.... also wenn distance grösser ist als 50 = Tor auf.

die Idee von dir mit dem warten bis das tor schließt finde ich sehr gut :slight_smile:
(Die buttons sind sozusagen nur für den notfall das der hc nicht richtig reagiert )

ich find das absurd, weil das Auto kann die Garage nicht verlassen wenn das Tor zu ist. *)
Aber mach halt.

*) Du kennst sicher den Witz mit Kühlschrank und Elefant.

Deswegen geht das Tor ja hoffentlich auf, wenn das Auto losfährt.

Das finde ich auch gut. Wenn das Auto durch die geschlossene Tür kommt, braucht sie nicht mehr geöffnet oder geschlossen zu werden.
:slight_smile:

Im Grunde ist hier das Tor immer auf, außer etwas löst innen einen TorZu -Befehl aus (Abstand < 50)