Pages: 1 2 [3] 4   Go Down
Author Topic: String/sprintf alternative specifically for Arduino.  (Read 6853 times)
0 Members and 1 Guest are viewing this topic.
North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 52
Posts: 1770
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

In a previous post here. I have an EEPROMWriter class, that can be used to print the data to eeprom and is passable to print so you can write the eeprom string out to another print, like serial.

EDIT, thanks for your printf input. I agree, the parameter method is best. I didn't intend to write to eeprom from it, just read. Which is what my EEPROMWriter class was originally for.
« Last Edit: May 25, 2013, 08:35:23 am by pYro_65 » Logged


North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 52
Posts: 1770
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

In addition to the code I linked to in my last post, here is the PROGMEMReader class.

Usage.
Code:
char c0[] PROGMEM = "Test string one.";
char c1[] PROGMEM = "Test string two.";

void setup(){
  
  PString p = c0;
  
  Serial.begin(115200);
  Serial.println( "-----------------------" );
  Serial.println( p );
  Serial.println( p = c1 );
  Serial.println( "-----------------------" );
}

void loop(){}

Output.
Quote
-----------------------
Test string one.
Test string two.
-----------------------

Class.
Code:
class PROGMEMReader : public Printable{
public:
template< typename T >
PROGMEMReader( T *t_DataPtr ) : Start( ( uint8_t* ) t_DataPtr )
{ return; }

PROGMEMReader &operator =( uint8_t *t )
{
Start = t;
return *this;
}
protected:

friend class Print;
size_t printTo(Print& p) const
{
uint8_t *u_Cursor = Start;
size_t s_Return = 0;

while( true ){
unsigned char u_Current = pgm_read_byte( u_Cursor++ );
if ( u_Current == 0 ) break;
s_Return += p.write( u_Current );
}
return s_Return;
}

private:
uint8_t *Start;
};

typedef PROGMEMReader PString;

This is included in the code I will release.
Logged


North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 52
Posts: 1770
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Woo Hoo. My test worked perfectly.

It writes a PROGMEM string into EEPROM then prints the EEPROM text over serial.
This code is also safe, the EString won't overwrite unchanged data, so it only writes the string once.

Code.
Code:
char c0[] PROGMEM = "Test string one.";

void setup(){
 
  PString p = c0;
  EString e( 0 );
 
  Serial.begin(115200);
  e.print( p );
  Serial.println( "-----------------------" );
  Serial.println( e );
  Serial.println( "-----------------------" );
}
void loop(){}

Output.
Quote
-----------------------
Test string one.
-----------------------
Logged


Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 167
Posts: 12417
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

+1
Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 52
Posts: 1770
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I have finished adding the last few bits to sprintf/printf. Here is an example of the latest features.

This expects the EEPROM value written in my previous post to still be valid.
Code:
char c0[] PROGMEM = "Test string two.";

void setup(){
  Serial.begin(115200);
  Serial.printf( "%30n\nEEPROM: %16r\nPROGMEM: %p\n%30n", '=', 0, c0, '=' );
}
void loop(){}

Output.
Quote
==============================
EEPROM: Test string one.
PROGMEM: Test string two.
==============================

I'll have the code ready shortly.
Logged


Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 167
Posts: 12417
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

impressive, major step forwards!

how much does the final version add to footprint compared to regular print?
Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 52
Posts: 1770
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

The s/printf function is large due to it including support for float & decimal conversions, PROGMEM, EEPROM.
You will see its benefit in apps where you already use these features, so no more code is added, just the sprintf function code.

These small examples are not a good test, I'll post a sketch testing all the string functionality where you can see the size drop just from my underlying framework, then converting the blocks into printf calls makes for even smaller code.

The three classes GString, EString, and PString are designed for use with print and println calls.

As a control:
Code:
 Serial.begin(115200);
  Serial.println( "Hi" );

1,926 bytes.

Testing printf:
Code:
 Serial.begin(115200);
  Serial.printf( "%30n\nEEPROM: %16r\nPROGMEM: %p\n%30n", '=', 0, c0, '=' );

5,318 bytes.

The 'small' app equivalent of the printf line above:
Code:
 EString e( 0, 16 );
  PString p = c0;  
  Serial.begin(115200);
  Serial.repeat( '=', 30 );
  Serial.print( "\nEEPROM: " );
  Serial.print( e );
  Serial.print( "\nPROGMEM: " );
  Serial.println( p );  
  Serial.repeat( '=', 30 );

2,492 bytes.

Each feature not found in the Arduino standard 'sprintf' can be disabled by defining certain macros.
As you can see, the last version is only half a kb more than the control. Its the float and decimal conversions that are hurting the code.

I have a few other things I'm testing, but they aren't included in my patch, yet.
« Last Edit: May 25, 2013, 12:19:50 pm by pYro_65 » Logged


North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 52
Posts: 1770
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I forgot this version, which also compiles to 2492 bytes.

Code:
  EString e( 0, 16 );
  PString p = c0;   
  Serial.begin(115200);
  Serial.repeat( '=', 30 );
  Serial.concat( "\nEEPROM: " ).concat( e ).concat( "\nPROGMEM: " ).concatln( p );
  Serial.repeat( '=', 30 );
Logged


North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 52
Posts: 1770
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Okay, here is the patch.

It adds in:
  • Print::repeat()
  • Print::concat()
  • Print::concatln()
  • Print::printf()
  • sprintf
  • ftoa
  • EString: EEPROM printable class.
  • PString: PROGMEM printable class.
  • GString: SRAM printable class.
  • NonStreamingIO: Inheritable Print extension.
Documentation will be available soon.

There are two files to download. But there are some steps required to install them properly.

Step 1.

Download files to the core folder where Print.h & Print.cpp are located.
On my machine, I have arduino in my D:\ so the path is here: 'D:\arduino-1.0.5\hardware\arduino\cores\arduino'
If you use the beta 1.5.X, you will have to find the AVR core folder, I don't have it but maybe someone can post the directoy.

Step 2.

Open Print.h.
Scroll to the very bottom.
Before the closing brace of the Print class, add the line: #include "Print_Ext.h"

Here is the last 10 lines of Print.h with the mod:
Quote
    size_t println(long, int = DEC);
    size_t println(unsigned long, int = DEC);
    size_t println(double, int = 2);
    size_t println(const Printable&);
    size_t println(void);
   
   #include "Print_Ext.h"
};

#endif

Save changes and close.

Step 3.

Open Print.cpp
Underneath the line '#include "Print.h"' add the line: #include "Print_Ext.source"

Here is the first 10 lines ( after comments ) of code in Print.h with the mod:
Quote
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "Arduino.h"

#include "Print.h"
#include "Print_Ext.source"

// Public Methods //////////////////////////////////////////////////////////////

Save changes and close.

Step 4. ( optional, otherwise PROGMEM code is doubled up. )

In Print.cpp find the function:
Code:
size_t Print::print(const __FlashStringHelper *ifsh)
{
  const char PROGMEM *p = (const char PROGMEM *)ifsh;
  size_t n = 0;
  while (1) {
    unsigned char c = pgm_read_byte(p++);
    if (c == 0) break;
    n += write(c);
  }
  return n;
}

Comment it out and replace it with this:
Code:
size_t Print::print(const __FlashStringHelper *ifsh)
{
  return print( PString( ifsh ) );
}

Hopefully thats it.

* Print_Ext.h (7.96 KB - downloaded 13 times.)
* Print_Ext.source (11.09 KB - downloaded 16 times.)
Logged


North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 52
Posts: 1770
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I have just uploaded a copy of the code to google, so anybody not logged in can try the library.

The file is PrintPatch_001.zip.
https://code.google.com/p/arduino-extensions/downloads/detail?name=PrintPatch_001.zip

Inside the zip are the two files attached to the post above. So the installation instructions are the same.
Logged


Anaheim CA.
Offline Offline
Faraday Member
**
Karma: 44
Posts: 2804
...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thank You, the modifications were basically some copy and paste stuff. I'm assuming that when I install Ver 1.05 (Enried's) Will I need to copy the files to all the Arduino installations?. Somehow I think that's a dumb question... ?

Doc
Logged

--> WA7EMS <--
“The solution of every problem is another problem.” -Johann Wolfgang von Goethe
I do answer technical questions PM'd to me with whatever is in my clipboard

North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 52
Posts: 1770
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Will I need to copy the files to all the Arduino installations?.

Not a dumb question, for the printf functionality, yes you will need to install it into the core.

I'm going to do up a new GString.h which will include all the functionality except the built in Print::printf ( not possible without inclusion into the core ).

The string sprintf, GString, PString & EString will all be available for reading and writing where applicable using printf.

I'm going to make the new include Due compatible as well. Maybe I can modify the printf functionality so it can pipe the output down any Print enabled class, so you can use something like this ( only an idea ):

Code:
//In setup.
SetPrintTarget( Serial );

//Later on in code.
printf( "Test number: %d", 1234 ); //Gets spit down serial.

This is a very viable option which I'll implement soon, I however do prefer the object orientated approach ( Serial.printf(); ),
 whereas this method may help the c-style programmers feel more at home.

However the built in methods ( can have both ), can be tailored to use the smallest footprint. One example is the String library, it uses itoa, ltoa and such. These can be rewritten to use GString, so there is potentially less code to compile, and the String lib uses them automatically. Even memory handling can rewritten using the GStirng.

Here is a small selection of re-implementations for:
  • memset
  • memcpy
  • strcpy
  • strcat
  • ftoa
  • itoa
  • utoa
  • ltoa
  • ultoa

Code:
inline void *memset( void *ptr, int value, size_t num )
{
GString( ptr ).repeat( value, num );
return ptr;
}

inline void *memcpy( void * destination, const void * source, size_t num )
{
GString( destination ).write( ( uint8_t* ) source, num );
return destination;
}

inline char *strcpy( char *destination, const char *source )
{
GString( destination ).print( source );
return destination;
}

inline char *strcat( char *destination, const char *source )
{
GString( strchr( destination, 0x00 ) ).print( source );
return destination;
}

inline char *ftoa( float value, char * str )
{
GString( str ).print( value );
return str;
}

inline char *itoa( int value, char * str, int base )
{
GString( str ).print( ( long ) value, base );
return str;
}

inline char *utoa( unsigned int value, char * str, int base )
{
GString( str ).print( ( unsigned long ) value, base );
return str;
}

inline char *ltoa( long value, char * str, int base )
{
GString( str ).print( value, base );
return str;
}

inline char *ultoa( unsigned long value, char * str, int base )
{
GString( str ).print( value, base );
return str;
}
Logged


Offline Offline
Newbie
*
Karma: 0
Posts: 4
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

sprintf seems quite handy smiley

Is there a way to format a float like having 4 decimals? Now it seems that sprintf(buf, "insert into math (pi) value (%f);", pi); gives 2 decimals like 3,14. Where in earth the 2 decimals is written so I could tweak it if it can not be given in format?
Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 167
Posts: 12417
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


Check - http://www.cplusplus.com/reference/cstdio/printf/ - for all details of (s)printf formatting.
Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Offline Offline
Newbie
*
Karma: 0
Posts: 4
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Nice try, but no banana smiley-grin
Please walk me through this...

Code:
#include <GString.h>
...
    char buff[64];
    sprintf(buff, "Pi %f.", 3.14159);
    Serial.println(buff);
    sprintf(buff, "Pi %e.", 3.14159);
    Serial.println(buff);
    sprintf(buff, "Pi %7.4f.", 3.14159);
    Serial.println(buff);
    sprintf(buff, "Pi %7f.", 3.14159);
    Serial.println(buff);
    sprintf(buff, "Pi %07f.", 3.14159);
    Serial.println(buff);
    sprintf (buff, "floats: %4.2f %+.0e %E", 3.1416, 3.1416, 3.1416);
    Serial.print("expected 'floats: 3.14 +3e+000 3.141600E+000', but we get '");
    Serial.print(buff);
    Serial.println("' instead.");
Code:
Pi 3.14.
Pi .
Pi        4f.
Pi    3.14.
Pi 0003.14.
expected 'floats: 3.14 +3e+000 3.141600E+000', but we get 'floats:     2f .0e ' instead.
Logged

Pages: 1 2 [3] 4   Go Up
Jump to: