Need to make my serial command array work more efficiently and reliably

I wrote this code to control a robotic arm, it takes 4 numbers separated by a comma in the serial monitor to control the position percentage (each number ranges from 0 to 100). I want to eventually use Processing to create a GUI that will communicate over serial with the same command structure. I have found the code to work pretty well but sometimes it bugs out and only does one of the commands until I restart the Arduino. I think the conversion from the Serial Monitor is not correct when using certain command such as '10,15,25,100' but works well for commands such as '0,0,0,0', '100,100,100,100', '25,25,50,100'. There is a possibility that after a certain amount of commands the Arduino starts acting erratic until I restart it, I programmed a serial flush but it seems to not work properly.

#include <AccelStepper.h>
#include <Servo.h>


#define limitSwitch1 11 
#define limitSwitch2 10 
#define limitSwitch3 9


AccelStepper stepper2(1, 3, 6);
AccelStepper stepper1(1, 4, 7);  
AccelStepper stepper3(1, 2, 5);

Servo gripperServo;  


int stepper1Position,stepper2Position, stepper3Position, gripperPosition;
int newz=0; 
double zpercent=0;
int newy=0;
double ypercent=0;
int newx=0;
double xpercent=0;
int newgrip=0;
double grippercent=0;
int zsteplimit=-1200; 
int zstepspeed=-650;
int ysteplimit=-500; 
int ystepspeed=-400;
int xsteplimit=300;
int xstepspeed=500; 
int griplowlimit=135;
int gripupperlimit=180;

void setup() {
   Serial.begin(115200);
   //Serial.setTimeout(10);

  pinMode(limitSwitch1, INPUT_PULLUP);
  pinMode(limitSwitch2, INPUT_PULLUP);
  pinMode(limitSwitch3, INPUT_PULLUP);

 
  stepper1.setMaxSpeed(6000);
  stepper1.setAcceleration(3000);
  stepper2.setMaxSpeed(6000);
  stepper2.setAcceleration(3000); 
  stepper3.setMaxSpeed(6000);
  stepper3.setAcceleration(3000);

  gripperServo.attach(A0, 600, 2600); 
  gripperServo.write(180);
  delay(1000);
  homing();
}

void loop() {
  if (Serial.available()) {
    String x_read  = Serial.readStringUntil(',');
    xpercent=x_read.toInt()*0.01;
    Serial.read();
    String y_read = Serial.readStringUntil(',');
    ypercent=y_read.toInt()*0.01;
    Serial.read();
    String z_read = Serial.readStringUntil(',');
    zpercent=z_read.toInt()*0.01;
    Serial.read();
    String grip_read  = Serial.readStringUntil('\0');
    grippercent=grip_read.toInt()*0.01;
   
    delay(1000);
    newx= xpercent *xsteplimit;
    newy= ypercent *ysteplimit;
    newz= zpercent *zsteplimit;
    newgrip= griplowlimit+(grippercent*(gripupperlimit-griplowlimit));
    movement();

    delay(1000);
  
  }
}


void serialFlush() {
  while (Serial.available() > 0) {  
    
    Serial.flush();
  }
}

void movement() {
  

  while (stepper1.currentPosition() != newz) {
    stepper1.moveTo(newz);
    while (stepper1.currentPosition() != newz) {
    stepper1.run();
  }}

 while (stepper2.currentPosition() != newy) {
    stepper2.moveTo(newy);
    while (stepper2.currentPosition() != newy) {
    stepper2.run();
  }}
   
  while (stepper3.currentPosition() != newx) {
    stepper3.moveTo(newx);
    while (stepper3.currentPosition() != newx) {
    stepper3.run();
  }}

  while (gripperPosition != newgrip){
    gripperServo.write(newgrip);
    delay(1000);
    gripperPosition = newgrip;
  }

  
  serialFlush();
  }


void homing() {
  
 
  while (digitalRead(limitSwitch1) != 0) {
    stepper1.setSpeed(zstepspeed);
    stepper1.runSpeed();
    stepper1.setCurrentPosition(zsteplimit); 
  }
  delay(20);

  stepper1.moveTo(zsteplimit/2);
  while (stepper1.currentPosition() != zsteplimit/2) {
    stepper1.run();
  }
  stepper1.setCurrentPosition(zsteplimit/2);


  while (digitalRead(limitSwitch2) != 0){
    stepper2.setSpeed(ystepspeed);
    stepper2.runSpeed();
    stepper2.setCurrentPosition(ysteplimit);}
  delay(20);

  stepper2.moveTo(ysteplimit/2);
  while (stepper2.currentPosition() != ysteplimit/2) {
    stepper2.run();
  }
stepper2.setCurrentPosition(ysteplimit/2);


  while (digitalRead(limitSwitch3) != 0) {
    stepper3.setSpeed(xstepspeed);
    stepper3.runSpeed();
    stepper3.setCurrentPosition(xsteplimit); 
  }
  delay(20);

  stepper3.moveTo(xsteplimit/2);
  while (stepper3.currentPosition() != xsteplimit/2) {
    stepper3.run();
  }
  stepper3.setCurrentPosition(xsteplimit/2);
  
  gripperServo.write(180); 
  gripperPosition= 180;
}

