Author Topic: Low-footprint printf replacement for embedded dev  (Read 5709 times)

0 Members and 1 Guest are viewing this topic.

Offline SiliconWizardTopic starter

  • Super Contributor
  • ***
  • Posts: 15933
  • Country: fr
Low-footprint printf replacement for embedded dev
« on: December 09, 2024, 03:57:01 am »
For those not already knowing it and who are looking for a "printf" implementation that is relatively low-footprint and doesn't make any dynamic allocation, short of writing your own, I can recommend this one:

https://github.com/charlesnicholson/nanoprintf
 
The following users thanked this post: kgavionics, peter-h, newbrain, Dazed_N_Confused

Online newbrain

  • Super Contributor
  • ***
  • Posts: 1813
  • Country: se
Re: Low-footprint printf replacement for embedded dev
« Reply #1 on: December 09, 2024, 09:10:21 am »
Interesting, thanks!
I mostly use this: https://github.com/mpaland/printf
Works quite well, and can be tailored to reduce footprint vs. functionality (as can nanoprintf, AFAICS), but it's not been updated in a while.

I might give nanoprintf a go.

Not a great fan of Unlicense, but the author smartly dual-licenses it under 0BSD.
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline SiliconWizardTopic starter

  • Super Contributor
  • ***
  • Posts: 15933
  • Country: fr
Re: Low-footprint printf replacement for embedded dev
« Reply #2 on: December 09, 2024, 09:34:52 am »
Will be interesting to compare the footprint for equivalent functionality between the two.
 

Online newbrain

  • Super Contributor
  • ***
  • Posts: 1813
  • Country: se
Re: Low-footprint printf replacement for embedded dev
« Reply #3 on: December 09, 2024, 10:25:23 am »
Will be interesting to compare the footprint for equivalent functionality between the two.
Yes, if I feel like it, I might try to replace it in an STM32H7 project where I use the mpaland one and check the mem size change.
Being it an STM32H750, flash space is at premium more than RAM.
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline peter-h

  • Super Contributor
  • ***
  • Posts: 4433
  • Country: gb
  • Doing electronics since the 1960s...
Re: Low-footprint printf replacement for embedded dev
« Reply #4 on: December 09, 2024, 02:55:22 pm »
I use the Marco Paland one and am totally happy with it. No problems at all found.

Not sure how to find the size because it includes loads of functions so it would take time. I see 0x238 for f_printf.

With 32F chips you need to be very careful to select the right libc.a library, for hardware single floats, etc. Some long previous threads on libc.a you can look up... I spent ages on that.

Sorry - just realised you know all this already; way more than I do :)
« Last Edit: December 09, 2024, 04:33:19 pm by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline dare

  • Contributor
  • Posts: 40
  • Country: us
Re: Low-footprint printf replacement for embedded dev
« Reply #5 on: December 09, 2024, 05:20:38 pm »
Also consider eyalroz printf (https://github.com/eyalroz/printf), which is an evolved version of mpaland printf.  The latter appears to have been abandoned by its author and hasn't had an update since 2019.

eyalroz forked the original codebase with the goal of continuing and improving upon Marco's work.  I've used it in a number of embedded projects.
 
The following users thanked this post: newbrain

Online HwAoRrDk

  • Super Contributor
  • ***
  • Posts: 1618
  • Country: gb
Re: Low-footprint printf replacement for embedded dev
« Reply #6 on: December 09, 2024, 05:24:08 pm »
Nice. Hadn't come across this one before. Or maybe I had, but forgot. ;D

I tried plugging this into a project where I was already using the mpaland printf, and it's smaller when configured like-for-like. Before my overall firmware size was 13,136 bytes, and now with nanoprintf it's 12,880 bytes - a saving of 256 bytes. :-+ This was when compiled for RISC-V, with GCC 13.3.0.

The mpaland printf was configured with:

Code: [Select]
#define PRINTF_DISABLE_SUPPORT_FLOAT
#define PRINTF_DISABLE_SUPPORT_EXPONENTIAL
#define PRINTF_DISABLE_SUPPORT_LONG_LONG

And nanoprintf was configured with:

Code: [Select]
#define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 0
#define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 0
#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 0
#define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0

Although, I suppose I should probably have set NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS to '1' for a fairer comparison, because IIRC the mpaland printf supports binary output specifiers by default. But I'm not using that, so I disabled it.
 

Online newbrain

  • Super Contributor
  • ***
  • Posts: 1813
  • Country: se
Re: Low-footprint printf replacement for embedded dev
« Reply #7 on: December 09, 2024, 08:53:17 pm »
With 32F chips you need to be very careful to select the right libc.a library, for hardware single floats, etc. Some long previous threads on libc.a you can look up... I spent ages on that.
STM32H7xx's FPU supports DP too.
Since we are talking alternatives to library functions, I started using picolibc years ago on a whim, and make sure to compile it at best for MCU at hand.
For quick RPi pico(2) stuff, I just use their default library as it takes care of their weird FP accelerations.

Sorry - just realised you know all this already; way more than I do :)
;) See my signature...still a lot of learning opportunities!
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 28502
  • Country: nl
    • NCT Developments
