Trouble reading String from Serial input

    1     //COM3
    2     //pins
    3     int buttonPin = 11;
    4     int button = 0;
    5     int comPin = 12; //Communication pin to other stepper motor
    6     int stoPin = 10; //Pin that stops other stepper motor simultaneously. 
    7     int ledPin = 13;
    8     int pins[] = {2, 5, 3, 4};
    9
    10    //Variables
    11    int stepCount = 0;
    12    String steps = "";
    13
    14    //Functions
    15    void stepUp(int pins[], int comPin, int stoPin, String steps, int stepDelay);
    16    void stepDown(int pins[], int comPin, int stoPin, String steps, int stepDelay);
    17
    18
    19    void setup() {
    20      //Stepper Pins
    21      pinMode(pins[0], OUTPUT);
    22      pinMode(pins[1], OUTPUT);
    23      pinMode(pins[2], OUTPUT);
    24      pinMode(pins[3], OUTPUT);
    25      pinMode(ledPin, OUTPUT); //Built in led for debugging
    26      pinMode(buttonPin, INPUT); //Push button pin
    27
    28      //Start Terminal
    29      Serial.begin(9600);
    30   }
    31
    32
    33    void loop() {
    34      if (digitalRead(buttonPin) == HIGH){ //If button pressed
    35        button = HIGH;
    36      }
    37      if (button == HIGH){
    38        while (Serial.available() > 0) { //While text is available in Serial port
    39          steps = Serial.readStringUntil('\n');
    40          if (steps[0] == 'z'){ //If Z dimension motor is prioritized, send information to other motor
    41            pinMode(comPin, OUTPUT); 
    42            pinMode(stoPin, OUTPUT);
    43            digitalWrite(stoPin, LOW); //If Y dimension motor is prioritized, recieve all information from other motor.
    44          }else if (steps[0] == 'y'){
    45            pinMode(comPin, INPUT);
    46            pinMode(stoPin, INPUT);
    47          }
    48          if (steps[1] == 'd'){ //If 2nd letter is d for down
    49            stepDown(pins, comPin, stoPin, steps, 500);
    50          }else if (steps[1] == 'u'){ //If 2nd letter is u for up
    51            stepUp(pins, comPin, stoPin, steps, 500);
    52          }
    53        }
    54      }
    55    }
    56
    57
    58    //move Z dimension motor down 
    59    void stepDown(int pins[], int comPin, int stoPin, String steps, int stepDelay){
    60      stepCount = 0;
    61      if (steps[0] == 'z'){ //If z is prioritized, send info
    62        while (stepCount < steps.length()){
    63          if (steps[stepCount] == '1'){ //1 represents y motor
    64            digitalWrite(comPin, HIGH);
    65          }
    66          digitalWrite(ledPin, HIGH); //Led for debugging 
    67          digitalWrite(pins[4-stepCount%4], HIGH); //First pin turns on
    68          delay(stepDelay); //Delay controls speed
    69          digitalWrite(pins[4-stepCount%4], LOW); //Turn previous varible off
    70          digitalWrite(ledPin, HIGH);
    71          digitalWrite(comPin, LOW);
    72          stepCount++;
    73        }
    74        digitalWrite(stoPin, HIGH); //Stop other motor aswell
    75        Serial.print(steps); 
    76      } else if (steps[0] == 'y') {  //If y is prioritized, recieve info
    77        while (true){
    78          if (digitalRead(comPin) == HIGH){ //If other ardiuno sent data
    79            digitalWrite(ledPin, HIGH); //led for debugging
    80            digitalWrite(pins[4-stepCount%4], HIGH); //First variable turns on
    82            delay(stepDelay); //Delay controls speed
    82            digitalWrite(pins[4-stepCount%4], LOW); //Turn previous varible off
    83            digitalWrite(ledPin, LOW);
    84          }
    85          if (digitalRead(stoPin) == HIGH)
    86            break;
    87        }
    89      }
    90    }

Hello ya'll,
My code tries to take serial input and control two stepper motors at the same time, by having two Arduinos talk to each other. Each serial input starts with the character z (for z dimension motor) and y (for y dimension motor). This is specifically my z dimension code, so we also check for the letter d for down, and u for up. The next characters of the input consist of a series of ones and zeros, which represents moving the y motor and z motor in a particular order. Lets just say for example my input is "zd001001001001" By using pin 13 for debugging, These are the if statements on lines 40 and 48:

    if (steps[0] == 'z'){
        pinMode(comPin, OUTPUT); 
        pinMode(stoPin, OUTPUT);
        digitalWrite(stoPin, LOW); //If Y dimension motor is prioritized, recieve all information from other motor.
    }
    if (steps[1] == 'd')
        stepDown(pins, comPin, stoPin, steps, 500);

They successfully execute, as expected. However, in the stepDown function, the exact same if statement "if (steps[0] == 'z')" is suggested in line 61, yet it never executes. It can't be a serial issue, because the first if statement works as intended. But since the exact same string is being read, I've got no clue what's going on here. After some further testing, it appears that the string that enters stepDown() is an empty string, which makes no sense to me. I thought it might be taking the initialized version of the steps string, but i tried changing its initialization to an actual word and it gave the same result. The actual string that's being processed is around 1200 characters long, which might have an effect? I'm just grasping at straws at this point.

