Reading input from serial communication.

Hey guys,

this is my first post and I'm hoping someone can help me. I'm quite new to arduino and programming in general and am having trouble when trying to make use of serial communication. What i would like to do is have a user input a string through the serial monitor and then store it for use later in the program. Where i get stuck is the fact that the length of the string is unknown until entered so i can't declare a variable to store it in. Any help on how to approach this would be greatly appreciated.

Cheers,
bresciano23

Decide upon a maximum length that the user is allowed to enter and allocate a variable that big. Anything over the decided length, discard. Maybe warn the user through the serial connection that it was truncated.

Download Arduino 1.0.1. You can use the new Serial.readString() method to store the input into the String class. For example...

String inString = "";
void setup(){
  Serial.begin(9600);
}
void loop()
{
  if(Serial.available() > 0){
    inString = Serial.readString();
  }
  if(inString != ""){
    //do something with inString here
    inString = "";
  }
}

Or if you perfer char arrays...

size_t len;
char buf[51];
void setup(){
  Serial.begin(9600);
}
void loop()
{
  if(Serial.available() > 0){
    len = Serial.readBytes(buf, 50);
    buf [len] = '\0';
  }
  if(strlen(buf) > 0){
    //do something with buf here
    *buf = '\0';
  }
}

In this example, the user can enter a string up to 50 characters long.

In this example, the user can enter a string up to 50 characters long.

...though the under-generous timeout may well truncate your string early, and may not work at all with a terminal emulator.

You need 52.35 ms to send 50 characters at 9600 baud. So, just forget about the timeout, and use the 1 second default. I edited my post.

Very simple test code that uses a delay to allow the input buffer to fill while captuting character into a string variable.

// zoomkat 7-30-11 serial I/O string test
// type a string in serial monitor. then send or enter
// for IDE 0019 and later

String readString;

void setup() {
  Serial.begin(9600);
  Serial.println("serial test 0021"); // so I can keep track of what is loaded
}

void loop() {

  while (Serial.available()) {
    delay(2);  //delay to allow byte to arrive in input buffer
    char c = Serial.read();
    readString += c;
  }

  if (readString.length() >0) {
    Serial.println(readString);

    readString="";
  } 
}

Decide upon a maximum length that the user is allowed to enter and allocate a variable that big.

That big + 1 for the terminating 0. And don't just truncate overlong input, actually make an error handler to report the error and get the user to re-do the entry.

If it's just a word then a 16-byte buffer should be plenty.

bresciano23, for your own good stay away from using C++ Strings in the tiny Arduino ram space. Sure, you -can- but then it gets to be a habit and when you try to do something "big" you will find your program is crashing/resetting mysteriously for no obvious reason. Part of the fix for that will be to unlearn the whifto C++ String way and then learn C string arrays that you should have from the start.


Serial comes in 1 character at a time with many cycles in between. You could write in a delay and collect your bytes and -then- check for good data or you can check your data as it comes in. Set your Serial Monitor to add a newline or carriage return (lower right on the monitor window) and watch for that (newline character is '\n', value is 10, carriage return is '\r', value is 13) and you will know the end of line is reached.
Watching characters is even more effective when it's a terminal or terminal emulator at the other end that sends characters as they are typed. In that case you can interrupt errors as they are being typed, also evaluate numbers and react to codes (A100, SW001, etc) without buffering and wanking around with strcmp() and like -at all-.

GoForSmoke:
That big + 1 for the terminating 0.

You mean terminating '\0'. '0' and '\0' is not the same; '\0' = 0 and '0' = 48.

My second example works very well without needing for a special character to signal the end of the string.

GoForSmoke:
In that case you can interrupt errors as they are being typed, also evaluate numbers and react to codes (A100, SW001, etc) without buffering and wanking around with strcmp() and like -at all-.

That's cool! How do you make an interrupt that triggers when some random condition evaluates to true (e.g. (foo < 90 && bar == 78))?

dkl65:

GoForSmoke:
That big + 1 for the terminating 0.

You mean terminating '\0'.

'A', '0', ' ', '\n', '\r', '\0' are all -ASCII characters- which is what the single quotes are for.
The bytes; 'A' = 65 and '\0' = 0. So terminating 0, you check the byte and it is value = zero.
It's not like I made the term up.

My second example works very well without needing for a special character to signal the end of the string.

And EOL mark is definite and serial communication is not guaranteed. Do you bother with error handling?

GoForSmoke:
In that case you can interrupt errors as they are being typed, also evaluate numbers and react to codes (A100, SW001, etc) without buffering and wanking around with strcmp() and like -at all-.

That's cool! How do you make an interrupt that triggers when some random condition evaluates to true (e.g. (foo < 90 && bar == 78))?

I don't need interrupts. I keep my loop() fast like 100x per millisec if I can. I get each character as it comes in and check it for what I want right then. Then I have many times through loop() before the next serial is available. The same loop checks other conditions and reacts as needed.
I use a state engine to keep track of progress in processes that require multiple loops, like evaluating serial input but what the states are depends on what I want to do.

