Go Down

Topic: Add Standard Input, Output and Error to Arduino painlessly (Read 2181 times) previous topic - next topic

krupski

Hi all,

Would you like to be able to print your serial data like this:

Code: [Select]
Serial.begin (9600);
const char *location = "outside";
int temperature;
temperature = ((readThermistor () * 1.8) + 32);
char cf = 'F';

fprintf (stdout, "Temperature %s is %d degrees %c\n", location, temperature, cf);


...instead of this:

Code: [Select]
Serial.begin (9600);
const char *location = "outside";
int temperature;
temperature = ((readThermistor () * 1.8) + 32);
char cf = 'F';

Serial.print ("Temperature ");
Serial.print (location);
Serial.print (" is ");
Serial.print ((int) temperature);
Serial.print (" degrees ");
Serial.print ((char) cf);
Serial.println ();


Well, it's simple, and what's more it's automatic. You open a serial device, stdin/out/error are automatically created. Close the serial device and they are destroyed. Open a different serial device (like Serial2 on a MEGA) and the stdin/out/err are automatically connected to that port!

First step: Go to your Arduino directory located at "arduino-1.x.x/hardware/arduino/cores/arduino" and make backup copies of HardwareSerial.cpp and HardwareSerial.h.  Be sure the backups don't have a valid "C" style name (i.e. CPP, C, H, INO, etc) or else they will also get pulled in during a compile and cause an error. I suggest these names:

HardwareSerial.cpp -> HardwareSerial.cpp.bak
HardwareSerial.h -> HardwareSerial.h.bak


Next, download the attached ZIP file containing "Stdinout.cpp" and "Stdinout.h". Unzip these files into a temporary directory and then COPY them to your Arduino directory located at "arduino-1.x.x/hardware/arduino/cores/arduino". Then open "HardwareSerial.cpp" and add a few lines:

Around line 348 or so is the function void HardwareSerial::begin (unsigned long baud).
Scroll down to the end of that function, looking for lines like this and INSERT the one line shown in blue:

    // assign the baud_setting, a.k.a. ubbr (USART Baud Rate Register)
    *_ubrrh = baud_setting >> 8;
    *_ubrrl = baud_setting;
    transmitting = false;
    sbi (*_ucsrb, _rxen);
    sbi (*_ucsrb, _txen);
    sbi (*_ucsrb, _rxcie);
    cbi (*_ucsrb, _udrie);

    STDIO.open (this);      // <-- INSERT HERE

}


Next will be another "Hardware::Serial::begin". Afain, scroll to the bottom of this function and ADD the line shown in blue.

    //set the data bits, parity, and stop bits
#if defined (__AVR_ATmega8__)
    config |= 0x80; // select UCSRC register (shared with UBRRH)
#endif
    *_ucsrc = config;
    sbi (*_ucsrb, _rxen);
    sbi (*_ucsrb, _txen);
    sbi (*_ucsrb, _rxcie);
    cbi (*_ucsrb, _udrie);

    STDIO.open (this); // connect stdin/out/err to current serial stream
}

Now, scroll down a bit more to "HardwareSerial::end()" and add the line in blue:

void HardwareSerial::end (void)
{
    // wait for transmission of outgoing data
    while (_tx_buffer->head != _tx_buffer->tail);

    cbi (*_ucsrb, _rxen);
    cbi (*_ucsrb, _txen);
    cbi (*_ucsrb, _rxcie);
    cbi (*_ucsrb, _udrie);

    // clear any received data
    _rx_buffer->head = _rx_buffer->tail;

    STDIO.close (); // flush, close and disconnect std streams
}


Now, close HardwareSerial.cpp and open HardwareSerial.h.

Near the top, add these two lines shown in blue:

#include <stdio.h>
#include <inttypes.h>
#include "Stream.h"

#include "Stdinout.h"
static STDINOUT STDIO; /* object to connect standard in/out/err to */


struct ring_buffer;
class HardwareSerial : public Stream

Now close HardwareSerial.h. You are done! Now, to be sure it works, close, then re-open your Arduino IDE (if it was open) and try this sketch:

