Problem understanding how Arduino breaks down a string.

Hi there!

To briefly explain what I want to do, I want Matlab to be able to send an array which contains a letter and 2 coordinates (Example: 'B1020' for 'B', '10', '20'). That's it, I know it is documented a lot on the Internet, but I couldn't find any solution for this. I also read Robin2 kind of tutorial on how Arduino is reading multiple characters and things like that.

So I will post my code and explain after what is going wrong/right. I need to precise that I'm using an Arduino Uno,
Code :

int a=0;
char t[3];
void setup()
{
  Serial.begin(9600);
}

void loop()
{
  serialRoutine();
}

void serialRoutine()
{
  if(Serial.available()>0)
  {
    for (int i=0; i<3; i++)
    {
      t[i] = Serial.read();
    }
    
    if (t[0] =='T')
    {
      for( int i=0; i<3; i++)
      {
        Serial.println(t[i]);
      }
    }  
    else if (t[0] =='R')
    {
      Serial.println("NO");
    }
    else if (t[0]== 'M')
    {  
      a++;
      Serial.println(a);
    }
  }
}

So here I'm just sending 3 letters for example. Let's say 'TOP'. I'm using the monitor since it's easier to visualize.
What I expect the code to do is :

-> Read 'TOP'
-> Put each letter in each cell of t - T in t[0], O in t[1],...
-> If t[0] equal 'T', then let me view the content of the other cells

But, I'm nowhere near this result. What I have is : Test1.png test2.png

As you can see, if I'm writing 'TOP', the first T is well saved into the first cell, but O and P kind of disappear. And if I'm sending 'OTP', instead of T being stored in the 2nd cell, it is stored in the first one.
So there's basically something I don't understand going on.

Any insights on this?
Best regards!

EDIT: Ok I found the solution, as the serial communication is a bit slow, I needed to put a delay() in the for loop that is storing the data. So now my first problem is fixed. My second is how can I break the string to go from B1034 to t[0] = 'B', t[1] = '10', t[2] = '34' ?

Test1.png

test2.png

I needed to put a delay() in the for loop that is storing the data.

That is not a reliable fix. It would be better if the message had a start and end character so that the program could positively identify when a new message is received and when reception of the message is complete. Use of this technique is explained in Robin's thread.

Thanks for your fast answer!

Yes, I will work on that, as it will be more reliable.

Concerning my EDIT in my last post, should I use strchr and atoi function? I never used them before, but it appears to be pretty reliable in my case? May I ask for your opinion on the subject?

for (int i=0; i<3; i++)
    {
      t[i] = Serial.read();
    }

Remember that with a baudrate of 9600 you are receiving characters roughly every millisecond. You Arduino executes 16 000 000 instructions per second, or 16 000 instructions per millisecond. Do you see where the problem is?

You are reading the serial buffer after the first character was received (Serial.available() > 0). Then you immediately copy 3 bytes from the serial buffer. But at that point the next two bytes haven't been received yet (cause serial@9600 is too slow).

EDIT: Do something like this

void serialRoutine()
{
	// Read byte from buffer if available
	if(Serial.available()>0)
	{
		rxBuffer[rxIndex++] = Serial.read();
	}
	
	// If 3 or more bytes have been received you can process them
	if(rxIndex >= 3)
	{
		processMessage(rxBuffer);
                rxIndex = 0;
	}
}

You are reading the serial buffer after the first character was received (Serial.available() > 0). Then you immediately copy 3 bytes from the serial buffer. But at that point the next two bytes haven't been received yet (cause serial@9600 is too slow).

EDIT: Do something like this

void serialRoutine()
{
	// Read byte from buffer if available
	if(Serial.available()>0)
	{
		rxBuffer[rxIndex++] = Serial.read();
	}
	
	// If 3 or more bytes have been received you can process them
	if(rxIndex >= 3)
	{
		processMessage(rxBuffer);
                rxIndex = 0;
	}
}

[/quote]
Hi, thanks for your answer!

I 100% agree on what you said. But there's one thing I don't understand. rxbuffer is here a char array and rxIndex++ represents a "loop" that enables the buffer's data to be stored into the char array. And then, once rxbuffer has more than 3 values, you "process the message", or in my case, store the value into another char array.
But in my opinion, the problem is the same as before... If I do not have a delay(), the rxbuffer may be filled with "nothing" since the Arduino is way faster.
So shouldn't I combine the delay in my loop, as I stated in my EDIT in #1, and the begin-end character?
If it appears that I have a misunderstanding of your code, do not hesitate to tell me!

Best regards.

Ohmyduddy:
Hi, thanks for your answer!

