Go Down

Topic: String concatenation for Serial.print (Read 7362 times) previous topic - next topic

TonyWilk

I found it a pain to type multiple Serial.print()'s for debug output so came up with 'Sprint'.

It just does a real cut-down printf():

Code: [Select]
//
// Example of 'Sprint' - a cut-down printf()
// - saves time typing in debug output for Serial.print
// TonyWilk


//-----------------------------
// Serial.print helper function
// - a real cut-down printf()
//
void Sprint( char *fmt, ... )
{
  char c;
  va_list args;
  va_start( args, fmt );
  while( (c=*fmt) != 0 ){
    switch( c )
    {
    case '%':
      c= *(++fmt);
      switch( c )
      {
      case 'd': Serial.print( va_arg(args,int) ); break;
      case 'f': Serial.print( va_arg(args,double) ); break;
      case 'h': Serial.print( va_arg(args,int), HEX ); break;
      case 'c': Serial.print( (char)va_arg(args,int) ); break;
      case 's': Serial.print( va_arg(args, char *) ); break;
      default:  break;
      }
      break;
    case '\\':
      c= *(++fmt);
      if( c == 'n' )
        Serial.println();
      else
        Serial.print( c );
      break;
    default:
      Serial.print( c );
      break;
    }
    ++fmt;
  }
  va_end( args );
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(19200);
  Serial.println("boot...");
  char *astring= "test string";

  Sprint("This is an example...\n");
  Sprint("int:%d, float:%f, hex:0x%h, char: '%c', string:\"%s\".\nThe end\n",
          42, 123.45, 0xFACE, 'x', astring );
}

void loop() {
  // put your main code here, to run repeatedly:

}


Efficient? well, it's a lot easier to type in "int:%d, float:%f, hex:0x%h\n" than the equivalent in separate statements and it doesn't need yet another buffer (like printf() would).

Anyway, I find it handy.

Yours,
  TonyWilk

P.S. dunno how portable this is across all Arduinos, I dimply remember something about the types used with va_arg causing me some bother. I run only run Arduino Pro Mini (AtMega328).

Delta_G

I didn't do this because I don't care one bit if my program takes 300 microseconds to run or 310 microseconds, nor do I care if the program ends up being 22K or 25K in size.

But I DO care about being able to write standard code and have it work the way that I expect it to... usually the first time... as opposed to using a whole bunch of "print" calls and then going back and editing tiny glitches (such as a missing space between a message and a variable display).

I don't understand why everyone is so concerned about microseconds and a few extra K of flash?
You seem awfully concerned with yourself here.  The OP was asking about code size and execution efficiency.  That's the only reason I was suggesting that they be used as metric in this case. 
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

-dev

#32
Dec 17, 2017, 04:12 pm Last Edit: Dec 18, 2017, 05:09 pm by -dev Reason: Streaming correction
Regarding the question about the example sketches:

Quote
Does this look right? I have no idea what I'm supposed to be seeing.......
Yes, each sketch prints a few things, based on your original example sketch in reply #1.  To get a "random" value for printing, it takes the lower 8 bits of the current micros clock:

    value = millis() & 0xFF;

Each sketch grabs the current micros clock value before the print:

    unsigned long start = micros();

and calculates the elapsed time after the print:

    micros() - start

It then prints that elapsed time so you can see how long the print formatting took.  This time is not affected by the baud rate, because all characters are simply added to the Serial output buffer.  Interrupts will eventually send them over the USB, where the Serial Monitor window will eventually read and display them on the PC.

The delay statement at the end gives the interrupts time to empty the output buffer, in the background.

To measure the RAM and program size, get the numbers from the IDE build window.
To measure the execution speed, upload and run each sketch.  The execution time varies by a few TIMER0 ticks (±4us).

Regarding the piece-wise printing technique, I share your annoyance:

Quote
This saves me the headaches of using multiple "Serial.print" calls and makes for easier reading of the source code itself.
... and...

Quote
I found it a pain to type multiple Serial.print()'s
We have discussed this before.  If your metric is lines of code, then instead of individual prints:

    Serial.print( "RAM string" );
    Serial.print( f );
    Serial.print( ',' );
    Serial.println( i, HEX );