Code: [Select]
void setup (void)
{
    Serial.begin (9600) // whatever baud rate YOU want is OK
    Serial.println ("Regular function works!");
    printf ("New function works too!\n");
}
void loop (void);


Both lines should print. If you get an error message from the IDE, or if only the first line prints, then recheck the steps above. If nothing works, PM me and I'll try to help you.  If all else fails, simply revert to your backed up versions of HardwareSerial.cpp and .h

Good luck!
Gentlemen may prefer Blondes, but Real Men prefer Redheads!

Coding Badly


Is modifying HardwareSerial.* required to use your code?

Assuming I follow your post, this would also work...
Code: [Select]

void setup( void )
{
  Serial.begin( 115200 );
  STDIO.open( Serial );
}

krupski

Is modifying HardwareSerial.* required to use your code?

Assuming I follow your post, this would also work...
Code: [Select]

void setup( void )
{
  Serial.begin( 115200 );
  STDIO.open( Serial );
}


No, you don't have to modify HardwareSerial. To use it stand-alone, just include the library:

#include <Stdinout.h>

Then do as you said:

Serial.begin( 115200 );
STDIO.open( Serial );


If, by chance, you want to connect the standard paths to a different serial port, simply call it again with the new serial device, for example:

Serial2.begin( 115200 );
STDIO.open( Serial2 );


The code does a close() before an open, so you don't need to explicitly close the first one before defining the second one.


Gentlemen may prefer Blondes, but Real Men prefer Redheads!

Coding Badly

#3
Mar 30, 2015, 08:21 pm Last Edit: Mar 30, 2015, 08:21 pm by Coding Badly
Code: [Select]
fprintf (stdout...

Cant that be replaced by a call to printf?  Does AVR-Libc include printf?


krupski

Code: [Select]
fprintf (stdout...

Cant that be replaced by a call to printf?  Does AVR-Libc include printf?


Yes and yes.
Gentlemen may prefer Blondes, but Real Men prefer Redheads!

westfw

Hmm.  Can you actually DO fdev_close() on a "file" that was setup with fdev_setup_stream()?
I thought that latter specifically avoided the malloc()/etc that close would presumably clean up...


westfw

(to answer my own question: yes, fdev_close() is safe on files that weren't fdef_open()ed.  There's an explicit check for malloc()ed storage in the source code
(however, it doesn't actually DO anything...))


westfw

Are you going to put this on github or somewhere similar, so we can submit "issues" and feature requests and such?
For example, I think there's a pretty trivial patch that will make it work on newlib based platforms (Energia Tiva for sure, maybe Due/etc as well.)

krupski

#8
Apr 06, 2015, 02:18 pm Last Edit: Apr 06, 2015, 02:26 pm by Krupski Reason: crappy editor keeps adding characters!
Are you going to put this on github or somewhere similar, so we can submit "issues" and feature requests and such?
For example, I think there's a pretty trivial patch that will make it work on newlib based platforms (Energia Tiva for sure, maybe Due/etc as well.)

Probably will eventually post it on GitHub. I'm also working on extending it so that other character devices can be used. For example, I want to be able to do something like "stdio.open (LCD);" (assuming that "LCD" is setup as usual) and then simply be able to "printf ("Hello");" and have it appear on the LCD.

I've got it working, but it requires modifying the LiquidCrystal library to belong to "Stream" rather than "Print" and then adding dummy functions for unsupported calls (for example, an LCD doesn't have a "read()" or "available()").

So far it's turning out nicely. A user will be able to switch stdio streams on the fly and printf, scanf, getc, putc, etc... to any character device.

I also have it working in my KS-0108 compatible Noritake VFD graphic display driver library.

The only downside to all this is that simply by using a "printf" type of function adds around 1.4K to a sketch size. If additionally the floating point LIBC is used to support "printf (%f)", approximately another 1.1K is added to the sketch. The additional size is a one-time thing though... once you use printf, you may as well use it all the time.

This causes moderately complex sketches to come "dangerously close" to the limit for a 32K board like the Uno, but it's no problem on a Mega.

The final step will probably be another option in "Preferences" to enable or disable the standard file paths. The attached screenshot shows the current state of the Preferences options.

(click image for full res)

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

Osqui

In developer mail list there is an long discussion about this topic

Go Up