Re: Low-footprint printf replacement for embedded dev
« Reply #8 on: December 09, 2024, 09:10:37 pm »
Interesting, thanks!
I mostly use this: https://github.com/mpaland/printf
Works quite well, and can be tailored to reduce footprint vs. functionality (as can nanoprintf, AFAICS), but it's not been updated in a while.

I might give nanoprintf a go.

Not a great fan of Unlicense, but the author smartly dual-licenses it under 0BSD.
Still rather complicated. A really efficient printf comes with an older GCC for the MSP430 microcontrollers. Easy to follow code as well. If you want to skip formatting, even smaller printfs can be constructed (or found).
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline SiliconWizardTopic starter

  • Super Contributor
  • ***
  • Posts: 15933
  • Country: fr
Re: Low-footprint printf replacement for embedded dev
« Reply #9 on: December 09, 2024, 09:39:17 pm »
Depending on which of the xxprintf functions you use and the formats you use, sometimes what's provided by newlib nano takes less code size than nanoprintf. But the win here is that the latter doesn't use any heap allocation.

As I think was also discussed before, alternatives to printf altogether can be a good idea too - but sometimes you just need printf-compatible for an existing code base, or the flexibility they provide.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4839
  • Country: nz
Re: Low-footprint printf replacement for embedded dev
« Reply #10 on: December 09, 2024, 10:14:21 pm »
Since we are talking alternatives to library functions, I started using picolibc years ago on a whim, and make sure to compile it at best for MCU at hand.

Can't have been very long ago ... I'm pretty sure Keith only started work on it in maybe late 2019 or early 2020 when we were at SiFive together.
 
The following users thanked this post: newbrain

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4839
  • Country: nz
Re: Low-footprint printf replacement for embedded dev
« Reply #11 on: December 09, 2024, 10:26:12 pm »
Interesting, thanks!
I mostly use this: https://github.com/mpaland/printf
Works quite well, and can be tailored to reduce footprint vs. functionality (as can nanoprintf, AFAICS), but it's not been updated in a while.

I might give nanoprintf a go.

Not a great fan of Unlicense, but the author smartly dual-licenses it under 0BSD.
Still rather complicated. A really efficient printf comes with an older GCC for the MSP430 microcontrollers. Easy to follow code as well. If you want to skip formatting, even smaller printfs can be constructed (or found).

These all look rather large.

99% of the time, all I need is printing chars, strings, and long/ulong in dec/hex, with optional field padding with spaces or 0s. Rather than bloating printf(), I'd be happy to use a separate ftoa() to a buffer on the few times I need it and then printf() that.
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4361
  • Country: us
Re: Low-footprint printf replacement for embedded dev
« Reply #12 on: December 10, 2024, 09:20:47 am »
It might also be worthwhile to look at the printf() implementation in avr-libc.
It has "some" avr-specific bits, but it looks like they'd be pretty easy to port to anything...
 

Offline nfmax

  • Super Contributor
  • ***
  • Posts: 1627
  • Country: gb
Re: Low-footprint printf replacement for embedded dev
« Reply #13 on: December 10, 2024, 10:24:29 am »
An alternative approach might be to send an unformatted packet (byte count, type, bytes) using a minimal function on the target, and interpose a packet interpreter using another microcontroller that does nothing other than interpret the packets and fully printf() their payload
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 7308
  • Country: fi
    • My home page and email address
Re: Low-footprint printf replacement for embedded dev
« Reply #14 on: December 10, 2024, 11:38:25 am »
I like building the strings in reverse when severely constrained.
 

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 9455
  • Country: fi
Re: Low-footprint printf replacement for embedded dev
« Reply #15 on: December 10, 2024, 12:19:33 pm »
An alternative approach might be to send an unformatted packet (byte count, type, bytes) using a minimal function on the target, and interpose a packet interpreter using another microcontroller that does nothing other than interpret the packets and fully printf() their payload

Or, in a simpler way, since the UART (or similar) would be connected to some sort of target PC, do the parsing on that PC. Yeah, it prevents using Hyperterminal if the customer wants to look at the prints, but then again today's customers usually want custom tools anyway (and don't even know about the existence of generic terminal programs).

Hence, tooling. Tooling almost always pays back for itself. It makes very little sense to have run-time parsing of printf format strings (both code size and execution time) on small embedded targets when you could just log the binary data itself. Like, timestamp, some identifier code and data values. This way you can fit surprisingly lot even on a small embedded RAM buffer, and transfer that blob to a target PC, and parse it there.

Not only you save on the implementation itself; you also save by not needing the strings.

I mean, if your program only prints 1-2 things, why bother with printf at all? Just do it with atoi() or whatever ad-hoc mess makes it work. If your program prints 1000 different things, I understand the convenience of the printf, but isn't the elephant in the room then the storage of those strings, not just the printf? If standard printf doesn't fit, would a smaller one? You would still have the strings.

Hence, get rid of both printf, and the strings. A two-byte identifier is able to describe 65536 different messages, which can be then parsed and printed on the host PC. Like a binary stream where U8 num_of_following_bytes, U16 message_identifier, variable-length bytes of data. Then a parser on PC where within switch-case you printf() the data.

For those who say it is a large project, I challenge you; just do it, I bet it's a smaller project than you think. Something similar to the time used trying out different printf implementations and fine-tuning their configuration macros trying to squeeze them down.
« Last Edit: December 10, 2024, 12:26:49 pm by Siwastaja »
 
The following users thanked this post: whitehorsesoft

Online newbrain

  • Super Contributor
  • ***
  • Posts: 1813
  • Country: se
Re: Low-footprint printf replacement for embedded dev
« Reply #16 on: December 10, 2024, 01:08:58 pm »
It might also be worthwhile to look at the printf() implementation in avr-libc.
It has "some" avr-specific bits, but it looks like they'd be pretty easy to port to anything...
That's just the one used by picolibc, BTW!

Can't have been very long ago ... I'm pretty sure Keith only started work on it in maybe late 2019 or early 2020 when we were at SiFive together.
Sounds about right...
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4839
  • Country: nz
Re: Low-footprint printf replacement for embedded dev
« Reply #17 on: December 10, 2024, 01:10:47 pm »
Intermediate position, if you're not too worried about ROM/flash space, is to compile printfs as usual, but link to a library that doesn't actually format but just counts %s in the string and makes up a binary packet with the string address and N vararg arguments and a length count. The host can extract the format strings from the original elf file and do the formatting.

Slightly more work, before linking, replace each string body with a single byte saying how many arguments there are. Each format string will then have a unique address but only take 1 byte in flash. The host will then have to match the address in the reduced binary to the label, to the original string (maybe in the .s)
 
The following users thanked this post: Siwastaja

Online nctnico

  • Super Contributor
  • ***
  • Posts: 28502
  • Country: nl
    • NCT Developments
Re: Low-footprint printf replacement for embedded dev
« Reply #18 on: December 10, 2024, 01:25:26 pm »
Hence, get rid of both printf, and the strings. A two-byte identifier is able to describe 65536 different messages, which can be then parsed and printed on the host PC. Like a binary stream where U8 num_of_following_bytes, U16 message_identifier, variable-length bytes of data. Then a parser on PC where within switch-case you printf() the data.
Bad idea. You need to have a version of the debugging tool for Windows, Linux, Mac, Iphone and Android nowadays. And rights to install. I have been down this road many times: using Putty, Teraterm, Hyperterminal, serial USB terminal (Android), - whatever- is a much more convenient route compared to a dedicated tool. Keep in mind you need to change/update/distribute the tool when (not if) you add / change debugging info. And how about logging? Or uploading data? Terminal programs support protocols like xmodem/ymodem and sending/receiving plain files.

A super minimalistic printf takes like 10 lines of code and you keep the rest of the code standard.

