uart.c

/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <joerg@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.        Joerg Wunsch
 * ----------------------------------------------------------------------------
 *
 * Stdio demo, UART implementation
 *
 * $Id$
 */

#include "defines.h"

#include <stdint.h>
#include <stdio.h>

#include <avr/io.h>

#include "uart.h"

/*
 * Initialize the UART to 9600 Bd, tx/rx, 8N1.
 */
void
uart_init(void)
{
#if F_CPU < 2000000UL && defined(U2X)
  UCSRA = _BV(U2X);             /* improve baud rate error by using 2x clk */
  UBRRL = (F_CPU / (8UL * UART_BAUD)) - 1;
#else
  UBRRL = (F_CPU / (16UL * UART_BAUD)) - 1;
#endif
  UCSRB = _BV(TXEN) | _BV(RXEN); /* tx/rx enable */
}

/*
 * Send character c down the UART Tx, wait until tx holding register
 * is empty.
 */
int
uart_putchar(char c, FILE *stream)
{

  if (c == '\a')
    {
      fputs("*ring*\n", stderr);
      return 0;
    }

  if (c == '\n')
    uart_putchar('\r', stream);
  loop_until_bit_is_set(UCSRA, UDRE);
  UDR = c;

  return 0;
}

/*
 * Receive a character from the UART Rx.
 *
 * This features a simple line-editor that allows to delete and
 * re-edit the characters entered, until either CR or NL is entered.
 * Printable characters entered will be echoed using uart_putchar().
 *
 * Editing characters:
 *
 * . \b (BS) or \177 (DEL) delete the previous character
 * . ^u kills the entire input buffer
 * . ^w deletes the previous word
 * . ^r sends a CR, and then reprints the buffer
 * . \t will be replaced by a single space
 *
 * All other control characters will be ignored.
 *
 * The internal line buffer is RX_BUFSIZE (80) characters long, which
 * includes the terminating \n (but no terminating \0).  If the buffer
 * is full (i. e., at RX_BUFSIZE-1 characters in order to keep space for
 * the trailing \n), any further input attempts will send a \a to
 * uart_putchar() (BEL character), although line editing is still
 * allowed.
 *
 * Input errors while talking to the UART will cause an immediate
 * return of -1 (error indication).  Notably, this will be caused by a
 * framing error (e. g. serial line "break" condition), by an input
 * overrun, and by a parity error (if parity was enabled and automatic
 * parity recognition is supported by hardware).
 *
 * Successive calls to uart_getchar() will be satisfied from the
 * internal buffer until that buffer is emptied again.
 */
int
uart_getchar(FILE *stream)
{
  uint8_t c;
  char *cp, *cp2;
  static char b[RX_BUFSIZE];
  static char *rxp;

  if (rxp == 0)
    for (cp = b;;)
      {
        loop_until_bit_is_set(UCSRA, RXC);
        if (UCSRA & _BV(FE))
          return _FDEV_EOF;
        if (UCSRA & _BV(DOR))
          return _FDEV_ERR;
        c = UDR;
        /* behaviour similar to Unix stty ICRNL */
        if (c == '\r')
          c = '\n';
        if (c == '\n')
          {
            *cp = c;
            uart_putchar(c, stream);
            rxp = b;
            break;
          }
        else if (c == '\t')
          c = ' ';

        if ((c >= (uint8_t)' ' && c <= (uint8_t)'\x7e') ||
            c >= (uint8_t)'\xa0')
          {
            if (cp == b + RX_BUFSIZE - 1)
              uart_putchar('\a', stream);
            else
              {
                *cp++ = c;
                uart_putchar(c, stream);
              }
            continue;
          }

        switch (c)
          {
          case 'c' & 0x1f:
            return -1;

          case '\b':
          case '\x7f':
            if (cp > b)
              {
                uart_putchar('\b', stream);
                uart_putchar(' ', stream);
                uart_putchar('\b', stream);
                cp--;
              }
            break;

          case 'r' & 0x1f:
            uart_putchar('\r', stream);
            for (cp2 = b; cp2 < cp; cp2++)
              uart_putchar(*cp2, stream);
            break;

          case 'u' & 0x1f:
            while (cp > b)
              {
                uart_putchar('\b', stream);
                uart_putchar(' ', stream);
                uart_putchar('\b', stream);
                cp--;
              }
            break;

          case 'w' & 0x1f:
            while (cp > b && cp[-1] != ' ')
              {
                uart_putchar('\b', stream);
                uart_putchar(' ', stream);
                uart_putchar('\b', stream);
                cp--;
              }
            break;
          }
      }

  c = *rxp++;
  if (c == '\n')
    rxp = 0;

  return c;
}