If you use e.g. an Uno then the use of String type variables will garble the small memory after some time.

Look at the serial input basics tutorial to see how to receive and parse serial data without using the problematic String class.

Example #5 of the tutorial shows how to receive and parse comma delimited data.

See here for why to not use Strings.

how do you generate data for transmission?
Why you send percentage instead of coordinates?

is there a way to clear the memory or do I have to get a Mega in this case?

I know the limits of the motion, 0 is one extreme and 100 is the other. I am satisfied with 100 increments between each extreme and so I went with a percentage system instead as I am not good at coding. I plan on implementing inverse kinematics (coordinate based direction) into Processing but still send a percentage for each stepper to the Arduino.

As of right now I manually input the percentage for each stepper (i.e: 100,25,25,0)

I thought so.
Instead of sending 100 you send "100".
next input
"percentage_for_x==one_hundred,percentage_for_x==one_hundred,percentage_for_y==one_hundred,percentage_for_z==one_hundred,percentage_for_grip==one_hundred"

No, that's the problem. Use char arrays instead of Strings.

Years (long, long) I wrote a serial calculator for the nano as a bet with a friend. I had numerous issues with software architecture around parsing because different commands took different numbers of parameters. I solved the serial issue in this post:

Don't Cross The Streams (FP scientific calculator serial co-processor) - Community / Exhibition / Gallery - Arduino Forum


int newz = 0;
int newy = 0;
int newx = 0;
int newgrip = 0;

void setup() {
  Serial.begin(115200);
}

void loop() {
  if (Serial.available()) {
    delay(100);

    newx = GetValue();
    newy = GetValue();
    newz = GetValue();
    newgrip = GetValue();

    while (Serial.available())Serial.read();
    Serial.print(newx);
    Serial.print(newy);
    Serial.print(newz);
    Serial.println(newgrip);
  }
}

int GetValue() {
  int value = 0;
  int Char = 0;
  bool isOK = true;
  while (isOK) {
    Char = Serial.read();
    if (Char > 47 && Char < 58) value = value * 10 + Char - 48;
    else isOK = false;
  }
  return value;
}

I think your problem was looking for a null character ('\0') which I don't think you can send through Serial Monitor. You should probably send a Newline character ('\n') at the end of each line and look for that. The Serial Monitor can be set to add a Newline after every line and your Processing GUI can put an '\n' at the end of the output.

This can be replaced with:

int GetValue() 
{
  return Serial.parseInt();
}

or
#define GetValue Serial.parseInt

1 Like

This is perhaps the least reliable way to read serial input. If the ENTIRE line of input is not FULLY received within a window of only 100mSec, data WILL be lost. That makes is all but impossible to manually input commands, even for testing. It also blocks all processing for that 100mSec delay. If the command is not fully received within that 100mSec window, the data you receive WILL be just plain wrong.

Input should be collected in a buffer, and ONLY processed once receipt of an ENTIRE, CORRECTLY FORMATTED string has been confirmed. This typically means waiting for a terminator character (like '\n' or '\r') has been received. Then, and only then, examine the string to ensure it is properly formatted. If it is, parse it, and execute the command. If not, discard it and start over. Never, EVER use delay() to wait for Serial input. That creates far more problems than it solves, and imposes a completely unnecessary constraint on the allowable timing of the command.

1 Like

Completely agree; the following sketch would do it (just convert the strings to whatever type is required):

#include "Streaming.h"

String commandline = "";

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.println("Start");
}

void loop() {
  // put your main code here, to run repeatedly:
  HandleSerial();
}

void HandleSerial(){

  while (Serial.available()){
     char c = Serial.read();
     if (c >= ' ') commandline += c;
     if (c == '\n') {
                    Serial << commandline << "\n\n";
                    SplitAndPrint(',');
                    commandline = "";
                  }
  }

}

void SplitAndPrint(char delimiter){
    int No = 1;
    int p = commandline.indexOf(delimiter);
    while (p > 0 ) {
        String s = commandline.substring(0,p);
        Serial << "This is parameter No " << No << " = " << s << "\n";
        commandline = commandline.substring(p+1);
        p = commandline.indexOf(delimiter);
        No++;
    }
    if (commandline.length() > 0) 
       Serial << "This is parameter No " << No << " = " << commandline << "\n";;
}

Feel free to test it on Wokwi

https://wokwi.com/projects/326496478818206292

Yes, that is much better. However, I very much prefer to avoid Strings (though not for the usual reasons given here..) in favor of good old char arrays, and use strtok() to do the token parsing.

@kolaha ,johnwasser Thanks for the code!. I got it working and it seems to be responding much faster with no errors, going to have to stress test during the week and will update this post accordingly.

@RayLivingston Interesting information. Thanks. I will look into improving the char array. Do you happen to have an example code that doesn't process until after it has parsed correctly.
Would increasing the delay fix the issue as I can wait up to a second between commands.

This is the code that I have updated to include a char array instead of string:

#include <AccelStepper.h>
#include <Servo.h>

