Problem with fdev_open and certain printf formats

Go To Last Post
8 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,

 

For all of my AVR projects, I set up the standard input, output and error streams using fdev_open() instead of the ridiculous "Java-like or Arduino-like" method of building up a message out of multiple print and println calls. It works fine, but I seem to have run into a possible bug in avr-libc.

 

I've found that printing characters (either single characters using %c or strings using %s) uses, in Arduino's Print.cpp library, the Print::print ((char) c) function.  That is, printf/fprintf send a single character for %c format or multiple characters for %s format (and usually it works).

 

The problem/bug that I've run into is this:

 

const char *msg = "Hello there!\n";
fprintf (stdout, "%s", msg); // works (AVR)
fprintf (stdout, "%s", "Hello there!\n"); // works (AVR)
fprintf (stdout, "Hello there!\n"); // DOES NOT WORK!!! (AVR)

 

The last example (that doesn't work) only displays the FIRST CHARACTER of the string (in this case, "H"). And, the program ends in a while(1); loop, so it's not that problem!  :)

 

If I compile the same code, using GCC, as a console program in Linux, it, of course, works for all three examples.

 

Anyone possibly have any idea WHY this is? Am I doing something wrong, or is there a bug in AVR-LIBC?

 

By the way, this is the exact code in my Print.cpp library that prints the individual characters and adds the CR to a LF if necessary:

 

size_t Print::print (char c)
{
    size_t n = 0;

    if (c == '\n') {
        n += write ((uint8_t) '\r'); // add CR to LF
    }

    n += write ((uint8_t) c);
    return n;
}

 

And, of course, write() is the virtual function that is over-ridden depending on whether the character device is Serial, LCD, or something else.

 

Any help will be appreciated!

 

This topic has a solution.

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

Last Edited: Sat. Oct 5, 2019 - 10:39 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


What is fdev_open()? Do you mean fdev_setup_stream() ? Also why do you choose to do this dynamically rather than the rather more normal, statically allocated FDEV_SETUP_STREAM ?

 

If I build this...

#include <stdio.h>
#include <avr/io.h>

int uart_putchar(char c, FILE *stream);
FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

int uart_putchar(char c, FILE *stream) {
	PORTB = c;
	return 0;
}

int main(void)
{
	stdout = &uart_stream;
	printf("%s", "hello ");
	printf("world");
	while (1)
	{
	}
}

then put a breakpoint on the PORTB write and simulate...

 

then that shows on the first hit it wrote 'h' to PORTB. If I just keep using Continue then I see it write e, l, l, o, ' ' and then on the next step it writes:

 

so that is the 'w' of "world". If I run again then 

 

 

so that is the 'o' and continuing I see 'r', 'l' and 'd'. So both printf() here worked.

 

In the putchar() you have implemented you are doing the "return 0" aren't you?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

What is fdev_open()? Do you mean fdev_setup_stream() ? Also why do you choose to do this dynamically rather than the rather more normal, statically allocated FDEV_SETUP_STREAM ?

 

In the putchar() you have implemented you are doing the "return 0" aren't you?

 

 

OK, I have a library which will setup the stdin/out/err paths based on the device passed to it. This is the library:

 

Stdinout.cpp:

#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();

	stdin = fdevopen (NULL, _getchar0); // setup stdin
	_stream_ptr0 = &inpstr;

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

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

// disconnect stdio from stream(s) & free memory
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;
}

// read a char from stdin
int STDINOUT::_getchar0 (FILE *fp)
{
	// 0 == stdin
	while (! _stream_ptr0->available());
	return _stream_ptr0->read();
}

// write a char to stdout
int STDINOUT::_putchar1 (char c, FILE *fp)
{
	// 1 == stdout
	return _stream_ptr1->print (c);
}

// write a char to stderr
int STDINOUT::_putchar2 (char c, FILE *fp)
{
	// 2 == stderr
	return _stream_ptr2->print (c);
}

STDINOUT STDIO; // Preinstantiate STDIO object

 

Stdinout.h:

#ifndef STD_IN_OUT_H
#define STD_IN_OUT_H

#include <stdio.h>
#include "Print.h"

static Print *_stream_ptr0; // stdin
static Print *_stream_ptr1; // stdout
static Print *_stream_ptr2; // stderr

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

 

 

Now, with this I can simply use STDIO.open (Serial); (after the usual Serial.begin (115200); of course) then printf or fprintf (stdout... to the Serial device.

 

If I want to print to a different device, say an LCD, then after setting up the LiquidCrystal device, I can use STDIO.open (LCD); and print to it.

 

By the way, after tinkering around last night, I discovered the problem (although I'm not sure why it IS a problem). When printf() is called, it seems like no matter what is printed, printf  parses the format characters and creates the output, printing it character by character as a (char) (which calls Print::print ((char) c) in the Print library.

 

What I had done in Print (char) was, like any other part of the library, to count the characters output and return the number to the caller. Print (char) will output one byte normally, of if the character is a linefeed (0x0A), then it outputs a CR, then the linefeed and returns a count of two.

 

It seems like the avr-libc implementation of printf looks at the return value of Print::print() call as an error flag and if a non-zero is returned, it aborts.  Changing the return code to a zero made it all work!

 

I have no idea why any of the Arduino print functions return a character count, but the fact that Print::print returned a 1 or a 2 caused printf() to abort.

 

To make it clearer, here are the differences:

 

Before:

size_t Print::print (char c)
{
	size_t n = 0;

	if (c == '\n') {
		// properly print "xxx\n" as "xxx\r\n"
		n += write ((uint8_t) '\r'); // add CR to LF
	}

	n += write ((uint8_t) c);
	return n; // returns 1 or 2
}

 

 

After:

size_t Print::print (char c)
{
	if (c == '\n') {
		// properly print "xxx\n" as "xxx\r\n"
		write ((uint8_t) '\r'); // add CR to LF
	}
	write ((uint8_t) c);
	return 0; // must return 0 for fprintf()
}

 

Or, based on the question you asked me about "putchar() returning 0", should the Print library count characters, but the Stdinout library return a zero after calling pointer->print() ?

 

Thanks!

 

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I came across this same problem when I was hooking up my little UART library to avr-libc's stdio. I wrongly assumed that the "put" function had the same type of return value as the standard "fputc" function (i.e., return the character converted to unsigned char, or EOF on error), but it actually is supposed to return 0 on success and nonzero on error.

 

In your case, since Print::print returns a character count, instead of modifying Print::print, you can convert that to 0/nonzero in STDINOUT::_putchar1 with:

return _stream_ptr1->print (c) == 0;
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks!  Actually, I did it a slightly different way... I did the stream->print(c); then return 0;

 

Your way would make the function return non-zero only if there was an error in the print call, which I suppose is even a better way to do it. I'm going to change it now!

 

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I finally changed the charout function as you suggested. Works perfectly! Thanks!

return _stream_ptr1->print (c) == 0;

--Roger

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

Last Edited: Sat. Oct 5, 2019 - 10:44 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

See last sentence of #2 ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

yup, you said it first. I missed it the first time. My bad :(

Gentlemen may prefer Blondes, but Real Men prefer Redheads!