A long time ago I inherited a project written by somebody who wanted to prevent using printf so he made all kinds of convoluted printing functions which where a nightmare to use. And on top of that, the C library's vuprintf got linked in anyway because this was used by some functions he wasn't aware of. IOW: solve the problem at the root, if the C library's printf (or more specifically: vuprintf) is too big, replace it with a smaller one.
« Last Edit: December 10, 2024, 01:53:19 pm by nctnico »
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline peter-h

  • Super Contributor
  • ***
  • Posts: 4433
  • Country: gb
  • Doing electronics since the 1960s...
Re: Low-footprint printf replacement for embedded dev
« Reply #19 on: December 10, 2024, 02:06:54 pm »
The EYALROZ printf states

Marco therefore decided to write his own implementation, with the following goals in mind (I've dropped a few relative to his original description):

Very small implementation
NO dependencies on other packages or libraries; no multiple compiled objects, just one object file.
Support for all standard specifiers and flags, and all width and precision sub-specifiers (see below).
Support of decimal/floating number representation (with an internal, relatively fast itoa/ftoa implementation)
Reentrancy and thread-safety; malloc() freeness.
Clean, robust code.
Extensive test coverage.
MIT license

Marco's repository upheld most of these goals - but did not quite make it all of the way. As of mid-2021, it still had many C-standard-non-compliance bugs; the test suite was quite lacking in coverage; some goals were simply discarded (like avoiding global/local-static constants) etc.


Does anyone know where the "global/local-static constants" are used in Marco's code? That obviously does not make it thread-safe. But also it should be easy to fix.

The newlib printf uses statics and the heap and all kinds of crap. Extensively discussed here years ago. I dealt with that problem but it was complicated because ST did not supply the source to their libc.a. I won't post the whole story here but basically I kept some libc.a stuff (e.g. the heap code) so I mutexed the API for that stuff. I think heap code cannot possibly be thread-safe...



Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline AVI-crak

  • Regular Contributor
  • *
  • Posts: 141
  • Country: ru
    • Rtos
Re: Low-footprint printf replacement for embedded dev
« Reply #20 on: December 10, 2024, 02:28:08 pm »
When I was looking for a compact replacement for “printf”, I had to write my own variant. I had only 2k flash, and a double precision printf requirement.
https://github.com/AVI-crak/Rtos_cortex/tree/master/printo
printo("text", double, float, uint(8-16-32-64)_t, int(8-16-32-64)_t )
minimum weight +140 bytes, complete set 1684 bytes,
maximum speed 20~470 ticks,
 

Online HwAoRrDk

  • Super Contributor
  • ***
  • Posts: 1618
  • Country: gb
Re: Low-footprint printf replacement for embedded dev
« Reply #21 on: December 10, 2024, 02:55:01 pm »
Does anyone know where the "global/local-static constants" are used in Marco's code? That obviously does not make it thread-safe. But also it should be easy to fix.

Only thing I can find is here, in _ftoa(): https://github.com/mpaland/printf/blob/d3b984684bb8a8bdc48cc7a1abecb93ce59bbe3e/printf.c#L346

Code: [Select]
  // powers of 10
  static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };

But that's just literal constants, and unless I'm wrong, doesn't affect thread-safety or re-entrancy at all - such constant values will typically get stored in and read from flash.

Other than that, there's nothing I can see. There are no global variables at all.
 
The following users thanked this post: peter-h

Offline peter-h

  • Super Contributor
  • ***
  • Posts: 4433
  • Country: gb
  • Doing electronics since the 1960s...
Re: Low-footprint printf replacement for embedded dev
« Reply #22 on: December 10, 2024, 05:04:52 pm »
Yes that should be fine.

I wonder if there is any info on "C-standard-non-compliance bugs; the test suite was quite lacking in coverage". I've been using Marco's printf for years and never saw anything weird.

There are many old printf sources floating (no pun intended) around. Here is one from 1993, which came with the Hitech C compiler. The company was taken over by Microchip and those tools have since been abandoned. This code doesn't use itoa, ltoa, etc.

Code: [Select]
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <conio.h>
#include <sys.h>
#include <math.h>
#include <float.h>

/*
 * doprnt - long, non-float version depends on conditional compilation.
 * Copyright (C) 1993 HI-TECH Software
 *
 * This version only supports 32 bit floating point
 */


