Serial Communication Processing - Arduino, slowed down by stepper motors?

Hello! :grinning:

I have a problem sending and receiving values via serial communication as close to real time as possible, I hope someone can help me out.

I am currently working on a project that uses serial communication to send values from Processing to Arduino. I am taking sound input from my headset attached to my laptop and Processing converts them to values that will be send to Arduino to move 4 (later 6) different stepper motors. (Processing is also visualising a movement, that will be translated to the Motors.)

There should be around 5-8 values/ second send back and forth between processing and arduino while arduino uses them to controle the steppermotors. It doesnt seem that much to me or am I wrong?
Im still fairly new, i did a lot of research and testing and this is what i came up with.

I already removed the delays out of the stepper motors step functions using millis() with examples that I adjusted from the following two links
http://forum.arduino.cc/index.php?topic=277692.0

And I used Robins Serial input basic examples from here
http://forum.arduino.cc/index.php?topic=396450.0

To control the received values, I also send them back from the arduino to processing.
The values are send and received in the right order when no motors are included. But when I add them one by one, the values send back from the arduino to processing are getting slower and slower, data gets lost, overwritten and <> sometimes appear though they shoudlnt.

This is my current code

Processing
the whole code is too long due to the visualisation, so i'll show the parts send

void inhaleCalcSendPrint() {
  calcMotorValues();
  myPort.write("<inhale,"+m1+","+ m2+","+ m3+","+ m4+">");
  println("processing inhale: "+m1+","+ m2+","+ m3+","+ m4+",");
  output.println("processing inhale: "+m1+","+ m2+","+ m3+","+ m4+",");
}

void exhaleCalcSendPrint() {
  calcMotorValues();
  myPort.write("<exhale,"+m1+","+ m2+","+ m3+","+ m4+">"); // t8
  println("processing exhale: "+m1+","+ m2+","+ m3+","+ m4+",");
  output.println("processing exhale: "+m1+","+ m2+","+ m3+","+ m4+","); 
}

and read

void serialEvent (Serial myPort) {
  if (myPort.available() >0) {
    String str = myPort.readString();
    if (str != null) {
      print(str);
      output.print(str);
    }
  }
}

Arduino

class StepperMotor
{ // Class Member Variables
    // These are initialized at startup
    int dirPin;
    int stepPin;
    int motorStep = LOW;
    int motorDir = LOW;
    long OnTime; // its not millis?
    long OffTime;
    unsigned long previousMillis = 0; //will store last time motor was updated
    unsigned long currentMillis = 0;
    int currentSteps = 0;
    int stepsForward = 0;
    int stepsBackward = 0;

    // Constructor - creates a StepperMotor
    // and initializes the member variables and state
  public:
    StepperMotor(int pDirPin, int pStepPin, long on, long off)
    {
      dirPin = pDirPin;
      stepPin = pStepPin;
      pinMode(stepPin, OUTPUT);
      pinMode(dirPin, OUTPUT);
      digitalWrite(stepPin, LOW);

      OnTime = on;
      OffTime = off;

      previousMillis = 0;
      currentMillis = 0;
    }

    void turnMotorForward(int steps) {
      stepsForward = steps;
      digitalWrite(dirPin, HIGH);
      while (currentSteps < stepsForward) { // if
        currentMillis = millis();
        plusStep();
      }
      currentSteps = 0;
    }

    void turnMotorBackward(int steps) {
      stepsBackward = steps;
      digitalWrite(dirPin, LOW);
      while (currentSteps < stepsBackward) { // if
        currentMillis = millis();
        plusStep();
      }
      currentSteps = 0;
    }

    void plusStep() {
      if ((motorStep == HIGH) && (currentMillis - previousMillis >= OnTime)) {
        motorStep = LOW;  // Turn it off
        previousMillis = currentMillis;
        //previousMillis += OnTime;  // Remember the time
        digitalWrite(stepPin, motorStep);
      }
      else if ((motorStep == LOW) && (currentMillis - previousMillis >= OffTime)) {
        motorStep = HIGH;  // turn it on
        previousMillis = currentMillis;
        //previousMillis += OffTime;   // Remember the time
        currentSteps++;
        digitalWrite(stepPin, motorStep);
      }
    }
};

StepperMotor stepperMotorA(2, 3, 1, 1);
StepperMotor stepperMotorB(4, 5, 1, 1);
StepperMotor stepperMotorC(6, 7, 1, 1);
StepperMotor stepperMotorD(8, 9, 1, 1);
//StepperMotor stepperMotorE(10, 11, 10, 10);
//StepperMotor stepperMotorF(12, 13, 10, 10);

const byte numChars = 32;
char receivedChars[numChars];

boolean newData = false;
String parts[4];

void setup() {
  Serial.begin(19200); // 19200
  Serial.println("<Arduino is ready>");
}

void loop() {
  recvWithStartEndMarkers();
  showNewData();
}

void recvWithStartEndMarkers() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;

  while (Serial.available() > 0 && newData == false) {
    rc = Serial.read();

    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      }
      else {
        receivedChars[ndx] = '\0'; // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }

    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}

void showNewData() {
  if (newData == true) {
    Serial.print(receivedChars);
    char* token = strtok(receivedChars, ",");
    char* te = token;
    token = strtok(0, ",");
    int r1 = atoi(token);
    token = strtok(0, ",");
    int r2 = atoi(token);
    token = strtok(0, ",");
    int r3 = atoi(token);
    token = strtok(0, ",");
    int r4 = atoi(token);

    String type(te);

    if (type == "exhale" ) {
      stepperMotorA.turnMotorForward(r1);
      stepperMotorB.turnMotorForward(r2);
      stepperMotorC.turnMotorForward(r3);
      stepperMotorD.turnMotorForward(r4);

    } else if (type == "inhale") {
      stepperMotorA.turnMotorBackward(-r1);
      stepperMotorB.turnMotorBackward(-r2);
      stepperMotorC.turnMotorBackward(-r3);
      stepperMotorD.turnMotorBackward(-r4);
    }
    newData = false;
  }
}

I believe the problem must be with the way i changed the steps method (turnMotorForward/ turn MotorBackward methods) because when i used those methods with delay shown below and set the delays to 0, the values are send and received in the correct order but of course the motors won't turn with the delays = 0

Code with delay part

void showNewData() {
  if (newData == true) {
    Serial.print(receivedChars);
    char* token = strtok(receivedChars, ",");
    char* te = token;
    token = strtok(0, ",");
    int r1 = atoi(token);
    token = strtok(0, ",");
    int r2 = atoi(token);
    token = strtok(0, ",");
    int r3 = atoi(token);
    token = strtok(0, ",");
    int r4 = atoi(token);
    
    String type(te);
    if (type == "exhale" ) {
      turnMotorForwards(dirPinA, stepPinA, r1);
      turnMotorForwards(dirPinB, stepPinB, r2);
      turnMotorForwards(dirPinC, stepPinC, r3);
      turnMotorForwards(dirPinD, stepPinD, r4);

    } else if (type == "inhale") {
      turnMotorBackwards(dirPinA, stepPinA, r1);
      turnMotorBackwards(dirPinB, stepPinB, r2);
      turnMotorBackwards(dirPinC, stepPinC, r3);
      turnMotorBackwards(dirPinD, stepPinD, r4);
    }
    newData = false;
  }
}

void turnMotorBackwards(int dirPin, int stepPin, int steps) {
  //Serial.println(steps);
  digitalWrite(dirPin, LOW);
  for (int i = 0; i > steps; i--) {
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(highDelay);
    digitalWrite(stepPin, LOW);
    
    delay(lowDelay);
  }
}
void turnMotorForwards(int dirPin, int stepPin, int steps) {

  digitalWrite(dirPin, HIGH); // set direction
  for (int i = 0; i < steps; i++) {
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(highDelay);
    digitalWrite(stepPin, LOW);

    delay(lowDelay);
  }
}

Can anyone help me out please? :sweat_smile: :sweat_smile:

It doesnt seem that much to me or am I wrong?

That depends on what the Arduino is supposed to do with the values.

  myPort.write("<inhale,"+m1+","+ m2+","+ m3+","+ m4+">");

Somespaceswouldcertainlymakethatcodemorereadable.

  myPort.write("<exhale,"+m1+","+ m2+","+ m3+","+ m4+">"); // t8

Should that comment read "Roses are read; violets are blue"?

    StepperMotor(int pDirPin, int pStepPin, long on, long off)
    {
      dirPin = pDirPin;
      stepPin = pStepPin;
      pinMode(stepPin, OUTPUT);
      pinMode(dirPin, OUTPUT);

The constructor should NOT be f**king with the mode of the pins. The hardware may not be ready when the constructor is called.

      while (currentSteps < stepsForward) { // if

Useless ass comment.

    char* te = token;

This makes te and token point to the same place. Changing where token points will then change where te points. Later, when you (uselessly) make the String type from what te points to, the type will NOT be inhale or exhale. You must COPY the data that token points to into the te array (NOT pointer).

Your code is blocking. When the number of steps to take is received, the code blocks until those steps have all been taken. If the Processing app sends new data before the Arduino is done stepping, data WILL be lost.

lyhui:
There should be around 5-8 values/ second send back and forth between processing and arduino while arduino uses them to controle the steppermotors. It doesnt seem that much to me or am I wrong?

That does not explain the relationship that you want to have between the data and the movement of the motors.

For example, I have an Arduino program to control 3 steppers on a small CNC lathe. The message that is sent from the PC is deliberately small enough (less than 64 bytes) to fit into the Serial Input Buffer. The system works by getting the message from the buffer and then sending a message to the PC telling it that it can send another message. Then the Arduino moves the motors according to the message it has just taken from the buffer. By the time the movement is finished the next message will be waiting in the Input Buffer. That way the communication does not interfere with the motor movements.

...R

Hi Paul,
thank you for your reply!
The values sent are the amount of steps the motors should take.
There are indeed some useless comments I forgot to delete. iwillalsoadjust the spacing.

    StepperMotor(int pDirPin, int pStepPin, long on, long off)
    {
      dirPin = pDirPin;
      stepPin = pStepPin;
      pinMode(stepPin, OUTPUT);
      pinMode(dirPin, OUTPUT);

do you mean i should not set the pin modes in the constructor at all

digitalWrite(stepPin, LOW);

or only delete this line?

So I changed the parsing method. Will that do? I still have String command(receivedChars); because I thought I'd need to convert the char array to a String to be able to compare it (?)

void parseData() {
  
  // split the data into its parts
  char * strtokIndx; // this is used by strtok() as an index

  strtokIndx = strtok(tempChars, ",");   
  strcpy(receivedChars, strtokIndx); 

  strtokIndx = strtok(NULL, ","); 
  int r1 = atoi(strtokIndx);     

  strtokIndx = strtok(NULL, ",");
  int r2 = atoi(strtokIndx);

  strtokIndx = strtok(NULL, ",");
  int r3 = atoi(strtokIndx);

  strtokIndx = strtok(NULL, ",");
  int r4 = atoi(strtokIndx);

  String command(receivedChars);

  if (command == "exhale" ) {
    stepperMotorA.turnMotorForward(r1);
    stepperMotorB.turnMotorForward(r2);
    stepperMotorC.turnMotorForward(r3);
    stepperMotorD.turnMotorForward(r4);

  } else if (command == "inhale") {
    stepperMotorA.turnMotorBackward(-r1);
    stepperMotorB.turnMotorBackward(-r2);
    stepperMotorC.turnMotorBackward(-r3);
    stepperMotorD.turnMotorBackward(-r4);
  }
}

How can I change the code so it won't be blocking?

Hello Robin,
thank you for your reply!
The data send from processing is the amount of steps the motors should do in one or the other direction. The data sent is a String of around 24 characters so it should be small enough just as in the example you described, right? Your example describes what I would like to do. (Although I would like to coordinate it so that the message should be waiting as little as possible in the Input buffer until its read and moved the motor.) What would I need to change or add to achieve that in my code?

lyhui:
What would I need to change or add to achieve that in my code?

You need to post the complete program - add it as an attachment if it is too long. Even better, make a short program that illustrates the problem.

...R

Hi,
i shortened my processing program enough to fit here, the complete arduino program is in my first post.
I'm afraid the code might be confusing, sorry. In general I take sound from my microphone for in- and outbreaths. I'm assuming that theres a small pause inbetween to distinguish. The duration of one in- and outbreath will be divided, so maybe during one in- or outbreath i'll send 5-8 values to arduino.
(there are 4 types of circles, each for one movement of a motor, also attached the file including method for visualisation which was too long to post)

import processing.sound.*;
import processing.serial.*;

AudioIn input;
Amplitude analyzer;
Serial myPort;
int inStart, exStart;//
int inhalationTime, exhalationTime; 

boolean change = true;
boolean exhale = false; 
int preBreath = 0;
int countdown = 3;
int sTime;
long tempMillis;

int noiseTolerance = 10;
float averageVol;
float ambientNoise;
float currentV;

int circleSize1 = 130; //max size
int circleSize2 = 110;
int circleSize3 = 90;
int circleSize4 = 70;
int circle1, circle2, circle3, circle4; //current size
int upScale, downScale;
int m1, m2, m3, m4;  
int r1, r2, r3, r4;
int rotation1, rotation2, rotation3, rotation4;


void setup() {
  String portName = Serial.list()[1];
  myPort = new Serial(this, portName, 19200);

  frameRate(20);
  size(800, 800);
  stroke(255);

  // Start listening to the microphone
  input = new AudioIn(this, 0);
  input.start();
  analyzer = new Amplitude(this);
  analyzer.input(input);
  sTime = millis()/1000 + countdown;
}

void draw() {
  background(70, 140, 255);
  textSize(50);
  textAlign(CENTER);
  int seconds = sTime - millis()/1000;
  if (seconds >= 0 ) {
    text(seconds, width/2, height/2);
  } else if (preBreath < 2) {
    getAmbientNoise();
    text("breathe", width/2, height/2);
    preBreathCounting();
  }
  //start
  else { 
    upScale = (circleSize1/exhalationTime) * 2;
    downScale = (circleSize1/inhalationTime) * 2;
    currentV = analyzer.analyze();
    currentV = map(currentV, 0, 1, 0, 400);
    //breathing
    if (currentV - ambientNoise >= noiseTolerance) { // noise
      if (!exhale && change) { // inhale start
        inStart = frameCount;
        change = false;
      } else if (exhale && change) { // exhale start
        exStart = frameCount;
        change = false;
      } else if (!exhale && !change) { //still inhaling
        if (circle4 >= downScale ) {
          circle4-=downScale;
        } else { 
          circle4= 0;
        }
        circle3 = circleScaleDown(circle4, circleSize4, circle3);
        circle2 = circleScaleDown(circle3, circleSize3, circle2);
        circle1 = circleScaleDown(circle2, circleSize2, circle1);
        inhaleCalcSendPrint();
      } else if (exhale && !change) { // still exhaling
        if (circle1 <= circleSize1 - upScale ) { 
          circle1 += upScale;
        } else if (circle1 < circleSize1) { 
          circle1 = circleSize1;
        }
        circle2 = circleScaleUp(circle1, circleSize1, circle2, circleSize2);
        circle3 = circleScaleUp(circle2, circleSize2, circle3, circleSize3);
        circle4 = circleScaleUp(circle3, circleSize3, circle4, circleSize4);
        exhaleCalcSendPrint();
      }
    } else { // no noise
      if (tempMillis == 0) {
        tempMillis = millis();
      } else if (millis() - tempMillis > 500) { // breath noise not consistent
        if (!exhale && !change) {
          circle1 = 0;
          circle2 = 0;
          circle3 = 0;
          circle4 = 0;
          tempMillis = 0;
          inhaleCalcSendPrint();
          int x = inhalationTime;
          inhalationTime = frameCount - inStart; 
          inhalationTime = (inhalationTime + x)/2; // update average     
          exhale = true;
        } else if (exhale && !change) {
          circle1 = circleSize1;
          circle2 = circleSize2;
          circle3 = circleSize3;
          circle4 = circleSize4;
          tempMillis = 0;
          exhaleCalcSendPrint();
          int x = exhalationTime;
          exhalationTime = frameCount - exStart; 
          exhalationTime = (exhalationTime + x)/2;
          exhale = false;
        }
        change = true;
      }
    }
  }
}

void preBreathCounting() {
  currentV = analyzer.analyze();
  currentV = map(currentV, 0, 1, 0, 400);

  if (currentV - ambientNoise >= noiseTolerance) { // noise
    if (!exhale && change) { // inhale start
      inStart = frameCount;
    } else if (exhale && change) { // exhale start
      exStart = frameCount;
    }
    change = false;
  } else { // no noise
    if (!exhale && !change) {
      if (preBreath == 1) {
        int x = inhalationTime;
        inhalationTime = frameCount - inStart; 
        inhalationTime = (inhalationTime + x)/2; // update average
      } else {
        inhalationTime = frameCount - inStart;
      }
      exhale = true;
    } else if (exhale && !change) {
      if (preBreath == 1) {
        int x = exhalationTime;
        exhalationTime = frameCount - exStart; 
        exhalationTime = (exhalationTime + x)/2;
      } else {
        exhalationTime = frameCount - exStart; 
        exhale = false;
      }
      preBreath++;
    }
    change = true;
  }
}

void getAmbientNoise() {
  if (averageVol == 0.0 ) {
    averageVol = analyzer.analyze(); 
    ambientNoise = map(averageVol, 0, 1, 0, 400);
  }
}

int circleScaleDown(int bigCircle, int bigCircleSize, int smallCircle) {
  if (bigCircle < bigCircleSize/2) {   
    if ( smallCircle >= downScale) {
      return smallCircle - downScale;
    } else {
      return 0;
    }
  } 
  return smallCircle;
}

int circleScaleUp(int smallCircle, int smallCircleSize, int bigCircle, int bigCircleSize) {
  if (smallCircle >= smallCircleSize/2) {
    if (bigCircle <= bigCircleSize - upScale) { 
      return bigCircle + upScale;
    } else if (bigCircle < bigCircleSize) { 
      return bigCircleSize;
    }
  }
  return bigCircle;
}

void calcMotorValues() { //add method
  if (r1 == 0) {
    rotation1 = int(map(circle1, 0, circleSize1, 0, 200)); //stepper motor 200steps/rev
    m1 = rotation1;
    r1 = rotation1;
  } else {
    rotation1 = int(map(circle1, 0, circleSize1, 0, 200));
    m1 = rotation1 - r1;
    r1 = rotation1;
  }
  if (r2 == 0) { 
    rotation2 = int(map(circle2, 0, circleSize2, 0, 200));
    m2 = rotation2;
    r2 = rotation2;
  } else {
    rotation2 = int(map(circle2, 0, circleSize2, 0, 200)); 
    m2 = rotation2 - r2;
    r2 = rotation2;
  }
  if (r3 == 0) {  
    rotation3 = int(map(circle3, 0, circleSize3, 0, 200));
    m3 = rotation3;
    r3 = rotation3;
  } else {
    rotation3 = int(map(circle3, 0, circleSize3, 0, 200));
    m3 = rotation3 - r3;
    r3 = rotation3;
  }
  if (r4 == 0) {
    rotation4 = int(map(circle4, 0, circleSize4, 0, 200));
    m4 = rotation4;
    r4 = rotation4;
  } else {    
    rotation4 = int(map(circle4, 0, circleSize4, 0, 200));
    m4 = rotation4 - r4;
    r4 = rotation4;
  }
}

void serialEvent (Serial myPort) {
  if (myPort.available() >0) {
    String str = myPort.readString();
    if (str != null) {
      print(str);
    }
  }
}

void inhaleCalcSendPrint() {
  calcMotorValues();
  myPort.write("<inhale," + m1 + "," + m2 + "," + m3 + "," + m4 + ">");
  println("processing inhale: " + m1 + "," + m2 + "," + m3 + "," + m4 + ",");
  println();
}

void exhaleCalcSendPrint() {
  calcMotorValues();
  myPort.write("<exhale," + m1 + "," + m2 + "," + m3 + "," + m4 + ">"); 
  println("processing exhale: " + m1 + "," + m2 + "," + m3 + "," + m4 + ",");
  println();
}

breathing_visualisation.pde (8.13 KB)

lyhui:
the complete arduino program is in my first post.

I am not familiar with Processing so I won't comment on that part.

For your application I think your code in loop() needs to be like this

void loop() {
  moveMotors();
  getData();
}

and the code in getData() should be something like this

void getData() {
   if (motorMovesComplete == true) {
     if (Serial.available() >= NNN) { // assumes message is known to be NNN bytes long
       while (Serial.available() > 0 && newData == false) {
          // etc
       }
       Serial.println("M"); // tell the PC to send the data for the next move
       motorMovesComplete = false;
     }
}

In other words no attempt is made to get the data until the moves are complete.

...R

I still have String command(receivedChars); because I thought I'd need to convert the char array to a String to be able to compare it (?)

Either that or get off your lazy ass and learn to use strcmp().

How can I change the code so it won't be blocking?

Stop calling blocking methods. Your stepper class is full of blocking functions. Why you felt the need to create ANOTHER blocking stepper class is a mystery. There are non-blocking stepper libraries around.

PaulS:
Why you felt the need to create ANOTHER blocking stepper class is a mystery.

I work on the assumption that someone who has sufficient knowledge to create a class knows what he wants :slight_smile:

...R

Well, I know I suck at programming but I am trying. Clearly wasn't enough for you. Thank you for your suggestions so far Paul, but don't feel obligated to answer :slight_smile: If someone wants to help, I appreciate it, if not that's fine too.

Thanks Robin, I'll try that