...use the streaming operators (aka "stream insertion"):

    Serial << "RAM string " << f << ',' << _HEX(i) << endl;

(See correction note below.)

This single line of code can be used in place of printf, and does not use RAM buffers at all.  In fact, it actually resolves to individual Serial.print calls through the magic of C++ templates.  It has the same RAM, speed and program size performance as the piece-wise print technique.  Here is the sketch for comparison:

Code: ("Streaming operators") [Select]
#include <Streaming.h>

volatile int value;

char hexDigit( int v )
{
  v &= 0x0F; // just the lower 4 bits
  if (v < 10)
    return '0' + v;
  else
    return 'A' + (v-10);
}

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

void loop () {
    value = millis() & 0xFF;

    unsigned long start = micros();

    Serial << F("The value is currently 0x") << hexDigit( value >> 4 ) << hexDigit( value ) << F(" units") << endl;

    Serial.print( micros() - start );
    Serial.println( F("us") );

    delay (1000); // print out voltage once a second
}

For more information, see

*  A commonly-used streaming template for the Arduino, with similar discussion about multiple print statements.

*  The wikipedia page about C++ operators has a footnote about the << operator also being used for I/O streams.

*  The wikipedia page about C++ I/O has a section about formatting modifiers and manipulator (e.g., HEX and endl in the example above).

Notes:

*  The Arduino streaming template does not implement all modifiers and manipulators, but it would not be difficult to fill in the blanks.  You can also provide the missing bits locally, inside your sketch.

*  The Arduino does not really implement the standard I/O stream (with good reason).  The Print and Stream classes are poorly-partitioned versions of ostream, istream and/or iostream (further distractions here).
Really, I used to be /dev.  :(

-dev

Sorry, I have a correction.  This does not work:

    Serial << "RAM string " << f << ',' << HEX << i << endl;

It does in Cosa, but not in the standard Arduino core.  With the Streaming library, you have to do this

    Serial << "RAM string " << f << ',' << _HEX(i) << endl;

Noted above.
Really, I used to be /dev.  :(

krupski

And then both Serial.print() and printf() (every stdio-based hack I've seen for Arduino) end up calling Serial.write() one byte at a time, anyway.
I'm not sure that I would consider using a standard AVR-GCC function (fdevopen) a "hack". Anyway, you are right, stdout and stderr do indeed call "xxx.write()" once for each character.  To that I ask "so what?" Are we worrying about data transfer speed?  If so, then why do most people use Serial.begin (9600)?

Getting back to printf and standard IO streams, it's equally easy to connect different devices to different streams. For example, you can connect stdin to serial and stdout/stderr to an LCD display... or even better connect input to serial, output to an LCD and error to a DIFFERENT LCD so that program text and errors display on different screens.

Look how ridiculously simple it is (Stdinout.cpp):
Code: [Select]
#include <Stdinout.h>

// connect stdin, stdout and stderr to same device
void STDINOUT::open (Print &iostr)
{
    open (iostr, iostr, iostr);
}

// connect stdin to input device, stdout and stderr to output device
void STDINOUT::open (Print &inpstr, Print &outstr)
{
    open (inpstr, outstr, outstr);
}

// connect each stream to it's own device
void STDINOUT::open (Print &inpstr, Print &outstr, Print &errstr)
{
    close();  // close any that may be open

    stdin = fdevopen (NULL, _getchar0);
    _stream_ptr0 = (Stream *) &inpstr;

    stdout = fdevopen (_putchar1, NULL);
    _stream_ptr1 = &outstr;

    stderr = fdevopen (_putchar2, NULL);
    _stream_ptr2 = &errstr;
}

// disconnect stdio from stream(s)
void STDINOUT::close (void)
{
    fclose (stdin);
    stdin = NULL;
    _stream_ptr0 = NULL;

    fclose (stdout);
    stdout = NULL;
    _stream_ptr1 = NULL;

    fclose (stderr);
    stderr = NULL;
    _stream_ptr2 = NULL;
}

// Function that fgetc, fread, scanf and related
// will use to read a char from stdin
int STDINOUT::_getchar0 (FILE *fp)
{
    while (! (_stream_ptr0->available()));  // wait until a character is available...
    return (_stream_ptr0->read());  // ...then grab it and return
}

// function that printf and related will use
// to write a char to stdout
int STDINOUT::_putchar1 (char c, FILE *fp)
{
    if (c == '\n') { // \n sends crlf
        stream_ptr1->write ((uint8_t) '\r'); // send C/R
    }

    stream_ptr1->write ((uint8_t) c); // send one character to device
    return 0;
}

// function that printf and related will use
// to write a char to stderr
int STDINOUT::_putchar2 (char c, FILE *fp)
{
    if (c == '\n') { // \n sends crlf
        stream_ptr2->write ((uint8_t) '\r'); // send C/R
    }

    stream_ptr2->write ((uint8_t) c); // send one character to device
    return 0;
}

STDINOUT STDIO; // Preinstantiate STDIO object


Stdinout.h:
Code: [Select]
#ifndef STD_IN_OUT_H
#define STD_IN_OUT_H

#include <Stream.h>

static Stream *_stream_ptr0 = NULL; // stdin stream pointer
static Print *_stream_ptr1 = NULL; // stdout stream pointer
static Print *_stream_ptr2 = NULL; // stderr stream pointer

class STDINOUT
{
    public:
        void open (Print &);
        void open (Print &, Print &);
        void open (Print &, Print &, Print &);
        void close (void);
    private:
        static int _getchar0 (FILE *); // char read for stdin
        static int _putchar1 (char, FILE *); // char write for stdout
        static int _putchar2 (char, FILE *); // char write for stderr
};

extern STDINOUT STDIO; // Expose STDIO object

#endif // #ifndef STD_IN_OUT_H


This simple code, or Serial.print on top of Serial.print on top of Serial.print... ad-nauseaum?
Gentlemen may prefer Blondes, but Real Men prefer Redheads!

krupski

#35
Dec 18, 2017, 07:50 pm Last Edit: Dec 19, 2017, 09:12 am by krupski
Regarding the piece-wise printing technique, I share your annoyance:
I just did a test (not sure how valid it is).

Anyway, this sketch:
Code: [Select]
void loop (void)
{
    // nuthin
}

void setup (void)
{
    Serial.begin (115200);
    Serial.println ("Now is the time for all good men to come to the aid of the party");
}


compiled uses 3578 bytes of flash and 337 bytes of sram (which seems like a lot for a "nothing" program).....

Using Stdinout:
Code: [Select]
#include <Stdinout.h>
void loop (void)
{
    // nuthin
}

void setup (void)
{
    Serial.begin (115200);
    STDIO.open (Serial);
    printf ("Now is the time for all good men to come to the aid of the party\n");
}


compiled it uses 4668 bytes of flash and 358 bytes of sram.

Using printf takes 1090 more bytes of flash and 21 more bytes of sram.

Now, real floating point vs dtostrf:
Code: [Select]
void loop (void)
{
    // nuthin
}

void setup (void)
{
    char buf [16];
    double d;
    Serial.begin (115200);

    d = 123.456789;
    dtostrf (d, 8, 2, buf);
    Serial.println (buf);

    d = 12.3456789;
    dtostrf (d, 8, 2, buf);
    Serial.println (buf);

}


prints this:

123.46
 12.35

resources used: flash 5080 bytes, sram 273 bytes.


Code: [Select]
void loop (void)
{
    // nuthin
}

void setup (void)
{
    char buf [32];
    double d;
    Serial.begin (115200);

    d = 123.456789;
    sprintf (buf, "%8.2f", d);
    Serial.println (buf);

    d = 12.3456789;
    sprintf (buf, "%8.2f", d);
    Serial.println (buf);
}


prints this:

123.46
 12.35

resources used: flash: 6608 bytes, sram: 279 bytes

Difference: 1528 bytes more flash, 6 bytes more sram.

About 1K for printf and about 1.5K for real floating point. Really, I don't think that's bad (IMHO).  :)

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

Delta_G

Not bad depending on what you're going for.  Convenience or code size.  I'd like to remind that the OP in this case was concerned with code size and efficiency.  So your answer may be right in some scenarios but is a loser at the metric requested. 
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

TonyWilk

Not bad depending on what you're going for.  Convenience or code size.  I'd like to remind that the OP in this case was concerned with code size and efficiency.  So your answer may be right in some scenarios but is a loser at the metric requested.  
Yes, although 'code size and efficiency' is somewhat dependant on requirements; simple debug output is one thing, formatting several lines for an LCD is another.

e.g. I have a project which needs no text output, apart from debug, and is at the point where sprintf() won't fit, so I (obviously) like my mini-printf function because it avoids the overhead in both RAM/ROM of using sprintf(), agrees with my (unreasonable?) loathing of streaming operators, is identical in timing to multiple Serial.print()'s but has the overhead of the function itself (but thereafter is more ROM 'efficient' than multiple calls of Serial.print) which I live with for the convenience.

Now, if 'efficiency' included 'neatness', 'readability' or 'convenience'... there'd be more points of view than programmers :)

Yours,
 TonyWilk

Delta_G

Point being that there's no *right* answer to the question of which is best.  Only which is best for this particular situation or that one.  And in this particular situation the OP asked about something particular.  All the people going on and on like their solution is the only solution anyone would ever need are deluding themselves. 
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

TonyWilk

Point being that there's no *right* answer to the question of which is best.  Only which is best for this particular situation or that one.  And in this particular situation the OP asked about something particular.  All the people going on and on like their solution is the only solution anyone would ever need are deluding themselves.  
Completely agree with this.

Yours,
 TonyWilk

krupski

Yes, although 'code size and efficiency' is somewhat dependant on requirements; simple debug output is one thing, formatting several lines for an LCD is another.
When I did 68HC11 assembler programming, I had good luck formatting and printing text on an LCD display simply by making a "virtual" LCD screen in ram, then placing characters and numbers where I wanted them, then called a simple block copy subroutine to copy the "virtual" LCD screen to the real one.

The 68HC11, BTW, only has 256 bytes of SRAM, 512 bytes of EEPROM and a 64K address space (shared by eeprom, sram and registers).

Now THAT'S a processor where you need to watch each and every byte!

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

krupski

Not bad depending on what you're going for.  Convenience or code size.  I'd like to remind that the OP in this case was concerned with code size and efficiency.
Ah, but what is "efficiency"? Ease of writing code? Easy to read and debug source? Small, compact machine code? Tight, fast running loops?

"Efficiency" can mean many different things.

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

TonyWilk

Now THAT'S a processor where you need to watch each and every byte!
Getting off-topic now...
Ah yes, programmers these days think they're hard done by with Kilobytes of memory.

Many moons ago I worked for Texas Instruments and then National Semiconductor (now joined) on very early micros. The NatSemi COP400 series was a 4-bit processor with a huge 64 NIBBLES of RAM. You didn't even have a byte to watch.

Can't exactly say those were the good old days tho' :)

Yours,
  TonyWilk
 

westfw

Attiny13, which is sort-of supported by tinycore, has 1k flash and 64byes RAM.
Fortunately, it doesn't have. Serial port, either, so the choice between serial.print and printf is a bit moot.

I would have thought that the atmega48, with 4K flash, would have gotten more attention, but I guess the mega8 passed I in price

Delta_G

Ah, but what is "efficiency"? Ease of writing code? Easy to read and debug source? Small, compact machine code? Tight, fast running loops?

"Efficiency" can mean many different things.


Efficiency can mean many things.  In this case it was explicitly laid out that the OP was not concerned with it being efficient to write but rather efficient for the processor to run. See reply #4 where the OP made that quite clear.  Seems you're so distracted by your rant about why the IDE doesn't include some feature that you desire that you've completely forgotten that we are answering a concrete question with explicit parameters for which your answer is still wrong.  Changing the question makes it different sure.  But for the question here as asked and clarified in #4 adding extra overhead to have the processor format the text is the opposite of what the OP asked.  You can rant and rave about all the reasons why YOU like this way or that way better.  But the question we are answering isn't about what you like but about which method is less work for the processor. 
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

Go Up