I have a question. Why did you use void loops in this way? You couldn't have make a void loop like this.

VoidstepUp();{
//whatever you need in this loop
}

Lines 15 and 16 are function prototypes to let the compiler know that the functions exist and that their definitions are elsewhere in the code. Such function prototypes are required by C/C++ but the Arduino IDE will add them to the code as it compiles if you don't supply them. This is done to make things easier for new users

Neither of the function prototypes are loops in any way. The name of each function declaration/definition must be preceded by the name of data type that it will return to the code that called it. If no data is to be returned then the data type is set to void

Your proposed function layout

VoidstepUp();{
//whatever you need in this loop
}

has some faults

  • Void should be void
  • The return data type should not be concatenated with the function name
  • The semicolon most certainly should not be there

Take a look at one of the function definitions in the sketch

void stepDown(int pins[], int comPin, int stoPin, String steps, int stepDelay){
    stepCount = 0;
    if (steps[0] == 'z'){ //If z is prioritized, send info

Do you see the difference ?

Personally I prefer to put each { and } alone on its own line for clarity but the compiler does not care

I tried removing the function prototypes, but there was no difference in performance.

The IDE will have put them back in as part of compiling the code

I'm more confused on how anything even could go wrong here. I'm just reading the string to the function, and it seems like the function just chooses not to listen. I've successfully printed out the string before and after the function is run, but only when the string enters the function is when i have issues. I've tested all other inputs into the function, and they're being read alright. I suspect that there might be some string documentation that I'm unaware of, something relating to how you input it to a function.

When you declare the function like this (word wrapped to make it easier to see)

The caller passes a copy of a pointer to an array, a copy of two int, a copy of a String and a copy of another int. Copying an int is entirely reasonable and the default thing to do; the same can be said for the array. But for a struct/class instance (same thing mostly), this declaration asks for a copy on the stack. For ones that are reasonably small, this is fine.

This applies to String as well: if it is small enough, IIRC the threshold is 14 bytes or less, the bytes are "inside" the object, and 16 bytes total. If larger, then the string has to allocate memory from the heap, and when you make a copy, it duplicates that heap data. (AFAIK, it does not try to intern them.)

But all that is an implementation detail. It is inefficient to copy large objects; it is required only if you want to modify the copy without altering the original, of if the original might somehow be changed "from outside" the function while it is executing. Seems like neither exception applies here.

Since steps is global, you don't have to pass it at all. If you change the signature to

   void stepDown(int pins[], int comPin, int stoPin,
                 int stepDelay) {

the sketch should still compile. The same could be said for the pins array (which was not being copied anyway)

   void stepDown(int comPin, int stoPin, int stepDelay) {

However, the function doesn't have to rely on the fact that some variables are global. You can instead pass the String by reference with &

   void stepDown(int pins[], int comPin, int stoPin,
                 String &steps, int stepDelay) {

And no copy is made either. It is modifiable, if you want. It is sort of like passing a pointer with String *steps, but you can't shoot yourself in the foot by using the pointer inappropriately.

Try one of these three function declarations to see if it behaves differently.

Try printing the length of the steps String that you receive. Is it what you expect ?

what have you got the Line Ending set to in the Serial monitor ?

Yep, that must've been the issue because when I use steps as the global variable it is, it works! Thank you so much.

My program requires Serial input from MATLAB in order give the specific direction of my stepper motors. This is the code:

//COM3
//pins
int Zpins[] = {8, 11, 9, 10};
int oppZpins[] = {10, 9, 11, 8};
int Ypins[] = {2, 5, 3, 4};
int buttonPin = 12;
int ledPin = 13;
int startUp = 920;

//Variables
String steps = "";
int stepDelay = 20;

void setup() {
  //Stepper Pins
  for (int i = 0; i < 4; i++){
    pinMode(Zpins[i], OUTPUT);
    pinMode(Ypins[i], OUTPUT);
  }
  pinMode(buttonPin, INPUT); //Push button pin
  pinMode(ledPin, OUTPUT); //Push button pin

  //Start Terminal
  Serial.begin(9600);
}

void loop() { 
  if (Serial.available() > 0){ //While text is available in Serial port
    steps = Serial.readStringUntil('\n');
    if (steps[0] == 'z'){ //If 1st letter is z for zdim
      if (steps[1] == 'd'){
        motorsMove(Zpins, Ypins);  
      }else{
        motorsMove(oppZpins, Ypins);
      }
    }else if (steps[0] == 'y'){ //If 1st letter is y for ydim
      if (steps[1] == 'd'){
        motorsMove(Ypins, Zpins);  
      }else{
        motorsMove(Ypins, oppZpins);
      }
    }
  }
}
//Move Z dimension motor up 
void motorsMove(int master[], int follower[]){
  int masterCount = 1;
  int followCount = 0;
  while (masterCount < steps.length()){
    digitalWrite(follower[followCount%4], LOW); //First variable turns on
    digitalWrite(master[(masterCount-2)%4], LOW); //Turn previous varible off
    masterCount++;
    if ((steps[0] == 'z' && steps[masterCount] == '1') || (steps[0] == 'y' && steps[masterCount] == '0')){
      digitalWrite(follower[followCount%4], HIGH); //First variable turns on
      digitalWrite(master[(masterCount-2)%4], HIGH); //First variable turns on
      delay(stepDelay);
      followCount++;
    }else{
      digitalWrite(master[(masterCount-2)%4], HIGH); //First variable turns on
      delay(stepDelay);
    }
  }
  Serial.println(steps);
}

From the Serial input, my motors receive a string of characters that start with z or y with the next letter being d or u, followed by a series of zeros and ones. For example, "zd0001001101..."
the characters 'z' and 'y' represent the z dimension motor and y dimension motor, and the d or u represents up or down specifically for the z motor. Each 0 represents a step in the Z dimension, and a 1 represents a steps in the Y dimension.

In the motorsMove function, one of these motors is declared the "master" motor, which is the motor that has the most stepper steps. This motor goes at a constant rate, which is currently a step per 20 milliseconds. The follower motor turns on when its corresponding number appears in the Serial string. For example, if master[] == Zpins[] and follower = Ypins[], then whenever a 1 appeared in the sequence, the follower motor would move. If Ypins[] was the master, it would search for a '0' in the sequence to make the Z motor move.

The master motor seems to work as expected, however its the follower motor that gives me an issue. It moves back and forth seeming randomly, even though i know writing the pins {2, 5, 3, 4} in that order should make it go forward. I even have testing code where i just run the motor and it works perfectly, so i know its not a hardware issue. Here is the testing code:


//pins
int pins[] = {2, 5, 3, 4};
int buttonPin = 12;
int button = LOW;

void setup() {
  //Start Terminal
  Serial.begin(9600);
  for (int i = 0; i < 4; i++){
    pinMode(pins[i], OUTPUT);
    digitalWrite(pins[i], LOW);
  }
  pinMode(buttonPin, INPUT); //Push button pin
}

void loop() {
  if (digitalRead(buttonPin) == HIGH){ //If button pressed
    button = HIGH;
    delay(1000);
  }
  if (button == HIGH){
      for (int i = 0; i < loops; i++){
        digitalWrite(pins[i%4], HIGH); //First variable turns on
        delay(stepDelay); //Delay controls speed
        digitalWrite(pins[i%4], LOW); //Turn previous variable off
     }
  }
}

Note: Although I said the master motor works as expected, the Z motor is usually the master. Therefore, it might be a problem with the Y motor itself, but it worked with the test code, so i haven't a clue what's going on.

This sounds suspiciously like your other topic

It's not. Before I was having trouble receiving serial input from the serial ports. Now I'm getting serial input, but my motors aren't moving as intended based on that input.

It is. There should be only one topic for a project, as all details of the project may be important to help. If you have different problems within one project, stick to the same topic.

1 Like

My question in the other topic has already been solved. do you want me to copy and paste this question into the other topic?

No

I will merge the two topics so that all the information is in the same place

Topics merged

Just from looking at the code ( I cannot test because I don't know your setup and your steppers), I guess it's the followCount++

masterCount is incremented before the output is set HIGH, so with the next loop this output is set to LOW again.
followCount is incremented after the output is set HIGH. With the next loop you reset the wrong output.

I've tried with this code,

void motorsMove(int master[], int follower[]){
  int masterCount = 1;
  int followCount = -1;
  while (masterCount < steps.length()){
    digitalWrite(follower[followCount%4], LOW); //First variable turns on
    digitalWrite(master[(masterCount-2)%4], LOW); //Turn previous varible off
    masterCount++;
    if ((steps[0] == 'z' && steps[masterCount] == '1') || (steps[0] == 'y' && steps[masterCount] == '0')){
      followCount++;
      digitalWrite(follower[followCount%4], HIGH); //First variable turns on
      digitalWrite(master[(masterCount-2)%4], HIGH); //First variable turns on
      delay(stepDelay);
    }else{
      digitalWrite(master[(masterCount-2)%4], HIGH); //First variable turns on
      delay(stepDelay);
    }
  }
  Serial.println(steps);
}

but no good. I have noticed that it goes foward the first step, does nothing the second step, goes backward the 3rd step, and nothing the fourth step. It mostly follows that pattern, but then there are times when it just doesn't, leaving me really confused. But i know it can't be a hardware issue, because I have code that moves correctly that only focuses on one motor. If it helps, here is the first string from serial input. "zd101010101010101101010101010101101010101010101101010101010101101010101010101101010101010101101010101010101101010101010101011010101010101011010101010101011010101010101011010101010101011010101010101011010101010101011010101010101010110101010101010110101010101010110101010101010110101010101010110101010101010110101010101010110101010101010101101010101010101101...."

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