Enviolo (Nuvinci) + Arduino für Rollstuhl

//Waage
#include "HX711.h"
HX711 scale;
uint8_t dataPin = 6;
uint8_t clockPin = 7;
//Motor
const int SensorPin = 2;  //Interrupt Pin (nur 2 oder 3 @ Arduino Uno)
const byte Brake = 8;//Pinausgang Bremse, HIGH = Vollbremsung
const byte PWM = 9;// Pinausgang für Drehzahl
byte power = 200;// Motor-Drehzahl-Normalbetrieb
const byte Drehrichtung = 10;// Pin Drehrichtungswechsel LOW/HIGH Drehrichtung
unsigned long FlagMillis;
unsigned int Counter;
bool setupStep = LOW;
bool Normalbetrieb = LOW;
String Programmposition;

void motor(bool Stop, bool Richtung, int pwmSignal) {
  digitalWrite (Brake, Stop);
  digitalWrite (Drehrichtung, Richtung);
  analogWrite (PWM, pwmSignal);
}
void M_Stop() {
  motor(HIGH, LOW, 0);
}
void M_Hoch() {
  motor(LOW, HIGH, power);
}
void M_Runter() {
  motor(LOW, LOW, power);
}
void count () {
  Counter++;
}
void setup() {
  Serial.begin(9600);
  //Waage
  scale.begin(dataPin, clockPin);
  scale.set_scale(127.15);
  scale.tare();
  pinMode(dataPin, INPUT);
  pinMode(clockPin , OUTPUT);

  //Motor
  pinMode(SensorPin, INPUT); //definiertes Potenzial (HIGH/LOW) von einem Hall-Sensor
  pinMode(Brake , OUTPUT);
  pinMode(PWM , OUTPUT);
  pinMode(Drehrichtung , OUTPUT);//Drehrichung
}
void loop() {
  motorSetup();
  motorMatik();
  Serial.print("\t Programmposition: ");
  Serial.print(Programmposition);
  Serial.print("\t Counts: ");
  Serial.print(Counter);
  Serial.print("\t setupStep: ");
  Serial.println(setupStep);
}
void motorSetup() { //MIT WÄGEZELLE WIRD DIESE ANWEISUNG KOMPLETT ÜBERSPRUNGEN
  if (!setupStep) {  //Variable um den Setupvorgang vom Normalbetrieb zu trennen
    Counter = 0;
    attachInterrupt(digitalPinToInterrupt(SensorPin), count, CHANGE);
    delay(500);//kann delay sein, weil motorSetup nur einmal und ohne Wägezelle durchlaufen wird
    detachInterrupt(digitalPinToInterrupt(SensorPin));//Stop zur Ermittlung der Drehzahl über 500ms
    motor(LOW, LOW, 30); //Kriechgang (30) zum Anfahren der Endposition (Anschlag)
    Programmposition = 1;
  }
  static  unsigned long SetupMillis = 0;
  if ((Counter < 10) && ((millis() - SetupMillis >= 1000))) {// 1s um den Anlaufprozess zu überbrücken
    M_Stop (); // Drehzahl sinkt am Anschlag unter (10) , der Motor stopt
    setupStep = HIGH;
    Counter = 1000;// Counter wir af 1000 gesetzt um ein "Überfahren" in den negativen Bereich zu verhindern
    Programmposition = 2;
  }
  // Der Zähler wird auf Positionsmods umgestellt, absolute Werte werden gespeichert
  if (setupStep && !Normalbetrieb) {
    attachInterrupt(digitalPinToInterrupt(SensorPin), count, CHANGE);
    motor (LOW, HIGH, 120); //Richtungswechsel nach Anschlag
    if (Counter >= 1100) {//Fahrt auf Ausgangsposition (z.B X + 100)
      M_Stop ();//Setup-Phase ist beendet, Motor steht in Ausgangsposition
      Normalbetrieb = HIGH;//Umschalten auf Normalbetrieb
      Programmposition = 3;
    }
  }
}
void motorMatik () {//muss noch erweitert werden, die Grundfunktion ist ok
  if (Normalbetrieb) {
    attachInterrupt(digitalPinToInterrupt(SensorPin), count, CHANGE);
        float wz = scale.get_units();
    long druck[] = {2000, 3000, 4000, 6000};
    long positionM[] = {1000, 12000, 1600, 1800, 1900};
        if (wz > druck[0] && wz < druck[1] && Counter < positionM[1]) {
    M_Runter();
    Programmposition = 4;
  }
      if (Counter >= positionM[2]) {
  M_Stop();
  Programmposition = 5;
}
    Serial.print("\t UNIT wz: ");
    Serial.print(wz);
  }
}