#define limitSwitch1 11 
#define limitSwitch2 10 
#define limitSwitch3 9 


AccelStepper stepper2(1, 3, 6); 
AccelStepper stepper1(1, 4, 7);  
AccelStepper stepper3(1, 2, 5); 

Servo gripperServo;  


int stepper1Position,stepper2Position, stepper3Position, gripperPosition;
int newz=0; 
double zpercent=0;
int newy=0;
double ypercent=0;
int newx=0;
double xpercent=0;
int newgrip=0;
double grippercent=0;
int zsteplimit=-1200; 
int zstepspeed=-650; 
int ysteplimit=-500; 
int ystepspeed=-400; 
int xsteplimit=300; 
int xstepspeed=500;
int griplowlimit=135;
int gripupperlimit=180;

void setup() {
   Serial.begin(115200);
  

  pinMode(limitSwitch1, INPUT_PULLUP);
  pinMode(limitSwitch2, INPUT_PULLUP);
  pinMode(limitSwitch3, INPUT_PULLUP);

  
  stepper1.setMaxSpeed(6000);
  stepper1.setAcceleration(3000);
  stepper2.setMaxSpeed(6000);
  stepper2.setAcceleration(3000); 
  stepper3.setMaxSpeed(6000);
  stepper3.setAcceleration(3000);

  gripperServo.attach(A0, 600, 2600);
  gripperServo.write(180);

  delay(1000);
//  homing();
}

void loop() {
  if (Serial.available()) {
    delay(100);
    xpercent=GetValue()*0.01;
    ypercent=GetValue()*0.01;
    zpercent=GetValue()*0.01;
    grippercent=GetValue()*0.01;
    newx= xpercent *xsteplimit;
    newy= ypercent *ysteplimit;
    newz= zpercent *zsteplimit;
    newgrip= griplowlimit+(grippercent*(gripupperlimit-griplowlimit));
   // movement();
    Serial.println(xpercent);
    Serial.println(ypercent);
    Serial.println(zpercent);
    Serial.println(grippercent);
  }
}


void serialFlush() {
  while (Serial.available() > 0) { 
    
    Serial.flush();
  }
}

void movement() {
  


  while (stepper1.currentPosition() != newz) {
    stepper1.moveTo(newz);
    while (stepper1.currentPosition() != newz) {
    stepper1.run();
  }}


 while (stepper2.currentPosition() != newy) {
    stepper2.moveTo(newy);
    while (stepper2.currentPosition() != newy) {
    stepper2.run();
  }}
  
  while (stepper3.currentPosition() != newx) {
    stepper3.moveTo(newx);
    while (stepper3.currentPosition() != newx) {
    stepper3.run();
  }}

  while (gripperPosition != newgrip){
    gripperServo.write(newgrip);
    gripperPosition = newgrip;
  }
  }


void homing() {
  

  while (digitalRead(limitSwitch1) != 0) {
    stepper1.setSpeed(zstepspeed);
    stepper1.runSpeed();
    stepper1.setCurrentPosition(zsteplimit); 
  }
  delay(20);

  stepper1.moveTo(zsteplimit/2);
  while (stepper1.currentPosition() != zsteplimit/2) {
    stepper1.run();
  }
  stepper1.setCurrentPosition(zsteplimit/2);


  while (digitalRead(limitSwitch2) != 0){
    stepper2.setSpeed(ystepspeed);
    stepper2.runSpeed();
    stepper2.setCurrentPosition(ysteplimit); 
  }
  delay(20);

  stepper2.moveTo(ysteplimit/2);
  while (stepper2.currentPosition() != ysteplimit/2) {
    stepper2.run();
  }
stepper2.setCurrentPosition(ysteplimit/2);


  while (digitalRead(limitSwitch3) != 0) {
    stepper3.setSpeed(xstepspeed);
    stepper3.runSpeed();
    stepper3.setCurrentPosition(xsteplimit); 
  }
  delay(20);

  stepper3.moveTo(xsteplimit/2);
  while (stepper3.currentPosition() != xsteplimit/2) {
    stepper3.run();
  }
  stepper3.setCurrentPosition(xsteplimit/2);
  
  gripperServo.write(180); 
  gripperPosition= 180;
}


int GetValue() 
{
  return Serial.parseInt();
}

The serial input basics tutorial shows how to receive an entire message and parse the message. The message is received into a string so to avoid the potential problems associated with Strings. Use of start and end markers help to make reception less prone to errors. The tutorial code is non-blocking, unlike the parseInt() functionn so likely faster.

The code here:

Need to make my serial command array work more efficiently and reliably - Using Arduino / Programming Questions - Arduino Forum

shows the basic idea. Accumulate input in a string until the terminating character is received, THEN parse it, validate it, and handle the command. Do not ever use a while loop, for loop, do-while loop or other looping structure that may be waiting for something that never comes. Do not ever use delay() to waste time hoping all needed input will arrive while the delay() is running. Make the code so it still works properly even if, valid input arrives early or late, or corrupted, or never arrives at all.

Using delay() is virtually NEVER the correct solution, no matter what the problem is.

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