Help with String() Object

Hello, I am trying to take a message from the serial port and and send it to and LCD (4X20).

However, String() object (NOT char array, ie ‘string’) and all of its companion functions fail after a certain amount of characters.

Depending on what method I use, I either get trash or an empty String.

Here is my latest attempt:

// Function to Read Serial Message into Text/String
String SerialToString(){
        
        int tbuffersize = 81;
        char ftext[tbuffersize];
        String fstring = "00000000001111111111222222222233333333334444444444555555555566666666667777777777";
                
        // Read Serial Message into a char array ('text') then Store it in a String
        delay(100);
        
        for(int i = 0; i < tbuffersize - 1; i++ ){
             ftext[i] = Serial.read();
             if(ftext[i] == -1){ ftext[i] = 0; } // Add terminating NULL
        }
        
        Serial.print("fstring before: ");
        Serial.println(fstring);
        
        endOF = fstring.indexOf(-1);
        endOF2 = fstring.lastIndexOf(-1);
       
        Serial.println(endOF);
        Serial.println(endOF2);
        
        
        fstring = String(ftext);
        Serial.print("fstring after: ");
        Serial.println(fstring);
        Serial.print("ftext after: ");
        Serial.println(ftext);
        return fstring;
}

//SerialToString() sits inside a if(Serial.available() > 0) statement later in the code, then  
FMessage = SerialToString(); 
Serial.println(FMessage); // Goes to LCD later in the code.

Typical Output:

fstring before: 00000000001111111111222222222233333333334444444444555555555566666666667777777777
-1
-1
fstring after: asdgasdfsdf
ftext after: asdgasdfsdf
FMessage: asdgasdfsdf


fstring before: 00000000001111111111222222222233333333334444444444555555555566666666667777777777
-1
-1
fstring after: 
ftext after: asdgasdf
FMessage:

The code definitely fails when converting ftext Char array into fstring String() (fstring = String(ftext);).

I have tried declaring fstring as empty, then adding the chars from the serial directly, but it sometimes misses and stores a trash char. (I got around it by initializing fstring with 80 chars.)

I have tried storing/replacing all the chars from ftext into fstring, then manipulating fstring with trim() and substring() to get rid of the trash, but they fail after a certain amount of characters.

I’m sure the answer is simple, but I can’t for the life of me find the answer searching through Google and the Arduino Boards.
The only solution I can think of is to use substring() 4 times and then += them together, but there has to be a more elegant solution.

Any help is appreciated.

Thanks in advance.

All right new problem.

I condensed the code as such:

String SerialToString(){
        
        int tbuffersize = 81;
        char ftext[tbuffersize];
        
        // Read Seria1 Message into a char array ('text') then Store it in a String
        delay(100);
                           
        for(int i = 0; i < tbuffersize; i++ ){
             ftext[i] = Serial.read();
             if(ftext[i] == -1){ ftext[i] = 0; }
        }
                
        return ftext;
}

// Elsewhere in the code:
// FMessage = "00000000001111111111222222222233333333334444444444555555555566666666667777777777 ";
// FMessage = SerialToString();

And it works perfectly on its own, never misses, no trash.

However, I have two other processes going on at the same time:
WiiDataProcess();
IRSensorsProcess();

If I comment them out, it works correctly, but when active, the ATMega starts to miss the message greater than x characters.

WiiDataProcess();

void WiiDataProcess()
{
    //Wii IR Sensor Read
    Wire.beginTransmission(slaveAddress);
    Wire.send(0x36);
    Wire.endTransmission();

    Wire.requestFrom(slaveAddress, 16);        // Request the 2 byte heading (MSB comes first)
    for (i=0;i<16;i++) { data_buf[i]=0; }
    i=0;
    while(Wire.available() && i < 16) { 
        data_buf[i] = Wire.receive();
        i++;
    }

    Ix[0] = data_buf[1];
    Iy[0] = data_buf[2];
    s   = data_buf[3];
    Ix[0] += (s & 0x30) <<4;
    Iy[0] += (s & 0xC0) <<2;

    Ix[1] = data_buf[4];
    Iy[1] = data_buf[5];
    s   = data_buf[6];
    Ix[1] += (s & 0x30) <<4;
    Iy[1] += (s & 0xC0) <<2;

    Ix[2] = data_buf[7];
    Iy[2] = data_buf[8];
    s   = data_buf[9];
    Ix[2] += (s & 0x30) <<4;
    Iy[2] += (s & 0xC0) <<2;

    Ix[3] = data_buf[10];
    Iy[3] = data_buf[11];
    s   = data_buf[12];
    Ix[3] += (s & 0x30) <<4;
    Iy[3] += (s & 0xC0) <<2;

    for(int i =0; i < 4; i++){ 
        itoa(Ix[i], MasterSignalArray[i * 2], 10);
        itoa(Iy[i], MasterSignalArray[(i * 2) + 1], 10);
    }
    
    delay(3);
    ////////////////////////////////////////////////////
}

IRSensorsProcess();

void IRSensorsProcess()
{
    // IR Sensor Code
    //Initialize Analog Values for Sensors
    int SanalogValue0 = analogRead(SensorPin0);
    int SanalogValue1 = analogRead(SensorPin1);
    int SanalogValue2 = analogRead(SensorPin2);
    int SanalogValue3 = analogRead(SensorPin3);
    
    // IR Conditionals
        if (SanalogValue0 > SThreshold0) {
        SensorArray[0] = 1;
        //Serial.println("Sensor 0 Tripped: TRUE " + String(SensorArray[0]));
        digitalWrite(LEDPin0, HIGH);
        } 
        else {
        SensorArray[0] = 0;
        //Serial.println("Sensor 0 Tripped: FALSE " + String(SensorArray[0]));
        digitalWrite(LEDPin0, LOW);
        }
                
        if (SanalogValue1 > SThreshold1) {
        SensorArray[1] = 1;
        //Serial.println("Sensor 1 Tripped: TRUE " + String(SensorArray[1]));
        digitalWrite(LEDPin1, HIGH);
        } 
        else {
        SensorArray[1] = 0;
        //Serial.println("Sensor 1 Tripped: FALSE " + String(SensorArray[1]));
        digitalWrite(LEDPin1,LOW); 
        }
             
        if (SanalogValue2 > SThreshold2) {
        SensorArray[2] = 1;
        //Serial.println("Sensor 2 Tripped: TRUE " + String(SensorArray[2]));
        digitalWrite(LEDPin2, HIGH);
        } 
        else {
        SensorArray[2] = 0;
        //Serial.println("Sensor 2 Tripped: FALSE " + String(SensorArray[2]));
        digitalWrite(LEDPin2,LOW); 
        }
                
        if (SanalogValue3 > SThreshold3) {
        SensorArray[3] = 1;
        //Serial.println("Sensor 3 Tripped: TRUE "  + String(SensorArray[3]));
        digitalWrite(LEDPin3, HIGH);
        } 
        else {
        SensorArray[3] = 0;
        //Serial.println("Sensor 3 Tripped: FALSE "  + String(SensorArray[3]));
        digitalWrite(LEDPin3,LOW); 
        }
        
        // Store IR Sensor Trip data into Master Signal Array
        for(int i = 0; i < 4; i++){  itoa(SensorArray[i], MasterSignalArray[i + 8], 10);  }
}

I’ve tried to skip over them using an if statement, but I get the same result:

// Elsewhere:
// boolean XBInterrupt = false;
if(!XBInterrupt){ 
        WiiDataProcess();
        IRSensorsProcess();
    }
// At beginning of loop()
// if(XBInterrupt){ XBInterrupt = false; }

As far as I can tell, either process has nothing to do with or mess with any resources that are used by SerialToString(), so… what the hell?

Again, any help is appreciated, thanks in advance.

I'd need to study the code more to provide anything definite, but the first thing that pops out is that you are reading 81 values, whether they are present or not. You should be using Serial.available() to determine how many bytes there are to read, and not trying to read more than that.

If Serial data arrives after the first Serial.read fails, but before the loop completes, that data will never be processed.

Do you enable/disable interrupts anywhere?

        fstring = String(ftext);

fstring pointed to data before. Now, it points to new data/memory location. The old data/memory location is still there, but nothing points to it anymore, so it can never be deallocated/reused. I think you have a memory leak here. Eventually, you will run out of memory.

fstring = String(ftext);
        return fstring;

fstring is a local variable. It goes out of scope at the end of the function, resulting in the String destructor being called. And, yet, you return a pointer to that memory location that is no longer valid. Do you think that is a good idea?

Thanks for the reply, what you said did occur to me, so I no longer use fstring, I just store ftext in FMessage (I don’t know it you saw my reply to myself.)

What my code looks like now:

