Avr Gcc Printf Seriale

Posted on

Overview

While stdio and hence printf() isn't built into the Arduino environment, there are severalways to get xxprintf() type output support in the Arduino environment.Each way has its pluses and minuses.
The alternatives include:

  • adding printf() support to the Print class
  • creating a separate wrapper function for each device class
  • Hooking into the embedded libc stdio support to make printf() work


See the printf man pagefor details on how to build format strings.

Avr Gcc Printf Serial Podcast Season ARM CPU running linux on an Atmel 1. Probably built as a dare, this project showed me that reliable simulators can be built on the 8bit Atmel platform and an external device could be used to represent the larger main memory expected by the CPU being simulated. When writing C program code for 8-bit Atmel AVR microcontrollers and using Atmel Studio, functions such as printf() and sprintf() do not print floating. The image below shows the output from the serial port of an AVR microcontroller that printed a floating point number to a string using the sprintf() function.

While xxprintf() routines are very convenient for formatting output, it comes with a costof added code size.

Also note that by default the Arduino IDE does not set the AVR linker optionsto support floating point in the xxprintf() routines.So while that saves quite a bit of code space onthe AVR builds, it means that printf() functions cannot be used for floating point outputon the AVR. Floating support is included by default for the other processors.

Advantages/Disadvantages of Each

The advantage of adding printf() to the Print class is that once added,it will transparently add printf() to all the device classes for any CPU archtecture that use the Print class.
i.e. once it is done, nothing else has to be done on any device that uses the Print class.This allows it to be easily used on lcds, HardwareSerial, SoftSerial, etc...with no modification to any other files.The sketch code can use it by simply referencing the device class object directly:

It can also transparently support using the F() macro to put the printf() formattingstrings into flash.

The disadvantage is that it requires modifying each of the Arduino systemPrint class header files, which exists in eachCPU architecture core directory and also requires using a hidden temporary buffer.

Note that the addtional code space for the printf() code is not used unless theprintf() function is used.

Similar to adding printf() support to the Print classCreating a wrapper function for each device has the advantageof working on any CPU architecture but has the disadvantages ofrequiring a separate wrapper for each device class.

Hooking into the libC embedded stdio support has the advantageof actually making the printf() function work.
The disadvantage is that the sketch usually has to be involved with settingup the stdio linkage with custom code for each device classto make it work, and the code to make it workwill be different for each cpu architecture toolset.So for example using Arduino AVR with serial vs USB will be different, vsTeensy AVR serial/USB, vs DUE ARM, vs Teensy3 ARM, vs chipkit etc...

Adding printf() to Print class

Avr Gcc Windows

To add printf() support to the Print class you must modify the Print class header file.Because of the way the Arduino code is built, each core has its own Print.h header file,so you will have to modify the Print.h header file for each architecture that you want toadd printf() support into the Print class.Here is the location of where the Print.h header files can be found:

  • AVR {installdir}/hardware/arduino/cores/arduino
  • robot {installdir}/hardware/arduino/cores/robot
  • AVR(teensy) {installdir}/hardware/teensy/cores/teensy
  • teensy3 {installdir)/hardware/teensy/cores/teensy3
  • DUE {installdir}/hardware/arduino/sam/cores/arduino
  • chipKIT {installdir}/hardware/pic32/cores/pic32

Insert the following code into the Print.h header file.The code must be inserted into the Print class definition.Just copy and past this code into the bottom of the public section.

#include <stdarg.h>
#define PRINTF_BUF 80 // define the tmp buffer size (change if desired)
voidprintf(constchar*format, ...)
{
char buf[PRINTF_BUF];
va_list ap;
va_start(ap, format);
vsnprintf(buf,sizeof(buf), format, ap);
for(char*p =&buf[0];*p; p++)// emulate cooked mode for newlines
{
if(*p 'n')
write('r');
write(*p);
}
va_end(ap);
}
#ifdef F // check to see if F() macro is available
voidprintf(const __FlashStringHelper *format, ...)
{
char buf[PRINTF_BUF];
va_list ap;
va_start(ap, format);
#ifdef __AVR__
vsnprintf_P(buf,sizeof(buf),(constchar*)format, ap);// progmem for AVR
#else
vsnprintf(buf,sizeof(buf),(constchar*)format, ap);// for the rest of the world
#endif
for(char*p =&buf[0];*p; p++)// emulate cooked mode for newlines
{
if(*p 'n')
write('r');
write(*p);
}
va_end(ap);
}
#endif

Once this is inserted into the Print class header, sketches canuse printf() functionality by simply referencing the function in the device object.
i.e. Serial.printf('Hello Worldn');

Creating a printf() wrapper

This function emulates the stdio.h printf() functionality in C and will send the output to the Serial connection using Serial.print(). The resulting string sent over serial is limited to 128 chars.
See the sprintf man page or the examples herein for details on how to build format strings.

#include <stdarg.h>
void p(char*fmt, ... ){
char buf[128];// resulting string limited to 128 chars
va_list args;
va_start (args, fmt );
vsnprintf(buf,128, fmt, args);
va_end (args);
Serial.print(buf);
}
The above function won't work with the F() macro, but if you add this function as well, it will:
void p(const __FlashStringHelper *fmt, ... ){
char buf[128];// resulting string limited to 128 chars
va_list args;
va_start (args, fmt);
#ifdef __AVR__
vsnprintf_P(buf,sizeof(buf),(constchar*)fmt, args);// progmem for AVR
#else
vsnprintf(buf,sizeof(buf),(constchar*)fmt, args);// for the rest of the world
#endif
va_end(args);
Serial.print(buf);
}
Examples:
p('%s','Hello world');
p('%sn','Hello world');// with line break
unsignedlong a=0xFFFFFFFF;
p('Decimal a: %lnDecimal unsigned a: %lun', a, a);
p('Hex a: %xn', a);
p(F('Best to store long strings to flash to save %s'),'memory');

If anyone has a better version please feel free to remove or edit this version.

You can also avoid using Serial.print() altogether and redirect STDIN and STDOUT to UART.

Hooking up printf() on AVR

The avr-gcc C library, avr-libc, provides the printf() family of functions. You can use them in Arduino sketches after some preparation. General details are in the avr-libc documentation for file stdio.h. These notes are specific to the Arduino development environment.

You must:

  • create a FILE structure instance,
  • create a character output function,
  • write the function address into the FILE structure,
  • assign the structure address to that of 'stdout'.

printf() makes your executable object ~1000 bytes larger, so you may not want to use it if size is a problem.

Example

// we need fundamental FILE definitions and printf declarations
#include <stdio.h>
// create a FILE structure to reference our UART output function
static FILE uartout ={0};
// create a output function
// This works because Serial.write, although of
// type virtual, already exists.
staticint uart_putchar (char c, FILE *stream)
{
Serial.write(c);
return0;
}
void setup(void)
{
// Start the UART
Serial.begin(9600);
// fill in the UART file descriptor with pointer to writer.
fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE);
// The uart is the standard output device STDOUT.
stdout =&uartout ;
}
void loop(void)
{
float seconds ;
// wait 1000 milliseconds
delay(1000);
// calculate seconds as a floating point value
seconds =(float) millis()/1000.0;
// report seconds since starting
printf('Alive %.3f sec', seconds );
/*
// without printf(), you would do this:
Serial.print('Alive ') ;
Serial.print(seconds,3) ;
Serial.print('sec') ;
*/

#if 0
// you can explicitly use a FILE structure like this:
fprintf(&uartout,'Alive %.3f sec', seconds );
#endif
}

Extensions

If you can define a character-based output function and reference it in a FILE structure, you can use fprintf() as well. An example for an LCD shield follows below.

Example

#include <LiquidCrystal.h>
// register the LiquidCrystal control and data pins
// for the dfrobots LCD shield.
LiquidCrystal LCD(8,9,4,5,6,7);
static FILE lcdout ={0};// LCD FILE structure
// LCD character writer
staticint lcd_putchar(char ch, FILE* stream)
{
LCD.write(ch);
return(0);
}
void setup(void)
{
// LCD shield is 16 columns by 2 rows
LCD.begin(16,2);
// fill in the LCD FILE structure
fdev_setup_stream (&lcdout, lcd_putchar, NULL, _FDEV_SETUP_WRITE);
}
void loop(void)
{
float seconds ;
// calculate seconds as a floating point value
seconds =(float) millis()/1000.0;
// set the LCD cursor position to home
LCD.setCursor(0,0);
// report seconds since starting
// in the X.xxx floating point format.
fprintf(&lcdout,'Alive %.3f sec', seconds );
// wait 1000 milliseconds
delay(1000);
}

Using Flash Memory for string storage

Version 1.0 of the Arduino IDE introduced the F() syntax for storing strings in flash memory rather than RAM. e.g.

Serial.println(F('This string will be stored in flash memory'));

The same result can be achieved with printf by using the _P versions of printf, fprintf, etc and wrapping your string in the PSTR() macro. You'll need to #include <avr/pgmspace.h>, e.g.

#include <avr/pgmspace.h>
// do all the stdout setup as above here
printf_P(PSTR('This string will be stored in flash memory'));
Avr Gcc Printf Seriale

Using flash memory this way only works for the format string. If you include a string (%s) in your format you'll need to pass a regular string, e.g.

// this works
printf_P(PSTR('string %s'),'embedded string');
// this fails
printf_P(PSTR('string %s'), PSTR('embedded string'));

Tweaked version of the above example using printf_P:

Avr Gcc Compiler

// we need fundamental FILE definitions and printf declarations
#include <stdio.h>
#include <avr/pgmspace.h>
// create a FILE structure to reference our UART output function
static FILE uartout ={0};
// create a output function
// This works because Serial.write, although of
// type virtual, already exists.
staticint uart_putchar (char c, FILE *stream)
{
Serial.write(c);
return0;
}
void setup(void)
{
// Start the UART
Serial.begin(9600);
// fill in the UART file descriptor with pointer to writer.
fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE);
// The uart is the standard output device STDOUT.
stdout =&uartout ;
}
void loop(void)
{
int seconds ;
// wait 1000 milliseconds
delay(1000);
seconds = millis()/1000;
// use flash storage for the format string
printf_P(PSTR('Alive %d secn'), seconds);
/*
// without printf_P(), you would do this:
Serial.print(F('Alive ')) ;
Serial.print(seconds) ;
Serial.println(F(' sec')) ;
*/

#if 0
// you can explicitly use a FILE structure like this:
fprintf_P(&uartout, PSTR('Alive %d secn'), seconds );
#endif
}

Stm32 Gcc Printf

I've tweaked the previous example to use regular integers rather than floating point values. My environment (Ubuntu Linux 10.04, Arduino IDE 1.0) doesn't include support for floating points (%f) by default when compiling printf support. According to notes in stdio.h it's possible to change this behaviour by passing gcc extra parameters. I'm not sure if this limitation applies to Windows and OSX environments.