Projekt ist fertig, alles funktioniert.

Schick - und wie?

Ich will hier keine Panik verbreiten oder Euren vorbildlichen Einsatzeifer bremsen!
Aber Ihr befindet Euch hier im Medical-Bereich!
Egal ob sozial oder nicht - Hier gelten strenge Richtlinien / gesetzliche Vorgaben!
Kommt nicht unter die Räder!
:slight_smile: Martin

Hallo Martin,
vielen Dank für den Hinweis, das ist uns natürlich bekannt,
da haben wir uns schon durchgewühlt.
:man_in_manual_wheelchair: René

Es muß nicht unbedingt eine Primzahl sein, aber sie zeigt die Unabhängigkeit der Zeitzyklen, weil es kein kleinstes gemeinsames Vielfaches gibt.

Weil ich das Gefühl habe, es gibt noch Verbesserungspotential, habe ich am Ende von #38 noch ein paar Überlegungen angestellt, auf die Du nicht reagiert hast. Siehe es bitte als Angebot.

Wie gewollt:
Motor startet im Kriechgang,
Messung dessen Drehzahl, bis die unter 10 sinkt, bedeutet Anschlag,
dann Normalbetrieb, Kraft am Sensor erhöht sich durch Bedienung-> runter schalten,
und umgekehrt.
Der Sketch ist natürlich überarbeitet.
Oder war Deine Frage anders gemeint?

Ah, ok, danke für die Erklärung.

ich bin in #39 kurz darauf eingegangen, die Verwendung des delay war unkritisch,
da die Funktion "motorSetup()" nicht noch einmal angesprochen wird
und an dieser Stelle die Wägezelle noch nicht nötig ist, ich habe die Initialisierung der Wägezelle "nach hinten" verlegt und dadurch das Problem gelöst

Bitte gerne :slightly_smiling_face:

Ich verstehe sie so: Wenn man in einem öffentlichen Forum nach Hilfe fragt, sollte man nicht nur nehmen, sondern auch geben. Sollte es also für die Allgemeinheit interessante Informationen geben, so wäre es eine gute Idee, diese mit der Öffentlichkeit zu teilen, damit andere davon lernen können. Leider wird dies nur allzu häufig vergessen.

Wann Du das machst und in welchem Umfang, bleibt Dir überlassen. Das Listing Deines nun funktionierenden Programms wäre aber ein Anfang.

Dann könnte ich da auch noch mal einen kritischen Blick drauf werfen, ob meine Bauchschmerzen berechtigt sind oder nicht. Ich verstehe Deine Ungeduld, aber es soll doch auch zuverlässig funktionieren, oder?

Sorry, sorry,
also daran soll's ja nun nicht im Mindesten liegen.
Ich bin für jeden Hinweis dankbar und stelle den fertigen Sketch gern hier noch einmal ein.
Ich finde Deinen Hinweis im Übrigen auch sehr "sozialkompetent" und kollegial.
Wie Du ja weist, sind meine eigenen Kompetenzen auch dem Gebiet der Programmierung
sehr übersichtlich, so dass ich an dieser Stelle auch tatsächlich bis auf einen gemeinsam erarbeiteten Sketch nur wenig zurückgeben kann.
Ich kann aber an dieser Stelle der Community gern Hilfe bei CAD Konstruktionen anbieten,
sollte dies hier mal versehentlich gewünscht sein.

