3
votes

I'm writing a program to run on an ATmega328p - bare avr-libc rather than Arduino, though it does use the Arduino bootloader over serial (I don't imagine that affects the following question though).

I have set up stdout to write to the UART in the hopefully-obvious manner:

void uart_putc(char c)
{
  // Turn LFs into CRLFs
  if(c == '\n')
    uart_putc('\r');

  while(!(UCSR0A & _BV(UDRE0)))
    ;

  UDR0 = c;
}

static int _putc(char c, FILE *_)
{
  uart_putc(c);
  return 0;
}

...
  fdev_setup_stream(stdout, &_putc, NULL, _FDEV_SETUP_WRITE);

If I now write my program using only fputc and fputs then all works fine. I can even call snprintf() to render formatted strings into a char buffer[16] and then fputs() them out; these things all work fine.

fputs("Hello, world\n", stdout);  /* works fine */

char buffer[16];
snprintf(buffer, sizeof buffer, "Hello, %s\n", "world");
fputs(buffer, stdout);             /* also works fine */

However, the moment I try to perform a real fprintf() to stdout the program crashes and reboots the ATmega:

fprintf(stdout, "Hello, %s\n", "world");

It crashes immediately, before even outputting the initial H here.

Can anyone suggest something that might be missing, that would stop fprintf() working, when both snprintf() and fputs() can work fine?

1
You might see if there's a simulator with a debugger which you could run it on. - Chris Stratton
Step into fprintf, and "walk" the disassembly until a crash occurs. Then do it again, and just before executing the instruction that causes the crash, open a register-view, copy all the registers, and publish them here. In addition, publish the instruction that causes the crash. This might provide us with some significant piece of information. BTW, you can do the same with fputs and see for yourself how come one is working and the other one isn't working. - barak manos
A crash related to any printf function makes me think of a stack overflow. Try increasing the stack size. - kkrambo

1 Answers

1
votes

You are using fdev_setup_stream() to update the assignment of stdout. The documentation of fdev_setup_stream() explicitly states though:

Note: No assignments to the standard streams will be performed by fdev_setup_stream(). If standard streams are to be used, these need to be assigned by the user.

The same document points to a code example for using stdio without malloc. The code there looks quite similar to yours except for two things:

  1. A custom stream is define using the macro FDEV_SETUP_STREAM():

    static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
    
  2. stdout is initialized to this custom stream:

    stdout = &mystdout;
    

I think, your code is using an uninitialized stdout stream. The "Introduction to the Standard IO facilities" states:

The standard streams stdin, stdout, and stderr are provided, but contrary to the C standard, since avr-libc has no knowledge about applicable devices, these streams are not already pre-initialized at application startup.

When uninitialized, the behavior of all function calls using stdout is undefined. Depending on where stdout is actually pointing to, some calls might still work.