Motor stops spinning, as soon a something is written to the display

I'm currently trying to build a belt-conveyor with a Steppermotor.

Planed Components are:
ESP8266 NodeMCU
KY-040 Encoder
Keyes Stepper Motor for arduino controll with A4988 Driver
Stepperonline Nema17 Steppermotor (1.8°, 2.0A)
APKLVSR 1.3" 128x64 OLED-Display (SH1106/I²C)

The Steppermotor has a seperate 24V DC 5A Powersupply. The ESP is powered trough USB from an external Powerdistributionboard.
The Driver-Logic and the Display also get 5V from that board. The Encoder gets 3.3V fron the ESP.

Cureently I am at implementing the Display, but now the Motor stopped working correctly and I am unsure why.

Based on the Encoder- and ContinuousStepper-Libary I programed the following code, that works fine.

I can change the speed of the Stepper by turning the encoder, it only goes up to the desired maximum speed and starts and stops by pressing the button.

#include <ContinuousStepper.h>              // Library for stepper motor control.
#include <Encoder.h>                        // Library for encoder evaluation.

// For the encoder:
Encoder myEnc(D5, D6);                      // Define the pins for the encoder.

long oldPosition  = -999;                   // Set an unlikely value for the first encoder evaluation loop.
const uint16_t potReadInterval = 500;       // in milliseconds

// For the encoder button:
const int SW = D7;                          // Define the pin for the encoder button.
int state = LOW;                            // Current state of the encoder button.
int reading;                                // Temporary storage for the button reading.
int previous = LOW;                         // Stores the last read value.
long LastTime = 0;                          // Stores the last timestamp when the button was pressed.
long DebounceInterval = 200;                // Sets the debounce time interval.

// For the stepper motor:
const uint8_t stepPin = D3;                 // Define the STEP pin.
const uint8_t dirPin = D4;                  // Define the DIR pin.
ContinuousStepper<StepperDriver> stepper;   // Specify the stepper motor control method.

int stepperSpeed = 0;                       // Initial stepper motor speed in [steps/second].
int maxSpeed = 2500;                        // Maximum stepper motor speed [steps/second].

void setup() {

  stepper.begin(stepPin, dirPin);           // Set the pins for motor control.
  //stepper.powerOff();                       // Set the motor status to off, so it doesn't start spinning immediately.
  Serial.begin(9600);                       // Start serial communication at a baud rate of 9600.

  pinMode(SW, INPUT_PULLUP);                // Enable the pull-up resistor for the button pin.
}

void loop() {

  // Encoder:
  long newPosition = myEnc.read();          // Read the encoder.
  if (newPosition != oldPosition) {         // Check if the encoder position has changed.
    oldPosition = newPosition;              // Update the old position.
    Serial.println(newPosition);            // Print the current encoder value.
  }

  // Button:
  reading = digitalRead(SW);                // Read the encoder button.

  if (reading == HIGH && previous == LOW && millis() - LastTime > DebounceInterval)
  {
    if (state == HIGH){                     // Check the current button state.
      state = LOW;                          // Change the button state to LOW.
      stepper.powerOn();                    // Power on the motor at the current speed.
    }
    else{
      state = HIGH;                         // Change the button state to HIGH.
      stepper.powerOff();                   // Power off the motor; speed value is retained.
    }
    LastTime = millis();                    // Store the last activation timestamp.
  }
  previous = reading;                       // Save the last button state for activation detection.

  // Stepper motor:
  stepperSpeed = newPosition * 10;          // Set the speed as a multiple of the encoder value.

  if (stepperSpeed >= maxSpeed){            // Check if the maximum positive speed is exceeded.
    stepperSpeed = maxSpeed;                // Limit the speed to the maximum.
  }
  if (stepperSpeed <= 0 - maxSpeed){        // Check if the maximum negative speed is exceeded.
    stepperSpeed = 0 - maxSpeed;            // Limit the speed to the maximum.
  }
  
  stepper.spin(stepperSpeed);               // Spin the stepper motor at the set speed.

  stepper.loop();                           // Must be called as often as possible to ensure continuous motor rotation.
}