//Waage
#include "HX711.h"
HX711 scale;
uint8_t dataPin = 6;
uint8_t clockPin = 7;
//Motor
const int SensorPin = 2;  //Interrupt Pin (nur 2 oder 3 @ Arduino Uno)
const byte Brake = 8;//Pinausgang Bremse, HIGH = Vollbremsung
const byte PWM = 9;// Pinausgang für Drehzahl
const byte power = 100;// Motor-Drehzahl-Normalbetrieb
const byte Drehrichtung = 10;// Pin Drehrichtungswechsel LOW/HIGH Drehrichtung
volatile long Counter;
volatile bool dir; // true: incr ; false: decr
bool setupStep = LOW;
bool Normalbetrieb = LOW;
String P;//dient nur der Kontrolle: Programmposition

void count() {
  Counter += dir ? +1 : -1;
}
void motor(bool Stop, bool Richtung, int pwmSignal) {
  digitalWrite (Brake, Stop);
  digitalWrite (Drehrichtung, Richtung);
  analogWrite (PWM, pwmSignal);
}
void M_Stop() {
  motor(HIGH, LOW, 0);
}
void M_Hoch() {
  motor(LOW, HIGH, power);
}
void M_Runter() {
  motor(LOW, LOW, power);
}
//**********************************************
void setup() {
  Serial.begin(9600);
  //Waage
  scale.begin(dataPin, clockPin);
  scale.set_scale(127.15);//es wird nur der Relativwert benötigt
  pinMode(dataPin, INPUT);
  pinMode(clockPin , OUTPUT);
  //Motor
  pinMode(SensorPin, INPUT); //definiertes Potenzial (HIGH/LOW) von einem Hall-Sensor
  pinMode(Brake , OUTPUT);
  pinMode(PWM , OUTPUT);
  pinMode(Drehrichtung , OUTPUT);//Drehrichung
}
//**********************************************
void loop() {
  motorSetup();
  motorMatik();
  /*
    Serial.print("\t Counts: ");
    Serial.println(Counter);
    // */
}
void motorSetup() {
  if (!setupStep) {  //Variable um den Setupvorgang vom Normalbetrieb zu trennen
    Counter = 0;
    dir = 1;
    attachInterrupt(digitalPinToInterrupt(SensorPin), count, CHANGE);
    delay(500);//kann delay sein, weil motorSetup nur einmal und ohne Wägezelle durchlaufen wird
    detachInterrupt(digitalPinToInterrupt(SensorPin));//Stop zur Ermittlung der Drehzahl über 500ms
    motor(LOW, LOW, 30); //Kriechgang (30) zum Anfahren der Endposition (Anschlag)
  }
  static unsigned long SetupMillis;
  if ((Counter < 10) && ((millis() - SetupMillis >= 1000))) {// 1s um den Anlaufprozess zu überbrücken
    M_Stop (); // Drehzahl sinkt am Anschlag unter (10) , der Motor stopt
    setupStep = HIGH;
    Counter = 1000;// Counter wir auf 100 gesetzt um ein "Überfahren" in den negativen Bereich zu verhindern
  }
  // Der Zähler wird auf Positionsmods umgestellt, absolute Werte werden gespeichert
  if (setupStep && !Normalbetrieb) {
    attachInterrupt(digitalPinToInterrupt(SensorPin), count, CHANGE);
    motor (LOW, HIGH, 120); //Richtungswechsel nach Anschlag
    if (Counter >= 1010) {//Fahrt auf Ausgangsposition (z.B X + 10)
      M_Stop ();//Setup-Phase ist beendet, Motor steht in Ausgangsposition
      Normalbetrieb = HIGH;//Umschalten auf Normalbetrieb
    }
  }
}
void motorMatik () {
  if (Normalbetrieb) {
    static unsigned long m_Millis;
    static unsigned long m_MillisH;
    long wz = scale.get_units();
    long positionM[] = {1040, 1600};
    long druckPosition = map (wz, 260, 4000, 1000, 1600);

    if (druckPosition > (Counter + 1) && Counter < positionM[1]) {
      M_Stop();
      P = 1;
      if (millis() - m_Millis >= 1000) {//Pause, um M_Hoch auslaufen zu lassen
        dir = 1;
        M_Runter();//Druck läßt nach
        m_MillisH = millis();
      }
    }
    else if (druckPosition < (Counter - 1) && Counter > positionM[0] ) {
      M_Stop();
      P = 2;
      if (millis() - m_MillisH >= 1000) {//Pause, um M_Runter auslaufen zu lassen
        dir = 0;
        M_Hoch();//Druck steigt
        m_Millis = millis();
      }
    }
    else {
      m_Millis = millis();
      m_MillisH = millis();
      M_Stop();
      P = 3;
    }
    /*
      Serial.print("druckPosition: ");
      Serial.print(druckPosition);
      Serial.print("\t millis: ");
      Serial.print(millis());
      Serial.print("\t m_Millis: ");
      Serial.print(m_Millis);
      Serial.print("\t M_H: ");
      Serial.print(m_MillisH);
      Serial.print("\t P_Position: ");
      Serial.print(P);
      // */
  }
}

Dachte ich mir :slightly_smiling_face:

Was mir spontan auffällt: Der Typ String kann bei Arduino-AVRs den Speicher zumüllen, was nach längerem Programmlauf zu unerklärlichen Programmabstürzen führen kann. Da Du eh nur Zahlen speicherst, wäre byte P; die bessere Wahl.

Danke, gut zu wissen, also String vermeiden, wenn's nicht Not tut, ok.

Deine Qualitätsabteilung meldet sich noch mal.

Durch M_Stop(); wird während der Bewegung ein 65 µs Bremsimpuls erzeugt, der möglicherweise Deiner Mechanik schadet.

Oben Brake und unten PWM.

Besser so:

    if (druckPosition > (Counter + 1) && Counter < positionM[1]) {
      P = 1;
      if (millis() - m_Millis >= 1000) {//Pause, um M_Hoch auslaufen zu lassen
        dir = 1;
        M_Runter();//Druck läßt nach
        m_MillisH = millis();
      } else {
        M_Stop();
      }
    }
    else if (druckPosition < (Counter - 1) && Counter > positionM[0] ) {
      P = 2;
      if (millis() - m_MillisH >= 1000) {//Pause, um M_Runter auslaufen zu lassen
        dir = 0;
        M_Hoch();//Druck steigt
        m_Millis = millis();
      } else {
        M_Stop();
      }
    }
    else {
      m_Millis = millis();
      m_MillisH = millis();
      M_Stop();
      P = 3;
    }

Oder?

Du bist ja nicht nur QS, Du bist ja genial. Wenn Du wüsstest, wie lange ich überhaupt gebraucht hatte, um heraus zu finden, dass ich den Motor nicht nur im "letzten else" stoppen muss. Das man "else" so variabel einbauen kann, war mir als Rookie nicht bewusst. Das werde ich dann gleich ausprobieren, wenn ich das Teil wieder hier habe. Aktuell schraubt der Rollstuhlbauer dran rum. Vielen Dank!

Ich spüre Verantwortung auf meinen Schultern :thinking:

  1. Bei analogWrite() nutzt Du einen Ausgang mit 490 Hz. Hast Du auch schon mal einen mit 980 Hz probiert?
  2. Bei analogWrite() verwendest Du 30 und 100 als Tastverhältnis. Gibt es da tatsächlich einen Geschwindigkeitsunterschied? Wenn ja, wäre ein Sanftanlauf eine sinnvolle Idee?

Kosmetik mit Typ- und Namensanpassungen:

const byte dataPin = 6;
const byte clockPin = 7;
//Motor
const byte sensorPin = 2;        // Interrupt Pin (nur 2 oder 3 @ Arduino Uno)
const byte brakePin = 8;         // Pinausgang Bremse, HIGH = Vollbremsung
const byte SCHLEICH_POWER = 30;  // Motor-Drehzahl-Schleichgang
const byte NORMAL_POWER = 100;   // Motor-Drehzahl-Normalbetrieb
const byte pwmPin = 9;           // Pinausgang für Drehzahl
const byte richtungsPin = 10;    // Pin Drehrichtungswechsel LOW/HIGH Drehrichtung