#if (sizeof(long) == sizeof(int) || defined(__FLOAT)) && !defined(__LONG)
#define __LONG 1
#endif

#if sizeof(double) == sizeof(long) && DBL_MAX_EXP == 128
#define frexp(val, ptr) (void)(*(ptr) = ((*(unsigned long *)&val >> 23) & 255) - 126)
#endif

#ifdef __LONG
#define value long
#define NDIG 12 /* max number of digits to be printed */
#else
#define value int
#define NDIG 6 /* max number of digits to be printed */
#endif

const static unsigned value dpowers[] = {1, 10, 100, 1000, 10000,
#ifdef __LONG
100000, 1000000, 10000000, 100000000,
1000000000
#endif
};
const static unsigned value hexpowers[] = {1, 0x10, 0x100, 0x1000,
#if __LONG
0x10000, 0x100000, 0x1000000, 0x10000000
#endif
};
const static unsigned value octpowers[] = {1, 010, 0100, 01000, 010000, 0100000,
#ifdef __LONG
01000000,
010000000, 0100000000, 01000000000, 010000000000,
0100000000000
#endif
};

#ifdef __FLOAT
#if sizeof(long) != sizeof(double)
#error This fnum is only for 32 bit doubles
#endif


#if DBL_MAX_10_EXP > 120
#define expon int
#else
#define expon signed char
#endif

extern const double _powers_[], _npowers_[];
extern unsigned long _div_to_l_(float, float);

/* this routine returns a value to round to the number of decimal
places specified */

double
fround(unsigned char prec)
{
/* prec is guaranteed to be less than NDIG */

if(prec > 10)
return 0.5 * _npowers_[prec/10+9] * _npowers_[prec%10];
return 0.5 * _npowers_[prec];
}

/* this routine returns a scaling factor equal to 1 to the decimal
   power supplied */

static double
scale(expon scl)
{

if(scl < 0) {
scl = -scl;
if(scl > 10)
return _npowers_[scl/10+9] * _npowers_[scl%10];
return _npowers_[scl];
}
if(scl > 10)
return _powers_[scl/10+9] * _powers_[scl%10];
return _powers_[scl];
}


#endif /* __FLOAT */


#define OPTSIGN 0x00
#define SPCSIGN 0x01
#define MANSIGN 0x02
#define NEGSIGN 0x03
#define FILL 0x04
#define LEFT 0x08
#define LONG 0x10
#define UPCASE 0x20
#define TEN 0x00
#define EIGHT 0x40
#define SIXTEEN 0x80
#define UNSIGN 0xC0
#define BASEM 0xC0
#define EFMT 0x100
#define GFMT 0x200
#define FFMT 0x400
#define ALTERN 0x800
#define DEFPREC 0x1000

