Hi all,
Almost all "display" devices print THROUGH the Arduino core "Print.cpp".
I've written a small library that automatically and correctly (memory management wise) connects to the standard streams and provides stdin, stdout and stderr (so that I can use things like "printf" and "getc" directly).
My original version which worked perfectly, sent it's write calls and got it's read calls directly from the opened device. For example, if I "opened" the "Serial" object, the library worked like this:
MYSKETCH printf --> Serial.write
MYSKETCH getc <-- Serial.read
If I "open" an instance of LiquidCrystal, it works like this:
MYSKETCH printf --> LCD.write
MYSKETCH.getc <-- fails because an LCD doesn't read
So far, so good... but now what I want to do is have all output (printing) calls GO THROUGH the Arduino Print.cpp core code like "Serial.print" and "LCD.print" does.
Here's the current version of my modified code that's supposed to work, but gives me the compile error:
Stdinout.cpp
////////////////////////////////////////////////
/// NOTE Some pieces snipped out for clarity ///
////////////////////////////////////////////////
#include "Stdinout.h"
static Print *stream_ptr0 = NULL; // stdin stream pointer
static Print *stream_ptr1 = NULL; // stdout stream pointer
static Print *stream_ptr2 = NULL; // stderr stream pointer
// connect each stream to it's own device
void STDINOUT::open (Print &inpstr, Print &outstr, Print &errstr)
{
close(); // close any that may be open
if (stdin == NULL) { // open stdin
stdin = fdevopen (NULL, getchar0);
stream_ptr0 = &inpstr;
}
if (stdout == NULL) { // open stdout
stdout = fdevopen (putchar1, NULL);
stream_ptr1 = &outstr;
}
if (stderr == NULL) { // open stderr
stderr = fdevopen (putchar2, NULL);
stream_ptr2 = &errstr;
}
}
// disconnect stdio from stream(s)
void STDINOUT::close (void)
{
if (stderr) { // close stderr
fclose (stderr);
stderr = NULL;
stream_ptr2 = NULL;
}
if (stdout) { // close stdout
fclose (stdout);
stdout = NULL;
stream_ptr1 = NULL;
}
if (stdin) { // close stdin
fclose (stdin);
stdin = NULL;
stream_ptr0 = NULL;
}
}
// Function that fgetc, fread, scanf and related
// will use to read a char from stdin
int STDINOUT::getchar0 (FILE *fp)
{
if (fp == stdin) {
while (! (stream_ptr0->available())); // wait until a character is available...
return (stream_ptr0->read()); // ...then grab it and return
} else {
return _FDEV_ERR;
}
}
// function that printf and related will use to write
// a char to stdout and auto-add a CR to a LF
int STDINOUT::putchar1 (char c, FILE *fp)
{
if (fp == stdout) {
if (c == '\n') { // if linefeed
stream_ptr1->write ((char) '\r'); // also send CR
}
stream_ptr1->write ((char) c);
return 0;
} else {
return _FDEV_ERR;
}
}
// function that printf and related will use to write
// a char to stdout and auto-add a CR to a LF
int STDINOUT::putchar2 (char c, FILE *fp)
{
if (fp == stderr) {
if (c == '\n') { // if linefeed
stream_ptr2->write ((char) '\r'); // also send CR
}
stream_ptr2->write ((char) c);
return 0;
} else {
return _FDEV_ERR;
}
}
STDINOUT STDIO; // Preinstantiate STDIO object
Stdinout.h
#ifndef STD_IN_OUT_H
#define STD_IN_OUT_H
#include <stdio.h>
#include "Print.h"
class STDINOUT : public Print
{
public:
void open (Print &);
void open (Print &, Print &);
void open (Print &, Print &, Print &);
void close (void);
Print &getStream (FILE *);
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
virtual size_t write (uint8_t);
using Print::write;
};
extern STDINOUT STDIO;
#endif // #ifndef STD_IN_OUT_H
I've got a simple test sketch which simply does this:
fprintf (stdout, "This is a test\n"); // inside setup/loop crap of course
This is the error message I get from the compiler:
[b]Arduino: 1.0.6 (Linux), Board: "atmega2560 (22.1M)"
/tmp/build1182073295482001577.tmp/core.a(Stdinout.cpp.o): In function `_GLOBAL__sub_I__ZN8STDINOUT4openER5Print':
Stdinout.cpp:(.text.startup._GLOBAL__sub_I__ZN8STDINOUT4openER5Print+0x8): undefined reference to `vtable for STDINOUT'
Stdinout.cpp:(.text.startup._GLOBAL__sub_I__ZN8STDINOUT4openER5Print+0xa): undefined reference to `vtable for STDINOUT'
collect2: error: ld returned 1 exit status[/b]
By the way, the IDE version listed is irrelevant since the AVR-GCC toolchain is this version:
[b]root@michael:/# avr-gcc -v
Using built-in specs.
Reading specs from /usr/local/lib/gcc/avr/6.3.0/device-specs/specs-avr2
COLLECT_GCC=avr-gcc
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/avr/6.3.0/lto-wrapper
Target: avr
Configured with: ../configure --target=avr --enable-languages=c,c++ --disable-nls --disable-libssp --with-dwarf2
Thread model: single
gcc version 6.3.0 (GCC)[/b]
What the heck is a "vtable"?? I searched online, looked at other code for clues and I've been at it for two days straight now and can't seem to figure it out. I'm SURE it's a simple problem, and I'm sure I'll be kicking myself when I find out the simple, tiny mistake I'm making... but right now I just don't see it.
Anyone have any ideas? I'd really appreciate it, and if anyone has a test to try, let me know and I'll do it.
Thanks!