The header file stdio.h
consists of types, macros and
functions that provide support to perform input and output operations on files and
streams. When a file is opened, it is associated with a stream. A stream is a pipeline
for the flow of data into and out of files. Because different systems use different
properties, the stream provides more uniform properties to allow reading and writing of
the files.
Streams can be text streams or binary streams. Text streams consist of a
sequence of characters divided into lines. Each line is terminated with a newline
(‘\n
’) character. The characters may be altered in their internal
representation, particularly in regards to line endings. Binary streams consist of
sequences of bytes of information. The bytes transmitted to the binary stream are not
altered. There is no concept of lines - the file is just a series of bytes.
At start-up, three streams are automatically opened: stdin
,
stdout
and stderr
. stdin
provides
a stream for standard input, stdout
is standard output and
stderr
is the standard error. Additional streams may be created
with the fopen
function. See fopen
for the different
types of file access that are permitted. These access types are used by
fopen
and freopen
.
The type FILE
is used to store information about each opened
file stream. It includes such things as error indicators, end-of-file indicators, file
position indicators and other internal status information needed to control a stream.
Many functions in the stdio use FILE
as an argument.
There are three types of buffering: unbuffered, line buffered and fully
buffered. Unbuffered means a character or byte is transferred one at a time. Line
buffered collects and transfers an entire line at a time (i.e., the newline character
indicates the end of a line). Fully buffered allows blocks of an arbitrary size to be
transmitted. The functions setbuf
and setvbuf
control
file buffering.
The stdio.h
file also contains functions that use input and
output formats. The input formats, or scan formats, are used for reading data. Their
descriptions can be found under scanf
, but they are also used by
fscanf
and sscanf
. The output formats, or print
formats, are used for writing data. Their descriptions can be found under
printf
. These print formats are also used by
fprintf
, sprintf
, vfprintf
,
vprintf
and vsprintf
.
Compiler Options
Certain compiler options may affect how standard I/O performs. In an effort to provide a more tailored version of the formatted I/O routines, the tool chain may convert a call to a printf or scanf style function to a different call. The options are summarized below:
-msmart-io
option, when enabled, will attempt to convert printf
,
scanf
and other functions that use the input output formats to
an integer-only variant. The functionality is the same as that of the C standard
forms, minus the support for floating-point output. -msmart-io=0
disables this feature and no conversion will take place.
-msmart-io=1
or -msmart-io
(the default) will
convert a function call if it can be proven that an I/O function will never be
presented with a floating-point conversion. -msmart-io=2
is more
optimistic than the default and will assume that non-constant format strings or
otherwise unknown format strings will not contain a floating-point format. In the
event that -msmart-io=2
is used with a floating-point format, the
format letter will appear as literal text and its corresponding argument will not be
consumed.-fno-short-double
will cause the compiler to generate calls to
formatted I/O routines that support double as if it were a long
double
type.Mixing modules compiled with these options may result in a larger executable size or incorrect execution if large and small double-sized data is shared across modules.
Customizing STDIO
The standard I/O relies on helper functions described in “Standard C
Libraries - Support Functions.” These functions include read()
,
write()
, open()
, and close()
which are called to read, write, open or close handles that are associated with standard
I/O FILE
pointers. The sources for these libraries are provided for you
to customize as you wish.
It is recommended that these customized functions be allocated in a named section that
begins with .lib
. This will cause the linker to allocate them near to
the rest of the library functions, which is where they ought to be.
The simplest way to redirect standard I/O to the peripheral of your choice is to select
one of the default handles already in use. Also, you could open files with a specific
name via fopen()
by rewriting open()
to return a new
handle to be recognized by read()
or write()
as
appropriate.
If only a specific peripheral is required, then you could associate handle 1 ==
stdout
or 2 == stderr
to another peripheral by writing the
correct code to talk to the interested peripheral.
A complete generic solution might be:
/* should be in a header file */
enum my_handles {
handle_stdin,
handle_stdout,
handle_stderr,
handle_can1,
handle_can2,
handle_spi1,
handle_spi2,
};
int _ ___attribute_ ___((_ ___weak_ ___, _ ___section_ ___(".libc")))
open(const char *name, int access, int mode) {
switch (name[0]) {
case 'i' : return handle_stdin;
case 'o' : return handle_stdout;
case 'e' : return handle_stderr;
case 'c' : return handle_can1;
case 'C' : return handle_can2;
case 's' : return handle_spi1;
case 'S' : return handle_spi2;
default: return handle_stderr;
}
}
Single letters were used in this example because they are faster to check and use less
memory. However, if memory is not an issue, you could use strcmp
to
compare full names.
In write()
, you would write:
int __attribute__((__section__(".libc.write")))
write(int handle, void *buffer, unsigned int len) {
int i;
volatile UxMODEBITS *umode = &U1MODEbits;
volatile UxSTABITS *ustatus = &U1STAbits;
volatile unsigned int *txreg = &U1TXREG;
volatile unsigned int *brg = &U1BRG;
switch (handle)
{
default:
case 0:
case 1:
case 2:
if ((_ ___C30_UART != 1) && (&U2BRG)) {
umode = &U2MODEbits;
ustatus = &U2STAbits;
txreg = &U2TXREG;
brg = &U2BRG;
}
if ((umode->UARTEN) == 0)
{
*brg = 0;
umode->UARTEN = 1;
}
if ((ustatus->UTXEN) == 0)
{
ustatus->UTXEN = 1;
}
for (i = len; i; --i)
{
while ((ustatus->TRMT) ==0);
*txreg = *(char*)buffer++;
}
break;
case handle_can1: /* code to support can1 */
break;
case handle_can2: /* code to support can2 */
break;
case handle_spi1: /* code to support spi1 */
break;
case handle_spi2: /* code to support spi2 */
break;
}
return(len);
}
where you would fill in the appropriate code as specified in the comments.
Now you can use the generic C STDIO features to write to another port:
FILE *can1 = fopen("c","w");
fprintf(can1,"This will be output through the can\n");