Wartungsfreundlichkeit:
In motorSetup() hast Du eine Schrittkette (=finite state machine; =endlicher Automat) verwendet, die man m. E. etwas deutlicher und damit servicefreundlicher schreiben kann.

  1. START initialisiert
  2. KRIECH schleicht sich an den mechanischen Anschlag, der durch Blockieren und damit wenigen Impulsen erkannt wird
  3. ANSCHLAG läßt sich die Bewegung am Anschlag beruhigen
  4. STARTPOSITION setzt die Startposition und schaltet in den Normalbetrieb
  5. BETRIEB sollte eigentlich nie erreicht werden. Hier könnte man die ganze Funktion motorMatik() unterbringen, muß aber auch nicht.

Da ich die Hardware nicht habe, ein gänzlich ungetesteter Vorschlag:

//Waage
#include "HX711.h"
HX711 scale;
uint8_t dataPin = 6;
uint8_t clockPin = 7;

//Motor
const uint8_t sensorPin = 2;        // Interrupt Pin (nur 2 oder 3 @ Arduino Uno)
const uint8_t brakePin = 8;         // Pinausgang Bremse, HIGH = Vollbremsung
const uint8_t KRIECH_POWER = 30;    // Motor-Drehzahl-Kriechgang
const uint8_t NORMAL_POWER = 100;   // Motor-Drehzahl-Normalbetrieb
const uint8_t pwmPin = 9;           // Pinausgang für Drehzahl
const uint8_t richtungsPin = 10;    // Pin Drehrichtungswechsel LOW/HIGH Drehrichtung
const uint32_t MESSINTERVALL = 500;
const uint32_t PAUSENINTERVALL = 1000;

volatile long Counter;
volatile bool dir;               // true: incr ; false: decr
bool normalbetrieb = false;
uint8_t P = 0;

void count() {
  Counter += dir ? +1 : -1;
}
void motor(bool Stop, bool Richtung, int pwmSignal) {
  digitalWrite (brakePin, Stop);
  digitalWrite (richtungsPin, Richtung);
  analogWrite (pwmPin, pwmSignal);
}
void M_Stop() {
  motor(HIGH, LOW, 0);
}
void M_Hoch() {
  motor(LOW, HIGH, NORMAL_POWER);
}
void M_Runter() {
  motor(LOW, LOW, NORMAL_POWER);
}
//**********************************************
void setup() {
  Serial.begin(9600);

  //Waage
  scale.begin(dataPin, clockPin);
  scale.set_scale(127.15);//es wird nur der Relativwert benötigt
  pinMode(dataPin, INPUT);
  pinMode(clockPin , OUTPUT);

  //Motor
  pinMode(sensorPin, INPUT);  // definiertes Potenzial (HIGH/LOW) von einem Hall-Sensor
  pinMode(brakePin , OUTPUT);
  pinMode(pwmPin , OUTPUT);
  pinMode(richtungsPin , OUTPUT);  // Drehrichtung

  attachInterrupt(digitalPinToInterrupt(sensorPin), count, CHANGE);
}
//**********************************************
void loop() {
  if (normalbetrieb) {
    motorMatik();
  } else {
    motorSetup();
  }
}

void motorSetup() {
  uint32_t jetzt = millis();
  static uint32_t setupMillis = jetzt;
  enum {START, KRIECH, ANSCHLAG, STARTPOSITION, BETRIEB};
  static byte schritt = KRIECH;

  switch (schritt) {
    case START:
      Counter = 0;
      dir = 1;
      setupMillis = jetzt;
      motor(LOW, LOW, KRIECH_POWER); //Kriechgang zum Anfahren der Endposition (Anschlag)
      schritt = KRIECH;
      break;
    case KRIECH:
      if (jetzt - setupMillis >= MESSINTERVALL) {
        setupMillis = jetzt;
        if (Counter < 10) {
          M_Stop (); // Drehzahl sinkt am Anschlag unter (10) , der Motor stoppt
          schritt = ANSCHLAG;
        } else {
          Counter = 0;
        }
      }
      break;
    case ANSCHLAG:
      if (jetzt - setupMillis >= PAUSENINTERVALL) {
        Counter = 1000;  // Counter wir auf 1000 gesetzt um ein "Überfahren" in den negativen Bereich zu verhindern
        motor (LOW, HIGH, NORMAL_POWER);  // Richtungswechsel nach Anschlag
        schritt = STARTPOSITION;
      }
      break;
    case STARTPOSITION:
      if (Counter >= 1010) {//Fahrt auf Ausgangsposition (z.B X + 10)
        M_Stop ();//Setup-Phase ist beendet, Motor steht in Ausgangsposition
        normalbetrieb = true;
        schritt = BETRIEB;
      }
      break;
    case BETRIEB:
      break;
    default:
      schritt = START;
      break;
  }
}

