Continuously adjusting motor speed while running

Hello! I am trying to adjust the speed of a motor while it's running. The Arduino receives a string from PC to set the speed, acceleration, and running for three motors. In particular, the string has the following format: <mode, motorID, value1, value2, value3>.

  • "mode" can be "SPEED", "ACCEL", and "RUN"
  • "motorID" is a combination of integers 1, 2, and 3 (corresponds to three motors)
  • "value1", "value2", and "value3" are the parameters set for motors under a specified mode. For example, if the "mode" is "SPEED", the values are motor speeds.
    What I want the motors to do is adjusting the speed while running. So I send the following commands to Arduino:
  • 1st command: <SPEED,1,100,0,0> //set speed for motor 1
  • 2nd command: <ACCEL,1,5000,0,0> //set accel for motor 1
  • 3rd command: <RUN,1,10000,0,0> //move 10000 steps from the current position for motor 1
  • 4th command: <SPEED,1,1000,0,0> //change the speed for motor 1

After sending the first three commands, the motor can work as expected. If we assume the time that Arduino received the 3rd command as t=0s, and we send the 4th command at t=10s, I want motor 1 can increase its speed from 100 to 1000 to finish the remaining steps (roughly 10000-100x10=9000 steps). However, it seems that my code can't set the speed 1000 for motor 1. Can anyone help me with this? Thanks in advance!

#include <AccelStepper.h>

// Define the pins for each motor
#define X_DIR 5
#define X_STP 2

#define Y_DIR 6
#define Y_STP 3

#define Z_DIR 7
#define Z_STP 4

// Define the constants for the motors
#define X_SPEED 1000 // X steps per second
#define Y_SPEED 1000 // Y
#define Z_SPEED 1000 // Z

#define X_ACCEL 5000.0 // X steps per second per second
#define Y_ACCEL 5000.0 // Y
#define Z_ACCEL 5000.0 // Z

// Define the baud rate of the Arduino
#define BAUD_RATE 230400

// ---------------
// Motor variables

int motorID = 0;
int motors[3] = {0,0,0};

AccelStepper stepper1(AccelStepper::DRIVER, X_STP, X_DIR);
AccelStepper stepper2(AccelStepper::DRIVER, Y_STP, Y_DIR);
AccelStepper stepper3(AccelStepper::DRIVER, Z_STP, Z_DIR);

AccelStepper steppers[3] = {stepper1, stepper2, stepper3};

// -----------------------
// Communication variables

const byte buffSize = 64;

char inputBuffer[buffSize];
char mode[buffSize] = {0};
float value1 = 0.0;
float value2 = 0.0;
float value3 = 0.0;

boolean readInProgress = false;
boolean newDataFromPC = false;

// ----------------
// Action variables

float values[3];

//=================
// PUBLIC FUNCTIONS
//=================

// ----------------------------------------------------
// Function called once to initialise the Arduino board
void setup() {
  
  // Initialise the baud rate of the Serial port
  Serial.begin(BAUD_RATE);

  // Set the initial max speed of the system
  stepper1.setMaxSpeed(X_SPEED);
  stepper2.setMaxSpeed(Y_SPEED);
  stepper3.setMaxSpeed(Z_SPEED);

  // Set the acceleration of the system
  stepper1.setAcceleration(X_ACCEL);
  stepper2.setAcceleration(Y_ACCEL);
  stepper3.setAcceleration(Z_ACCEL);

  // Set the Arduino as ready
  Serial.println("<READY>");
  
}

// ---------------------------------------------
// Function called repeatedly to run the Arduino
void loop() {
    
      // Get the data from the PC
      getDataFromPC();

      if(newDataFromPC){
         executeThisFunction();
      }
  
}

const char startMarker = '<';
const char endMarker = '>';

byte bytesRecvd = 0;

// -------------------------------------------------
// Read the serial port and extract commands from it
void getDataFromPC() {
  
  // Check if data is coming through the serial port
  if (Serial.available() > 0)
  {

    // Read the next incoming character
    char x = Serial.read();

    // Detect the end marker and call the parser - Needs to be 1st if statement
    if (x == endMarker) {
      readInProgress = false;
      newDataFromPC = true;
      
      // Clear the buffer
      inputBuffer[bytesRecvd] = 0;
      
      return parseData();
    }

    // Save the current character in memory - Needs to be 2nd if statement
    if (readInProgress) {
      
      // Add the character to the buffer
      inputBuffer[bytesRecvd] = x;
      bytesRecvd ++;

      // Correct if size is exceeding buffer size
      if (bytesRecvd == buffSize) {
        bytesRecvd = buffSize - 1;
      }
    }

    // Detect the start marker and collect characters - Needs to be 3rd if statement
    if (x == startMarker) {
      bytesRecvd = 0;
      readInProgress = true;
    }
    
  }
}

// -----------------------
// Parse the incoming data
void parseData() {

  char * strtokIndx; // this is used by strtok() as an index

  // Extract the MODE
  strtokIndx = strtok(inputBuffer, ",");
  strcpy(mode, strtokIndx);

  // Extract the MOTORID
  strtokIndx = strtok(NULL, ",");
  String motorstr(strtokIndx);

  // Convert into an integer
  motorID = atoi(strtokIndx);

  // Enable the motors
  toggleMotors(motorstr);

  // Extract the VALUE for motor 1
  strtokIndx = strtok(NULL, ",");
  value1 = atof(strtokIndx);

  // Extract the VALUE for motor 2
  strtokIndx = strtok(NULL, ",");
  value2 = atof(strtokIndx);

   // Extract the VALUE for motor 3
  strtokIndx = strtok(NULL, ",");
  value3 = atof(strtokIndx);

  // Set the "value 1-3" for array "values"
  values[0] = value1;
  values[1] = value2;
  values[2] = value3;

  // Communicate back to the computer
  newDataFromPC = true;
  
}

// Select the function to execute
void executeThisFunction() {
  
  // Go to the SPEED setting function
  if (strcmp(mode, "SPEED") == 0) {
    updateSpeed();
  }

  // Go to the ACCEL setting function
  else if (strcmp(mode, "ACCEL") == 0) {
    updateAccel();
  }

  // Go to the RUN function
  else if (strcmp(mode, "RUN") == 0) {
    runFew();
  }

}

void toggleMotors( String motorstr ) {
  
  if(motorstr.indexOf("1") >= 0) {
    motors[0] = 1;
  }
  if(motorstr.indexOf("2") >= 0) {
    motors[1] = 1;
  }
  if(motorstr.indexOf("3") >= 0) {
    motors[2] = 1;
  }  
}

// Update the speed of the motor
void updateSpeed() {
  
  switch (motorID) {      
    
    // Apply settings to motor 1
    case 1:
        stepper1.setMaxSpeed(value1);
        Serial.println("update speed for motor 1");
      break;

    // Apply settings to motor 2
    case 2:
        stepper2.setMaxSpeed(value2);
      break;

    // Apply settings to motor 3
    case 3:
        stepper3.setMaxSpeed(value3);
      break;
  }
  
  // Reset all variables
  clearVariables();
}


// --------------------------------
// Update the accel of the motor
void updateAccel() {

  switch (motorID) {

    // Apply settings to motor 1
    case 1:
        stepper1.setAcceleration(value1);
        Serial.println("update accel for motor 1");
      break;

    // Apply settings to motor 2
    case 2:
        stepper2.setAcceleration(value2);
      break;

    // Apply settings to motor 3
    case 3:
        stepper3.setAcceleration(value3);
      break;
  }
  
  // Reset all variables
  clearVariables();
}



// ------------------------------------
// Clear all variables after the action
void clearVariables() {
  
  memset(mode,0,sizeof(mode));
  motorID = 0;
  value1 = 0.0;
  value2 = 0.0;
  value3 = 0.0;
}

