EmbeddedRelated.com

Software UART receiver

March 31, 2013 Coded in C

This module decodes asynchronous serial data in software out of a periodic interrupt.  This is useful when you have to receive data from a second serial source but the microcontroller has only one hardware serial port.  The code is implemented as a direct-vectored state machine, i.e. a function pointer points to a decision function for the current state and is invoked directly from the system's periodic interrupt.  For a 1200 baud data rate, an interrupt of approximately 200 microseconds is suitable, as shown in this example.

At each state, the function returns true if a character has been received, and false otherwise.  Any time the function returns true, the latest character is immediately available in soft_uart_rx_buf. 

/**
 * @file
 * Software serial (UART) receiver
 *
 * This module implements the receive engine for asynchronous serial
 * communications using polling ("bit banging").  Transmission capability
 * is not provided.
 *
 * The data format is <tt>8-N-1</tt>:
 * - Eight data bits
 * - No parity
 * - One stop bit
 *
 * <h2>Structural overview</h2>
 * The receiver is implemented as a polled finite state machine.  The state
 * of the I/O pin is passed as an argument to the state machine animation
 * function <code>soft_uart_rx()</code>.  The polling function must be called
 * on a stable timebase at a frequency at least three times
 * the bit rate.  The function returns a flag to indicate that a character has
 * been received and places the received character in a fixed buffer.
 *
 * <h2>Timing</h2>
 * The baud rate of the transmitter constrains the ossortment of possible
 * interrupt rates.  However, this receiver is designed to be configurable so
 * as to maximize those choices.
 *
 * Any frequency multiple of at least 3 is suitable.  Is this example, the
 * sample rate is four times the serial data bit rate:
 *
 * <pre>
 *  Given
 *  =====
 *  Baud rate specification: 1200 +/- 4%
 *  System interrupt rate:   5 kHz (200 us)
 *
 *  Selecting a sample rate
 *  =======================
 *  Chosen multiplier:       samples per bit
 *  Sample rate:             5 kHz / 4 == 1250 baud (4.16% high)
 * </pre>
 *
 * Since the baud rate is high in this example, We will have a tendency to
 * sample earlier and earlier on each successive bit.  Therefore it is desirable
 * to sample slightly later in the bit time if possible.
 * <pre>
 * \#define SOFT_SOFT_UART_RX_BIT_TIME  5
 * \#define SOFT_UART_RX_START_SAMPLES  2
 * </pre>
 * The diagram below shows the resultant timing.  The actual bit times are 4%
 * slower, owing to the fact that the system interrupy is not an exact multiple
 * of the bit time.
 *
 * The sample timing error at the stop bit is (4% X 9) = 36% too early.
 * <pre>
 *  _______                 _______________                     _______________
 *         \\_______________/               \\...________________/
 * +-------+---+---+---+---+---+---+---+---+...+---+---+---+---+---+---+---+---+
 * | Cycle | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |   | 8 | 9 | A | B | C | D | E | F |
 * +-------+---+---+---+---+---+---+---+---+...+---+---+---+---+---+---+---+---+
 * | Data  | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |   | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
 * |       |   Start bit   |  Data bit 0   |   |  Data bit N   |   Stop bit    |
 * | Samp. | X | X |   |   |   |   | X |   |   |   |   | X |   |   |   | X |   |
 * +-------+---+---+---+---+---+---+---+---+...+---+---+---+---+---+---+---+---+
 *           ^   ^                                       |<------------->|
 *           |   |                                               |
 *           |   |                  SOFT_UART_RX_BIT_TIME -------+
 *           |   |
 *           +---+---- SOFT_UART_RX_START_SAMPLES
 * </pre>
 * Here is an explanation of how a character is received:
 * -# We sample the line continuously until the START (logic zero) bit is seen.
 * -# Just to make sure it wasn't noise, we sample the line a second (or third
 *    or fourth, depending on the setting) time with the expectation that the
 *    state hasn't changed.
 * -# We continue to sample the start bit until we have reached the center of
 *    the bit time.  The line must stay in the low state.  This shifts us to
 *    safety away from edges.
 * -# We delay (frequency multiplier) cycles, ignoring the state of the line.
 *    This puts us in the middle of the first data bit.
 * -# We sample and save the data bit, then wait (frequency multiplier - 1)
 *    cycles.
 * -# We repeat until we have sampled all data (payload) bits.  The last bit
 *    is sampled and must be a logic one.
 *
 * <h2>Limitations</h2>
 * For speed, the receive buffer is implemented as a global variable that is
 * to be accessed directly by the calling code.  Also, the state variable
 * is private to this module.  Therefore, only one instance of the soft
 * UART receiver is supported in a given project. 
 *
 * @author Justin Dobbs
 */