Seperately I created the following code for the display:

#include <Arduino.h>
#include <U8g2lib.h>

#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

float Text = 30.015678;

void setup(void) {
  pinMode(4, OUTPUT); //SDA
  pinMode(5, OUTPUT); //SCL
  digitalWrite(4, 0);
  digitalWrite(5, 0);

  u8g2.begin();
  u8g2.setPowerSave(0);
}

void loop(void) {

  u8g2.clearBuffer();					// clear the internal memory
  u8g2.setFont(u8g2_font_spleen6x12_mf);	// choose a suitable font
  u8g2.drawStr(5,10,"set Speed:");	// write something to the internal memory
  u8g2.setFont(u8g2_font_spleen32x64_mn);	// choose a suitable font
  u8g2.setCursor(0,63);
  u8g2.print(Text);
  u8g2.sendBuffer();					// transfer internal memory to the display 
}

That Code also does what I expected it to do.

Now I have tried to merge the Code, sothat the Display shows the speed, that is set by the encoder:

#include <ContinuousStepper.h>              // Library for stepper motor control.
#include <Encoder.h>                        // Library for encoder evaluation.
#include <Arduino.h>
#include <U8g2lib.h>

#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

// For the encoder:
Encoder myEnc(D5, D6);                      // Define the pins for the encoder.

long oldPosition  = -999;                   // Set an unlikely value for the first encoder evaluation loop.
const uint16_t potReadInterval = 500;       // in milliseconds.

// For the encoder button:
const int SW = D7;                          // Define the pin for the encoder button.
int state = LOW;                            // Current state of the encoder button.
int reading;                                // Temporary storage for the button reading.
int previous = LOW;                         // Stores the last read value.
long LastTime = 0;                          // Stores the last timestamp when the button was pressed.
long DebounceInterval = 200;                // Sets the debounce time interval.

// For the stepper motor:
const uint8_t stepPin = D3;                 // Define the STEP pin.
const uint8_t dirPin = D4;                  // Define the DIR pin.
ContinuousStepper<StepperDriver> stepper;   // Specify the stepper motor control method.

int stepperSpeed = 0;                       // Initial stepper motor speed in [steps/second].
int maxSpeed = 2500;                        // Maximum stepper motor speed [steps/second].

// For the display:
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

float Display = 30.015;

void setup() {

  stepper.begin(stepPin, dirPin);           // Set the pins for motor control.
  //stepper.powerOff();                       // Set the motor status to off, so it doesn't start spinning immediately.
  Serial.begin(9600);                       // Start serial communication at a baud rate of 9600.

  pinMode(SW, INPUT_PULLUP);                // Enable the pull-up resistor for the button pin.

  pinMode(4, OUTPUT); // SDA
  pinMode(5, OUTPUT); // SCL
  digitalWrite(4, 0);
  digitalWrite(5, 0);

  u8g2.begin();
  u8g2.setPowerSave(0);
}

void loop() {

  // Encoder:
  long newPosition = myEnc.read();          // Read the encoder.

  // Button:
  reading = digitalRead(SW);                // Read the encoder button.

  if (reading == HIGH && previous == LOW && millis() - LastTime > DebounceInterval)
  {
    if (state == HIGH){                     // Check the current button state.
      state = LOW;                          // Change the button state to LOW.
      stepper.powerOn();                    // Power on the motor at the current speed.
    }
    else{
      state = HIGH;                         // Change the button state to HIGH.
      stepper.powerOff();                   // Power off the motor; speed value is retained.
    }
    LastTime = millis();                    // Store the last activation timestamp.
  }
  previous = reading;                       // Save the last button state for activation detection.

  // Stepper motor:
  stepperSpeed = newPosition * 10;          // Set the speed as a multiple of the encoder value.

  if (stepperSpeed >= maxSpeed){            // Check if the maximum positive speed is exceeded.
    stepperSpeed = maxSpeed;                // Limit the speed to the maximum.
  }
  if (stepperSpeed <= 0 - maxSpeed){        // Check if the maximum negative speed is exceeded.
    stepperSpeed = 0 - maxSpeed;            // Limit the speed to the maximum.
  }
  
  stepper.spin(stepperSpeed);               // Spin the stepper motor at the set speed.

  stepper.loop();                           // Must be called as often as possible to ensure continuous motor rotation.

  if (newPosition != oldPosition) {         // Check if the encoder position has changed.
    oldPosition = newPosition;              // Update the old position.
    Serial.println(newPosition);            // Print the current encoder value.
    Display = newPosition;
    u8g2.clearBuffer();                     // Clear the display buffer.

    u8g2.setFont(u8g2_font_spleen6x12_mf);  // Set font for the heading.
    u8g2.drawStr(5,10,"set Speed:");        // Write the heading to the buffer.
    u8g2.setFont(u8g2_font_spleen32x64_mn); // Set font for the value display.
    u8g2.setCursor(0,63);                   // Set the position for value display.
    u8g2.print(Display);                    // Write the value to the buffer.
    u8g2.sendBuffer();                      // Send the buffer content to the display.
  }
}

The Encoder is still working as before and the Display does as expected, but the Motor stoped working correctly, it does not spin constantly, every time I turn the encoder the motor stops for a splitsecont and then returns to spinning. If I increase the speed the Motor stalls at 1/3rd of the speed it reached before.

What did I do wrong?

Kind regards,
Christian

Sorry for the load of code, but I did not know a better way to discripe my Problem.

  • Are you wanting stepperSpeed to be set to -2500 ? :thinking:

Yes,
If the Encoder is turned backwards, the stepperSpeed gets negative, the continuousStepper libary automaticly switches the DIR-Pin for that.

This if-condition limits the maximum reverse Speed to 2500 Steps/second, witch is 750 rpm in my configuration.
As soon as the stepperSpeed gets lower than -2500, it is set to be fixed at -2500 untill the encoder is turned back below this value.

Kind regards, Christian

But you are using type int :thinking:

int stepperSpeed = 0; 

. . .

stepperSpeed = 0 - maxSpeed;

Sorry, but I don't understand. What am I missing?

Integer not the right type for the task? Should I avoid negative integer numbers?

Kind regards, Christian

  • My mistake, an integer can be positive or it can be negative.
    :woozy_face:
1 Like

No Problem,
can happen.
It isn't like I know what I am doing, just trying my way. Might be totally possible, that I used some variable type, that creates a conflict or something similar.

Could there be something in the if condition, that takes realy long to process?

  if (newPosition != oldPosition) {         // Check if the encoder position has changed.
    oldPosition = newPosition;              // Update the old position.
    Serial.println(newPosition);            // Print the current encoder value.
    Display = newPosition;
    u8g2.clearBuffer();                     // Clear the display buffer.

    u8g2.setFont(u8g2_font_spleen6x12_mf);  // Set font for the heading.
    u8g2.drawStr(5,10,"set Speed:");        // Write the heading to the buffer.
    u8g2.setFont(u8g2_font_spleen32x64_mn); // Set font for the value display.
    u8g2.setCursor(0,63);                   // Set the position for value display.
    u8g2.print(Display);                    // Write the value to the buffer.
    u8g2.sendBuffer();                      // Send the buffer content to the display.
  }

I have the feeling, that always, when the Encoder-Value changes, and the loop goes into this if-condition, the loop just takes so long, that the stepper.loop() isn't run frequently enough.

But that is just a feeling, with no evidence to back it up. :thinking:

Kind regards, Christian

So I have done some testing, and it seems like the Display-Code is extremly slow.

With just the Encoder and Steppermotor (Code 1 in the first post) I measured cycle times between 0.X and 1ms with little dips into the 9ms region, if the encoder was turned fast, but instantainiously going back down to between 0 and 1ms.

With just the Display (code 2 in the first post) I had cycletimes of constantly 34ms. I checked for the i²C Bus Clockspeed, it is set to 400kHz per default and changing it by setBusClock to higher values did not change anything. Reduceing it to 200kHz doubled the cycle time to 71ms, so the setting must have worked.

Same on the merged Code (Code 3 in the first Post), cycle time was always around 35ms, witch seems to be to slow for the stepper.loop call for maintaining the steppers motion.

So it seems the culpit lyies somewhere in the displaycode I wrote. What is it, that slows my ESP down so tremendously?

So what did I do wrong? I have absolutly no more ideas how to go on... :face_exhaling:

Kind regards, Christian

I think nothing - these display calls are slow.

You could try another stepper library. The MobaTools library doesn't rely on short loop times. It creates the step pulses in timer interrupts, independent of loop cycle times.

2 Likes

My blind guess is that the display function are blocking for some time (even few ms) the output of the step signal. This lead to a stall of the motor, that can be recovered when the frequency of the impulses come back to slower values.

To be sure just check with an oscilloscope the step signal: you should 1. still see the train of pulses even when the stepper does not rotate anymore and 2. (not so easy to detect) a pause in the pulses when the display is updated.

To avoid this issue you can put the stepper.loop(); call inside an interrupt of a timer.
A quick & dirty hack, not recommended, just to see if it works is to split the long series of calls for the display (u8g2.[...]) so every time the stepper.loop() is called in between.

1 Like

You could rate limit this:

... by keeping track of the time of the last update with this completely untested snippet:

  static uint32_t lastDisplayMs = 0;
  ...

  if (newPosition != oldPosition && millis() - lastDisplayMs > 100) {         // Check if the encoder position has changed.
    lastDisplayMs = millis();
    oldPosition = newPosition;              // Update the old position.

It won't cure the slow update, but it might limit the number of updates.

I don't know the library or how much of an effect it would have, but you might also streamline the U8g2lib display code by moving the static graphics into setup(), and update only this section that changes:

Maybe the updateDisplay() function would help:

1 Like

Sounds good, to be told I did nothing wrong, but I would have prefered a answer in the likes of: "You messed up this line, change it to this and that and everything will work" :upside_down_face:

I have takes a brief look into the MobaTools libary, at the moment it reads to me like it focuses more on positional Stepper Movements rather then continuous speed based motion. Will have to dig a little deeper into that.

Thanks a lot for your answer!

Kind regards, Christian

I guess the same, as there is no acceleration ramp after the Stepper stalls, it cant return to higher speeds.

Unfortunately I do not have an Osscioscope. :pensive: Would love to get one, but they are just to expencive compared to how little usecases I would have for it..
But i can hear the ticking/buzzing of the step pulses after the motor stalled, so I would guess the oscilloscope would show exactly what you are saying.

The Idea with the interups sounds interessting. I haven't worked much with interups yet, only when I tried to read out the Switch of the encoder, but that was very unsuccessful as I got many false readings and crasy amounts of bouncing.

How would I call the interupt? The Loop seems to be stuck at sending the Displayoutput or is it possible to create an interupt, that is called periodicly ever X milliseconds?

Thankyou for your answer, I guess I will take a deeper Look into this topic.

Kind regards, Christian

There a lot of resources online, for example:

You should define the maximum frequency of the step signal, that is the maximum speed of your motor, and set the timer to fire at least with the same period.

Take a look at the tutorial around this page:

You can piggyback on the timer0 configuration used for millis() and get an interrupt about every millisecond.

I like that idea, I also tried to limit the number of updates by putting them into the if case,, your approche seems to take that quite a bit further.

Reducing the amount of Lines, that have to be refreshed also seems to be a good idea. I will definately test that on the Display only code to see if that makes a difference.

Would it also be a good idea, trying to limit the changing text to as few of the horizontal sectors as possible? (Not sure If Iam right on this, but I think to remember that these OLED-Displays always right in sections of 7px hight. Staying within as few of them as possible should reduce the transfer time?)

The updateDisplay command looks promissing, maybe I can get that to work. Will have to read a lot deeper into the topic.

Thanks a lot for your answer!
Kind regards, Christian

@Mark81
@DaveX

Thankyou, to both of you.

I will dig myself a deeper into that toppic, sounds realy promissing to me. Somewhat like the fixed cycletimes of a PLC :thinking:

Actually I was about to ask, if it would be possible to solve my problem by using a 2 Core Controller like the ESP32, and letting that thing run two seperate loops. Then I read about the CPU_0 Core on the ESP32 handling the I²C Bus and was thinking, maybe that would allready solve the problem, by freeing up CPU_1 for continuing the loop while CPU_0 sends the Buffer over the Bus. Wild Dreams of a deperate "Programmer" I guess :sweat_smile:

kind regards,
Christian

So, little update:

I got it running.

Thanks to your input about the TimerInterups @DaveX & @Mark81 I have read some into that topic, when I rememberd something I read while going trough the ContinuousStepper libary, there were also mentions of Timers. So I got back and read a little more into it.

When I first read these things, I looked at the TimerOne and TimerThree-examples and they stated, that those are only suitable for teensy microcontrollers, therefor I did not read any further, but there is a third Version using Ticker/Tone. I tried it and it works beautiful!

So big sudos to @BenoitB for that awesome libary!

If I understand it right, the Tone-Version is basicly doing something very similar to the timerInterupt suggestion, by frequently calling the stepper.loop funktion in the background.

In the End I only had to change two lines. And some cleanup.

Here is my current working Code, just in Case someone needs something similar.

#include <ContinuousStepper.h>                          // Bibliothek für die Schrittmotorsteuerung.
#include <ContinuousStepper/Tickers/Tone.hpp>           // Unterbibliothek für die Schrittmotorsteuerung.
#include <Encoder.h>                                    // Bibliothek für die Encoderauswertung.
#include <Arduino.h>                                    // Bibliothek für Standardbefehle
#include <U8g2lib.h>                                    // Bibliothek für die Displayansteuerung

#ifdef U8X8_HAVE_HW_I2C                                 // Auswahl des Hardware basierten I²C-Protokolls
#include <Wire.h>                                       // Bibliothek für das I²C-Protokoll
#endif

// Für den Encoder:
Encoder myEnc(D5, D6);                                  // Festlegen der Pins für den Encoder

long oldPosition  = -999;                               // Setzen eines unwahrscheinlichen Werts für die Erste Encoderauswertungsschleife.

// Für den Encoder-Taster:
const int SW = D7;                                      // Festlegen des Pins für den Encodertaster.
int state = LOW;                                        // Aktueller Status des Encodertasters.
int reading;                                            // Zwischenspeicherung der Auslesung.
int previous = LOW;                                     // Speichern des letzten ausgelesenen Wertes.
long LastTime = 0;                                      // Speicher den letzten Leitpunkt, dass der Taster betätigt wurde.
long DebounceInterval = 200;                            // Setzt den Zeitinfall für die Entprellung fest.

// Für den Schrittmotor:
const uint8_t stepPin = D3;                             // Setzen des STEP-Pins.
const uint8_t dirPin = D4;                              // Setzen des DIR-Pins
ContinuousStepper<StepperDriver, ToneTicker> stepper;   // Festlegen des Motoransteuerungsverfahrens.

int stepperSpeed = 0;                                   // Festlegen der initialen Schrittmotorgeschwindigkeit in [Steps/second]
int maxSpeed = 2500;                                    // Festlegen der maximalen Schrittfrequenz für den Schrittmotor [Steps/second]

// Für das Display:
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // Auswahl des Displaycontrollers

float Display = 0;                                      // Ausgabevariable für die Displayanzeige.

void setup() {

  stepper.begin(stepPin, dirPin);                       // Festlegen der Motorsteuerungspins
  Serial.begin(9600);                                   // Starten der Seriellen Übertragung mit einer Baudrate von 9600. 

  pinMode(SW, INPUT_PULLUP);                            // Einschalten des PULLUP-Widerstandes für den Taster-Pin
  
  pinMode(4, OUTPUT);                                   // Festlegen des SDA-Pins (verwendet die ESP-Seitigen Pin Nummern)
  pinMode(5, OUTPUT);                                   // Festlegen des SCL-Pins (verwendet die ESP-Seitigen Pin Nummern)
  digitalWrite(4, 0);                                   // 
  digitalWrite(5, 0);

  u8g2.begin();                                         // Starten der u8g2-Bibliothek                      
  u8g2.setPowerSave(0);                                 // Verhindert das Abschalten des Dislays
}

void loop() {

  // Encoder:
  long newPosition = myEnc.read();                      // Auslesen des Encoders.

  // Taster:
  reading = digitalRead(SW);                            // Auslesen des Encoder-Tasters.

  if (reading == HIGH && previous == LOW && millis() - LastTime > DebounceInterval) //Entprellbedingung für den Encodertaster.
  {
    if (state == HIGH){                                 // Prüft den aktuellen Tasterstatus.
      state = LOW;                                      // Wechselt den aktuellen Tasterstatus zu LOW
      stepper.powerOn();                                // Lässt den Motor auf den aktuell eingestellten Drehzahlwert hochdrehen.
    }
    else{
      state = HIGH;                                     // Wechselt den aktuellen Tasterstatus zu HIGH.
      stepper.powerOff();                               // Motor abschalten, der Drehzahlwert bleibt gespeichert.
    }
    LastTime = millis();                                // Zwischenspeichern der letzten Auslösezeit
  }
  previous = reading;                                   // Speichern des letzten Status, zur Erkennung einer Auslösung.

  // Schrittmotor:
  stepperSpeed = newPosition * 10;                      // Setzt die Drehzahl als Vielfaches des Encoderwertes. 

  if (stepperSpeed >= maxSpeed){                        // Überprüft, ob die maximale positive Drehzahl überschritten wird.
    stepperSpeed = maxSpeed;                            // Begrenzt die Geschwindigkeit auf die Maximalgeschwindigkeit.
    myEnc.write(250);                                   // Begrenzt den Encoderwert auf den zur Maximalgeschwindigkeit gehörenden Wert.
  }
  if (stepperSpeed <= 0-maxSpeed){                      // Überprüft, ob die maximale negative Drehzahl überschritten wird.
    stepperSpeed = 0-maxSpeed;                          // Begrenzt die Geschwindigkeit auf die maximalgeschwindigkeit.
    myEnc.write(-250);                                  // Begrenzt den Encoderwert auf den zur Maximalgeschwindigkeit gehörenden Wert.
  }
  
  stepper.spin(stepperSpeed);                           // Lässt den Schrittmotor mit der eingestellten Geschwindigkeit drehen.

  stepper.loop();                                       // Muss sooft es geht angesteuert werden, damit der Motor sich kontinuierlich dreht.
  
  if (newPosition != oldPosition) {                     // Überprüfen, ob sich die Encoderstellung geändert hat.
    oldPosition = newPosition;                          // Alte Position aktualisieren.
    //Serial.println(newPosition);                      // Aktuellen Encoderwert Ausgeben.
    Display = stepperSpeed / 80.46;                     // Konvertieren der Schrittzahl zur Bandgeschwindigkeit.

    u8g2.clearBuffer();					                        // Speicher Bereinigen

    u8g2.setFont(u8g2_font_spleen6x12_mf);	            // Schriftart der Überschrift
    u8g2.drawStr(5,10,"set Speed:");	                  // Überschrift in den Speicher schreiben
    u8g2.setFont(u8g2_font_spleen32x64_mn);	            // Schriftart für die Werteausgabe
    u8g2.setCursor(0,63);                               // Position der Werteausgabe
    u8g2.print(Display);                                // Schreiben des Wertes in den Speicher
    u8g2.sendBuffer();              					          // Ausgeben der gespeicherten Anzeigewerte auf dem Display
  }
}

This time I leave the Comments in German, to reduce translation errors.

Thank you very much to all of you.

Kind regards, Christian

This lists TimerOne and TimerThree working on the Uno, Leonardo, and Mega:

https://www.pjrc.com/teensy/td_libs_TimerOne.html

The early Teensies used the Leonardo chip, and Paul did a lot of development/maintenance on the AVR ecosystem to make things general and fast.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.