#ifdef _HOSTED
#define pputc(c) (putc(c, fp) != EOF && ++ccnt)
int
vfprintf(FILE * fp, register const  char * f, register va_list ap)
{
char cbuf[2];
#else /* _HOSTED */
#define pputc(c) if(pb->ptr) (*pb->ptr++ = c),++ccnt; else ((pb->func(c)),++ccnt)
int
_doprnt(struct __prbuf * pb, register const  char * f, register va_list ap)
{
#endif /* _HOSTED */
int prec;
char c;
int width;
unsigned flag;
int ccnt = 0;
#ifdef __FLOAT
double fval;
int exp;
union {
unsigned value _val;
struct {
#if i8086 && SMALL_DATA
    far char * _cp;
#else
    char * _cp;
#endif
    unsigned _len;
} _str;
double _integ;
} _val;
#else
union {
unsigned value _val;
struct {
#if i8086 && SMALL_DATA
    far char * _cp;
#else
    char * _cp;
#endif
    unsigned _len;
} _str;
} _val;
#endif
#define val _val._val
#define cp _val._str._cp
#define len _val._str._len
#define integ _val._integ


flag = 0;
while(c = *f++) {
if(c != '%') {
pputc(c);
continue;
}
width = 0;
flag = 0;
for(;;) {

switch(*f) {

case '-':
flag |= LEFT;
f++;
continue;

case ' ':
flag |= SPCSIGN;
f++;
continue;

case '+':
flag |= MANSIGN;
f++;
continue;

case '#':
flag |= ALTERN;
f++;
continue;

case '0':
flag |= FILL;
f++;
continue;
}
break;
}
if(flag & MANSIGN)
flag &= ~SPCSIGN;
if(flag & LEFT)
flag &= ~FILL;
if(isdigit((unsigned)*f)) {
width = 0;
do
width = width*10 + *f++ - '0';
while(isdigit((unsigned)*f));
} else if(*f == '*') {
width = va_arg(ap, int);
f++;
}
if(*f == '.')
if(*++f == '*') {
prec = va_arg(ap, int);
f++;
} else {
prec = 0;
while(isdigit((unsigned)*f))
prec = prec*10 + *f++ - '0';
}
else {
prec = 0;
#ifdef __FLOAT
flag |= DEFPREC;
#endif
}
#ifdef __LONG
loop:
#endif
switch(c = *f++) {

case 0:
return ccnt;

case 'l':
#ifdef __LONG
flag |= LONG;
goto loop;
#else
cp = "(non-long printf)";
goto strings;
#endif

#ifndef __FLOAT
case 'E':
case 'f':
case 'e':
case 'g':
cp = "(non-float printf)";
goto strings;
#else
case 'f':
flag |= FFMT;
break;

case 'E':
flag |= UPCASE;
case 'e':
flag |= EFMT;
break;

case 'g':
flag |= GFMT;
break;
#endif
case 'o':
flag |= EIGHT;
break;

case 'd':
case 'i':
break;

case 'X':
case 'p':
flag |= UPCASE;
case 'x':
flag |= SIXTEEN;
break;

case 's':
#if i8086 && SMALL_DATA
if(flag & LONG)
cp = va_arg(ap, far char *);
else
#endif
cp = va_arg(ap, char *);
#if !defined(__FLOAT)
strings:
#endif
if(!cp)
cp = "(null)";
len = 0;
while(cp[len])
len++;
dostring:
if(prec && prec < len)
len = prec;
if(width > len)
width -= len;
else
width = 0;
if(!(flag & LEFT))
while(width--)
pputc(' ');
while(len--)
pputc(*cp++);
if(flag & LEFT)
while(width--)
pputc(' ');
continue;
case 'c':
#if _HOSTED
val = va_arg(ap, int);
c = val >> 8;
if(flag & LONG && c && (unsigned char)c != 0xFF) {
cbuf[0] = c;
cbuf[1] = val;
len = 2;
} else {
cbuf[0] = val;
len = 1;
}
cp = cbuf;
goto dostring;
#else
c = va_arg(ap, int);
#endif
default:
cp = &c;
len = 1;
goto dostring;

case 'u':
flag |= UNSIGN;
break;

}
#ifdef __FLOAT
if(flag & (EFMT|GFMT|FFMT)) {
if(flag & DEFPREC)
prec = 6;
fval = va_arg(ap, double);
if(fval < 0.0) {
fval = -fval;
flag |= NEGSIGN;
}
exp = 0;
frexp(fval, &exp); /* get binary exponent */
exp--; /* adjust 0.5 -> 1.0 */
exp *= 3;
exp /= 10; /* estimate decimal exponent */
if(exp < 0)
exp--;
integ = fval * scale(-exp);
if(integ < 1.0)
exp--;
else if(integ >= 10.0)
exp++;
if(exp <= 0)
c = 1;
else
c = exp;
#if 0
if(!(flag & ALTERN) && flag & FFMT && prec == 0) {
val = (long)(fval + 0.5);
flag |= LONG;
goto integer;
}
if(!(flag & ALTERN) && flag & GFMT && exp >= 0 && c <= prec) {
integ = fval + fround(prec - c);
if((unsigned)exp > sizeof dpowers/sizeof dpowers[0] || integ - (float)(unsigned long)integ < fround(prec-c-1)) {
val = (long)integ;
flag |= LONG;
prec = 0;
goto integer;
}
}
#endif
if(flag & EFMT || flag & GFMT && (exp < -4 || exp >= (int)prec)) { /* use e format */
if(prec && flag & GFMT)
prec--; /* g format precision includes integer digit */
if(prec > sizeof dpowers/sizeof dpowers[0] - 1)
c = sizeof dpowers/sizeof dpowers[0] - 1;
else
c = prec;
fval *= scale(c-exp);
if((unsigned long)fval >= dpowers[c+1]) {
fval *= 1e-1;
exp++;
} else if((unsigned long)fval < dpowers[c]) {
fval *= 10.0;
exp--;
}
#if 0
if(fval != 0.0) {
while(fval >= 10.0) {
fval *= 1e-1;
exp++;
if(flag & GFMT)
prec++;
}
while(fval < 1.0) {
fval *= 10.0;
exp--;
if(flag & GFMT)
prec--;
}
}
#endif
fval += 0.5;
if(flag & GFMT && !(flag & ALTERN)) { /* g format, precision means something different */
if(prec > (int)(sizeof dpowers/sizeof dpowers[0]))
prec = sizeof dpowers/sizeof dpowers[0];
val = (unsigned long)fval;
while(val && val % 10 == 0) {
prec--;
val /= 10;
}
if(prec < c) {
fval *= scale(prec-c);
c = prec;
}

}
width -=  prec + 5;
if(prec || flag & ALTERN)
width--;
if(flag & (MANSIGN|SPCSIGN))
width--;
if(exp >= 100 || exp <= -100) /* 3 digit exponent */
width--;
if(flag & FILL) {
if(flag & MANSIGN)
pputc(flag & SPCSIGN ? '-' : '+');
else if(flag & SPCSIGN)
pputc(' ');
while(width > 0) {
pputc('0');
width--;
}
} else {
if(!(flag & LEFT))
while(width > 0) {
pputc(' ');
width--;
}
if(flag & MANSIGN)
pputc(flag & SPCSIGN ? '-' : '+');
else if(flag & SPCSIGN)
pputc(' ');
}
val = (unsigned long)fval;
pputc(val/dpowers[c] + '0');
if(prec || flag & ALTERN) {
pputc('.');
prec -= c;
while(c) {
pputc('0' + (val/dpowers[--c]) % 10);
}
while(prec) {
pputc('0');
prec--;
}
}
if(flag & UPCASE)
pputc('E');
else
pputc('e');
if(exp < 0) {
exp = -exp;
pputc('-');
} else
pputc('+');
if(exp >= 100) {
pputc(exp / 100 + '0');
exp %= 100;
}
pputc(exp / 10 + '0');
pputc(exp % 10 + '0');
if((flag & LEFT) && width)
do
pputc(' ');
while(--width);
continue;
}
/* here for f format */

if(flag & GFMT) {
if(exp < 0)
prec -= exp-1;
val = (unsigned long)fval;
for(c = 1 ; c != sizeof dpowers/sizeof dpowers[0] ; c++)
if(val < dpowers[c])
break;
prec -= c;
val = (unsigned long)((fval-(double)val) * scale(prec)+0.5);
while(prec && val % 10 == 0) {
val /= 10;
prec--;
}
}
if(prec <= NDIG)
fval += fround(prec);
if(exp > (int)(sizeof dpowers/sizeof dpowers[0])-1) {
exp -= sizeof dpowers/sizeof dpowers[0]-1;
val = _div_to_l_(fval, scale(exp));
fval = 0.0;
} else {
val = (unsigned long)fval;
fval -= (float)val;
exp = 0;
}
for(c = 1 ; c != sizeof dpowers/sizeof dpowers[0] ; c++)
if(val < dpowers[c])
break;
width -= prec + c + exp;
if(flag & ALTERN || prec)
width--;
if(flag & (MANSIGN|SPCSIGN))
width--;
if(flag & FILL) {
if(flag & MANSIGN)
pputc(flag & SPCSIGN ? '-' : '+');
else if(flag & SPCSIGN)
pputc(' ');
while(width > 0) {
pputc('0');
width--;
}
} else {
if(!(flag & LEFT))
while(width > 0) {
pputc(' ');
width--;
}
if(flag & MANSIGN)
pputc(flag & SPCSIGN ? '-' : '+');
else if(flag & SPCSIGN)
pputc(' ');
}
while(c--)
pputc('0' + (val/dpowers[c]) % 10);
while(exp > 0) {
pputc('0');
exp--;
}
if(prec > (int)(sizeof dpowers/sizeof dpowers[0]))
c = sizeof dpowers/sizeof dpowers[0];
else
c = prec;
prec -= c;
if(c || flag & ALTERN)
pputc('.');
val = (long)(fval * scale(c));
while(c) {
pputc('0' + (val/dpowers[--c]) % 10);
}
while(prec) {
pputc('0');
prec--;
}
if((flag & LEFT) && width)
do
pputc(' ');
while(--width);
continue;
}
#endif /* __FLOAT */
if((flag & BASEM) == TEN) {
#ifdef __LONG
if(flag & LONG)
val = va_arg(ap, long);
else
#endif
val = (value)va_arg(ap, int);
if((value)val < 0) {
flag |= NEGSIGN;
val = -val;
}
} else {
#ifdef __LONG
if(flag & LONG)
val = va_arg(ap, unsigned long);
else
#endif
val = va_arg(ap, unsigned);
}
#ifdef __FLOAT
integer:
#endif
if(prec == 0 && val == 0)
prec++;
switch((unsigned char)(flag & BASEM)) {

case TEN:
case UNSIGN:
for(c = 1 ; c != sizeof dpowers/sizeof dpowers[0] ; c++)
if(val < dpowers[c])
break;
break;
case SIXTEEN:
for(c = 1 ; c != sizeof hexpowers/sizeof hexpowers[0] ; c++)
if(val < hexpowers[c])
break;
break;

case EIGHT:
for(c = 1 ; c != sizeof octpowers/sizeof octpowers[0] ; c++)
if(val < octpowers[c])
break;
break;
}
if(c < prec)
c = prec;
else if(prec < c)
prec = c;
if(width && flag & NEGSIGN)
width--;
if(width > prec)
width -= prec;
else
width = 0;
if((flag & (FILL|BASEM|ALTERN)) == (EIGHT|ALTERN)) {
if(width)
width--;
} else if((flag & (BASEM|ALTERN)) == (SIXTEEN|ALTERN)) {
if(width > 2)
width -= 2;
else
width = 0;
}
if(flag & FILL) {
if(flag & MANSIGN)
pputc(flag & SPCSIGN ? '-' : '+');
else if(flag & SPCSIGN)
pputc(' ');
else if((flag & (BASEM|ALTERN)) == (SIXTEEN|ALTERN)) {
pputc('0');
pputc(flag & UPCASE ? 'X' : 'x');
}
if(width)
do
pputc('0');
while(--width);
} else {
if(width && !(flag & LEFT))
do
pputc(' ');
while(--width);
if(flag & MANSIGN)
pputc(flag & SPCSIGN ? '-' : '+');
else if(flag & SPCSIGN)
pputc(' ');
if((flag & (BASEM|ALTERN)) == (EIGHT|ALTERN))
pputc('0');
else if((flag & (BASEM|ALTERN)) == (SIXTEEN|ALTERN)) {
pputc('0');
pputc(flag & UPCASE ? 'X' : 'x');
}
}
while(prec > c)
pputc('0');
while(prec--) {
switch((unsigned char)(flag & BASEM)) {

case TEN:
case UNSIGN:
c = (val / dpowers[prec]) % 10 + '0';
break;

case SIXTEEN:
c = (flag & UPCASE ? "0123456789ABCDEF" : "0123456789abcdef")[(val / hexpowers[prec]) & 0xF];
break;

case EIGHT:
c = ((val / octpowers[prec]) & 07) + '0';
break;
}
pputc(c);
}
if((flag & LEFT) && width)
do
pputc(' ');
while(--width);
}
return ccnt;
}
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 
The following users thanked this post: RAPo

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4361
  • Country: us
Re: Low-footprint printf replacement for embedded dev
« Reply #23 on: December 11, 2024, 02:58:58 am »
Quote
>> It might also be worthwhile to look at the printf() implementation in avr-libc.
That's just the one used by picolibc, BTW!
Ah; good.  It was my perception that picolibc was using some of the same ideas as avr-libc, but I haven't had the opportunity to actually look at it in any detail.
 

Offline SiliconWizardTopic starter

  • Super Contributor
  • ***
  • Posts: 15933
  • Country: fr
Re: Low-footprint printf replacement for embedded dev
« Reply #24 on: December 11, 2024, 09:02:41 am »
I like building the strings in reverse when severely constrained.

Yes, that's an option of course, although reversing the string at the end of the conversion only takes a loop with half its length iterations and one byte of temporary storage (which should fit in a register unless you use a very, very small/odd target with only a handful of registers like, say, a Z80... or an 8-bit PIC!)
« Last Edit: December 11, 2024, 09:05:01 am by SiliconWizard »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf