Arduino String: C++ style string vs old
style c string Compared. Should you use the Arduino String class or old
style c strings? Is there a memory saving? Which is easier? Find out
here with a Full Serial command decoder example.
Arduino string: What's the difference between an older style C-string and a C++ string?... and should you worry about it?
Save 2k Flash if you use C-string syntax.
C++ Strings are easier to code...
...but they use dynamic memory,
...so you might run out of memory = hang.
learn how to decode serial data into avariable.
You use the Arduino String Class to manage lines of text stored in
SRAM and you can control strings in different ways:
Initialize them - easily setup text messages.
Concatenate them (add characters or append another string).
Search through them (identify what is within a string).
and more.
They provide the human interface to and from the outside world. So
for instance you will use Strings to store, and later send messages to
an LCD or to receive and send data through serial link etc.
Note: In this page the word 'String' (in this page) with capital 1st letter refers
to the String class (C++) whereas non-capitalized 'string' refers to C
style strings. You can use either in the Arduino Compiler (avr
compiler).
Learn Arduino String by Example
To learn how to use Arduino Strings lets solve a simple problem, receiving and transmitting data through the serial port.
One problem with compiling C/C++ code is that to change a constant in
a program requires re-compilation and this takes time. Using the serial
port allows you to update internal variables in real time without
recompiling the code.
The code below accepts
data from the serial port in the form of text commands that control
simple on/off variables. A further section below shows you how to get
integer text values into an integer variable while your Arduino program is running.
It's quite an interesting problem, as data from the RS232 interface is "serial" so you have to build up a commands from the
serial port byte by byte. You also need to identify when a command is complete and then take action on the command.
The first thing to do, as you do for all serial communications, is
initialize the Serial class in the setup() function with the baud rate
(a startup message is also useful to show the Code has begun):
For this tutorial the
operational code will be in the loop function but as you program
grows, you will want to create a separate function to contain the
serial decode operation. For now lets leave it in loop().
First off you need to
declare a variable of type String to hold the data
from the serial port; Here this will be sdata (a String class
object).
Stringsdata="";
As each byte of data comes in you also need a temporary storage area to process it:
bytech=' ';
The function needed to decide if a byte has been received is
Serial.available()
Which returns true if anything has been received at the serial port.
Next you need to read the data from the serial port into a variable using the String class member function read():
Serial.read();
This returns a byte of data.
Arduino String Serial Command Decode Structure
With these four bits of code you can make up the serial receiver as follows:
Stringsdata="";// Initialised to nothing.
voidsetup(void){
Serial.begin(9600);
Serial.println("Command Interpreter");
}
voidloop(void){
bytech;
if(Serial.available()){
ch=Serial.read();
sdata+=(char)ch;
if(ch=='\r'){// Command recevied and ready.
sdata.trim();
// Process command in sdata.
sdata="";// Clear the string ready for the next command.
}
}
}
The code in setup() initializes the serial port to 9600 Baud and prints out the first string message.
In loop() the serial port is continuously monitored and if a byte is
received it is placed into variable ch and appended to the string sdata.
Notice how the C++ String syntax allows you to use intuitive '+='
operator. This adds one string to the end of
another by using the overloaded '+' operator, here using the shortened
form '+='.This is not possible with standard c-strings; for that you
would use a function strcat (short for string concatenate).
If
the byte was a Carriage Return (CR - when you hit the return key), then
the trim function is used to remove all white space at the end of
sdata. Now sdata is ready to be used in the rest of your program.
Just before leaving the if condition block, sdata is reset to empty, ready for the next command decode.
Arduino String Serial Command Control
Now lets look at the section that you will write after the comment:
// Process command in sdata.
This is where you can decode the serial data which ended in \r (now removed by trim().
The easiest way to do it is to examine the 1st character and use that
in a switch statement. To access a character at a specific position see
the charAt member of Class String.
sdata.charAt(0)
Here's the switch statement that decodes each command
// Process command in sdata.
switch(sdata.charAt(0)){
case's':
start=1;
Serial.println("Start Process");
break;
case't':
test=1;
Serial.println("Test");
break;
default:Serial.println(sdata);
}// switch
Notice how a Serial.println is used to send information back to
the user. This is not really necessary but allows you to see that a
command has been actioned - making the program easier to debug. The variables start and test are defined earlier in the program.
The above code gives you simple control of variables within a microcontroller.
Arduino String Serial Command Value Entry
To enter a value into a variable, under serial control, you can use the following as part of the switch statement.
This simply uses the functions substring() and toInt() to retrieve a
number string from the serial port and convert it to internal number
format.
If the serial data input begins with a v then this section of the
switch statement is activated. if there is more data after the 'v' i.e.
you typed a number, then valStr is set to a new string that starts from
index 1 of sdata to the end i.e. avoiding the 'v' character.
valStr is then decoded from string to int using the toInt() function
and the value placed in variable 'val'. 'val' is then printed back to the
serial port to show what happened.
If you only typed 'v' then only the last two print statements are
executed since the string's length is now one thus printing out the
current value of 'val'. In this way you can either change or just query
the value of
'val'.
Arduino String Sketch Examples
Full Command Decoder using Arduino String
The following Arduino String Sketch example pulls all of the above
code snippets together showing you how to decode serial data for
entering values into variables in any Arduino Sketch.
Stringsdata="";// Initialised to nothing.
bytetest,start;
voidsetup(void){
Serial.begin(9600);
Serial.println("Command Interpreter");
}
voidloop(void){
bytech;
StringvalStr;
intval;
if(Serial.available()){
ch=Serial.read();
sdata+=(char)ch;
if(ch=='\r'){// Command received and ready.
sdata.trim();
// Process command in sdata.
switch(sdata.charAt(0)){
case's':
start=1;
Serial.println("Start Process");
break;
case't':
test=1;
Serial.println("Test");
break;
case'v':
if(sdata.length()>1){
valStr=sdata.substring(1);
val=valStr.toInt();
}
Serial.print("Val ");
Serial.println(val);
break;
default:Serial.println(sdata);
}// switch
sdata="";// Clear the string ready for the next command.
}// if \r
}// available
}
[file:string.ino]
Statistics for Above String based command decoder (IDE ver 1.8.8):
Sketch uses 3912 bytes (12%) of program storage space. Maximum is 30720 bytes.
Global variables use 248 bytes (12%) of dynamic memory, leaving 1800 bytes for local variables. Maximum is 2048 bytes.
Serial Monitor Results for Command Decoder
Here's what happens when you type 's', 't', 'v198' - after each command hit the return Key (Enter key).
Command Interpreter
Start Process
Test
Val 198
This shows that each section of the switch statement was activated
and that the variable val has the decimal value 198. This is true since
the code...
Serial.println(val);
...converts the internal binary representation of 'val' into text readable form and sends the data out to the serial port.
Full Command Decoder using c string
Now lets look at the c string version of the same operation (you'll
see why in a while!). The code is essentially the same as the previous
String code but use 'c' access operations so instead of sdata.charAt you
use the array access method sdata[0]. Instead of valStr.toInt() you use
the function atoi().
bytetest,start;
#define BUF_LEN 20
voidsetup(void){
Serial.begin(9600);
Serial.println("Command Interpreter");
}
voidloop(void){
staticcharsdata[BUF_LEN],*pSdata=sdata;
bytech;
intval;
if(Serial.available()){
ch=Serial.read();
// -1 for null terminator space
if((pSdata-sdata)>=BUF_LEN-1){
pSdata--;
Serial.print("BUFFER OVERRUN\n");
}
*pSdata++=(char)ch;
if(ch=='\r'){// Command received and ready.
pSdata--;// Don't add \r to string.
*pSdata='\0';// Null terminate the string.
// Process command in sdata.
switch(sdata[0]){
case's':
start=1;
Serial.println("Start Process");
break;
case't':
test=1;
Serial.println("Test");
break;
case'v':
if(strlen(sdata)>1)
val=atoi(&sdata[1]);
Serial.print("Val ");
Serial.println(val);
break;
default:Serial.println(sdata);
}// switch
pSdata=sdata;// Reset pointer to start of string.
}// if \r
}// available
}
Statistics for above Arduino string (c string based) command decoder (IDE ver 1.8.8):
Sketch uses 2076 bytes (6%) of program storage space. Maximum is 30720 bytes.
Global variables use 270 bytes (13%) of dynamic memory, leaving 1778 bytes for local variables. Maximum is 2048 bytes.
Note: The output and ultimate operation of the above program is exactly the same as for the 'String-class' based one.
Arduino String References
Arduino String Objects
For C object Strings a good place to look is the Arduino String Reference. This shows you the available member functions (scroll down for links).
However the above link does not detail the functions available for c-style strings. For that look to a standard reference. This link is good as it orders the functions in the order of most used.
P.S. Have a look at strtok() as this can allow you to process a
command line with multiple commands separated with a delimiter e.g. a
semi-colon - Useful for a more complex serial command decoder.
Comparison of String and c string
The following table shows the difference in memory usage:
Type
Flash
SRAM
String
3912
248
c string
2076
270
Difference String cf c string
+1836
-22
Note: It appears that the String class uses
less SRAM but it, in fact uses the heap and uses more than c string uses
(it is just not easy to measure because it can change all the time as strings are created and destroyed).
Using Class String to control strings is undoubtedly the easiest way
and is very similar to higher level languages such as python but it comes
at a cost - that cost is Flash program memory and an unknown amount of SRAM!
Flash memory
Although this c-string program performs exactly the same operation as the String based one, the String based one adds a further 1836 bytes of Flash. This is the String class performing magic in the background.
This amount of memory may not matter to a device with large Flash
memory but it is fairly significant when using an Arduino Uno (32k
Flash).
Arduino String SRAM Use
You can see that the SRAM use for c strings is higher than for String
- this is because the buffer is declared as a static array of bytes
before the program runs:
staticcharsdata[BUF_LEN],
This means the SRAM for this string is reserved before run time. When
using the heap, (in the String Class) memory is allocated at run time.
That really means the
compiler does not known how much memory is (or will be) used by the
program when using String objects (and it will be larger as there is an
overhead in managing blocks of SRAM using malloc and free which are the
memory allocation and deallocation functions used in C and C++).
SRAM Memory Size
The other thing to notice is:
In the String program there is no memory declaration.
There is no specification of the size of memory used to store the
string - in fact the strings are stored in SRAM but they use the heap.
The heap is unused memory that sits between the stack and the variable
space.
How is SRAM used for String
The String class member functions use SRAM starting from the top end of memory and
building down. The blocks of memory are allocated and 'freed' using a
cleanup algorithm.
Should You Use String?
If you search on the Web, you will find that there can be problems in
using heap based systems and that is due to memory fragmentation.
Fragmentation in the heap is caused when strings of larger length can
not be allocated to a 'freed' memory block (since the block that had
been previously 'freed' up is too small); they have to be allocated in
new memory. This leaves lots of small blocks of unused memory.
In the extreme case memory fragmentation, caused when you have low
SRAM! and lots of string manipulations, can cause your program to
hang - the solution is to press the reset button! For small programs
with low numbers of Strings, its fine (there won't be #enough memory
fragmentation to cause the program to hang.
The following statement is from a consortium of car
manufacturers MISRA (Motor Industry Software Reliability Association).
They specifically forbid using heap based memory management because
safety is crucial:
MISRA C++ rule 18-4-1, dynamic heap memory allocation cannot be used.
This document describes static, dynamic memory fragmentation etc. but its final conclusion is this:
"Exhaustion is still
our major impediment to using dynamic memory in real-time embedded
systems. A good failure policy based around std::bad_alloc can
address many of the issues, but in high integrity systems dynamic
memory usage will remain unacceptable."
For programs that do not use many strings the Arduino string class is
fine. However if you have low levels of SRAM and use lots of string
manipulations you could run into fragmentation problems (random
failure).
What exactly is a String?
As noted at the start, a string is a line of text stored in SRAM.
Note: This section applies to both Strings and c-strings.
Both Strings and 'strings' are dynamic entities meaning they can be
changed whenever you want since they exist in SRAM. For an embedded
system that typically has very low SRAM available (Arduino Uno 2k Byte)
it means the SRAM will disappear fast unless you put all your constant
strings into Flash.
In fact strings are doubly wasteful in C or C++, because they use
both Flash and SRAM.
The first reason is initialisation. You probably want to start off a
string with some information in it e.g. "Initialising I2C" etc.;
Sometimes you don't e.g. a receive buffer for a serial input. To do the
initialisation you need somewhere to store the string when the power is
off, and that store is Flash memory.
The second reason is that strings are defined as existing in RAM so
that they can be changed as your program runs.
In some cases you don't want an
updatable string e.g for a text message that never changes. The problem
is that the compiler won't know that the string is never going to
change.
Putting Strings into Flash memory
To place strings into Flash, in Arduino code, enclose a fixed string
with the F macro e.g. Serial.print(F("My fixed string")); That leaves
the
string in Flash memory.
Using the F() macro stops the transfer
of initialization data from flash memory to SRAM and only uses the data
from the Flash memory, so you save SRAM.
TIP: There are other functions specially made for using Flash memory
e.g. string copy from Flash to 'a normal string in SRAM'. You can find
these by searching for "Arduino PROGMEM strcpy_P". These are specialized
functions that you won't need often but it is good to know they are
available.
Arduino Strings and c strings compared
Advantages of String class
Does not allow buffer overrun.
Easy to use and intuitive e.g. to append strings use the '+ operator
Disadvantages of the String class
Uses more Flash memory.
Uses the heap leading to heap fragmentation.
Uses the heap that uses an undefined amount of SRAM at run time.
Advantages of c strings
Uses minimum Flash memory.
Does not use the heap.
You have complete control of how memory is used.
Disadvantages of c strings
Not intuitive.
Need to learn slightly cryptic functions to use them.
Can get buffer overrun if not careful.
Conclusions
The basic truth is this:
String Class objects are easier.
String Class objects can cause failure due to memory fragmentation.
So if your primary concern is operational safety (or of lesser
importance - reliability), don't use String class objects - use c-style
code.
A PIR sensor lets your Arduino sense movement without contact. This tutorial covers PIR sensor basics, connecting one to an Arduino board and coding a motion detector.
Arduino Hall Effect Sensor: Add magnetic sensing superpowers to your Arduino projects with an easy-to-use hall effect sensor. With full code and layout...
Get started with an Arduino humidity sensor using the DHT11, which reports both humidity and temperature. Complete guide with full code for using this sensor
Comments
Have your say about what you just read! Leave me a comment in the box below.
Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.