I 100% agree on what you said. But there's one thing I don't understand. rxbuffer is here a char array and rxIndex++ represents a "loop" that enables the buffer's data to be stored into the char array. And then, once rxbuffer has more than 3 values, you "process the message", or in my case, store the value into another char array.
But in my opinion, the problem is the same as before... If I do not have a delay(), the rxbuffer may be filled with "nothing" since the Arduino is way faster.
So shouldn't I combine the delay in my loop, as I stated in my EDIT in #1, and the begin-end character?
If it appears that I have a misunderstanding of your code, do not hesitate to tell me!

Best regards.

Take a closer look at the first if-conditional. It checks, whether there is data in the serial buffer. If so, regardless of how many bytes, it reads only one byte from the buffer in the rxBuffer and moves on. If there is nothing inside the buffer nothing will get copied. Every Serial.read() will of course decrease the number of available bytes in the serial buffer by 1.

But in my opinion, the problem is the same as before.

No it is not.

The serialRoutine() should be called repeatedly until rxIndex is equal to or greater than 3 which signals that 3 characters have been received. The function can be called as frequently and as often as you like but processing of the received data will not occur until 3 characters have been received.

Having said that, take a look at Serial input basics - updated for better solutions

UKHeliBob:
No it is not.

The serialRoutine() should be called repeatedly until rxIndex is equal to or greater than 3 which signals that 3 characters have been received. The function can be called as frequently and as often as you like but processing of the received data will not occur until 3 characters have been received.

Having said that, take a look at Serial input basics - updated for better solutions

Oh sorry, I did not see there was an updated version of the thread.
Okay there was this slight difference that I did not understand. Thanks for telling me!

LightuC:
Take a closer look at the first if-conditional. It checks, whether there is data in the serial buffer. If so, regardless of how many bytes, it reads only one byte from the buffer in the rxBuffer and moves on. If there is nothing inside the buffer nothing will get copied. Every Serial.read() will, of course, decrease the number of available bytes in the serial buffer by 1.

Okay, understood. It really seemed like I misunderstood before. Thanks, both of you for giving me these solutions!

Ok, it will surely be my last question.

In this example :

const byte numChars = 32;
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;
float floatFromPC = 0.0;
char M = 'M';
boolean newData = false;

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

void setup() {
    Serial.begin(9600);
    Serial.println("This demo expects 3 pieces of data - text, an integer and a floating point value");
    Serial.println("Enter data in this style <HelloWorld, 12, 24.7>  ");
    Serial.println();
}

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

void loop() {
    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
 
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    integerFromPC = atoi(strtokIndx);     // convert this part to an integer

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

}

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

void showParsedData() {
    Serial.print("Message ");
    Serial.println(messageFromPC);
    Serial.print("Integer ");
    Serial.println(integerFromPC);
    Serial.print("Float ");
    Serial.println(floatFromPC);
    if(messageFromPC==M)  //I just add this condition that will serve for my matlab communication
    {
      Serial.println("OK");
    }
}

The variable messageFromPC is a char. So if I send via serial <M,12,14>, it should respond

Message M
Integer 12
Float 14
OK

Or this is what it answers: blabla.png
Even though both are char. I might be off, but is it a problem with the way I'm doing my condition? (I don't think so since I tested the condition with the integer value and it worked). Or is it a problem with how I stated 'M'?

Thanks for all your answer until now, it was really helpful for a beginner like me!

blabla.png

messageFromPC is a char array.

char messageFromPC[numChars]

How do you expect the compiler to compare the array (in this case the start-address of the array) to a character 'M'?

You need to make M (which is a terrible name for a variable) a string also and use strcmp to compare the two

if(strcmp(messageFromPC, M) == 0)
{
   // they are equal
}

Ok ok, I should have at least compared messageFromPC[0] if I really wanted to use my method.

Do I have another choice than to declare it as s String? I know it won't make much difference in this case, but I read many times that declaring a variable as a string should be avoided at all cost for memory and such.

I also did not know that such a function existed, will remember it. And yeah of course M is a terrible name, I will change it for my final code. Thanks for your help!

Ohmyduddy:
Ok ok, I should have at least compared messageFromPC[0] if I really wanted to use my method.

Do I have another choice than to declare it as s String? I know it won't make much difference in this case, but I read many times that declaring a variable as a string should be avoided at all cost for memory and such.

I also did not know that such a function existed, will remember it. And yeah of course M is a terrible name, I will change it for my final code. Thanks for your help!

I said use (c-)strings, not Strings (capital 'S'). Maybe I should've been more clear about that one.

LightuC:
I said use (c-)strings, not Strings (capital 'S'). Maybe I should've been more clear about that one.

I investigated it since when I used the String class, the function was not working. I went to take a look at the function strmpc and I saw that it was comparing each char of the 2 strings. So yeah I presumed I had to put the 'M' into a char array in order to compare it.