real string functions?

hello,

for a program i need to listen on the serial port and add all incoming characters to a string. if arduino receives a "carriage return" it shall compare it to other strings to see if the received string is a "command". then it should reset the string to "" (empty string).

the string examples in the tutorials don't work for me, because they all add letters in "while"-loop directly reading letters from the serial port. in my case, it might happen that there is a very long pause between the different characters. so i would exit the while-loop before the command is complete.

i looked into string manipulation in c, but wasn't successful.

basically i need something like this:

byte command[];

void handleIncomingByte( byte aCharacter) {
  if( aLetter=="13" ){   //if aLetter is a carriage return our string is complete
     //do some comparism
     if(command=="light"){
        //do something
     }

     //and important: reset command variable
     command="";
     
  }else{
      //add aCharacter to command
     command[strlen(command)]= aCharacter;
  }
}

both the string comparism and the resetting the string don't work.
what am i missing?

You can't compare a string like command == "light" in C because that would just compare pointer addresses, not the strings themselves. You have to either use "strcmp" or "strncmp" if it's available in Arduino or you have to write a while loop comparing the two strings bytewise, something like

char *cmd, *my_str;
int is_equal;

cmd = command;
my_str = /* the string to read in */;
is_equal = 1;

// compare bytewise
while (is_equal && *cmd && *my_str)
   is_equal = (*cmd++ == *my_str++);

// make sure both strings are of the same length, so that "blahx" != "blah"
if (is_equal)
   is_equal == (*cmd == 0 && *my_str == 0);

if (is_equal)
   // do the command

You can also put that into a function. As for reading in strings via serial, I've written a simple sketch to familiarize myself with the environment that will read in words or sentences from serial and "morse" them via a led connected to pin 13. It includes a portion of code to read in the strings which works fine even if there are long delays between the various letters. There are some initialization things missing because the whole sketch exceeds the character limit for posts on this forum, but the point is that I have this code at the beginning of my loop function and "word" will contain the read in string if a carriage return is received... If you can't make sense of the code below, I'll trim it down the most necessary and repost it...

    while (newWordInd < MAX_SERIAL_LEN && serialAvailable() > 0) {
      
      c = (char)serialRead();
      if (c == -1)
        break;
              
      if (c == '.' && newWordInd > 0) {
        want_repeat = 1;
        continue;
      } else if (c == ';' && newWordInd > 0) {
        want_repeat = 1;
        want_eeprom = 1;
        continue;
      }
      
      if ((c == '\n' || c == '\r') && newWordInd > 0) {
        new_word = 1;
        do_repeat = want_repeat;
        want_repeat = 0;
        
        // trim off any trailing spaces
        while(newWordInd > 0 && serialWord[newWordInd-1] == ' ')
            newWordInd--;
            
        if (newWordInd == 0)
          continue;
        
        // zero-terminate    
        serialWord[newWordInd] = '\0';
        newWordInd = 0;
        
        // save the word to the eeprom
        if (want_eeprom) {
          char *word = serialWord;
          int i = 0;
          while (*word && i < 511) {
             eeprom_write_byte((unsigned char *)i++, *word);
             word++;
          }
          
          eeprom_write_byte((unsigned char *)i, (char)0);
          
          printNewline();
          printString("\n\nARDUINO : saved in eeprom : ");
          printString(serialWord);
          printNewline();
          printNewline();
          
          want_eeprom = 0;
        }
        
        break;
      }
      
      // only dots directly before a newline trigger repeating...
      want_repeat = 0;
      
      // ignore spaces at beginning of word
      if (c == ' ' && newWordInd == 0)
        continue;
      
      // ignore more than one space in a row
      if (c == ' ' && serialWord[newWordInd-1] == ' ')
        continue;
      
      if (c != ' ') {                  
        // convert to upper case
        if (c & 32)
          c -= 32;
      
        if (c < 'A' || c > 'Z')
          continue;
      }
      
      serialWord[newWordInd++] = c;
    }
  }

  if (new_word) {
    char *word = serialWord;
    
    outputMorse(LOW);
    value = LOW;
    
    // swap buffers
    serialWord = morseWord;
    morseWord = word;
    
    wordInd = 0;
    letterInd = NULL;
    
    // pause of 7 DITs before starting a new word since we might be interrupting another
    // word
    curDelay = 7*DIT;

    // ensure that the first delay in the array will be taken...
    previousMillis = millis();
            
    new_word = 0;
    do_morse = 1;
  }

hey thank you very much.

can't test it at the moment, but your answer seems to solve all the problems i encountered. nice to know that about string comparism in c.

best,
kuk

hmm, can i add a single char to an existing string?

like

char *mystr = "hello worl";
char singleChar = "d";

*mystr = strcat(*mystr , ch):

printString(*mystr);

or similar?

basically, yes, you can add a characters to strings using strcat, BUT you have to make sure that there actually is enough space for the additional chars behind the current string. In your example code here, my_str is a pointer to a static string that resides somewhere in the data segment. Strcat'ing to it may work or not, but basically the result is unpredictable. Also be aware that the second argument to strcat must be a char* pointing to a C string (i.e. a zero-terminated string), not a char!

This one, for example is better.

char my_str[100];

strcpy(my_str, "hello worl");
strcat(my_str, "d");

this will work because my_str has enough space to hold the concatt'ed string and "d" is a zero-terminated C string.

guys

i have a quick comment on the solution.
We're on a microcontroller with 1k of ram, doing string manipulation in a way it's kinda risky. if you keep allocating new buffers (as the string grows) who's going to guarantee that in the long run the code it's not going to run out of memory.
and all of this to have human readable commands....
just use a lighter protocol. say that each command is a single uppercase letter and make sure you send the parametres using some predictable fixed length message.
your code will be lighter and more predictable.

remember this is not actionscript and you don't have 1gb of ram so it doesn't matter how you use your strings.

just my 2cent

massimo

you're totally right.

sorry for upsetting you. the examples were just chosen to simplify the problem.

of course i won't send "arduino, please turn all lights off exept no. 12". what i was trying to do is letting arduino understand and handle ESC sequences. a maximum of 3 characters at the moment.

after all, understanding (i'm not there yet) strings and pointers, seemed a good start for understanding why my programs keep crashing :slight_smile:
it works now.

the problem was that i tried to reset the string with: myString="";

or even: myString="\0"; // for zero termination

both didn't work.

strcpy(myString, "");

did the trick.
thank you, kuk

hey

you didn't upset me at all :))

I was just giving a piece of advice after having done tons of projects of this type.

I'm stuck in bed for 3 days with a nasty flu... my writing style is not particularly good at the moment :frowning:

massimo

yes, usually string handling may not be necessary, it's just that in this one sketch I was playing around with, I wanted to make the board morse (by blinking a led) sentences sent to it via serial, that's why I was playing around with strings...

anyways, I just wanted to add (in case you haven't found out yourself, kuk) that you can't reset a string like

char* str;

str="";
or
str="\0";

because that would just make the pointer str point to a static string in the data segment rather than setting the contents of the current address to an empty string. You can, however, save the call to "strcpy" for resetting the string (which might be useful because that may enable the Arduino IDE to omit linking to an external library containing that function, hence reducing code size) by doing this.

*str = '\0';

hi gck,

your last point is a good one. How do i know if my sketch is linking to an external library? the IDE does this linnking automatically, when i'm making use of "string functions" for example, right?

i thought that string.h was linked to anyway, like a kind of standard library. thats not right i guess.

and is there a way to tell how big those different libraries are? what libraries exist at all at the moment for arduino?

hey, and best wishes to massimo!

kuk

your last point is a good one. How do i know if my sketch is linking to an external library? the IDE does this linnking automatically, when i'm making use of "string functions" for example, right?

Actually, I have no idea wether the IDE links to a single library containing all the function or to multiple small ones that are used selectively. Usually, you have all the string functions and other stdlib stuff in a single library, but it may be different for those little microcontrollers since code size is a concern. I'm also new to this, I was just imagining this may be an issue.

Anyways, I assume that somewhere down the toolchain, some sort of "dead code stripping" may be applied, stripping away any symbols that are not referenced in your program i.e. functions that you do not call. I don't think uploading lots of functions you never use into 8K or so flash space made too much sense?

Maybe someone who is better informed could clarify this? Thanks!