Here is a sketch that I wrote to show someone one method of checksum and non-buffered user i/o. It could be better but it does suffice. The top comment block is from a post he sent to me.

/*
Checksum Calculation:
(FF) - (17 01 00 13 A2 00 40 63 D9 BB FF FE 02 44 31 04)
= (FF) - (57C)=(255 - 1404)
= (FFFFFFFFFFFFFB83)=(-1149)
Checksum = 83 (last two bytes of the HEX value)
*/

byte  B, message[50] = "17 01 00 13 A2 00 40 63 D9 BB FF FE 02 44 31 04";
byte  csum = 0xFF;
byte  userval = 0;
byte  count = 0;
byte  error = 0;

void  setup(void)
{
  Serial.begin(9600);
  Serial.println( "Checksum calculator." );
  Serial.println( "Enter hex numbers to check with a space between each." );
  Serial.println( "Be sure that your serial monitor sends a newline at line ending." );
  Serial.println( "Test data: " );
  Serial.println( (char *) message );
  Serial.println( " checksum = 83 HEX\n" );
}

void  loop(void)
{
  if ( Serial.available())
  {
    B = Serial.read();
    if ( B >= 'a' && B <= 'f' )
    {
      B &= 0xDF;  // turns lower case alpha into upper case
    }
    Serial.print( B );
    
    if ( B == '\n' ) {
      if ( !error ) {
        if ( count == 2 )  {
          csum -= userval;
        }
        else if ( count == 1 )
        {
          Serial.print( "Error " );
        }
        
        if ( !error )
        {
          Serial.print( " : Checksum = " );
          Serial.println( csum, HEX );
        }
        
        count = userval = 0;
        csum = 0xFF;  
        return; 
      }
      else
      {
        error = count = userval = 0;
        csum = 0xFF;  
        return;
      }
    }

    if ( error )  return;
    
    if (( count > 1 ) && ( B != ' ' ))
    {
      Serial.print( " Error " );
      error = 1;
    }
    
    if ( B >= '0' && B <= '9' )
    {
      B -= '0';
    }
    else if ( B >= 'A' && B <= 'F' )
    {
      B -= 55;
    }
    else if ( B == ' ' )
    {
      if ( count < 2 )  {
        Serial.print( " Error " );
        error = 1;
      }
      else  count = 2;
    }
    else
    {
      Serial.print( " Error " );
      error = 1;
      return;
    }
    
    switch ( count ) {
      
      case  0 :
        userval = B;
        count = 1;
        break;
      
      case  1 :
        userval *= 0x10;
        userval += B;
        count = 2;
        break;
        
      case  2 :
        csum -= userval;
        userval = count = 0;
    }
  }
}

dkl65:
Download Arduino 1.0.1. You can use the new Serial.readString() method to store the input into the String class. For example...

String inString = "";

void setup(){
  Serial.begin(9600);
}
void loop()
{
  if(Serial.available() > 0){
    inString = Serial.readString();
  }
  if(inString != ""){
    //do something with inString here
    inString = "";
  }
}




Or if you perfer char arrays...


size_t len;
char buf[51];
void setup(){
  Serial.begin(9600);
}
void loop()
{
  if(Serial.available() > 0){
    len = Serial.readBytes(buf, 50);
    buf [len] = '\0';
  }
  if(strlen(buf) > 0){
    //do something with buf here
    *buf = '\0';
  }
}


In this example, the user can enter a string up to 50 characters long.

I am using some already written code to do something, it is printing out a character to an lcd one at a time using serial read, each time it runs the loop it gets a new character and prints it,

i have coded it to move the cursor after each character thus making it look like it is slowly wirting a word, but
i want to build some string recognition into it, is there a way to take each caracter out to a string then compare that string after the whole message is sent ?

is there a way to take each caracter out to a string

Out from where?

Of course, you can store each character in (the next position in) an array, keeping the array NULL terminated (which defines a string).

then compare that string

That's what the strcmp() method is for.

after the whole message is sent ?

Ah, there's the tricky part. How do you know when that has happened?

spruce_m00se:
I am using some already written code to do something, it is printing out a character to an lcd one at a time using serial read, each time it runs the loop it gets a new character and prints it,

i have coded it to move the cursor after each character thus making it look like it is slowly wirting a word, but
i want to build some string recognition into it, is there a way to take each caracter out to a string then compare that string after the whole message is sent ?

Why wait for the whole message to be sent to make compares?

You get 1 character at a time through serial and your compare word(s) should be in character arrays as single characters all lined up nicely.
As the letters arrive you want to compare the those with each in the compare word in turn. The "big" work is keeping track of what letter in the compare word to check against the next serial character that comes in. I say "big" because it is only as big as your scheme to organize multiple compare words which doesn't have to be "big" at all.
Still, it can be confusing if you've not done much with arrays before. If you don't know pointers the cleanest way (using pointers) will seem like voodoo but I swear that pointers are a fundamental and actually basic/simple part of C.

How many compare words (or even phrases/sentences -- let's just say strings) are you needing?

im going to send a code, which will then print out a relevant word to the screen,
the code will begin with "Q" so as to avoid receipt of unwated codes from other sources,

somthing like this,
Q1 , Q2, Q3 etc for a station identifier,
Q1b - for low battery
Q1t for trigger,
Q1h for heartbeat
QCA change channel all,
Q1CC change channel confirmation

etc. just simple codes for functions i plan to build in,

So characters come in serial and you look for one that == 'Q'.
Then next one should be a number.

Do you know how ASCII works? Character '0' is value 48 decimal, for example. There is a whole standard table of 128 characters, 0 to 127, and extended table for 0 to 255.

char B = '7'; // I have set B to be the same value as a serial character 7 would be

// this code will print My character is the digit 7
if (( B >= '0' ) && ( B <= '9' ))
{
  Serial.print( "My character is the digit " );
  Serial.println( B );
}

// this code will set int N = 7
int N = B - '0';

its not that im looking for one that begins with Q really, all mesages that are not recognised will be ignored, if too many are received that are not recognised then i am going to make the channel number change on the Tx Rx as they will be considered noise,

Q is just a convenient way of stoppin noise being interpreted as a mesage as in english you dont see words that contain QN for example

That's fine. You can use any character instead of 'Q' or even use a sequence of characters which normally we call a string.

Important details are that
** Single ASCII characters are 8-bit values and can be coded as the letter or number or symbol inside single quotes. You can even do math with those since they are in fact 8-bit values; 'F' - 'A' == 5.
** Multiple ASCII characters, a sequence of characters in an array of 8-bit values with a zero added at the end are coded in double-quotes. You can do math with those and even with the positions of those within the string array. In the string "ABCEDF" I can subtract the position of 'A' from the position of 'F' and get 5 using the indexes of the positions ( in that string A is index 0, F is index 5 ) or using pointers. Pointers are really memory addresses in symbolic form. We code using the symbols that are easier to read but underneath it all is the address. ASCII is just the print-symbols of numbers too, the 8-bit values stored at the 16-bit (on our hardware) addresses.

You can use C string commands to manipulate strings without attending to all the detail. If you know what's behind them then it's not 'magic' and with a little though even the names make sense in a shorthand, less typing, sort of way. If you don't then it's easy to get confused and believe that the whole deal is far more complex than in fact it is and then you can believe further BS like that C++ String objects really are easier, etc. They are, if you're somewhat ignorant of what really goes on with C strings, or just a bit lazy.

examples, the if statement at the end is TRUE

char  buff[ 32 ]; // makes a char array of 32 signed ints named buff.

strcpy( buff, "ABCDEF" ); // puts ABCDEF into buff[0] through buff[5] and tacks a 0 (not '0') into buff[6] 
 
char  *ch; // makes a pointer to char variable that starts not pointing to any char, value is 0 aka NULL.

ch = buff; // now ch points to buff[0], buff without the [] points to the start of buff[].
*ch = 'X'; // puts ASCII 'X' into buff[0], buff is now "XBCDEF"

ch++; // now ch points to the next space in buff[], *ch == 'B'

ch = &buff[3]; // ch now points to the address of buff[3]

ch += 3; // ch now points to the terminating 0 if the string buff

if ( ch - buff == 6 ) 
{
  Serial.println( "pointer math says ch points to memory address 6 bytes more than buff" ); 
}

And that hopefully will tell you enough to evaluate serial characters -as they arrive-.

One pain the butt is knowing when the input ends. It's solved easily enough by setting up your terminal or serial monitor to add a newline ('\n') or carriage return ('\r') at the end of every line sent **and expect that in code, ie look for '\n' or '\r' or both or whatever you set up to mark end of line (EOL).
You can do this different ways in code to send a line end back. The simplest way is to use Serial.println() which tacks a newline at the end of the print pr you an use \n at the end of a printed line. Serial.print( "FOO\n\n\n" ) prints FOO on one line and adds two more empty lines after, you don't need a bunch of Serial.println() to print a bunch of empty lines.

If you followed all that then congrats, you've moved a ways up the learning curve. Good luck!

haha still lost,

i will have to read up on this, its annoying because in the 1.0.1 there is a new command as mentioned a few posts previously, which reads out the whole string, and i can manage to get that to detect "text" with no problems,

but the code i am modifying is written ( and needs ) one character at a time, so i need to learn how to append to a string. I WILL eventually dominate it

Try using strcat().

this code will print 0123456789

char  text[ 20 ], new[ 2 ];

memset( text, 0, 20 ); // sets memory at address to value for however-many bytes
memset( new, 0, 2 );   // so now both arrays are all zeros

for ( byte c = '0'; c <= '9'; c++ )
{
  *new = c;  // otoh, *new which is the same as new[0] could become your serial character
  strcat( text, new ); // every time it adds what is in new to text
}
Serial.println( text );

but I tell ya, buffering then parsing is clunkier than parsing on the fly!

so i could use this to add the incoming serial character to text?

then can i make a comparison like the follwing?

if (text==bob)
lcd.print("bobs here")

thanks for helping a newbie

so i could use this to add the incoming serial character to text?

then can i make a comparison like the follwing?

if (text==bob)
lcd.print("bobs here")

No. You need to use strcmp() to compare two strings.