All Projects → charlesnicholson → Nanoprintf

charlesnicholson / Nanoprintf

Licence: unlicense
A tiny embeddable printf replacement written in C99.

Programming Languages

c
50402 projects - #5 most used programming language

Projects that are alternatives of or similar to Nanoprintf

Embedded Ide
IDE for C embedded development centered on bare-metal ARM systems
Stars: ✭ 127 (-55.75%)
Mutual labels:  embedded-systems, cortex-m, embedded
Dynamic App Loading
Dynamically load apps to zephyr RTOS
Stars: ✭ 31 (-89.2%)
Mutual labels:  embedded, cortex-m
STM32F10x Servo Library
Servo library with stm developed by the Liek Software Team. We are working on new versions.
Stars: ✭ 14 (-95.12%)
Mutual labels:  embedded, embedded-systems
DoraOS
DoraOS 是我个人所写的RTOS内核,结合FreeRTOS、uCOS, RT-Thread, LiteOS 的特性所写,取其精华,去其糟粕,本项目将持续维护,欢迎大家fork与star。
Stars: ✭ 102 (-64.46%)
Mutual labels:  cortex-m, embedded-systems
w1-gpio-cl
Command line configured kernel mode 1-wire bus master driver. w1-gpio standard Linux module enhancement/substitution.
Stars: ✭ 17 (-94.08%)
Mutual labels:  embedded, embedded-systems
xForth
Experimental Forth cross compiler for tiny devices
Stars: ✭ 53 (-81.53%)
Mutual labels:  embedded, cortex-m
pydevmem
Python interface to /dev/mem
Stars: ✭ 41 (-85.71%)
Mutual labels:  embedded, embedded-systems
r3
R3-OS — Experimental static (μITRON-esque) RTOS for deeply embedded systems, testing the limit of Rust's const eval and generics
Stars: ✭ 87 (-69.69%)
Mutual labels:  cortex-m, embedded-systems
kocherga
Robust platform-agnostic Cyphal/DroneCAN bootloader for deeply embedded systems
Stars: ✭ 21 (-92.68%)
Mutual labels:  embedded, embedded-systems
async-stm32f1xx
Abstractions for asynchronous programming on the STM32F1xx family of microcontrollers.
Stars: ✭ 24 (-91.64%)
Mutual labels:  embedded, cortex-m
lwprintf
Lightweight printf library optimized for embedded systems
Stars: ✭ 98 (-65.85%)
Mutual labels:  embedded, embedded-systems
mish
A no-std libm implementation in Rust
Stars: ✭ 14 (-95.12%)
Mutual labels:  embedded, embedded-systems
openncc
OpenNCC Kit
Stars: ✭ 23 (-91.99%)
Mutual labels:  embedded, embedded-systems
bx-github-ci
This tutorial provides one example on how a CI (Continuous Integration) workflow with the IAR Build Tools for Linux can be set up on GitHub. The IAR Build Tools on Linux are available for Arm, RISC-V and Renesas (RH850, RL78 and RX).
Stars: ✭ 20 (-93.03%)
Mutual labels:  embedded, cortex-m
Embedded UKF Library
A compact Unscented Kalman Filter (UKF) library for Teensy4/Arduino system (or any real time embedded system in general)
Stars: ✭ 31 (-89.2%)
Mutual labels:  embedded, embedded-systems
mdepx
MDEPX — A BSD-style RTOS
Stars: ✭ 17 (-94.08%)
Mutual labels:  embedded, cortex-m
Tock
A secure embedded operating system for microcontrollers
Stars: ✭ 3,258 (+1035.19%)
Mutual labels:  cortex-m, embedded
Libonnx
A lightweight, portable pure C99 onnx inference engine for embedded devices with hardware acceleration support.
Stars: ✭ 217 (-24.39%)
Mutual labels:  embedded-systems, embedded
Libhydrogen
A lightweight, secure, easy-to-use crypto library suitable for constrained environments.
Stars: ✭ 247 (-13.94%)
Mutual labels:  embedded-systems, embedded
BIPES
BIPES: Block based Integrated Platform for Embedded Systems allows text and block based programming for several types of embedded systems and Internet of Things modules using MicroPython, CircuitPython, Python or Snek. You can connect, program, debug and monitor several types of boards using network, USB or Bluetooth. No software install needed!
Stars: ✭ 72 (-74.91%)
Mutual labels:  embedded, embedded-systems

nanoprintf

Presubmit Checks

nanoprintf is an implementation of snprintf and vsnprintf for embedded systems that, when fully enabled, aims for C11 standard compliance.

nanoprintf makes no memory allocations and uses less than 100 bytes of stack. nanoprintf compiles to somewhere between 1-3KB of code on a Cortex-M architecture.