String SerialToString(){
        
        int tbuffersize = 81;
        char ftext[tbuffersize];
        
        // Read Seria1 Message into a char array ('text') then Store it in a String
        delay(100);
                           
        for(int i = 0; i < tbuffersize; i++ ){
             ftext[i] = Serial.read();
             if(ftext[i] == -1){ ftext[i] = 0; }
        }
                
        //Serial.print("ftext after: ");
        //Serial.println(ftext);
        return ftext;
}

Then later in the code, a String FMessage is filled with the result of SerialToString: FMessage = SerialToString();
FMessage is initialized as ; FMessage = "00000000001111111111222222222233333333334444444444555555555566666666667777777777 ";

It works perfectly, on its own. But when I enable the other two processes, it starts to miss.

I have no interrupts, other than having those 2 processes skipped over in an if statement.

Thanks.

        char ftext[tbuffersize];

This is a local variable. It goes out of scope at the end of the function.

        return ftext;

But, you return the variable, anyway.

The memory allocated for the string doesn't go away. It's some physical location in memory. During the function, no other code can write to the space. But, when SerialToString ends, that space is no longer reserved, so any other function that gets called can overwrite that memory space.

When you try to reference it later, you may get the stuff some other function wrote there.

You could allocate space in the caller, and pass a pointer to that space to the SerialToString() function, for it to write to, or you could use malloc to allocate space, and return a pointer to that space (without freeing the space). The caller would then need to free the space. Or, you could have SerialToString write to a global variable. In any case, SerialToString needs to store its data in a place/way that will not allow other functions to overwrite that space.

Thanks for the help and advice. I have come to the conclusion that I cannot use string (char arrays) and String() (objects) together.

I wanted to send a message using XBee's. I finally got the transmitter to work properly using only strings (char arrays). On the Receiving end, merely declaring a String () object (initialized or not) was enough to mess up the receiving of a message.

So I guess you can't use both without some heavy memory juggling? I decided to just use strings (char arrays), which is a shame, I really liked that they included a String() object, very useful in Java.

Thanks again. :)

I really liked that they included a String() object, very useful in Java.

Seeing as how you can't use the Arduino String class in Java, I guess I'm missing something here.

The String class merely provides wrappers for the C string functions. Not using those wrappers doesn't seem like all that high a price to pay for smaller code.

On the Receiving end, merely declaring a String () object (initialized or not) was enough to mess up the receiving of a message.

It shouldn't have. Can you post the code that has problems?

Quote:
I really liked that they included a String() object, very useful in Java.
Seeing as how you can’t use the Arduino String class in Java, I guess I’m missing something here.

The String class merely provides wrappers for the C string functions. Not using those wrappers doesn’t seem like all that high a price to pay for smaller code.

Sorry, wrote this late at night. What I meant was I found the use of Strings as objects in Java useful in general and was glad to see they added this to Arduino C. Too bad i couldn’t use them properly here.

I have already found out how to get my setup working using the good old C strings (chars arrays). I agree with what you said about the price of the code, I didn’t have a chance to use malloc as you suggested earlier, I just settle for using the strings (chars array) as you normaly would.

But here is the code for the receiving end, just to see if you can spot something I didn’t:

#include <stdlib.h>
#include <string.h>

// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

// Function to Read Serial Message into Text/String /////// Just having these lines uncommented is enough to make things not  work correctly.
// String Line_1;// = "Hello!";
// String  Line_2;// = "How are you?";
// String  Line_3;// = "I'm Good.";
// String  Line_4;// = "Thanks for asking!";

static char Line_1[21];// = "Hello!";
static char Line_2[21];// = "How are you?";
static char Line_3[21];// = "I'm Good.";
static char Line_4[21];// = "Thanks for asking!";
//static String FMessage;
static char SMessage[81];
static char TMessage[81];
int SMessageLength = 0;

String SerialToString(){
        
        int tbuffersize = 81;
        static char ftext[81];
        
        // Read Seria1 Message into a char array ('text') then Store it in a String
        delay(100);
                           
        for(int i = 0; i < tbuffersize; i++ ){
             ftext[i] = Serial.read();
             if(ftext[i] == -1){ ftext[i] = 0; }
        }
                
         strcpy(SMessage, ftext);
        //return ftext;
}

//////////////////////////////////////////////////////////////////


void setup(){
  // set up the LCD's number of columns and rows: 
  lcd.begin(20, 4);
  // initialize the serial communications:
  Serial.begin(9600);
}

void loop()
{
  // when characters arrive over the serial port...
  if (Serial.available()) {
    // wait a bit for the entire message to arrive
    delay(1000);
    SerialToString();
    SMessageLength = strlen(SMessage);
    
    if(SMessageLength <= 20){ 
        for(int i = 0; i < 20; i++){  Line_1[i] = SMessage[i];  }
        strcpy(Line_2, "");
        strcpy(Line_3, "");
        strcpy(Line_4, "");
    }
    
    if(SMessageLength > 20 && SMessageLength <= 40){ 
        for(int i = 0; i < 20; i++){  Line_1[i] = SMessage[i];  }
        for(int i = 0; i < 20; i++){  Line_2[i] = SMessage[i + 20];  }
        strcpy(Line_3, "");
        strcpy(Line_4, "");
    }
    
    if(SMessageLength > 40 && SMessageLength <= 60){ 
        for(int i = 0; i < 20; i++){  Line_1[i] = SMessage[i];  }
        for(int i = 0; i < 20; i++){  Line_2[i] = SMessage[i + 20];  
                                      Line_3[i] = SMessage[i + 40];
                                   }
        strcpy(Line_4, "");
    }
    
    if(SMessageLength > 60){ 
        for(int i = 0; i < 20; i++){  Line_1[i] = SMessage[i];  }
        for(int i = 0; i < 20; i++){  Line_2[i] = SMessage[i + 20];  
                                      Line_3[i] = SMessage[i + 40];
                                      Line_4[i] = SMessage[i + 60];
                                   }
    }
    
// clear the screen
    lcd.clear();
    //////////////c, r
    lcd.setCursor(0, 0);
    lcd.print(Line_1);
    lcd.setCursor(0, 1);
    lcd.print(Line_2);
    lcd.setCursor(0, 2);
    lcd.print(Line_3);
    lcd.setCursor(0, 3);
    lcd.print(Line_4);
    //strcpy(SMessage, "");
    //Serial.println(SMessage);
    //Serial.println(SMessageLength);
    Serial.flush();
  }
}

Just incase you’re wondering why I break up the message, its because I have an 4X20 LCD attached, which doesn’t display the text as one would expect. In other words, when the 1st line is filled, instead of spilling over to the 2nd line, the message continues on to the 3rd line, THEN to the 2nd line, and finally to the 4th (normal behavior from what I have read). I had to adjust this.

Thanks again.

I'm not convinced that the String class is the culprit, here. You have 4 21 byte global arrays as well as 2 81 byte global arrays. You have local arrays, too. You may well be running out of SRAM. String objects are created in SRAM, too.

Serial.flush();

This is REALLY not a good idea. Some serial data arrived, and when loop started, it save that there was data to read. So, SerialToString was called to process any data that was available. When all available serial data was read, SerialToString() ends, and the strings are manipulated. Then, the strings are sent to the LCD, which is not a fast process. Finally, any serial data that arrived on the serial port while the strings were being manipulated and the written to the LCD is sent to the bit bucket. Why?

I’m not convinced that the String class is the culprit, here. You have 4 21 byte global arrays as well as 2 81 byte global arrays. You have local arrays, too. You may well be running out of SRAM. String objects are created in SRAM, too.

Your most likely correct. In any case, using only strings (char arrays) in the program works for my purposes.

This is REALLY not a good idea. Some serial data arrived, and when loop started, it save that there was data to read. So, SerialToString was called to process any data that was available. When all available serial data was read, SerialToString() ends, and the strings are manipulated. Then, the strings are sent to the LCD, which is not a fast process. Finally, any serial data that arrived on the serial port while the strings were being manipulated and the written to the LCD is sent to the bit bucket. Why?

I see, I didn’t write the code completely, I only wrote the SerialToString() function and the part that breaks up the message into four lines (and modified the lcd.write to lcd.print, as well as what it prints). This is for a school project, in case you’re wondering, so my teammate wrote the original code, which I modified (quite heavily I might add).

Yeah it’s not fast, and the 1 second delay doesn’t help either, but it’s actually pretty functional.

Finally, any serial data that arrived on the serial port while the strings were being manipulated and the written to the LCD is sent to the bit bucket. Why?

Haha, “bit bucket”, I don’t know why, but that made me laugh, I never heard that term before. By that I think you’re asking why I trash some incoming data while processing the previous string? I didn’t try sending more than one message in rapid succession, so never notice if this affects another incoming message. I would send a message, wait for a second for it to display, and then send another. Works pretty well, haven’t had any problems yet. But I will test it some more just to make sure.

Thanks again for the insight. :sunglasses: