Pages: 1 [2] 3 4   Go Down
Author Topic: String/sprintf alternative specifically for Arduino.  (Read 8872 times)
0 Members and 1 Guest are viewing this topic.
Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 217
Posts: 13739
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

progmem (+1) support would be great as it allows to build up (almost) any length output string.

Binary printing %b
int x = 15;
g.printf("%b", x); -> "1111"        // just the bits needed
g.printf("%0b", x); -> "00001111"    // preceding zero's
g.printf("%00b", x); -> "0000000000001111"    // 2 zeros makes it 2 bytes
g.printf("%0.0b", x); -> "0000.0000.0000.1111"   // . char as separator (to easier recognize individual bits

maybe a better alternative could be %4b %8b %16b %32b or %20b or %24b  in short to explicitly give the length



Direct EEPROM printing would be great too as otherwise I need to make a local copy to insert into the sprintf buffer.
%r  (r from rom?)

int offset = 65
g.printf("%4r", offset);  // 4 bytes from position 65-68

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: 70
Posts: 2171
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I would definitely like to do some abstract options too, I've focused mainly on the stuff supported by the Print library. PROGMEM support is within the print library so that'll get added no worries.

Also I'm almost finished testing a patch for the core, which requires my library and two other files. It inserts the print functionality and the new sprintf straight in. I'll finish up and post it shortly, but I just tested this code with flying colours.

Code:
 Serial.printf( "Characters: %c %c \n", 'a', 65 );
  Serial.printf( "Decimals: %d %ld\n", 1977, 650000 );
  Serial.printf( "floats: %f\n", 3.1416f );
  Serial.printf( "Preceding with blanks: %10d \n", 1977 );
  Serial.printf( "Preceding with zeros: %010d \n", 1977 );
  Serial.printf( "Preceding with zeros(width passed as param): %0*d \n", 10, 1977 );
  Serial.printf( "Some different radices: DEC: %ld HEX:%lx \n", 650000, 650000 );
  

Which produces this output:
Quote
Characters: a A
Decimals: 1977 650000
floats: 3.14
Preceding with blanks:       1977
Preceding with zeros: 0000001977
Preceding with zeros(width passed as param): 0000001977
Some different radices: DEC: 650000 HEX:9EB10

It replaced this ugly code:
Code:
sprintf ( buffer, "Characters: %c %c \n", 'a', 65 );
  Serial.print( buffer );  
  sprintf ( buffer, "Decimals: %d %ld\n", 1977, 650000 );
  Serial.print( buffer );
  sprintf ( buffer, "floats: %f\n", 3.1416f );
  Serial.print( buffer );
  sprintf ( buffer, "Preceding with blanks: %10d \n", 1977 );
  Serial.print( buffer );  
  sprintf ( buffer, "Preceding with zeros: %010d \n", 1977 );
  Serial.print( buffer );  
  sprintf ( buffer, "Preceding with zeros(width passed as param): %0*d \n", 10, 1977 );  
  Serial.print( buffer );    
  sprintf ( buffer, "Some different radices: DEC: %ld HEX:%lx \n", 650000, 650000 );
  Serial.print( buffer );

And that same functionality is available to all Print classes like: Ethernet, WiFi, SD, LiquidCrystal, Wire, SoftwareSerial.

The EEPROM idea is nice, I'll look into that, I'm thinking of making a collection of writeable objects so the EEPROM would fit there nicely.
Logged


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

+++++

the eeprom printing should have additional modifiers to indicate the type

int offset = 65
g.printf("%4br", offset);  // default in this case 4 bytes from position 65-68

g.printf("%br", offset);  // print byte 65 as a byte
g.printf("%cr", offset);  // print byte 65 as an (ASCII) character
g.printf("%ir", offset);  // print byte 65 66 as a int
g.printf("%ur", offset);  // print byte 65 66 as an unsigned int
g.printf("%lr", offset);  // print byte 65-68 as a long
g.printf("%ulr", offset);  // print byte 65-68 as an unsigned long

the numeric offset makes printing  strings easy.



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: 70
Posts: 2171
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I have applied a few optimisations and PROGMEM strings are working now. It is quite different to the GString version as it uses the print pipeline for everything whereas GString could write to memory for a few things. Maybe a bit slower, but not larger.

Quote
p:   PROGMEM string. No formatting takes place, the string is printed directly.

I'm going to add the EEPROM test asap, then I'll post the files for the patch.
Logged


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

I've been experimenting some different ways to do things.

Here are a few common functions implemented using the Print library.

Code:
inline int sprintf( char * str, const char * format, ... )
{
va_list v_List;
va_start( v_List, format );
GString g( str );
int i_Return = g._printf( format, v_List );
g.end();
va_end( v_List );
return i_Return;
}

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;
}

On a large test sketch the optimisations made a huge difference.

  • 7762 bytes: Original Arduino code.
  • 7388 bytes: Print based sprintf
  • 7274 bytes: using Print based itoa, utoa, ltoa, ultoa
  • 7014 bytes: using printf
  • 6958 bytes: force itoa and utoa using 32-bit conversions. ( sprintf does not use 16 bit Print code. )

I have the eeprom code ready too. I'll give it a testing soon too.
Logged


Netherlands
Offline Offline
Jr. Member
**
Karma: 1
Posts: 93
Profile before you Optimize.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

PRG_MEM is not EEPROM but flash.  smiley-wink
Logged


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

PRG_MEM is not EEPROM but flash.  smiley-wink

Yup, this is reading strings from both eeprom and flash.
« Last Edit: May 23, 2013, 05:08:08 am by pYro_65 » Logged


Netherlands
Offline Offline
Jr. Member
**
Karma: 1
Posts: 93
Profile before you Optimize.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Yup, this is reading strings from both eeprom and flash.

Nice!

What syntax are going to use? / What would a call look like?
Logged


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

This feature is for my Print based printf code highlighted here: http://forum.arduino.cc/index.php?topic=166540.msg1247875#msg1247875

If you are to use the GString library by its self you can do this:
Code:
char buffer[32];
GString g( buffer );
g.print( EE( address ) );
//or
g.printEEPROM( address );
//or
g.printf( "%r", address );


Once I release my Print patch, the functionality will be added to every derived class (Serial, Ethernet, WiFi, SD, LiquidCrystal, Wire, SoftwareSerial).
Then you can use code like this ( each line is equivalent ):
Code:
wifi.printEEPROM( address );
glcd.print( EE( address ) );
Serial.printf( "%r", address );

PROGMEM support is already in the Print library, and uses the printf command '%p'.
« Last Edit: May 23, 2013, 07:13:00 am by pYro_65 » Logged


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

- a playground page could be written to show all % commands. (with samples)

- after this a scanf() implementation for streams?
Logged

Rob Tillaart

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

Anaheim CA.
Offline Offline
Faraday Member
**
Karma: 47
Posts: 2892
...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@ pYro65 thank You this is something I've waited for... and sweated over trying to do it the hard way, un-necessarily so. Thank You Again, I'll have it installed as soon as I can get to the machine. Tell me, Do you have a Pepsi account too??. When you do I will be there to take care of my obligation.

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: 70
Posts: 2171
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

@robtillaart:

The playground idea is a good idea, however I have only peeked at scanf, it seems like it might have a significant overhead, unless a lot of the functionality can be reproduced using the stream library, like printf does with Print.

@Docedison, the latest code I have released is only the GString version, the built in Print patch is coming shortly. I'll try post it before work today, otherwise tonight sometime.
Logged


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

I have finished a major overhaul, with far more possibilities.

I have created a generic printing class called NonStreamingIO and combined with the Arduino core provided 'Printable' Class, the eeprom code ( EString ) functions the same as the GString, except prints to ROM.

The Printable class allows printing strings directly into the print class. There is still more to go, but here is what the builtin version of GString and EString will look like, the printf functionality is builtin to Print now.

These allow things like:
Code:
EString e( 0, 10 ); //Set string from 0 to 0 + 10, or 11 characters.

//Send current string from EEPROM over serial.
Serial.print( e );

//Write a float to EEPROM.
e.print( 123.456f, 3 );

That code above is how the Print library and sprintf will deal with EEPROM and SRAM reading writing ( printing out / printing to ).

This is only a preliminary attempt, but here is the SRAMWriterand EEPROMWriter, which are typedef'd to GString and EString.

Code:
#ifndef HEADER_PRINTER
  #define HEADER_PRINTER
  
  class NonStreamingIO : public Print{
    public:
      size_t count( void ) const { return End - Start; }
    
      virtual size_t write(uint8_t) = 0;
    
      template< typename T > NonStreamingIO &operator +=( const T &t )
        {
          print( t );
          return *this;
        }  

      template< typename T > NonStreamingIO &concat( const T &t )
        {
          print( t );
          return *this;
        }
        
      template< typename T > NonStreamingIO &concat( const T &t, const int i )
        {
          print( t, i );
          return *this;
        }    

      template< typename T > inline void assign( const T *u_DataPtr, const uint16_t u_Length )
        {
          Start = ( uint8_t* ) u_DataPtr;
          End = Start + u_Length;
        }
    protected:
      NonStreamingIO( uint8_t *u_DataPtr ) : Start( u_DataPtr ) , End( Start )
        { return; }  
        
      NonStreamingIO( uint8_t *u_DataPtr, const unsigned int u_Length ) : Start( u_DataPtr ) , End( Start + u_Length )
        { return; }          
        
      uint8_t  *Start;
      uint8_t *End;        
    private:
  };
  
  #include <avr/eeprom.h>
  class EEPROMWriter : public NonStreamingIO, public Printable{
    public:
      template< typename T > EEPROMWriter( const T *t_DataPtr ) : NonStreamingIO( ( uint8_t* ) t_DataPtr ), Printable()
          { return; }
      template< typename T > EEPROMWriter( const T *t_DataPtr, const unsigned int u_Length  ) : NonStreamingIO( ( uint8_t* ) t_DataPtr, u_Length ), Printable()
          { return; }  

      size_t write( uint8_t u_Data )
        {
          if( eeprom_read_byte( End ) != u_Data )
            eeprom_write_byte( End, u_Data );
          ++End;
          return 0x01;
        }
    protected:
      friend Print;
      
      size_t printTo(Print& p) const  
        {
          uint8_t  *u_Cursor = Start;
          while( u_Cursor <= End ) p.write( ( uint8_t ) eeprom_read_byte( u_Cursor++ ) );
          return count();
        }
    private:
  };
  
  class SRAMWriter : public NonStreamingIO{
    public:
      template< typename T > SRAMWriter( const T *t_DataPtr ) : NonStreamingIO( ( uint8_t* ) t_DataPtr )
        { return; }
      template< typename T > SRAMWriter( const T *t_DataPtr, const unsigned int u_Length  ) : NonStreamingIO( ( uint8_t* ) t_DataPtr, u_Length )
          { return; }  
          
      operator char*( void )
        {
          *End = '\0';
          return ( char* ) Start;
        }  

      void end( void ) { *End++ = '\0'; }  

      size_t write( uint8_t u_Data )
        {
          *End++ = u_Data;
          return 0x01;
        }      
    protected:
    private:
  };
  
  typedef SRAMWriter GString;
  typedef EEPROMWriter EString;
    
#endif

I'll have to post the full patch later on tonight after work.
« Last Edit: May 23, 2013, 11:29:58 pm by pYro_65 » Logged


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

Just a quick question for all interested.

For EEPROM & PROGMEM control via printf, would you prefer either
  • Passing the address as a parameter to printf.
  • Have the address as part of the format string.

There are pros & cons to both sides, I have used the parameter approach for now.
Both methods could be written to allow formatting ( printf just prints the stored strings for now ).
Logged


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

Reading from EEPROM I have posted a few lines before using the %r with some modifiers.

addr = 12;
g.sprintf("%d : %r  ", addr, addr); // prints 12 : 75 (assuming the value 75 is in location 12)

Writing to EEPROM, -> passing the address as a parameter. Similar to


The format specifier can also contain sub-specifiers: flags, width, .precision and modifiers (in that order), which are optional and follow these specifications:

width   description
*   The width is not specified in the format string, but as an additional integer value argument preceding the argument that has to be formatted.

see - http://www.cplusplus.com/reference/cstdio/printf/ -


Another alternative might be that you create a class EELOC (== EEPROMlocation) which derives from print, to be able to print to EEPROM.

EELOC store = 15;
store.printf("a %d ", 10);

could be a 'prototype'for other external storage classes e.g. I2C EEPROM



Logged

Rob Tillaart

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

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