#include <stdbool.h>

/** The number of times to sample the start bit.

    This defines the phase shift of subsequent samples.  If the interrupt rate is
    a bit high relative to the baud rate, we want to sample late to
    minimize cumulative timing error. */
#define SOFT_UART_RX_START_SAMPLES  3

/** The inter-bit delay time, a.k.a. the frequency multiplier */
#define SOFT_UART_RX_BIT_TIME       4

/* State definitions */
static bool st_idle (bool);
static bool st_start_bit (bool);
static bool st_delay_rx0 (bool);
static bool st_delay_rx1 (bool);
static bool st_delay_rx2 (bool);
static bool st_delay_rx3 (bool);
static bool st_delay_rx4 (bool);
static bool st_delay_rx5 (bool);
static bool st_delay_rx6 (bool);
static bool st_delay_rx7 (bool);
static bool st_delay_stop (bool);
static bool st_abort_wait_for_idle (bool);

/**
 * Soft UART receiver polling function.
 *
 * This function implements the receiver.  It should be called on a stable
 * timebase at a fixed multiple of the bit rate.
 *
 * @note This is implemented as a pointer to a function to handle the current
 *       state.  The caller need only invoke the function using the pointer.
 *
 * @param[in]   x      the state of the input line:
 *                     - <code>true</code>: the line is high
 *                     - <code>false</code>: the line is low
 *
 * @retval      true   if a character is ready in <code>soft_uart_rx_buf</code>
 * @retval      false  otherwise
 */
bool (*soft_uart_rx)(bool) = st_idle;

/** Serial recieve buffer.  This should be immediately read after
    <code>soft_uart_rx()</code> returns <code>true</code>. */
unsigned char soft_uart_rx_buf;

/** Cycle counter, for timing. */
static unsigned char i;

/**
 * Sampling continuously, waiting for the start bit.
 */
static bool st_idle (bool x)
{
  if (!x)
  {
    i = SOFT_UART_RX_START_SAMPLES - 1;
    soft_uart_rx = st_start_bit;
  }
  return false;
}

/* -------------------------------------------------------------------------- */

/**
 * Sampling the start bit a few more times to make sure it's solid.  This also
 * provides time offset for sampling future bits in the middle of the bit time.
 */
static bool st_start_bit (bool x)
{
  /* Reject if the start bit does not last long enough */
  if (x)
  {
    soft_uart_rx = st_idle;
  }
  else if (--i == 0)
  {
    i = SOFT_UART_RX_BIT_TIME;
    soft_uart_rx_buf = 0;
    soft_uart_rx = st_delay_rx0;
  }
  return false;
}

/* -------------------------------------------------------------------------- */

/**
 * Waiting one bit time, then sampling the LSb (bit 0).
 */
static bool st_delay_rx0 (bool x)
{
  /* When it's time, shift in the data to the RX buffer.  If we have
   received all the data, go wait for the STOP bit. */
  if (--i == 0)
  {
    if (x)
    {
      soft_uart_rx_buf |= 0x01;
    }
    i = SOFT_UART_RX_BIT_TIME;
    soft_uart_rx = st_delay_rx1;
  }
  return false;
}

/* -------------------------------------------------------------------------- */

/**
 * Waiting one bit time, then sampling bit 1.
 */
static bool st_delay_rx1 (bool x)
{
  if (--i == 0)
  {
    if (x)
    {
      soft_uart_rx_buf |= 0x02;
    }
    i = SOFT_UART_RX_BIT_TIME;
    soft_uart_rx = st_delay_rx2;
  }
  return false;
}

/* -------------------------------------------------------------------------- */

/**
 * Waiting one bit time, then sampling bit 2.
 */
static bool st_delay_rx2 (bool x)
{
  if (--i == 0)
  {
    if (x)
    {
      soft_uart_rx_buf |= 0x04;
    }
    i = SOFT_UART_RX_BIT_TIME;
    soft_uart_rx = st_delay_rx3;
  }
  return false;
}

/* -------------------------------------------------------------------------- */

/**
 * Waiting one bit time, then sampling bit 3.
 */
static bool st_delay_rx3 (bool x)
{
  if (--i == 0)
  {
    if (x)
    {
      soft_uart_rx_buf |= 0x08;
    }
    i = SOFT_UART_RX_BIT_TIME;
    soft_uart_rx = st_delay_rx4;
  }
  return false;
}

/* -------------------------------------------------------------------------- */

/**
 * Waiting one bit time, then sampling bit 4.
 */
static bool st_delay_rx4 (bool x)
{
  if (--i == 0)
  {
    if (x)
    {
      soft_uart_rx_buf |= 0x10;
    }
    i = SOFT_UART_RX_BIT_TIME;
    soft_uart_rx = st_delay_rx5;
  }
  return false;
}

/* -------------------------------------------------------------------------- */

/**
 * Waiting one bit time, then sampling bit 5.
 */
static bool st_delay_rx5 (bool x)
{
  if (--i == 0)
  {
    if (x)
    {
      soft_uart_rx_buf |= 0x20;
    }
    i = SOFT_UART_RX_BIT_TIME;
    soft_uart_rx = st_delay_rx6;
  }
  return false;
}

/* -------------------------------------------------------------------------- */

/**
 * Waiting one bit time, then sampling bit 6.
 */
static bool st_delay_rx6 (bool x)
{
  if (--i == 0)
  {
    if (x)
    {
      soft_uart_rx_buf |= 0x40;
    }
    i = SOFT_UART_RX_BIT_TIME;
    soft_uart_rx = st_delay_rx7;
  }
  return false;
}

/* -------------------------------------------------------------------------- */

/**
 * Waiting one bit time, then sampling bit 7.
 */
static bool st_delay_rx7 (bool x)
{
  if (--i == 0)
  {
    if (x)
    {
      soft_uart_rx_buf |= 0x80;
    }
    i = SOFT_UART_RX_BIT_TIME;
    soft_uart_rx = st_delay_stop;
  }
  return false;
}

/* -------------------------------------------------------------------------- */

/**
 * Waiting one bit time, then sampling the stop bit.
 * @note The reception is aborted if the stop bit does not arrive on schedule.
 */
static bool st_delay_stop (bool x)
{
  if (--i == 0)
  {
    /* STOP bit is always logic ONE by definition */
    if (x)
    {
      soft_uart_rx = st_idle;
      return true;  /* Got a character */
    }
    else
    {
      /* Stop bit didn't happen when we expected it.  Go sit and wait 
         indefinitely for the line to go high. */
      soft_uart_rx = st_abort_wait_for_idle;
      return false;
    }
  }
  /* Haven't sampled the stop bit yet! */
  return false;
}

/* -------------------------------------------------------------------------- */

/**
 * Reception aborted; waiting as long as required for the line to idle high
 * again.
 */
static bool st_abort_wait_for_idle (bool x)
{
  /* NOW the line is finally high/idle again.  Start the receive process over.
     We did not get a character. */
  if (x)
  {
    soft_uart_rx = st_idle;
  }
  return false;
}

/* Header file */
#if !defined(_SOFT_UART_RX_H)
#define _SOFT_UART_RX_H

/**
 * @file
 * Soft UART receiver header file
 *  
 * This file implements the interface to the software UART reciever module.
 * The full documentation is located in @ref soft_uart_rx.c.
 *
 * @author Justin Dobbs
 */

#include <stdbool.h>

/* Actually a function pointer, but this is supposed to be opaque.  This is
   called from a periodic interrupt. 

  @param[in]  x  the state of the serial line (true == high) */
extern bool (*soft_uart_rx) (bool x);

/* The receive buffer */
extern unsigned char soft_uart_rx_buf;

#endif