How to transfer integers via serial port and store them in six arrays using Uno

Hi there!

My question is related with receiving integers via serial port and store them in six different arrays using UNO. The integers can change between 0 and 180 (they are used to control angular positions of six servo motors). The six integer' arrays must save incoming integers which are transmit by VB6 application via USB serial port. Every array stores the angular positions of only one servo motor. The servo's positions can be varying between 10 (at least) and reach up to 30, so every of these 6 arrays must have up to 30 elements.
My problem is, that every time the number of the servo motors positions can be different. For instance, I can have 15 combinations of different positions, i.e. 6 different integers between 0 and 180 in the first element of the arrays, then next combination of 6 different integers in the second elements, and so on until the 15-th arrays element.
Using VB6 app, and some tutorials like this, and this, that I have found here, helped me to create the following code (My code is based on the second tutorial. The first is not applicable to me, because takes great amount of UNO's dynamic memory in my case):

int servo01SP[30], servo02SP[30], servo03SP[30], servo04SP[30], servo05SP[30], servo06SP[30]; // for storing positions/steps
int speedDelay = 10;
int index = 0;
int dataIn;
const byte numChars = 30;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

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

        if (m == 10) {
          recvWithStartEndMarkers();
           if (newData == true) {
                strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            //   because strtok() used in parseData() replaces the commas with \0
        parseData();
        showParsedData();
        newData = false;
    }
        }


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 parseData() {      // split the data into its parts

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

    strtokIndx = strtok(tempChars,",");      // get the first part - the string
    strcpy(messageFromPC, strtokIndx); // copy it to messageFromPC
    servo01SP[0] = messageFromPC;
 
  for (int i = 1; i < 30; i++) {
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
//    integerFromPC = atoi(strtokIndx);     // convert this part to an integer
    servo01SP[i] = atoi(strtokIndx);
//    servo01SP[i] = integerFromPC;
    delay (20);
  }

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

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

}

void showParsedData() {
//    Serial.print("String ");
//    Serial.println(messageFromPC);

for (int i = 0; i < 30; i++) {
     Serial.println(servo01SP[i]);
     delay (20);
  }    

}

I can send via serial port strings, formatted like this "<95,95,-5,-5,-5,-5,-5,-5,79,79,79,79,79,99,99>" by VB6 app. But UNO return the string data like this:

10
9      <- ?!?
256  <-?!?
95   - OK
-5   - OK
-5   - OK
-5   - OK
-5   - OK
-5   - OK
-5   - OK
79   - OK
79   - OK
7     - OK
79   - OK
79   - OK
99   - OK
99 - OK
0
0
0
0
0

My questions are, related with Arduino code:

  1. How can I reliably parse the different incoming integers and store all them into given array?
  2. How can I distinguish the different incoming strings - which integer sequence in which of the six arrays must be stored?

Thank you in advance!

I would suggest you change the data format you are sending from your VB app. As the first parameter, include which servo you are addressing.
<1,95,95,.....> to indicate this is servo #1.

If the number of integers can vary as well, send that as the second parameter
<1,5, 10,20,30,40,50> to indicate servo #1 and there are 5 integers

Thank you for your suggestion blh64 !

I have reworked the VB6 app , and now the transmitted strings looks like follows:
<1,26,95,1,1,1,1,1,1,79,79,79,79,79,99,99,95,1,1,1,1,1,1,79,79,79,95>

First integer "<1," correspond to the number of servo (in the example it is Servo 1). The second integer "26" shows how many integers left to the end of the string. From third integer to the end are integers which related with the Servo 1 angular positions.

In the VBA6 app I have added six buttons in order to transmit six (similar as the above one) strings, each related to each of the six servos.

UNO code also have been modified as follow (but still based on the example 5 from this post " Receiving and parsing several pieces of data" ):

int servo01SP[26], servo02SP[26], servo03SP[26], servo04SP[26], servo05SP[26], servo06SP[26]; // for storing positions/steps
const byte numChars = 130;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

// variables to hold the parsed data
char messageFromPC[numChars] = {0};
int integerFromPC = 0;
boolean newData = false;


        //When button Transmit is pressed
        if (m == 10) {
          recvWithStartEndMarkers();
          if (newData == true) {
            strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            //   because strtok() used in parseData() replaces the commas with \0
            parseData();
            showParsedData();
            newData = false;
          }
        }

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 parseData() {      // split the data into its parts
char *strtokIndx; // this is used by strtok() as an index
int itter = 26; // Number of itterations for filling arrays elements

  strtokIndx = strtok(tempChars, ",");     // get the first part - the string
  strcpy(messageFromPC, strtokIndx); // copy it to messageFromPC
  
  if (messageFromPC[0] == 1) {
    for (int i = 2; i < itter; i++) {
      strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
      servo01SP[i] = atoi(strtokIndx);
      delay (10);
    }
  } 
  if (messageFromPC[0] == 2) {
    for (int i = 2; i < itter; i++) {
      strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
      servo02SP[i] = atoi(strtokIndx);
      delay (10);
    }
  } 
   if (messageFromPC[0] == 3) {
    for (int i = 2; i < itter; i++) {
      strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
      servo03SP[i] = atoi(strtokIndx);
      delay (10);
    }
  } 
  if (messageFromPC[0] == 4) {
    for (int i = 2; i < itter; i++) {
      strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
      servo04SP[i] = atoi(strtokIndx);
      delay (10);
    }
  } 
   if (messageFromPC[0] == 5) {
    for (int i = 2; i < itter; i++) {
      strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
      servo05SP[i] = atoi(strtokIndx);
      delay (10);
    }
  } 
  if (messageFromPC[0] == 6) {
    for (int i = 2; i < itter; i++) {
      strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
      servo06SP[i] = atoi(strtokIndx);
      delay (10);
    }
  }
}

void showParsedData() {
  Serial.print("Message: ");
  Serial.println(messageFromPC);
  Serial.print("Received chars: ");
  Serial.println(receivedChars);
  Serial.print("Temp chars: ");
  Serial.println(tempChars);
  Serial.print("Servo 1 Array: ");
  for (int i = 0; i < 38; i++) {
    Serial.print(servo01SP[i]+",");
}

If I execute above code and send the formatted like above string, I receive the following from the serial :

<1,26,95,1,1,1,1,1,1,79,79,79,79,79,99,99,95,1,1,1,1,1,1,79,79,79,95> // This is the integer string formatted by VB6
10 // -> this trigger execution of void procedures "recvWithStartEndMarkers()" , "parseData()" and "showParsedData() "
1 // -> I have no idea what is this !??
Message: 1 // -> the number of the Servo (i.e. the first integer from the string)
Received chars: 1,26,95,1,1,1,1,1,1,79,79,79,79,79,99,99,95,1,1,1,1,1,79,79,79,95 // received integer in the "receivedChars". they are the same as transmitted from VB6 integers in the string
Temp chars: 1 // -> again the number of the Servo (i.e. the first integer from the string)
Servo 1 Array: ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, // and here is the problem !!!

And my question is: Why Servo array (i.e. servo01SP[]) is empty ?!?
I receive the same result if I try to transmit angular positions to the other five servo0(2,3,4,5,6)SP[] arrays.

You are missing one important point from example 5 - atoi().

This function turns the ascii string you get from Serial into a number. The string "1" is not the same as the integer 1 so all your if()s are false.

You are also not using your second parameter to know how many integers are coming next - you just hardcode 26.

And finally, since you have 6 objects, it is much better to put them into an array and since each object is an array already, you can create an array of array and make things much simpler.

//int servo01SP[26], servo02SP[26], servo03SP[26], servo04SP[26], servo05SP[26], servo06SP[26]; // for storing positions/steps

const int servoCount = 6;
const int elementCount = 26;
int servoSP[servoCount][elementCount];
const byte numChars = 130;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

// variables to hold the parsed data
//char messageFromPC[numChars] = {0};
//int integerFromPC = 0;
boolean newData = false;


//When button Transmit is pressed
if (m == 10) {
  recvWithStartEndMarkers();
  if (newData == true) {
    strcpy(tempChars, receivedChars);
    // this temporary copy is necessary to protect the original data
    //   because strtok() used in parseData() replaces the commas with \0
    parseData();
    newData = false;
  }
}

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 parseData() {      // split the data into its parts
  char *strtokIndx; // this is used by strtok() as an index

  int servo;   // the servo number to address
  int elements;   // Number of elements for filling arrays elements

  strtokIndx = strtok(tempChars, ",");     // get the first part - the string
  servo = atoi(strtokIndx) - 1; // servo to address (0 based indexing)
  if (servo < 0 or servo >= servoCount ) {
    Serial.print( "Bad Servo index" );
    return;
  }
  
  strtokIndx = strtok(tempChars, ",");
  elements = atoi(strtokIndx); // servo to address
  if (elements < 0 or elements >= elementCount ) {
    Serial.print( "Bad element count" );
    return;
  }

  for (int i = 0; i < elements; i++) {
    strtokIndx = strtok(NULL, ",");
    servoSP[servo][i] = atoi(strtokIndx);
  }
  // blank remaining elements of less than max were sent
  for (int i = elements; i < elementCount; i++) {
    servoSP[servo][i] = 0;
  }

  showParsedData(servo,elements);
}

void showParsedData(int servo, int elements) {
  Serial.print("Message: ");
  Serial.println(receivedChars);
  Serial.println("Parsed Data: ");
  Serial.print("Servo: ");
  Serial.println(servo+1);
  Serial.print("Elements: ");
  Serial.println(elements);
  Serial.print("Servo Array: ");
  for (int i = 0; i < elementCount; i++) {
    Serial.print(servoSP[servo][i]); Serial.print(",");
  }
}

Thank you blh64 for your efforts to help me!

Now the code looks much more elegant... but I still encounter some problems. They are:

  1. The UNO can't read properly the second integer form the string, which is related with the number of angular positions, and it still can't fill the arrays with data. You can see the result of the code execution below:
After the VB6 button Send to Servo 1 is clicked:

<1,15,95,1,1,1,1,1,1,79,79,79,79,79,99,99>
10
Msg: 1,15,95,1,1,1,1,1,1,79,79,79,79,79,99,99
Parsed Data: 
Servo: 1
Elements: 1 // -> elements must be 15!? 
Srv Array: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 
___________________________________________________________

After the VB6 button Send to Servo 3 is clicked:
<3,15,84,84,154,154,154,154,154,154,154,154,154,90,90,90>
10
154
Msg: 3,15,84,84,154,154,154,154,154,154,154,154,154,90,90,90
Parsed Data: 
Servo: 3
Elements: 3 // -> elements must be 15!?
Srv Array: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,

And as you can see, the six servo arrays "servoSP[servo]" contains 26 zeros as result. In the showParsedData(servo,elements); procedure I can't see any reference to the variable with parsed data after Serial.println("Parsed Data: "); !?
2. The second problem is related with changing the names of arrays from static "servo01SP, servo02SP, ..." to dynamic “servoSP[servo]". I see that your intention was to simplify code, and it looks much more elegant now, but unfortunately this is not useful to me. The arrays "servo01SP, servo02SP..." are defined as global arrays in my project because they are used in other procedures of the UNO code. In my previous posts, I gave only those parts of the code which are related to data transfer, but overall project code contains much more. If both dynamic and static arrays remain, they occupy double amount of the UNO’s dynamic memory, and when I try to send the code, the compiler return:
```

  • Sketch uses 13538 bytes (41%) of program storage space. Maximum is 32256 bytes.
    Global variables use 1623 bytes (79%) of dynamic memory, leaving 425 bytes for local variables. Maximum is 2048 bytes.
    Low memory available, stability problems may occur.*
    ```
    So, it will be good these arrays keep as they are defined as static, but the main problem remains: static or dynamic, they still do not receive the parsed angular positions of the servos?!

Change those parts of the code to use the new multi dimensional array then?
The fact that you have given the arrays all generic names “servoXXSP” seems to indicate that the code may be identical anyway. If not they should/would have different names, servoLeftArm, servoRightLeg, servoHead, etc....

Or create an array of array pointers and keep both methods of access...

 int servo01SP[26], servo02SP[26], servo03SP[26], servo04SP[26], servo05SP[26], servo06SP[26]; 
int* servoSP[] = { servo01SP, servo02SP, servo03SP, servo04SP, servo05SP, servo06SP };

PS: A dynamic array is something quite different.

The second strtok() function should have passed a NULL, not tempChar again.

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

  int servo;   // the servo number to address
  int elements;   // Number of elements for filling arrays elements

  strtokIndx = strtok(tempChars, ",");     // get the first part - the string
  servo = atoi(strtokIndx) - 1; // servo to address (0 based indexing)
  if (servo < 0 or servo >= servoCount ) {
    Serial.print( "Bad Servo index" );
    return;
  }
 
  strtokIndx = strtok(NULL, ",");  //   <---- changed this line
  elements = atoi(strtokIndx); // servo to address
  if (elements < 0 or elements >= elementCount ) {
    Serial.print( "Bad element count" );
    return;
  }
//....

Thank you very much blh64 and pcbbc for your help!
I think that everything is OK now.

After the arrays redefinition as: "int* servoSP[] = { servo01SP, servo02SP, servo03SP, servo04SP, servo05SP, servo06SP };", the used dynamic memory is decreased to:

Sketch uses 13544 bytes (41%) of program storage space. Maximum is 32256 bytes.
Global variables use 1323 bytes (64%) of dynamic memory, leaving 725 bytes for local variables. Maximum is 2048 bytes.

and can be decreased even more if I remove some of the debug messages in the "showParsedData" procedure.

The other problem has also resolved, after the last correction from blh64. The six arrays which elements are properly filled now with the sent integers via the serial port are shown below:

<1,15,95,1,1,1,1,1,1,79,79,79,79,79,99,99>
10
79
Msg: 1,15,95,1,1,1,1,1,1,79,79,79,79,99,99
Parsed Data: 
Servo: 1
Elements: 15
Srv Array: 95,1,1,1,1,1,1,79,79,79,79,99,99,0,0,0,0,0,0,0,0,0,0,0,0,0,
_________________________________________________________________

<2,15,170,170,170,108,108,108,198,198,116,116,192,192,192,192>
10
8
Msg: 2,15,170,170,170,10,108,108,198,198,116,116,192,192,192,192
Parsed Data: 
Servo: 2
Elements: 15
Srv Array: 170,170,170,10,108,108,198,198,116,116,192,192,192,192,0,0,0,0,0,0,0,0,0,0,0,0,
_________________________________________________________________

<3,15,84,84,154,154,154,154,154,154,154,154,154,90,90,90>
10
Msg: 3,15,84,84,154,154,154,154,154,154,154,154,154,90,90,90
Parsed Data: 
Servo: 3
Elements: 15
Srv Array: 84,84,154,154,154,154,154,154,154,154,154,90,90,90,0,0,0,0,0,0,0,0,0,0,0,0,
__________________________________________________________________________

<4,15,80,80,80,80,80,80,80,80,80,80,80,80,80,80>
10
Msg: 4,15,80,8<4,15,80,80,80,80,80,80,80,80,80,80,80,80,80,80
Parsed Data: 
Servo: 4
Elements: 15
Srv Array: 80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,0,0,0,0,0,0,0,0,0,0,0,

___________________________________________________________________

<5,15,1,1,1,1,31,31,31,31,31,31,31,31,31,1>
10
Msg: 5,15,1,1,1,1,31,31,31,31,31,31,31,31,31,1
Parsed Data: 
Servo: 5
Elements: 15
Srv Array: 1,1,1,1,31,31,31,31,31,31,31,31,31,1,0,0,0,0,0,0,0,0,0,0,0,0,

______________________________________________________________________

<6,15,116,116,116,116,116,72,72,72,72,116,116,116,116,116>
10
116
Msg: 6,15,116,116,116,116,116,72,72,72,72,116,116,116,116,116
Parsed Data: 
Servo: 6
Elements: 15
Srv Array: 116,116,116,116,116,72,72,72,72,116,116,116,116,116,0,0,0,0,0,0,0,0,0,0,0,0,0,