Shop OBEX P1 Docs P2 Docs Learn Events
Tiny I/O: Big memory savings for LMM programs that print/get from the serial terminal — Parallax Forums

Tiny I/O: Big memory savings for LMM programs that print/get from the serial terminal

denominatordenominator Posts: 242
edited 2012-04-13 21:39 in Propeller 1
I'd like to announce a new library, the "Tiny" library, that allows you to perform I/O to the serial terminal without using a lot of memory.

One of the first things that anybody interested in C LMM programing does is start with "Hello World". As you all many know, while a minimal 'int main() {}' program takes 2.5K for the LMM kernel, 'main() { printf("Hi"); }' takes 15K, while adding -Dprintf=__simple_printf takes about 7.5K (all figures include the kernel). A common reaction is "Whoa - that's a lot of memory just for printing".

I wrote a Wiki page that helps folks out a bit here. However, I now have an even better solution. I wrote a library called libtiny.a with a small printf with useful features (padding, widths, justification) and a __simple_printf that's smaller than what appeared on the Wiki. It also supports: sprintf, scanf/sscanf, threaded use, long long, individual get/put functions, skinny C++ streams (e.g. int main() { cout << "Hello World"; }) and skinny C++ new/delete. (Note that floating support is *not* included.)

If you just use the __simple_printf version, it fits into less than 3.5K (including the kernel). You can also pick-and-choose the functionality; you'll just link in what you use. The entire library takes less than 10K if you use it all. I'm guessing that these functions are in general about 4X smaller footprint than the equivalent "standard" functions. To create the tiny library, I used the basic functionality from the existing library (props to its authors), but stripped out all the FILE stuff (which is more suitable for XMM programs) and simplified a bunch of other stuff.

This program should be a boon to anybody who's been working in the LMM model, using printf (and maybe getchar) for debugging or just to monitor the program, and is running short on room. (Though please note this won't help if you're working with the SD routines, as they require the FILE functionality).

The printf/sscanf functionality is signature-compatible, so if you stick to the functionality that's in common between library's "tinyio.h" and the PropGCC's <stdio.h>, you can compile by including <stdio.h> and just link using libtiny.a.

The library is in an attached .zip file (tiny.zip), including full source. Unzip it, and copy libtiny.a to your project. You may want to also copy tinyio.h if you plan to use the individual put/get routines; to do this change #include <stdio.h> in your programs to #include "tinyio.h". You may also want to copy tinystream.h if you want to use the tiny C++ streams; to do this change #include <iostream> in your programs to #include "tinystream.h". Then add libtiny.a to your link line.

This should all work for SimpleIDE users. To do this, follow the above instructions (add libtiny.a, tinyio.h, and perhaps tinystream.h to your project). Add "libtiny.a" to the lower empty line in the lower left-hand corner of SimpleIDE.

(See the attached
SIDE-tiny.png
photo). Rebuild - you're now good to go. Notice the program size!

You can find more information in the tinyio.h and tinystream.h headers. And there are two test programs: ttinyio.c (C) and ttinyios.cpp (C++).

Enjoy!

Quick link: tiny.zip
811 x 658 - 56K

Comments

  • jazzedjazzed Posts: 11,803
    edited 2012-04-11 07:36
    Hi Ted.

    Thanks for all your work on the tiny library!

    I'm adding better include and library support to SimpleIDE in the project manager.
    New project manager menu entries will look something like this.

    Add Include Path
    Add Library Path
    Add Library

    Cheers.
    --Steve
  • RossHRossH Posts: 5,512
    edited 2012-04-12 21:05

    If you just use the __simple_printf version, it fits into less than 3.5K (including the kernel). You can also pick-and-choose the functionality; you'll just link in what you use. The entire library takes less than 10K if you use it all. I'm guessing that these functions are in general about 4X smaller footprint than the equivalent "standard" functions.

    Wow! This is really neat!

    This library easily beats my own attempt to do much the same thing in Catalina (i.e. t_printf) - and it is also more functional (since it accepts more of the standard printf format string syntax). And I can confirm your guess about the code size compared to the standard library - here are the code size statistics for "Hello, World" under Catalina (3.5):


    Standard C library (floating point, printf)
    12388


    Standard C library (integer only, printf)
    5848


    Standard C library (integer only, t_printf)
    1692


    Tiny library (integer only, printf)
    2948


    Tiny library (integer only, __simple_printf)
    1296 !!!


    So, yes - the printf version of libtiny is indeed about 1/4 the size of the standard C library! And the __simple_printf version is only 1/10 the size!

    I had to make some minor changes to make the code ANSI C compliant, and also to allow it to use any I/O drivers (i.e. not just serial - with Catalina you can use this library with serial, TV, VGA or Proxy drivers) - I'll include this version in the next release of Catalina. I can send you the sources in advance if you'd like to make the same changes in your version.

    Ross.
  • denominatordenominator Posts: 242
    edited 2012-04-12 21:26
    RossH wrote: »
    I can send you the sources in advance if you'd like to make the same changes in your version.

    Ross,

    Indeed I'd love to have any changes that make it more standard. Send or post them anyway you'd like. Thanks!!!

    - Ted
  • RossHRossH Posts: 5,512
    edited 2012-04-13 04:14
    Ross,

    Indeed I'd love to have any changes that make it more standard. Send or post them anyway you'd like. Thanks!!!

    - Ted

    Hi Ted,

    All I needed to do was move the variable declarations to the top of each function, and remove the inline assembly ("asm") code.

    I use the symbol __CATALINA__ to enable or disable the inline assembly that propgcc needs. In fact, since propgcc is the only compiler on which this inline assembler would work, instead of saying "#ifdef __CATALINA__" it would be better to say something like "#findef <some_propgcc_symbol>" - I just wasn't sure of what symbol to use.

    I also renamed the printf function to tiny_printf and the scanf function tiny_scanf. This was so I could compile things that also needed components of the standard C library - i.e. use a command like:
    catalina othello.c -lc -ltiny -W-Dprintf=tiny_printf -W-Dscanf=tiny_scanf
    

    This subterfuge enables me to override the default printf and scanf functions (similar to what you do to use __simple_printf in place of printf) - but also use the standard library functions for everything else, such as putchar and getchar - you'll see what I mean when you look at how I modified the inline assembly.

    But if I were to incorporate these files into a new complete version of the standard C library (called something like libtinyc) then such subterfuge would not be necessary, and you could compile your program like this:
    catalina othello.c -ltinyc
    
    Ross.
  • denominatordenominator Posts: 242
    edited 2012-04-13 06:37
    Ross,

    Too bad about moving the variable declarations; IMHO code is sooo much more maintainable and readable when local variables are declared as used.

    In any case, being able to have the library be more usable trumps this - I'll go through this over the weekend and I'll get back to you if I have any questions. Thanks again!!!

    - Ted
  • RossHRossH Posts: 5,512
    edited 2012-04-13 16:34
    Ross,

    Too bad about moving the variable declarations; IMHO code is sooo much more maintainable and readable when local variables are declared as used.

    In any case, being able to have the library be more usable trumps this - I'll go through this over the weekend and I'll get back to you if I have any questions. Thanks again!!!

    - Ted

    Hi Ted.

    Yes, this was one of the nice features of C99. But you don't need to move the declarations if you don't want to - you can also tell the compiler the scope of them by adding { and } - I believe in C99 the scope of a variable defaults to being from the point of declaration to the end of the current block, but in ANSI C you have to explicitly tell the compiler by declaring a new block. For example, the following is valid in both ANSI C and C99:
    #include <stdio.h>
    
    int main (void) {
    
       printf("Hello, World!\n");
    
       { 
          int i = 2;
    
          printf ("i = \n", i);
    
          {
             int j = 2;
    
             printf ("i + j = %d\n", i+j);
          }
       }
    }
    

    Ross.
  • denominatordenominator Posts: 242
    edited 2012-04-13 17:51
    Yea, I know about the scoping rules. It's all personal preference, but I like to (a) declare variables where I first use them (b) avoid blocks if at all possible. Thus, I find it neater to not put arbitrary blocks in the code. It's a tiny sacrifice to clean it up so this can be common code and can be as widely useful as possible, so I'm good with it.
  • RossHRossH Posts: 5,512
    edited 2012-04-13 18:39
    Yea, I know about the scoping rules. It's all personal preference, but I like to (a) declare variables where I first use them (b) avoid blocks if at all possible. Thus, I find it neater to not put arbitrary blocks in the code. It's a tiny sacrifice to clean it up so this can be common code and can be as widely useful as possible, so I'm good with it.

    No worries, Ted - you do whatever you prefer, and thanks for a great contribution!

    One minor point - in your implementation of _doprintf_s you might consider adding ...
    while (isdigit(ch)) {
                ch = *fmt++;
            }
    
    
    ... just before you switch on the formatting character (ch). This way, programs that use size specifiers will still print out the correct information, even if it is not not necessarily formatted perfectly.

    For instance, a line like:
    printf("%2d\n", i)
    
    will print out "d" without this change, but the value of i with this change. I know the idea of __simple_printf is to be as small as possible, but I think this small addition would prevent many users having to modify existing programs to use this function.

    Ross.
  • denominatordenominator Posts: 242
    edited 2012-04-13 21:39
    RossH wrote: »
    One minor point - in your implementation of _doprintf_s you might consider adding ...
    while (isdigit(ch)) {
                ch = *fmt++;
            }
    
    
    ... just before you switch on the formatting character (ch). This way, programs that use size specifiers will still print out the correct information, even if it is not not necessarily formatted perfectly.

    For instance, a line like:
    printf("%2d\n", i)
    
    will print out "d" without this change, but the value of i with this change. I know the idea of __simple_printf is to be as small as possible, but I think this small addition would prevent many users having to modify existing programs to use this function.

    Ross.

    Ross,

    I'm inclined to agree. I'll look at the memory cost; I'm sure it's extremely cheap. And you're 100% right that this gives the users a significantly better experience. I'm inclined to change it to:
    while (isdigit(ch) || ch == '-') {
                ch = *fmt++;
            }
    

    For the same reason.

    Thanks for the suggestion!

    - Ted
Sign In or Register to comment.