nanoprintf is a single header file in the style of the stb libraries. The rest of the repository is tests and scaffolding and not required for use.

nanoprintf is written in a minimal dialect of C99 for maximal compiler compatibility, and compiles cleanly at the highest warning levels on clang, gcc, and msvc in both 32- and 64-bit modes. It's really hard to write portable C89 code, btw, when you don't have any guarantee about what integral type to use to hold a converted pointer representation.

nanoprintf does include C standard headers but only uses them for C99 types and argument lists; no calls are made into stdlib / libc, with the exception of any internal double-to-float conversion ABI calls your compiler might emit. As usual, some Windows-specific headers are required if you're compiling natively for msvc.

nanoprintf is statically configurable so users can find a balance between size, compiler requirements, and feature set. Floating point conversion, "large" length modifiers, and size write-back are all configurable and are only compiled if explicitly requested, see Configuration for details.

Motivation

tinyprintf doesn't print floating point values.

"printf" defines the actual standard library printf symbol, which isn't always what you want. It stores the final converted string (with padding and precision) in a temporary buffer, which makes supporting longer strings more costly. It also doesn't support the %n "write-back" specifier.

Also, no embedded-friendly printf projects that I could find are both in the public domain and have single-file implementations.

Philosophy

This code is optimized for size, not readability or structure. Unfortunately modularity and "cleanliness" even in C adds overhead at this small scale, so most of the functionality and logic is pushed together into npf_vpprintf. This is not what normal embedded systems code should look like; it's #ifdef soup and hard to make sense of, and I apologize if you have to spelunk around in the implementation. Hopefully the various tests will serve as guide rails if you hack around in it.

Alternately, perhaps you're a significantly better programmer than I! In that case, please help me make this code smaller and cleaner without making the footprint larger, or nudge me in the right direction. :)

Usage

Integrate nanoprintf into your codebase in one of two ways:

  1. Create a header file that sets up the flags and includes nanoprintf.h. Call the nanoprintf API directly wherever you want to use it. Add a c/c++ file that compiles the nanoprintf implementation.
  2. Create your own header file that wraps the parts of the nanoprintf API that you want to expose. Sandbox all of nanoprintf inside a single c/c++ file that forwards your function to nanoprintf.

Add the following code to one of your .c or .cpp files to compile the nanoprintf implementation:

#define NANOPRINTF_IMPLEMENTATION
#include "path/to/nanoprintf.h"

See the "Use nanoprintf directly" and "Wrap nanoprintf" examples for more details.

API

nanoprintf has 4 main functions:

  • npf_snprintf: Use like snprintf.
  • npf_vsnprintf: Use like vsnprintf (va_list support).
  • npf_pprintf: Use like printf with a per-character write callback (semihosting, UART, etc).
  • npf_vpprintf: Use like npf_pprintf but takes a va_list.

The pprintf variations take a callback that receives the character to print and a user-provided context pointer.

Pass NULL or nullptr to npf_[v]snprintf to write nothing, and only return the length of the formatted string.

nanoprintf does not provide printf or putchar itself; those are seen as system-level services and nanoprintf is a utility library. nanoprintf is hopefully a good building block for rolling your own printf, though.

Configuration

nanoprintf has the following static configuration flags. You can either inject them into your compiler (usually -D flags) or wrap nanoprintf.h in your own header that sets them up, and then #include your header instead of nanoprintf.h in your application.

If no configuration flags are specified, nanoprintf will default to "reasonable" embedded values in an attempt to be helpful: floats enabled, writeback and large formatters disabled. If any configuration flags are explicitly specified, nanoprintf requires that all flags are explicitly specified.

  • NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS: Set to 0 or 1. Enables field width specifiers.
  • NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS: Set to 0 or 1. Enables precision specifiers.
  • NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS: Set to 0 or 1. Enables floating-point specifiers.
  • NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS: Set to 0 or 1. Enables oversized modifiers.
  • NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS: Set to 0 or 1. Enables %n for write-back.
  • NANOPRINTF_VISIBILITY_STATIC: Optional define. Marks prototypes as static to sandbox nanoprintf.

If a disabled format specifier feature is used, no conversion will occur and the format specifier string simply will be printed instead.

Formatting

Like printf, nanoprintf expects a conversion specification string of the following form:

