Software UART receiver
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