// Run the motors to move the syringes
void runFew() {
  
  AccelStepper steppers[3] = {stepper1, stepper2, stepper3};

  // Set the distance, and apply to the motor
  for (int i = 0; i < 3; i += 1) {
    if (motors[i] == 1) {
      steppers[i].move(values[i]);
      Serial.println("update move steps");
    }
  }

  clearVariables();

  
  // Initialise the motor status for the run loop
  int stepperStatus[3] = {0, 0, 0};

  // Start the main loop
  while (array_sum(stepperStatus, 3) != array_sum(motors, 3)) {
    
    // Iteration over the 3 steppers
    for (int i = 0; i < 3; i += 1) {
      
      // Check if the stepper is selected
      if (motors[i] == 1) {
        
        // Ask the stepper to move to position at constant speed.
        if (stepperStatus[i] == 0 ) {
          steppers[i].run();
        }
        
        // Check if it reached it's position - if yes, set the status
        if (steppers[i].distanceToGo() == 0) {
          stepperStatus[i] = 1;
        }
      }
    }
    
    getDataFromPC();
      if (strcmp(mode, "SPEED") == 0){
        updateSpeed();
      }   
      if (strcmp(mode, "STOP") == 0){
        //return stopAll();
        break;
      } 
  }
  
  Serial.println("Jump while");
  clearVariables();
}


You have a wait loop, waiting for all active motors to complete their motion before going back to look for more commands.

Put the stepperN.run() calls at the top of loop() so the stepper can do their thing while you look for more input. That will allow you to move (change the destination), change speed, or stop while the steppers are moving.

Another problem is the time it takes to run the loop() which will limit the maximum speed.
See the complete stepper example in my tutorial on [Multi-tasking in Arduino] (Simple Multitasking Arduino on any board without using an RTOS) which shows you how to measure how fast your stepper can run and how to get it to run faster.
(Tip. call accelstepper.run() more often)
The stepper example is controlled by user input and a temperature sensor.

Hi, thanks for your reply. I am sorry that I didn't get your point. It's true that the condition of the while loop is waiting for all active motors to complete their motion (for the 3rd command, motor 1 should run 10000 steps). However, I called the getDataFromPC() inside the while loop to look for a new speed parameter. For example, if the Arduino receives the 4th command (<SPEED,1,1000,0,0>) at the end of the first iteration of the while loop, I think the speed should be set as 1000 during the second iteration of the while loop. Am I wrong? Could you please explain a little bit and give me more details?

What is the Serial Monitor saying is happening? When you send the "<SPEED,1,1000,0,0>" command, does it reply with: "update speed for motor 1"?

Could you put in some more Serial output, like logging the command received from the PC.

Hi John! Good to hear you again. Yes, after sending <SPEED,1,1000,0,0>, I can see "update speed for motor 1" from the serial monitor, which means stepper1.setMaxSpeed(1000) should have been executed. However, I can't see a speed change.

If I send <SPEED,1,1000,0,0> many times, I can also see these commands are received by arduino by observing the "update speed for motor 1".

Your sketch seems to be behaving correctly (after I added the missing array_sum() function). It looks like the AccelStepper library does not act on a change in stepper.setMaxSpeed() immediately. It doesn't even respond to stepper.setSpeed() immediately. There may some combination for library calls that will get your motor to change speed in the middle of a move, but I can't find it. I tried calling setSpeed() before and after setMaxSpeed() and that did not change anything. I know from the library sources that if the new value is the same as the old value the functions do nothing.

Good luck.

Hi John! I am sorry that I forgot to upload the array_sum() function. Thank you very much for debugging my code. Really appreciate your time.

Hi, thank you very much for pointing out this issue, and your tutorial is very helpful.

All speed changes are limited by the acceleration setting. Only the run() method actually updates the stepper steps.

You'd think that at an acceleration of 5000 it would not take long for the speed to change. The 10,000 steps starting at 100 steps per second and switching to 1000 steps per second always takes 100 seconds.

Can you come up with a way to get the speed to change before the move is complete? Of course it should be subject to acceleration.

AccellStepper is position controlled so that can interfere with your demanded speed.
If you want a speed control, with acceleration settings, try my Speed Control library
Stepper Speed Control Library project with plotting
This is still limited by position so you need to set the end stops well out of the way the get the speed.