[flags][field width][.precision][length modifier][conversion specifier]

  • Flags

    None or more of the following:

    • 0: Pad the field with leading zero characters.
    • -: Left-justify the conversion result in the field.
    • +: Signed conversions always begin with + or - characters.
    • : (space) A space character is inserted if the first converted character is not a sign.
    • #: Writes extra characters (0x for hex, . for empty floats, '0' for empty octals, etc).
  • Field width (if enabled)

    A number that specifies the total field width for the conversion, adds padding. If field width is *, the field width is read from the next vararg.

  • Precision (if enabled)

    Prefixed with a ., a number that specifies the precision of the number or string. If precision is *, the precision is read from the next vararg.

  • Length modifier

    None or more of the following:

    • h: Use short for integral and write-back vararg width.
    • L: Use long double for float vararg width (note: it will then be casted down to float)
    • l: Use long, double, or wide vararg width.
    • hh: Use char for integral and write-back vararg width.
    • ll: (large specifier) Use long long for integral and write-back vararg width.
    • j: (large specifier) Use the [u]intmax_t types for integral and write-back vararg width.
    • z: (large specifier) Use the size_t types for integral and write-back vararg width.
    • t: (large specifier) Use the ptrdiff_t types for integral and write-back vararg width.
  • Conversion specifier

    Exactly one of the following:

    • %%: Percent-sign literal
    • %c: Characters
    • %s: Null-terminated strings
    • %i/%d: Signed integers
    • %u: Unsigned integers
    • %o: Unsigned octal integers
    • %x / %X: Unsigned hexadecimal integers
    • %p: Pointers
    • %n: Write the number of bytes written to the pointer vararg
    • %f/%F: Floating-point values

Floating Point

Floating point conversion is performed by extracting the value into 64:64 fixed-point with an extra field that specifies the number of leading zero fractional digits before the first nonzero digit. No rounding is currently performed; values are simply truncated at the specified precision. This is done for simplicity, speed, and code footprint.

Despite nano in the name, there's no way to do away with double entirely, since the C language standard says that floats are promoted to double any time they're passed into variadic argument lists. nanoprintf casts all doubles back down to floats before doing any conversions.

Measurement

Compiling with all optional features disabled yields ~1KB of ARM Cortex-M0 object code:

Minimal configuration:
 .text.npf__bufputc_nop         0x2
 .text.npf__bufputc             0x16
 .text.npf_pprintf              0x2c
 .text.npf_snprintf             0x2c
 .text.npf__itoa_rev            0x42
 .text.npf_vsnprintf            0x48
 .text.npf__utoa_rev            0x4a
 .text.npf__parse_format_spec   0xca
 .text.npf_vpprintf             0x210
total:                          0x41e (1054 bytes)

Compiling with field width and precision specifiers enabled yields ~1.7KB:

"Small" configuration: (field witdh + precision)
 .text.npf__bufputc_nop         0x2
 .text.npf__bufputc             0x16
 .text.npf_pprintf              0x2c
 .text.npf_snprintf             0x2c
 .text.npf__itoa_rev            0x42
 .text.npf_vsnprintf            0x48
 .text.npf__utoa_rev            0x4a
 .text.npf__parse_format_spec   0x1a0
 .text.npf_vpprintf             0x3c4
total:                          0x6a8 (1704 bytes)

Compiling with all optional features enabled is closer to ~2.8KB:

Everything:
 .text.npf__bufputc_nop         0x2
 .text.npf__bufputc             0x16
 .text.npf_snprintf             0x2c
 .text.npf_pprintf              0x2c
 .text.npf_vsnprintf            0x48
 .text.npf__itoa_rev            0x5a
 .text.npf__utoa_rev            0x6c
 .text.npf__fsplit_abs          0x100
 .text.npf__ftoa_rev            0x130
 .text.npf__parse_format_spec   0x204
 .text.npf_vpprintf             0x528
total:                          0xada (2778 bytes)

Development

To get the environment and run tests (linux / mac only for now):

  1. Clone or fork this repository.
  2. Run ./b from the root.

This will build all of the unit, conformance, and compilation tests for your host environment. Any test failures will return a non-zero exit code.

The nanoprintf development environment uses cmake and ninja. If you have these in your path, ./b will use them. If not, ./b will download and deploy them into path/to/your/nanoprintf/external.

nanoprintf uses GitHub Actions for all continuous integration builds. The GitHub Linux builds use this Docker image on Docker Hub. The Dockerfile lives here.

The matrix builds [Debug, Release] x [32-bit, 64-bit] x [Mac, Windows, Linux] x [gcc, clang, msvc], minus the 32-bit clang Mac configurations.

Limitations

No wide-character support exists: the %lc and %ls fields require that the arg be converted to a char array as if by a call to wcrtomb. When locale and character set conversions get involved, it's hard to keep the name "nano". Accordingly, %lc and %ls behave like %c and %s, respectively.

Currently the only supported float conversions are the decimal forms: %f and %F. Pull requests welcome!

Acknowledgments

Float-to-int conversion is done using Wojciech Muła's float -> 64:64 fixed algorithm.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].