PString

A Lightweight String Class for Formatting Text

Note: PString 3 is now Arduino 1.0 compatible.

Since Print was introduced with Arduino 0012, several classes, including HardwareSerial, LiquidCrystal, Ethernet Client/Server, and my own NewSoftSerial, have been written to leverage its text rendering engine.  But getting formatted text to output devices not in this short list still requires either writing custom code or turning to expensive alternative solutions like sprintf().

PString (“Print-to-String”) is a new lightweight Print-derivative string class that renders text into a character buffer. With PStrings, you can use the Print renderer for any device, even those that do not directly support Print-style text formatting, by first “printing” to a string.

In its simplest use case, you deploy an “on-the-fly” constructor to format text:

char buffer[30];
#define pi 3.14159
PString(buffer, sizeof(buffer), pi);

This code uses Print’s float rendering functions to generate the string equivalent of pi into buffer.

Since PString inherits from Print, PString objects can do everything that other Print-derived classes do:

<code>char buffer[50];
PString mystring(buffer, sizeof(buffer));
char name[] = "Joe";
int age = 45;

mystring.print("Hi, my name is ");
mystring.print(name);
mystring.print(" and I am ");
mystring.print(age);
mystring.println(" years old.");

This generates the expected sentence in buffer the same as if you had printed to the Serial port.

Other member functions

PString is a fairly minimal string class. It can report its length and capacity and give const access to its internal string buffer:

Serial.print(str.length());
Serial.print(str.capacity());
Serial.print(str);

You can reuse a string by calling its begin() function. This effectively resets the position in the buffer where the next printed text will go:

str.print("Hello");
str.begin();
str.print("World");
// str contains "World" here

Operators

PString provides three operators for assignment, concatenation, and equivalency test:

char buffer[20];
PString str(buffer, sizeof(buffer));
str = "Yin"; // assignment
str += " Yang"; // concatenation
if (str == "Yin Yang") // comparison
{
  Serial.println("They are equal!");
}

Runtime safety

PStrings do not “own” their own buffers. Instead, they rely on preallocated static buffers that are passed in at the point of construction. PStrings never allocate memory dynamically, even when the result of a print(), assignment, or concatenation operation would seem to exceed the current buffer’s size. In these cases, the excess data is simply discarded and the string correctly terminated.

Because of these constraints, PStrings can make three key guarantees:

  • they will never cause a buffer overflow
  • a string’s buffer will always be valid memory, i.e. the original buffer
  • buffers will always contain valid (i.e. NULL-terminated) C string data.

Download

The latest version of PString is PString3.zip.

Revision History

Version 1 – initial release
Version 2 – include support for inline renderings with modifiers HEX, OCT, etc. (and eventually float precision)
Version 3 – Arduino 1.0 compatibility

Resource Consumption

PString objects consume 8 bytes of memory during their lifetimes. Depending on what features are used, #including the PString library usually adds only 100-600 bytes to a program’s size.

All input is appreciated.

Mikal Hart

Page last updated on July 3, 2013 at 7:42 pm
76 Responses → “PString”

  1. Anonymous

    14 years ago

    Hi … I was testing your library and the following code fails in one part!

    ———-
    #include

    char localBuffer[25];
    PString locBuf(localBuffer, sizeof(localBuffer));

    void setup() {
    Serial.begin(9600);
    locBuf.begin();
    }

    void loop() {
    locBuf = byte(70); // F
    locBuf += byte(97); // a
    locBuf += byte(105); // i
    locBuf += byte(112); // l
    Serial.print(locBuf); // prints out “Fail” OK
    Serial.print(locBuf.length()); // prints 0 -> FAIL shoud be 4
    }
    ———

    Do you have any idea why?

    thx!


  2. Mikal

    14 years ago

    Hmmm… on my Arduino, using 0017, this prints

    Faip4Faip4

    repeatedly, which is exactly what I would expect. If yours prints “Fail0”, I don’t understand where the ‘0’ comes from — or the ‘l’ for that matter! :)

    Mikal


  3. Anonymous

    14 years ago

    thx a lot… this is not the full code, I just simplified it!
    i’m using your library 2 make a “sensor package” 4 XBee transmission…
    I miss calculated the “l” sry bout that!
    it’s possibly some reference problem! i’ll try 2 figure it out!
    BTW Great job on the library!


  4. Jonathan Starke

    14 years ago

    Thank you thank you, you saved me a lot of frustration
    Thnx
    Jonathan


  5. Mark Kieling

    13 years ago

    Mikal, there is no call to va_end in your library. It is my understanding that a call to va_start must be balanced with a call to va_end. Is this not the case?


  6. Mikal

    13 years ago

    Thanks, Mark. I *think* it’s harmless in this case, but you’re quite right. I should put that in the next edition. Thanks.

    Mikal


  7. Frikosal

    13 years ago

    Thanks !!

    May I suggest to change slightly the example ?

    str.print(“The value of PI is “);
    str.print(PI,4);

    The “,4” to indicate the number of decimal places would be useful for people like me.


  8. Mikal

    13 years ago

    @Frikosal–

    Good idea!

    Thanks.


  9. robbok

    13 years ago

    Hey Mikal, just a note of thanks for creating this library. It literally saved my life with the Sparkfun 16×2 LCD. I was going out of my mind not being able to print numbers or easily concatenate strings and numbers. You’re a lifesaver!


  10. Mikal

    13 years ago

    @robbok,

    :)


  11. Carl Nobile

    13 years ago

    The format() method sounded like just what I needed, however, it doesn’t seem to work with floats. Below is what I see on the serial monitor:

    str.format(“CM: %04ld, SD: %6.2f, IN: %04ld, SD: %6.2f\n”,
    cmMsec, cmSD, inMsec, inSD);

    gives me this:

    CM: 0159, SD: ?, IN: 0062, SD: ?

    I think it may be the underlying implementation of vsnprintf. Also the final /n is not recognized at all as I am getting the typical staircase effect on the serial monitor.

    I have been looking for documentation on the Arduino implementation of there C/C++ libs, do you know if they exist and if so where to find them?

    Thanks, Carl


  12. Mikal

    13 years ago

    Yeah, unfortunately the tiny arv-gcc implementation of Xprintf() doesn’t support %f floating point conversions. Too bad!

    Mikal


  13. Christoph

    12 years ago

    Hi, I use PString in a few of my projects. After installing Arduino 1.0 I realized that PString doesn’t work with the new Arduino 1.0 standard.

    an update for PString that can be used with the new version would be very much appreciated

    Chris


  14. Mikal

    12 years ago

    @Christoph,

    PString has now been updated to support Arduino 1.0! :)


  15. Ria

    12 years ago

    hi am having a problem on how to read data from xbee through arduino am new to this . i have downloaded the library of xbee

    my transmitter xbee is sending analog data from my accelerometer X,Y,Z into packets i understand my packets when i use X-ctu and it seems right .

    but could you tell me how to program my Arduino so it can understand my packets when i connect my receiver to Arduino ??

    I NEED The CODE ?
    please help am stuck for week !!


  16. AdvancedNewbie

    12 years ago

    Thank you for this.


  17. Kenny

    12 years ago

    Hi Mikal,

    I’m tired and probably being stupid, but I’ve just tried running the test_PString sketch on the Arduino Uno – it gives me “PString was not declared in this scope”, as if the library was empty/missing yet the #include statement is there. The PString folder is sitting with all the other libraries that run successfully. What have I done?

    Kenny


  18. Kenny

    12 years ago

    Sorted – forgot to restart Arduino IDE


  19. Clart

    12 years ago

    Is it possible to access to a PString char by char, similar to an array?


  20. Clart

    12 years ago

    Forget my previous question, I finally figured that I could just access the buffer directly for this.


  21. Mikal

    12 years ago

    Yep.

    char c = ps[x];

    Just like an array.


  22. Scott

    11 years ago

    Hi, thanks for this work. These may be dumb ideas, but it would be nice to be able to compare two of these objects with “==” operator.

    One other issue I ran into was passing one of these in as an arg to sprintf causes program to hang. I can pass one as format string, but not as an arg for some reason. I had to pass the buffer.

    Of course, indexOf, substring etc would be nice too for someone wanting to replace String class with this guy.


  23. Scott

    11 years ago

    oops, it looks like the equality operator does indeed work between two of these… sorry! Thanks again.


  24. Pratik

    11 years ago

    Hello Mikal,

    Thanks for this library, it saved me from RAM overflow, previously I was using String constructors,

    Is it possible to trim or remove some chars?

    thanks.


  25. Mikal

    11 years ago

    @Pratik,

    That’s a good idea, but the current version of the library doesn’t support Trim or Remove. (Maybe next time?)


  26. denis

    11 years ago

    Many thanks!!

    Ifinaly can print float to my graphic LCD (sparkfun)

    Easy with PString


  27. K

    11 years ago

    This is a good idea, the builtin String library is rubbish, and really a string buffer is all I really need for my project.

    Though I have an issue with a function that returns a PString, as follows:

    PString xyposString(int x, int y) {
    char outStringBuff[10];
    PString outString(outStringBuff, sizeof(outStringBuff), “?x”);

    if (x<10) {
    outString.print("0");
    }
    outString.print(x);
    outString.print("?y");
    outString.print(y);
    Serial.println(outString);
    return outString;
    }

    void loop() {
    Serial.println(xyposString(10,1));
    }

    The Serial.println(outString); inside the function prints a string as expected: ?x10?y1
    But the call Serial.println(xyposString(10,1)); prints the following: ?ïÜ8

    any suggestions?


  28. K

    11 years ago

    ahh I get it now. The buffer only exists within the function, and the PString that is returned is now a pointer referencing where the buffer used to be.

    The buffer would need to be a part of the PString in order for my function to work.


  29. Mikal

    11 years ago

    @K,

    Yeah, as you observed later the “outStringBuff” disappears when the function xyposString exits. But if you declare outStringBuff static, it will work (assuming you never need more than one xyposString at a time):

    static char outStringBuff[10];

  30. Sparks

    11 years ago

    Mikal, I am very satisfied with PString: It is compact and reliable. Many thanks for creating and maintaining it.

    I have a question: I pass a Pstring variable named “message” as an argument to a function. After the function has completed, I find the expected content in “message” but if I try to concatenate an additional character with message.print(c)then the result is a string with only that character. With message.begin() you can “reset” the buffer-pointer to position 0. Is there a way to direct the buffer-pointer to the end of the buffer’s content ?
    Thanks for your suggestions !


  31. Mikal

    11 years ago

    Hi Sparks,

    I think I understand your question. The function you are calling is operating on a DIFFERENT PString that happens to point to the same Message buffer. I think the solution is as simple as declaring your function parameter call-by-reference, like this:

    void myfunc(PString &message)
    {
      ...
    }
    
    // caller
    PString message(buf);
    myfunc(message);
    

  32. Sparks

    11 years ago

    Mikal, thank you for your answer. With this finishing touch everything now works as I had intended.


  33. jordan

    11 years ago

    Wanted to know how to take the IP address that is returned from Ethernet.localIP() and somehow display it on a tft touch lcd. Apparently, as far as text stuff goes I only see that the Tft library does:
    Tft.drawChar(…)
    and
    Tft.drawString(…)
    After a while of not getting the result I wanted I came across this PString library.
    Here is the info for the TFT screen and its library for reference: http://www.seeedstudio.com/wiki/2.8%27%27_TFT_Touch_Shield_V1.0


  34. Mikal

    11 years ago

    I’m guessing:

    char buf[32];
    PString pstr(buf, 32);
    pstr.Print(IPAddress);
    Tft.drawString(buf);

  35. jordan

    11 years ago

    Thanks Mikal, I will try that out hopefully later on today or this weekend! Can’t wait, hope that solves it!!!!

    also, could you fill me in on the PString pstr(buf, 32).
    I realize your making an object of PString called pstr. but more so what is the requirements within the () doing?

    I notice the array of char type with a size of 32, what is the purpose of 32 in the …pstr(buf, 32)?
    Thanks in advance!


  36. jordan

    11 years ago

    Thanks anyway for trying Mikal, but that didn’t do what I wanted. For what its worth here is the code as it stands now. It might be ugly for now, but thats only until it works like a want then i will clean it up.
    arduino code:
    ********** BEGINNING ************
    *********************************

    #include
    #include
    #include
    #include
    #include
    #include

    // Enter a MAC address for your controller below.
    // Newer Ethernet shields have a MAC address printed on a sticker on the shield
    byte mac[] = {
    0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02 };

    // Initialize the Ethernet client library
    // with the IP address and port of the server
    // that you want to connect to (port 80 is default for HTTP):
    EthernetClient client;

    void setup()
    {
    Serial.begin(9600);

    Tft.init(); //init TFT library

    Ethernet.begin(mac); // start the Ethernet connection:

    char buf1[32];
    char buf2[32];

    PString str1(buf1, 32);
    PString str2(buf2, 32);

    str1.print(“My IP address is: “);
    str2.print(IPAddress());

    // ****print your local IP address:****
    Tft.setDisplayDirect(DOWN2UP);
    Tft.drawString(“My IP address is: ” ,1 , 318, 2, WHITE);
    Serial.print(str1);
    Serial.println(str2);
    Tft.setDisplayDirect(DOWN2UP);
    Tft.drawString(str2 , 18, 318, 2, WHITE);

    }

    void loop()
    {

    // char buffer3[40];
    // PString str3(buffer3 , sizeof(buffer3));
    //
    // for (byte thisByte = 0; thisByte < 4; thisByte++)
    // {
    // // print the value of each byte of the IP address:
    // str3.print(Ethernet.localIP()[thisByte], DEC);
    // str3.print(".");
    // }
    // Serial.println("here is str3! ");
    // Serial.println(str3);
    }

    **************************************
    *************** END ******************

    Ethernet library link – http://arduino.cc/en/Reference/Ethernet

    TFT touch lcd link – http://www.seeedstudio.com/wiki/2.8%27%27_TFT_Touch_Shield_V1.0

    and to reiterate what I can't get to work is displaying the IP address (assigned via dhcp) on the tft touch screen.
    Thanks for any assistance.


  37. jordan

    11 years ago

    Sorry, the libraries didn’t post for some reason a second ago, but heres what they look like in the header:
    #include SPI.h
    #include Ethernet.h
    #include stdint.h
    #include TouchScreen.h
    #include TFT.h
    #include PString.h

    all are surrounded with “”.


  38. jordan

    11 years ago

    *sigh*

    it didnt display the less than and greater than symbols that are used in the header, sorry for that and all the extra post that resulted!


  39. jordan

    11 years ago

    hmm, my code that I supplied in order to get assistance was deleted/removed. interesting?


  40. Mikal

    11 years ago

    @jordan,

    Does the commented out code in loop() print the IP address correctly to Serial? Assuming so, couldn’t you use the same technique to print to TFT?


  41. Frank

    11 years ago

    Hi, I was looking for a way to work around a problem I’m having with strings, and stumbled upon your library.
    I ran the included example and I am wondering if it behaves as intended. I get this output:

    notice the second line in particular, where I expected “Printing strings” folowed by the decimal and hexadecimal version of 86400.

    3.14
    15180
    The value of PI is 3.14
    The value of PI is 3.14
    The string’s length is 23
    Its capacity is 40
    Hello, world!
    Goodbye, cruel world!
    Yes, alas, goodbye indeed

    IDE 1.0.3, tested on a mega1280 clone


  42. Mikal

    11 years ago

    @Frank,

    Hi. I had to take a good look at this to figure it out, but the example doesn’t actually print “Printing strings” or the decimal value of 86400. Those lines are just showing examples of how to create those strings in the buffer. They overwrite each other, and then the only one actually printed is the HEX version of 86400 (15180).


  43. Frank

    11 years ago

    Thanks for your fast response, I could not quite figure out the logic behind this
    So the Pstring function overwrites previous content, and does not append to the existing content.
    Maybe write a comment in the example to clarify this?

    Meanwhile, I figured out how to work around my problem with Strings using your library, and save some program space (I think)

    to send some text using a wireless nRF24l01 module, and the Mirf library, I had this example to work with:
    writing down some code snippets here so others may benefit …

    unsigned long time = millis();
    Mirf.send((byte *)&time);

    When I try to send a string, garbage is sent, probably because I can’t quite grasp the whole concept of pointers and strings

    I got it working using a char array like this:

    char s[16];
    Mirf.send((byte *) &s );

    But I have a lot of character manipulation to do and a String library helps a lot
    I finally got it working using this:

    #include
    char buffer [16];
    PString pstr(buffer, sizeof(buffer));
    pstr.println (“test”);
    Mirf.send((byte *) &buffer[0] );

    If I’m not mistaken, &buffer did not work – it has to be &buffer[0] – but if I find some time I’ll look into it a bit deeper.

    Now I’m going to look into your streaming library, if I’m right that would make my string manipulations a bit easier too :)

    Thank you for keeping your library up-to-date!


  44. Michael

    11 years ago

    Love this library along with streaming also on your web site. It would be great if the begin() function returned the PString class, so that we could use the syntactic sugar:

    Serial << myString.begin() << "from the Start"<< "\n";

    or is there a better way of doing this?

    Thanks for the libs… great stuf


  45. Mikal

    11 years ago

    Love the syntactic sugar idea Michael. Will investigate for next release…!


  46. Pau Ortega

    11 years ago

    Thank you Mr. Hart, you did save me a lot of trouble trying to shove floats in character arrays. By the way, have you informed Arduino that you changed your website? Because I learned of this library thanks to arduino.cc

4 Trackbacks For This Post
  1. Introducing PStrings « Arduiniana

    […] Check out the new PString class. […]

  2. ka1kjz.com » Twittering Arduino Completed

    […] actual text for the tweet, the Arduino has very limited string manipulation functions.  But the PString library by Mikal Hart proved to be just what I […]

  3. Arduino net connected clock tells weather not time

    […] third party libraries were used; PString to handle parsing the incoming website, Flash for memory management, and Streaming for easier […]

  4. Arduino and Windows HyperTerminal | Binglong's space

    […] is a text string with an ending CR(Carriage Return, 0x0d), so is any reply from the board. Actually PString can be used to format the reply (or an active notification from board) easily. Messenger can be […]

Leave a Reply