To use the standard output functions putchar, puts, and printf, you need to customize the way that characters are written to the standard output device. These output functions rely on a function __putchar that outputs a character and returns an indication of whether it was successfully written.

The prototype for __putchar is

int __putchar(int ch);

Sending all output to the CrossStudio virtual terminal

You can send all output to the CrossStudio virtual terminal by supplying the following implementation of the__putchar function in your code:

#include <debugio.h>

int __putchar(int ch)
{
  return debug_putchar(ch);
}

This hands off output of the character ch to the low-level debug output routine, debug_putchar.

Whilst this is an adequate implementation of __putchar, it does consume stack space for an unnecessary nested call and associated register saving. A better way of achieving the same result is to define the low-level symbol for __putchar to be equivalent to the low-level symbol for debug_putchar. To do this, we need to instruct the linker to make the symbols equivalent.

Note that there are three leading underscores in ___putchar and a single leading underscore in _debug_putchar because the C compiler automatically prepends an underscore to all global symbols.

Sending all output to another device

If you need to output to a physical device, such as a UART, the following notes will help you:

The standard functions that perform input and output are the printf and scanf functions.These functions convert between internal binary and external printable data. In some cases, though, you need to read and write formatted data on other channels, such as other RS232 ports. This section shows how you can extend the I/O library to best implement these function.

Classic custom printf-style output

Assume that we need to output formatted data to two UARTs, numbered 0 and 1, and we have a functions uart0_putc and uart1_putc that do just that and whose prototypes are:

int uart0_putc(int ch, __printf_t *ctx);
int uart1_putc(int ch, __printf_t *ctx);

These functions return a positive value if there is no error outputting the character and EOF if there was an error. The second parameter, ctx, is the context that the high-level formatting routines use to implement the C standard library functions.

Using a classic implementation, you would use sprintf to format the string for output and then output it:

void uart0_printf(const char *fmt, ...)
{
  char buf[80], *p;
  va_list ap;
  va_start(ap, fmt);
  vsnprintf(buf, sizeof(buf), fmt, ap);
  for (p = buf; *p; ++p)
    uart0_putc(*p, 0);  // null context
  va_end(ap);
}

We would, of course, need an identical routine for outputting to the other UART. This code is portable, but it requires an intermediate buffer of 80 characters. On small systems, this is quite an overhead, so we could reduce the buffer size to compensate. Of course, the trouble with that means that the maximum number of characters that can be output by a single call to uart0_printf is also reduced. What would be good is a way to output characters to one of the UARTs without requiring an intermediate buffer.

CrossWorks printf-style output

CrossWorks provides a solution for just this case by using some internal functions and data types in the CrossWorks library. These functions and types are define in the header file <__vfprintf.h>.

The first thing to introduce is the __printf_t type which captures the current state and parameters of the format conversion:

typedef struct __printf_tag
{
  size_t charcount;
  size_t maxchars;
  char *string;
  int (*output_fn)(int, struct __printf_tag *ctx);
} __printf_t;

This type is used by the library functions to direct what the formatting routines do with each character they need to output. If string is non-zero, the character is appended is appended to the string pointed to by string; if output_fn is non-zero, the character is output through the function output_fn with the context passed as the second parameter.

The member charcount counts the number of characters currently output, and maxchars defines the maximum number of characters output by the formatting routine __vfprintf.

We can use this type and function to rewrite uart0_printf:

int uart0_printf(const char *fmt, ...)
{
  int n;
  va_list ap;
  __printf_t iod;
  va_start(ap, fmt);
  iod.string = 0;
  iod.maxchars = INT_MAX;
  iod.output_fn = uart0_putc;
  n = __vfprintf(\&iod, fmt, ap);
  va_end(ap);
  return n;
}

This function has no intermediate buffer: when a character is ready to be output by the formatting routine, it calls the output_fn function in the descriptor iod to output it immediately. The maximum number of characters isn't limited as the maxchars member is set to INT_MAX. if you wanted to limit the number of characters output you can simply set the maxchars member to the appropriate value before calling __vfprintf.

We can adapt this function to take a UART number as a parameter:

int uart_printf(int uart, const char *fmt, ...)
{
  int n;
  va_list ap;
  __printf_t iod;
  va_start(ap, fmt);
  iod.is_string = 0;
  iod.maxchars = INT_MAX;
  iod.output_fn = uart ? uart1_putc : uart0_putc;
  n = __vfprintf(\&iod, fmt, ap);
  va_end(ap);
  return n;
}

Now we can use:

uart_printf(0, "This is uart %d\n...", 0);
uart_printf(1, "..and this is uart %d\n", 1);

__vfprintf returns the actual number of characters printed, which you may wish to dispense with and make the uart_printf routine return void.

Extending input functions

The formatted input functions would be implemented in the same manner as the output functions: read a string into an intermediate buffer and parse using sscanf. However, we can use the low-level routines in the CrossWorks library for formatted input without requiring the intermediate buffer.

The type __stream_scanf_t is:

typedef struct
{
  char is_string;
  int (*getc_fn)(void);
  int (*ungetc_fn)(int);
} __stream_scanf_t;

The function getc_fn reads a single character from the UART, and ungetc_fn pushes back a character to the UART. You can push at most one character back onto the stream.

Here's an implementation of functions to read and write from a single UART:

static int uart0_ungot = EOF;

int uart0_getc(void)
{
  if (uart0_ungot)
    {
      int c = uart0_ungot;
      uart0_ungot = EOF;
      return c;
    }
  else
    return read_char_from_uart(0);
}
int uart0_ungetc{int c)
{
  uart0_ungot = c;
}

You can use these two functions to perform formatted input using the UART:

int uart0_scanf(const char *fmt, ...)
{
  __stream_scanf_t iod;
  va_list a;
  int n;
  va_start(a, fmt);
  iod.is_string = 0;
  iod.getc_fn = uart0_getc;
  iod.ungetc_fn = uart0_ungetc;
  n = __vfscanf((__scanf_t *)\&iod, (const unsigned char *)fmt, a);
  va_end(a);
  return n;
}

Using this template, we can add functions to do additional formatted input from other UARTs or devices, just as we did for formatted output.