void motorMatik () {
  static unsigned long m_Millis;
  static unsigned long m_MillisH;
  uint16_t wz = analogRead(A0);
  long positionM[] = {1040, 1600};
  long druckPosition = map (wz, 0, 1023, 1000, 1600);

  if (druckPosition > (Counter + 1) && Counter < positionM[1]) {
    P = 1;
    if (millis() - m_Millis >= 1000) {//Pause, um M_Hoch auslaufen zu lassen
      dir = 1;
      M_Runter();//Druck läßt nach
      m_MillisH = millis();
    } else {
      M_Stop();
    }
  }
  else if (druckPosition < (Counter - 1) && Counter > positionM[0] ) {
    P = 2;
    if (millis() - m_MillisH >= 1000) {//Pause, um M_Runter auslaufen zu lassen
      dir = 0;
      M_Hoch();//Druck steigt
      m_Millis = millis();
    } else {
      M_Stop();
    }
  }
  else {
    m_Millis = millis();
    m_MillisH = millis();
    M_Stop();
    P = 3;
  }
  Serial.print("druckPosition: "); Serial.print(druckPosition);
  Serial.print("\t Counter: "); Serial.print(Counter);
  Serial.print("\t P_Position: "); Serial.print(P);
  Serial.print("\t m_MillisH: "); Serial.print(m_MillisH);
  /*
    Serial.print("\t millis: ");
    Serial.print(millis());
    Serial.print("\t m_Millis: ");
    Serial.print(m_Millis);
    // */
  Serial.println();
}

Du wirst sicherlich den bisherigen Ablauf wiedererkennen und kleine Fehler von mir korrigieren können.

Interessiert Dich mein Vorschlag?

Jaaaa, "tu niemandem etwas Gutes, dann ...(hast du weniger Arbeit)."
Die Platine funktioniert super. Das war für das Projekt der beste Rat überhaupt.
Ich habe nicht einmal den direkten PWM Eingang sondern den Poti-Eingang genutzt.
Auch der Motor ist unglaublich leistungsstark, ich fahre den sogar nur mit 9V statt 24V Input
an der Platine und dann wird die Spannung durch den Treiber noch mal reduziert. Wahnsinn.
Das beantwortet vermutlich auch 1. . Die Frequenz ist der Treiberplatine wahrscheinlich
Wurscht, es wird nur die resultierende Spannung ausgewertet. Und zu 2.: die Ansteuerung ist gefühlt sehr linear, also der Unterschied ist groß, eben zwischen 0 und 255. Das funktioniert perfekt. Etwas empirisch musste ich bei der Findung der richtigen Reihenfolge der Hallsensoren
und Spulenanschlüsse vorgehen, aber das ist ja ganz normal.
Sanftanlauf ist m.E. nicht notwendig, da der Motor auf eine große Untersetzung wirkt, ergibt sich dieser von selbst. Eben aus den Komponenten Getriebespiel, Trägheit, und Übersetzungsverhältnis.

Ja, klar, sieht wesentlich aufgeräumter aus.
Ich kann nur z.Z. nicht testen, da die Hardware beim Rollstuhlbauer ist.

Warum hast Du den Motorstop wieder nach vorn geschoben?

Gut!

Weil ich dusselig bin :blush:

Habe es korrigiert.

Dann warten wir gespannt auf die Hardware :wink:

Ja, ich scharre auch schon mit den Hufen, aber ohne Hardware, habe ich gemerkt, kommt man nicht wirklich weiter.
Wie sagt es unser Goethe doch so schön:
"Grau, teurer Freund, ist alle Theorie und grün des Lebens goldner Baum".