How to controll two stepper motors simultaneously

Hello!

I am working on an art project, and I try to controll two stepper motors from vvvv in windows 7 and Arduino UNO board. My code below (many thanks to Sandy Noble from AccelStepper forum) works with one motor:

#include <AccelStepper.h>

AccelStepper stepper(AccelStepper::HALF4WIRE, 8, 9, 10, 11);

long input = 0;


int measurementInterval = 10;
// when the serial input was last checked.
long lastMeasurementTime = 0L;

void setup()
{ 
  stepper.setMaxSpeed(1000);
  stepper.setAcceleration(10000);
  Serial.begin(115200);
  
  // set timeout otherwise parseInt puts in a long pause.
  Serial.setTimeout(10);
}

void loop(){
  // If current time (millis) is higher than the last time
  // a measurement was taken, PLUS the measurement interval.
  if (millis() > (lastMeasurementTime + measurementInterval)) {
    // then read some integers
    input = Serial.parseInt();
    // and if it is NOT zero, set it as the motor target.
    if (input != 0) {
      stepper.moveTo(input);
    }
    Serial.flush();
    lastMeasurementTime = millis();
  }
    Serial.println(stepper.run());
}

Here is a little video to show how it is working: video. You can see how the stepper follow the cursor of the mouse from the right side of the video.

Now I want to controll two motors from vvvv in a similar way. In vvvv there is a motion detector module which sends a pair of integer values of the detected motion coordinates. I want to receive this positions in Arduino and to transmit to the two steppers. I tried to adapt the code from here where three values sent simultaneously are received in Arduino through Serial.parseint() to control an RGB led. But the way I adapted is not working. Here is the modified code and first I tried to controll the steppers, like in the linked video, manually:

#include <AccelStepper.h>

AccelStepper stepper1(AccelStepper::HALF4WIRE, 8, 9, 10, 11);
AccelStepper stepper2(AccelStepper::HALF4WIRE, 7, 6, 5, 4);

long input1 = 0;
long input2 = 0;

int measurementInterval = 20;

long lastMeasurementTime = 0L;

void setup()
{
  stepper1.setMaxSpeed(1000);
  stepper1.setAcceleration(100);
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(100);
  Serial.begin(115200);
 
  Serial.setTimeout(2);
}

void loop(){


  if (millis() > (lastMeasurementTime + measurementInterval)) {

    input1 = Serial.parseInt();
    input2 = Serial.parseInt();

    if (input1 != 0) {
      stepper1.moveTo(input1);
    }
    if (input2 != 0) {
      stepper2.moveTo(input2);
    }
    Serial.flush();
    lastMeasurementTime = millis();
  }

  Serial.println(stepper2.run());
  Serial.println(stepper1.run());
}

The code it is working for individual “pockets”, in a tested project, with six uses of Serial.parseInt(), to get values of speed,acceleration and position for each motor, sent from vvvv like this: 1000,100,750,2500,250,-450 . But it is not working with a pair of continuously changing positions, controlled by the mouse cursor.

I can not use Serial Monitor to see what is happening, because the port is open in vvvv. So I used an LCD instead. When I change manually the first value from serial message like in the video, the first motor moves accordingly, and the LCD display it correctly. The second value from the LCD is not changing, but the second motor makes little budges, but remains in the same position. When I change the second value from the serial message, the second motor moves accordingly and the second value from LCD display it correctly. But the first (now unchanged) value on LCD is changing randomly and the first motor is moving randomly. So there is some problem with the way the serial messege is interpreted by arduino… Probably this is not the right way, I am not a programmer
Somebody can help me with a suggestion, please?

Why have you stepper.run() inside a call to Serial.println()?

Serial.parseInt() blocks until it receives a value or the timeout expires. That may prevent stepper.run() from being called regularly.

This line

if (millis() > (lastMeasurementTime + measurementInterval)) {

should be

if (millis() - lastMeasurementTime >=  measurementInterval) {

A measurement interval of 20 millisecs is a very short time between data points from the Serial Monitor and also it is a very short time between updates to the stepper information.

Look at the Arduino code in this demo for a means to collect data without any interruptions to other activities and without needing fixed periods between checking for data.

...R

Hi Robin,

As I mentioned, I am not a programmer. But the code example simply doesnt worked when I disabled the Serial.println(stepper.run()); line. I dont understand why... The value of the measurement interval more than 50 caused a twitchy stepper movement.

I was able to control the two steppers with a single serial message which contains speed, acceleration and position values for both motors. Here is a part from that code included in the void loop ():

if (Serial.available() > 0)
    {
         int speed1 = Serial.parseInt(); Serial.print(speed1);Serial.print(",");
         int acc1 = Serial.parseInt();Serial.print(acc1); Serial.print(",");
         int pos1 = Serial.parseInt();Serial.print(pos1);Serial.print("//");
                  
         int speed2 = Serial.parseInt(); Serial.print(speed1);Serial.print(",");
         int acc2 = Serial.parseInt();Serial.print(acc2); Serial.print(",");
         int pos2 = Serial.parseInt();Serial.print(pos2);Serial.println("moved");
            stepper1.setMaxSpeed(speed1);
            stepper2.setMaxSpeed(speed2);
            stepper1.setAcceleration(acc1);
            stepper2.setAcceleration(acc2);
         stepper1.move(pos1);
         stepper2.move(pos2);
      }
    if (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0) // Run if has some distance left to go.
    {
      stepper1.run();
      stepper2.run();
    }

As you can see, here also worked only with Serial.print(speed)/(acc)/(pos) ... included. In this way I was able also to receive and to see the values displayed in the serial monitor or vvvv.

The problem begins when I tried to send constantly changing values like in the video, to controll only one of the two values (the positions). Now I will try to understand the code from the link you posted, and I will return here with the results. Thank you for your suggestions!

anegro:
But the code example simply doesn`t worked when I disabled the Serial.println(stepper.run());

I don't have much time now.

The stepper.run() part is essential. It's the Serial.println() that is not.

Just make the lines

stepper1.run();
stepprt2.run();

...R

Yes, I understand now. In the meantime, I modified the code according to your suggestion. I removed the "Serial.println" and modified the "if (millis() - lastMeasurementTime >= measurementInterval)" line. What I observed is that now I can control the two motors simultaneously if I change both positions in the same time. If I change only one, the according motor moves normally but the another one makes litle budges, though it remains in the same position.

Another thing - the movement of the motor follows the slider very slowly, more slower than in the video, when I was used only one stepper.

anegro:
Yes, I understand now. In the meantime, I modified the code according to your suggestion.

Please post your revised code.

...R

Hi Robin, here is the full code:

#include <AccelStepper.h>

AccelStepper stepper1(AccelStepper::HALF4WIRE, 8, 9, 10, 11);
AccelStepper stepper2(AccelStepper::HALF4WIRE, 7, 6, 5, 4);

long input1 = 0;
long input2 = 0;

int measurementInterval = 20;

long lastMeasurementTime = 0L;

void setup()
{

  stepper1.setMaxSpeed(1000);
  stepper1.setAcceleration(100);
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(100);
  Serial.begin(115200);

  Serial.setTimeout(2);
}

void loop(){

  if (millis() - lastMeasurementTime >=  measurementInterval)  {

    input1 = Serial.parseInt();
    input2 = Serial.parseInt();

    if (input1 != 0) {
      stepper1.moveTo(input1);
    }
    if (input2 != 0) {
      stepper2.moveTo(input2);
    }
    Serial.flush();
    lastMeasurementTime = millis();
  }
  stepper2.run();
  stepper1.run();
}

Another observation: even when the values are changed in the same time, and the two motors moves accordingly, the stepper1 is not moving so smoothly like the another, there is more visible vibration… I changed the motors and the driver boards also, and there is problem always with stepper1.

anegro:
Another observation: even when the values are changed in the same time, and the two motors moves accordingly, the stepper1 is not moving so smoothly like the another, there is more visible vibration... I changed the motors and the driver boards also, and there is problem always with stepper1.

This is almost certainly caused by parseInt() waiting for data and preventing stepper.run from being called regularly. I mentioned this in Reply #1

If I change only one, the according motor moves normally but the another one makes litle budges, though it remains in the same position.

At the moment your code tries to read two values. If it only gets one value it will automatically go into the first variable and therefore will only control the first motor.

Look at how the data is received without blocking in the demos here and here. You will also get some ideas about how to send two values. If you only want to send one value you have to send something that the Arduino can use to tell which motor the number is intended for.

...R

Thank you very much for your help!

I tried now to play with the codes from the links you indicated. They are very usefull for a beginner like me. I tried to adapt my code accordingly:

char inputChar = 'X';
byte inputByte = 255;

const byte buffSize = 32;
char inputSeveral[buffSize]; // space for 31 chars and a terminator

byte maxChars = 12; // a shorter limit to make it easier to see what happens

int input1 = 0; 
int input2 = 0; // I need two integers
char inputCsvString[12];
//===================
#include <AccelStepper.h>

AccelStepper stepper1(AccelStepper::HALF4WIRE, 8, 9, 10, 11);
AccelStepper stepper2(AccelStepper::HALF4WIRE, 4, 5, 6, 7);

//===================

void setup() {
  stepper1.setMaxSpeed(1000);
  stepper1.setAcceleration(100);
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(100);
  Serial.begin(115200);

  Serial.setTimeout(2);
}
void loop() {

  readCSV();

  delay(2);
}

void readCSV() {

  inputSeveral[0] = 0;
  maxChars = buffSize - 1; // use full size of buffer for this function
  byte charCount = 0;  
  byte ndx = 0;        

  if (Serial.available() > 0) {
    while (Serial.available() > 0) { 
      if (ndx > maxChars - 1) {
        ndx = maxChars;
      } 
      inputSeveral[ndx] = Serial.read();
      ndx ++;        
      charCount ++;
    }
    if (ndx > maxChars) { 
      ndx = maxChars;
    }
    inputSeveral[ndx] = 0; 
  }
  char * partOfString; // this is used by strtok() as an index

  partOfString = strtok(inputSeveral,",");      // get the first part - the string
  strcpy(inputCsvString, partOfString); // copy it to inputCsvString

    partOfString = strtok(NULL,","); 
  input1 = atoi(partOfString); // convert this part to an integer

    partOfString = strtok(NULL, ","); 
  input2 = atoi(partOfString);     // convert this part to an integer

  if (input1 != 0) {
    stepper1.moveTo(input1);
  }
  if (input2 != 0) {
    stepper2.moveTo(input2);
  }
  //Serial.flush();
  stepper1.run();
  stepper2.run();

}

I reduced the delay(800) value to be more responsive the movement, even so there is very much delay… I need only two integers to control the motors, but can not remove the first string otherwise it isn`t works! So I added it also in vvvv but remains always the same “A” character without changing. There are many problems though. Please, look at the video from the next link (sorry, this only with my webcam) video2

In the middle, between the two controll bars, there are the values sent through serial. The motors are moving very slow and they not follows always the cursor… And there are more problems…

I tried with other method from here (link to code in the last post). I modified the code a little bit for my setup, and i send the values formatted like this <0200150> where the “<” and “>” are start and end markers, the first value will be not used, the next three is for stepper1 position and the last three for stepper2.

#include <AccelStepper.h>

AccelStepper stepper1(AccelStepper::HALF4WIRE, 8, 9, 10, 11);
AccelStepper stepper2(AccelStepper::HALF4WIRE, 7, 6, 5, 4);
 
// buffer containing the data
char inData[50];
// index used for buffer
byte index;
// set start and end of packet of data sent
#define SOP '<'
#define EOP '>'

bool started = false;
bool ended = false;

// the 3 variables of interest (default values)
long _string = 0, _step1 = 0, _step2 = 0;

 
 
// setup
void setup(){
  stepper1.setMaxSpeed(1000);
  stepper1.setAcceleration(100);
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(100);
  Serial.begin(115200);
memset(inData, '\0', 50);
}
 
 
// loop
void loop() {
// saving serial data into buffer
while (Serial.available() > 0) {
char inChar = Serial.read();
if (inChar == SOP)
{
index = 0;
inData[index] = '\0';
started = true;
ended = false;
}
else if (inChar == EOP)
{
ended = true;
break;
}
else
{
if (index < 49)
{
inData[index] = inChar;
index++;
inData[index] = '\0';
}
}
}
// check packet
if (started && ended)
{
// process the packet
// get string (first char)
_string = inData[0] - '0';
// get step1 (next 3 chars)
char step1Temp[4];
step1Temp[0] = inData[1];
step1Temp[1] = inData[2];
step1Temp[2] = inData[3];
step1Temp[3] = '\0';
_step1 = atoi(step1Temp);
// get step2 (next - the last - 3 chars)
char step2Temp[4];
step2Temp[0] = inData[4];
step2Temp[1] = inData[5];
step2Temp[2] = inData[6];
step2Temp[3] = '\0';
_step2 = atoi(step2Temp);
// Print check for values
Serial.print("String ");
Serial.print(_string);
Serial.print(" stepper1: ");
Serial.print(_step1);
Serial.print(" stepper2: ");
Serial.println(_step2);
// command the stepper
if (_step1 != 0) {
      stepper1.moveTo(_step1);
    }
    if (_step2 != 0) {
      stepper2.moveTo(_step2);
    }
}
 stepper2.run();
 stepper1.run(); 
// reset for the next packet 
started = false;
ended = false;
index = 0;
inData[index] = '\0';
}

But no luck. The motors are not moving, and in serial monitor the Serial.print messeges not appears. Any idea where is the problem, please?

Yet another trying from here (I used the code from the last post). The second stepper moves, but the first one not…

//zoomkat 11-22-12 simple delimited ',' string parse 
//from serial port input (via serial monitor)
//and print result out serial port
//multi servos added 

String readString;
#include <AccelStepper.h>

AccelStepper stepper1(AccelStepper::HALF4WIRE, 8, 9, 10, 11);
AccelStepper stepper2(AccelStepper::HALF4WIRE, 4, 5, 6, 7);
//int step1=0;
//int step2=0;
long step1=0;
long step2=0;

void setup() {
  stepper1.setMaxSpeed(1000);
  stepper1.setAcceleration(100);
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(100);
  Serial.begin(115200);

}

void loop() {

  //expect single strings like 700a, or 1500c, or 2000d,
  //or like 30c, or 90a, or 180d,
  //or combined like 30c,180b,70a,120d,

  while (Serial.available())  {
    char c = Serial.read();  //gets one byte from serial buffer
    if (c == ',') {
      if (readString.length() >0) {
        Serial.println(readString); //prints string to serial port out
        int n = readString.toInt();  //convert readString into a number
        // auto select appropriate value, copied from someone elses code.
        if(n >= 0)
        {
          if(readString.indexOf('a') >0) {
            step1 = readString.toInt(); 
            stepper1.moveTo(step1);
            Serial.print("writing Microseconds1: ");
            Serial.println(step1);
            stepper1.run();
          }
          if(readString.indexOf('b') >0) {
            step2 = readString.toInt(); 
            stepper2.moveTo(step2);
            Serial.print("writing Microseconds2: ");
            Serial.println(step2);
            stepper2.run();
          }
          Serial.flush();
        }
        else
        {   
          Serial.println(n);
        }
        readString="0"; //clears variable for new input
      }
    }  
    else {     
      readString += c; //makes the string readString
    }
  }
  stepper1.run();
  stepper2.run();
}

I know, my coding knowlegde is very elementary. But I want to learn, I`m only waiting for a right direction. I still hope, that someone will pointing me to a working example… or say something usefull about the codes I posted here.

anegro:
I tried now to play with the codes from the links you indicated.

It's not reasonable to produce two completely separate programs and expect advice on both of them. It's just too confusing. Stick at one until you get it to work. You can't learn anything by chopping and changing.

I am only looking at the code in Reply #9

What input have you sent it - can you post an example?

Then I can try it out on my Arduino.

It would be easier to see what is going on if you put the control of the motors in their own function - not in the readCSV() function.

Why not add some Serial.println() statements so you can see what values are being received.

...R

Hi Robin,

I tried to find alternative solution after two days from my previous post without a single answer... I thought, another approach can inspire someone to implicate.

In the 9# post, as I described there, I sent messeges formatted like this: <0200150> where the "<" and ">" are start and end markers, the first value (0) will be not used, the next three(200) is for stepper1 position and the last three (150) for stepper2. But I not only want to send this kind of indivual "packages". In post 8# (and 1#) I linked some videos where you can see how in vvvv I try to controll the steppers with a slider. Every time one value are changed, is sent automatically one pocket with the two positions, probably many times per second. In serial monitor the Serial.print messages doesn`t appear, however on the bord the RX led is flashing when a pocket is sent. And the motors are not moving at all.

anegro:
In the 9# post, as I described there, I sent messeges formatted like this: <0200150> where the “<” and “>” are start and end markers, the first value (0) will be not used, the next three(200) is for stepper1 position and the last three (150) for stepper2.

I see I have screwed up (i.e. got confused by your two codes) - I meant to refer to the code in Reply #8. In it the function is called readCSV() which means that it expects the data items to be separated by commas.

I don’t have much time now. If you can tell me what data you sent for the code in Reply #8 it would be a big help. I will make a note to look at this again later today. The code in Reply #9 is just a rat’s nest with no formatting or anything to make it easy to follow. It would be much easier to help you with the code in Reply #8

The general problem seems to me is that you have blindly grabbed a bit of code without figuring out how it works and how it should be employed.

…R

If you really read that post, and you look at the linked video, you can see exactly what I make. Between the two sliders, which are controlled by the mouse cursor, there is displayed the messege sent through serial. In the post #1 there is another video with only one stepper with the desired behavior. Thanks.

Can't you just tell me so I don't have to look at videos?

Reading one line of text is much quicker.

...R

Hi Robin,

The serial messeges are like these: A,152,-183 or A, 12,184 or A, -25,0. I tried also with comma at the end: A,23,-57, but isn`t better.

However not only the messages are important, but also how they are sent. It is a continuously changing mode, a series of "packages", not only single messages with three values. With a single package of the values sent through Serial Monitor the code works, but with a series of packages from vvvv not. I don`t know how to explain better. A few second of visual explanation sometimes is more than a thousand words, this is why I inserted the link with the video.... Thanks for your patience.

Sorry, but my head is completely confused by all of this. The code you are trying to use was only meant as an illustration - not as a final product - and getting it to work right when you don’t know how to modify it will require a lot of work.

Can I suggest another direction which will give a reliable route without problems. Look at the Arduino code in this demo.

It contains two relevant functions getDataFromPC() and parseData(). Use them (plus their associated global variables) in place of the function readCSV().

You will need to put <> around your data so it looks like <A,152,-183> and you will need to change the last part of parseData() to get a second int rather than a float.

You will see how to use these functions in the rest of the program.

The point about this approach is that it can receive data from the PC whenever it arrives.

…R

PS. I did try (just now) to view your video but it is a file I would have to download, which I am not prepared to do. Also I think it is a Windows file and I don’t use Windows. Put any video you want me to see on YouTube.

Hi Robin,

Now it works with this code:

#include <AccelStepper.h>

AccelStepper stepper1(AccelStepper::HALF4WIRE, 8, 9, 10, 11);
AccelStepper stepper2(AccelStepper::HALF4WIRE, 7, 6, 5, 4);


const byte buffSize = 40;
char inputBuffer[buffSize];
const char startMarker = '<';
const char endMarker = '>';
byte bytesRecvd = 0;
boolean readInProgress = false;
boolean newDataFromPC = false;

char messageFromPC[buffSize] = {0};
int step1 = 0;
int step2 = 0;
unsigned long curMillis;

unsigned long prevReplyToPCmillis = 0;
unsigned long replyToPCinterval = 1000;

// setup
void setup(){
  stepper1.setMaxSpeed(1000);
  stepper1.setAcceleration(100);
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(100);
  Serial.begin(115200);

}


// loop
void loop() {
  curMillis = millis();
  getDataFromPC();
  replyToPC();
  parseData();
}

//=============
void getDataFromPC() {

  // receive data from PC and save it into inputBuffer

  if(Serial.available() > 0) {

    char x = Serial.read();

    // the order of these IF clauses is significant

    if (x == endMarker) {
      readInProgress = false;
      newDataFromPC = true;
      inputBuffer[bytesRecvd] = 0;
      parseData();
    }

    if(readInProgress) {
      inputBuffer[bytesRecvd] = x;
      bytesRecvd ++;
      if (bytesRecvd == buffSize) {
        bytesRecvd = buffSize - 1;
      }
    }

    if (x == startMarker) { 
      bytesRecvd = 0; 
      readInProgress = true;
    }
  }
}

//=============

void parseData() {

  // split the data into its parts

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

    strtokIndx = strtok(inputBuffer,",");      // get the first part - the string
  strcpy(messageFromPC, strtokIndx); // copy it to messageFromPC

  strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
  step1 = atoi(strtokIndx);     // convert this part to an integer

  strtokIndx = strtok(NULL, ","); 
  step2 = atoi(strtokIndx);     // convert this part to a float

  if (step1 != 0) {
    stepper1.moveTo(step1);
  }
  if (step2 != 0) {
    stepper2.moveTo(step2);
  }
  stepper1.run();  
  stepper2.run(); 

}

void replyToPC() {

  if (newDataFromPC) {
    newDataFromPC = false;
    Serial.print("<Msg ");
    Serial.print(messageFromPC);
    Serial.print(" stepper1 ");
    Serial.print(step1);
    Serial.print(" stepper2 ");
    Serial.print(step2);
    Serial.print(" Time ");
    Serial.print(curMillis >> 9); // divide by 512 is approx = half-seconds
    Serial.println(">");
  }
}

Both steppers works well, together and alone also. I also receive in vvvv correctly the replyToPC() message.The single problem is the speed… It is slow.

If you can look at the video from the first post (it is mp4, and Firefox can play it directly without downloading) you can see how fast the stepper follows the coursor. With this new code the speed is not half, what I could expect because of one extra motor, but more slower.

I inserted the stepper command part at the end of void parseData() because I didn`t succed to write a "void controlstepper () " code instead of “void moveservo()” from your original file. Maybe I made again some mistake… :confused:

I modified the initial speed and acceleration settings of the steppers, and now - is OK! The “setMaxSpeed(1000)” is modified to (4000) and “setAcceleration(100)” also to (4000). Is fast almost as in the single stepper example!

One single problem remains, but I think it is related to the driver boards: the motors lose ~40 steps at a full rotation of 400 steps. I need to set ~440 steps to make a full 360°.

Thank you Robin for your help and patience!

PS. First I tried with values fom 0 to 440. Now tried with negative values (from -220 to 220) and the motors somtimes jumps randomly in opposite direction…:frowning: