Schrittmotor lässt sich mit Poti nicht linear steuern

Hallo zusammen,

dies ist meine erste Frage in diesem Forum, da ich mich erst seit etwa 2 Wochen überhaupt mit dem Thema Arduino beschäftige - zwangsläufig im Rahmen meines Studiums.
Ich hoffe sehr, dass meine Frage nicht bereits beantwortet wurde. Ich habe lange gesucht, aber keinerlei vergleichbare Themen gefunden.

Ich muss in meinem Projekt mithilfe eines Arduino zwei Schrittmotoren ansteuern, deren Geschwindigkeit sich mithilfe zweier Potentiometer irgendwo in einem Bereich zwischen etwa 60rpm und 120rpm einstellen lassen soll. Schließlich soll die Geschwindigkeit auch noch auf einem 16x2 LCD angezeigt werden, was aber hier nicht das Problem ist.
Grundsätzlich klingt das ja mal recht easy; ich habe in meinem Fall die AccelStepper Library zur Hilfe genommen.

Zunächst dachte ich, dass ich mein Ziel erreicht hätte, habe jedoch dann festgestellt, dass sich die Schrittmotoren zwar bis zu einer Geschwindigkeit von ca. 60rpm einwandfrei linear mit der Drehung des Potis steuern lassen, danach jedoch nicht mehr. Drehe ich das Poti dann noch etwas weiter, springt der Motor plötzlich auf eine leicht höhere, aber nicht mehr veränderliche Geschwindigkeit. Drehe ich dann noch etwas weiter auf, springt der Motor auf eine noch höhere, aber wieder konstante Geschwindigkeit.

Im Display wird mir jedoch stets die eigentliche "Soll-Geschwindigkeit" weiterhin auf mehrere Nachkommastellen korrekt angezeigt. Daraus schließe ich, dass die Geschwindigkeitswerte, die ich mit meinem Potentiometer und mapping auf einen bestimmten Bereich erzeuge grundsätzlich korrekt sind und der Fehler irgendwo in der 'set.Speed()'-Funktion liegen muss. Ich komme jedoch einfach nicht darauf, wo hier der Fehler sein könnte.

Die 'set.Speed()'-Fkt hat als Eingangwerte direkt die gemappten Werte aus dem Potentiometer. Trotzdem ändert sich irgendwann die Geschwindigkeit nicht mehr bzw. sie springt nur noch auf weitestgehend konstante Werte auf und ab.
Könnte es auch am Motor liegen? Habe versucht etwas über den Motor herauszufinden, aber nicht arg viel herausfinden können, da mein Vorgänger das Setup gebaut hat. Die Beschriftung lautet aber ot-42hs40-005b. Ich denke es ist ein 1,8° Nema17.

Wenn mir hier jemand helfen könnte, wäre ich extrem dankbar, da ich mir daran seit Tagen die Zähne ausbeiße und dringend voran kommen muss. Im Anhang auch eine Grafik, die verdeutlichen soll, wie der Geschwindigkeitsverlauf ist.

Danke für eure Hilfe und LG aus Australien :slight_smile:

#include <AccelStepper.h>
#include <LiquidCrystal.h>

// Initialize LCD
LiquidCrystal lcd(7,6,5,4,3,2);

// Define the stepper and the pins it will use
AccelStepper stepper1(AccelStepper::DRIVER, 9, 8);
AccelStepper stepper2(AccelStepper::DRIVER, 11, 10);

// Define our input button pins
const int  STOP_PIN = 12;

// Define our analog pot input pin
#define  SPEED_PIN1 1
#define  SPEED_PIN2 2

// Define our maximum and minimum speed in steps per second (scale pot to these)
#define  MAX_SPEED1 200
#define  MIN_SPEED1 0.0

#define  MAX_SPEED2 200
#define  MIN_SPEED2 0.0

// Variables will change:
int buttonPushCounter = 0;   // counter for the number of button presses
int buttonState = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the button


void setup() {
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  
  // The only AccelStepper value we have to set here is the max speeed, which is higher than we'll ever go
  stepper1.setMaxSpeed(1000000.0);
  stepper2.setMaxSpeed(1000000.0);
  
  // Set up the button inputs, with pullups
  // pinMode(STOP_PIN, INPUT_PULLUP);
  pinMode(STOP_PIN, INPUT);
  
}

void loop() {

  static char sign = 0;                     // Holds 1 or 0 to turn the motor on/off 

// read the pushbutton input pin:
  buttonState = digitalRead(STOP_PIN);

  // compare the buttonState to its previous state
  if (buttonState != lastButtonState) {
    // if the state has changed, increment the counter
    if (buttonState == HIGH) {
      // if the current state is HIGH then the button went from off to on:
      buttonPushCounter++;
    }
    // Delay a little bit to avoid bouncing
    delay(50);
  }
  // save the current state as the last state, for next time through the loop
  lastButtonState = buttonState;

  if (buttonPushCounter % 2 == 0) {
    sign = 0;
  } else {
    sign = 1;
    }

//-----------------------------------------------------------------------------
  
  static int current_speed1 = 0.0;           // Holds current motor speed in steps/second
  static int current_speed2 = 0.0;           // Holds current motor speed in steps/second
  static int analog_read_counter = 0;    // Counts down to 0 to fire analog read
  static int analog_value1 = 0;              // Holds raw analog value.
  static int analog_value2 = 0;              // Holds raw analog value.
  static float speed1 = 0;
  static float speed2 = 0;
 


  // We only want to read the pot every so often (because it takes a long time we don't
  // want to do it every time through the main loop).  
  if (analog_read_counter > 0) {
    analog_read_counter--;
  }
  else {
    analog_read_counter = 3;
    // Now read the pot (from 0 to 1023)
    analog_value1 = analogRead(SPEED_PIN1);
    analog_value2 = analogRead(SPEED_PIN2);
    // Give the stepper a chance to step if it needs to
    stepper1.runSpeed();
    stepper2.runSpeed();
    //  Scale the pot's value from min to max speeds
    speed1 = (((analog_value1/1023.0) * (MAX_SPEED1 - MIN_SPEED1)) + MIN_SPEED1);
    speed2 = (((analog_value2/1023.0) * (MAX_SPEED2 - MIN_SPEED2)) + MIN_SPEED2);
    
    current_speed1 = sign * speed1;
    current_speed2 = sign * speed2;
    
    // Update the stepper to run at this new speed
    stepper1.setSpeed(current_speed1 * (-1));
    stepper2.setSpeed(current_speed2 * (-1));
  }

  // This will run the stepper at a constant speed
  stepper1.runSpeed();
  stepper2.runSpeed();

  lcd.setCursor(0, 0);
  lcd.print("Rod   SPS: ");
  lcd.setCursor(12,0);
  lcd.println(speed1);
  lcd.setCursor(0,1);
  lcd.print("Screw SPS: ");
  lcd.setCursor(12,1);
  lcd.println(speed2);
}

Update: Ich habe nun nochmal genauer beobachtet und es scheint, als würde der Motor immer nur kleine Geschwindigkeits-Sprünge machen, die aber mit der Zeit immer größer werden.
Zum einfacheren Verständnis habe ich eine Grafik angehängt.
Danke, lg :slight_smile:

Moin,

Warum benutzt du nicht die map() Funktion zum skalieren?

Andersrum könntest du mal beim skalieren probieren zuerst zu multiplizieren und erst danach zu dividieren. Dadurch erreichst du das die Zahlen größer werden und nicht erst mit kleinen ungenauen Zahlen gerechnet wird.

Weiterhin würde ich mal probieren mit seriellen Ausgaben auf dem seriellen Monitor die analogen Werte des Potis und die gemappten speedwerte auszugeben und die mal darstellen, ob da nicht schon eine nichtlinearität bzw nicht proportionalität bei rauskommt. Geht sicherlich auch mit dem seriellen Plotter.

Lieben Gruß,
Chris

Hallo,
was hast Du denn für einen Stepper, und wie wird er angesteuert? Insbesondere: wieviele Steps/ Umdrehung brauchst Du? Und welchen Arduino setzt Du ein - da Du dazu nichts sagst, gehe ich mal von einem Standard-UNO oder Mega aus.

Ich vermute sehr, dass das an deinen loop-Durchlaufzeiten liegt. Die AccelStepper macht pro runSpeed Aufruf maximal einen Step. Die lcd-Aufrufe sind auch recht zeitintensiv, und delay solltest Du gar nicht verwenden.
Die Funktion der AccelStepper ist stark vom Aufbau des loop abhängig. Wenn runSpeed nicht oft genug aufgerufen wird, klemmt es.

Du könntest es mal mit meinem MobaTools probieren. Da werden die Stepimpulse in Interrupts erzeugt, und das ist deshalb weitgehend unabhängig von dem, was im loop() passiert. Selbst delay() stört da nicht. Allerdings gibt es eine fixe Höchstgrenze für die Steprate/Motor. Gegebenenfalls lässt die sich aber über ein #define auch noch 'tunen', da Du ja nur 2 Stepper nutzt.
Installieren kannst Du die Lib über den Bibliotheksverwalter der IDE ( im Suchfeld 'mobatools' eingeben )

Edit: habe gerade gesehen, dass Du#define  MAX_SPEED1 200 definierst. Irgendwie passt das meiner Meinung nach nicht zu den gewünschten 120 rpm. Übliche Stepper haben ja 200Steps/Umdr. Das wären dann nur maximal 60 Umdr/Min. Deshalb wären die Daten deiner HW noch wichtig.

Hab's mir nochmal genauer angeschaut und ausgemessen. Das Problem sind definitiv die LCD Ausgaben. Die brauchen zu viel Zeit, und in der Zeit kann die AccelStepper keine Impulse ausgeben.
Etwas verbessern lässt sich das Ganze, wenn Du pro loop() Durchlauf immer nur einen der lcd-Aufrufe machst. Aber auch da gibt's welche, die über 3ms brauchen und den loop entsprechend lange blockieren.
AccelStepper und lcd-Lib geht nicht gut zusammen.

Reicht es nicht, das Display einmal alle 500ms zu aktualisieren, und dann nur die Änderungen? Lcd und das Auge sind doch eh träge.

Wobei der Stepper dann alle 500ms kurz 'stottert'.

Einen Tod muss er sterben. Besser als zu langsam. Oder anderes Display

Hallo,

hat die Lib nicht noch andere Funktionen zum dauerhaften drehen? Also unabhängig vom Loopdurchlauf mit Timer?
Ansonsten kann man das noch ein wenig optimieren. Die Textausgaben muss man nicht ständig wiederholen. Kostet nur Zeit. Man überschreibt nur das was sich ändert. Unter Umständen kann man die Display Lib für sein Display noch tunen mittels angepasster Wartezeiten. Die sind meistens sehr großzügig für alle passend ausgelegt. Fas perfekt umgehen kann man das Problem wenn man nur noch aktualisiert wenn sich der Drehzahlwert wirklich geändert hat. Ggf. die Werte filtern.
Wenn alles läuft wäre allgemein auch hier die Verwendung von struct von Vorteil. Dein Prof. sollte das jedenfalls positiv werten. Noch ein Tipp. Teile nicht durch 1023.0 sondern durch 1024.0 und gegen vorzeitige Rundungsfehler die Formel ggf. umstellen indem man versucht Divisionen möglichst am Ende der Rechnung zu machen.

Edit:
Deine loop kannste auch noch aufräumen. static Variablen sind dort eigentlich überflüssig. Kannste genauso gut global machen. Gut wäre auch wenn man Einiges in Funktionen auftrennt. In Funktionen macht static wieder Sinn wenn benötigt.

Ich denke das geht mit runspeed()

Hi

Wurde denn das Verhalten der MoBaTools in diesem Zusammenhang Mal getestet?
Dort wäre zumindest die Laufzeit von loop() und der darin enthaltene .run()-Aufruf egal bzw. nicht mehr vorhanden.
Kämpfe auch gerade mit einer 'Nullpunkt-Suche' unter Verwendung der MoBaTools - noch sitzt der Knoten aber fest im Hirn :slight_smile:

MfG

Doc_Arduino:
hat die Lib nicht noch andere Funktionen zum dauerhaften drehen? Also unabhängig vom Loopdurchlauf mit Timer?

Da bietet die AccelStepper leider nichts, was unabhängig vom loop-Durchlauf ist. Es gibt noch eine blockierende Funktion. Die ist dann aber für dauerhaftes Drehen erst recht nichts.

themanfrommoon:
Ich denke das geht mit runspeed()

Das ist ja die Funktion, die der TO nutzt. Aber die macht auch nur maximal 1 Step pro Aufruf und ist von der Durchlaufzeit des loop abhängig.

postmaster-ino:
Wurde denn das Verhalten der MoBaTools in diesem Zusammenhang Mal getestet?

Grundsätzlich geht's mit den MobaTools schon. Da es da allerdings keine direkt zu runSpeed() vergleichbare Funktion gibt, muss man das ein klein wenig anders aufbauen. Bei den MobaTools kann man nicht anhalten, indem man die Speed zu 0 setzt, das geht nur über die Zielposition.
In dem Zusammenhang habe ich aber noch 2 kleine Fehler in den MobaTools gefunden - es gibt also demnächst ein Update. Speed=0 wurde nicht abgefangen, und sehr schnelles wiederholtes Setzen einer Zielposition im Modus ohne Rampe führte zu Fehlverhalten.

Mal sehen, ob sich der TO nochmal meldet. Wie schon angesprochen wurde gäbe es da noch mehr Optimierungspotential :wink: