Code Snippets Submitted by jrd
A simple software timer system
/**
* @file
* Software timer facility.
*
* This module implements an unlimited number of 8-bit down-counting 10ms and
* 100ms timers. Timers are actually held in various places by the application
* code and are registered with this module for service from the system's
* timekeeping interrupt.
*
* A down-counting timer starts out set to a time interval and is
* automatically decremented via the system's periodic interrupt. Check for a
* zero value to know when the timer has expired:
*
* <pre>uint8_t my_timer = 10;
* timer_register_100ms(&my_timer);
*
* for (;;)
* {
* if (my_timer == 0)
* {
* do_something();
* my_timer = 10;
* }
* }</pre>
*
* Down-counting timers are restricted to 8 bits so that they can be
* atomically manipulated outside interrupt code on 8-bit architectures
* without resorting to disable interrupts.
*
* @warning All variables used as timers must be declared
* <code>volatile</code>, because they are modified from an interrupt
* context that may not be understood by the compiler. GCC in
* particular is known to optimize away timer variables that aren't
* declared <code>volatile</code>.
*
* <h2>Configuration</h2>
* The number of available 10ms and 100ms timer slots is set using
* {@link MAX_100MS_TIMERS} and {@link MAX_10MS_TIMERS}.
*/
#include <stdlib.h> /* for NULL */
#include <stdint.h> /* uint8_t, etc. */
#include <stdbool.h> /* bool type, true, false */
#include "timer.h"
/** Maximum number of 100ms timers that can be registered. */
#define MAX_100MS_TIMERS 10
/** Maximum number of 10ms timers that can be registered. */
#define MAX_10MS_TIMERS 10
/** The polling frequency for the 10ms timers is scaled by this factor to
service the 100ms timers. */
#define PRESCALE_100MS 10
/* ------------------------------------------------------------------------ */
/** 10ms timer array. These are pointers to the actual timers elsewhere in
the application code. */
static volatile uint8_t *timers_10ms [MAX_10MS_TIMERS];
/** 100ms timer array. These are pointers to the actual timers elsewhere in
the application code. */
static volatile uint8_t *timers_100ms [MAX_100MS_TIMERS];
bool timer_register_10ms (volatile uint8_t *t)
{
uint8_t k;
for (k = 0; k < MAX_10MS_TIMERS; ++k)
{
if (NULL == timers_10ms[k])
{
/* Success--found an unused slot */
timers_10ms[k] = t;
return false;
}
}
/* Failure */
return true;
}
bool timer_register_100ms (volatile uint8_t *t)
{
uint8_t k;
for (k = 0; k < MAX_100MS_TIMERS; ++k)
{
if (NULL == timers_100ms[k])
{
/* Success--found an unused slot */
timers_100ms[k] = t;
return false;
}
}
/* Failure */
return true;
}
void timer_poll (void)
{
static uint8_t prescaler = PRESCALE_100MS;
volatile uint8_t *t;
uint8_t k;
/* Service the 10ms timers */
for (k = 0; k < MAX_10MS_TIMERS; ++k)
{
t = timers_10ms[k];
/* First NULL entry marks the end of the registered timers */
if (t == NULL)
{
break;
}
if (*t > 0)
{
-- *t;
}
}
/* Now divide the frequency by 10 and service the 100ms timers every 10th
time through. */
if (--prescaler == 0)
{
prescaler = PRESCALE_100MS;
for (k = 0; k < MAX_100MS_TIMERS; ++k)
{
t = timers_100ms[k];
if (t == NULL)
{
break;
}
if (*t > 0)
{
-- *t;
}
}
}
}
/* Header file */
#if !defined(TIMER_H)
#define TIMER_H
/**
* @file
*/
#include <stdbool.h>
#include <stdlib.h>
/**
* Registers a 10-millisecond timer for service.
*
* @param[in] t pointer to the variable used for timing
*
* @retval true if registration failed
* @retval false if registration succeeded (normal return)
*/
bool timer_register_10ms (volatile uint8_t *t);
/**
* Registers a 100-millisecond timer for service.
*
* @param[in] t pointer to the variable used for timing
*
* @retval true if registration failed
* @retval false if registration succeeded (normal return)
*/
bool timer_register_100ms (volatile uint8_t *t);
/**
* Maintains all registered timers.
*
* This function should be called from a stable 10-millisecond time base,
* preferably from an interrupt.
*/
void timer_poll (void);
#endif /* TIMER_H */
Integer PI control with integrator anti-windup
/**
* @file
* Proportional-integral (PI) control law.
*
* This module implements a simple position-type PI controller:
* <pre>
* u = [ kp * e + ki * sum(e) ] >> shift
* </pre>
* <tt>shift</tt> is a right bit shift used to scale the output of the
* controller down from the 32-bit intermediate result.
*
* An anti-windup provision is implemented on the PI integrator to prevent
* deep saturation (aka integrator windup):
* - The new control output with the latest integrator value is computed.
* - If the control output exceeds either output limit, <i>and</i> the latest
* change in the integrator is in the same direction, then the new integrator
* value is not saved for the next call.
* - Otherwise, the integrator is saved for the next call.
*/
#include <stdbool.h>
#include "pi_control.h"
/**
* Proportional-integral (PI) control law.
*
* @param[in,out] p control parameter and state structure
* @param[in] e error signal
*
* @return control output <code>u</code>
*/
int pi_control (struct PIControl *p, int e)
{
bool int_ok; /* Whether or not the integrator should update */
long new_i; /* Proposed new integrator value */
long u; /* Control output */
/* Compute new integrator and the final control output. */
new_i = p->i + e;
u = (p->kp * (long)e + p->ki * new_i) >> p->shift;
/* Check for saturation. In the event of saturation in any one direction,
inhibit saving the integrator if doing so would deepen the saturation. */
int_ok = true;
/* Positive saturation? */
if (u > p->max)
{
/* Clamp the output */
u = p->max;
/* Error is the same sign? Inhibit integration. */
if (e > 0)
{
int_ok = false;
}
}
/* Repeat for negative sign */
else if (u < p->min)
{
u = p->min;
if (e < 0)
{
int_ok = false;
}
}
/* Update the integrator if allowed. */
if (int_ok)
{
p->i = new_i;
}
return (int)u;
}
/**
* Initializes the PI control.
*
* This function resets the PI integrator to zero.
*
* @param[in,out] p control parameter structure
*/
void pi_control_init (struct PIControl *p)
{
p->i = 0L;
}
/* Header file */
#if !defined(_PI_CONTROL_H)
#define _PI_CONTROL_H
/**
* @file
* Proportional-integral (PI) control law header file.
*/
/** PI control data structure. This structure contains configuration (the
proportional and integral gain, plus a final divisor), output limits, and
an integration accumulator (the PI controller's state variable). */
struct PIControl
{
int kp; /**< Proportional gain constant */
int ki; /**< Integral gain constant */
unsigned char shift; /**< Right shift to divide */
int max; /**< Maximum value */
int min; /**< Minimum value */
long i; /**< Current integrator value */
};
/* Prototypes */
int pi_control (struct PIControl *p, int e);
void pi_control_init (struct PIControl *p);
#endif /* _PI_CONTROL_H */
1D and 2D table lookup
/**
* @file
* Table lookup with interpolation (1-D and 2-D).
*
* This is a 1/2-D table lookup facility. Each routine looks up data in a table
* structure, interpolating as needed between data points. The 2-D version
* looks up along 2 axes and interpolates in two dimensions.
*
* <h2>Limitations</h2>
* - The table axes (input values) must monotonically increase, or the lookup
* will fail.
* - The index data type is nominally 8 bits, limiting the table length to
* 256 elements. Change <code>index_t</code> if larger tables are needed.
*/
#include <stdint.h>
#include <stdbool.h>
#include "lookup.h"
/** Index data type */
typedef uint8_t index_t;
/**
* 1-D table lookup.
*
* This function performs a 1-D table lookup with interpolation. The output
* value is clamped to either of the table end values when the input value is
* out of bounds.
*
* @param[in] t table data structure
* @param[in] ix input (X-axis) value
* @param[out] o output data
*
* @retval true if the lookup result is suspect due to clipping
* @retval false on successful lookup
*/
bool lookup1d (Table1d *t, int ix, int *o)
{
index_t i;
/* ------------------------------------------------------------------------ */
/* Off the end of the table */
if (ix > t->columns[t->ncols - 1])
{
*o = t->table[t->ncols - 1];
return true;
}
/* Off beginning of the table */
else if (ix < t->columns[0])
{
*o = t->table[0];
return true;
}
/* Within the bounds of the table */
for (i = 0; i < t->ncols - 1; ++i)
{
if ( ix >= t->columns[i]
&& ix <= t->columns[i + 1])
{
/* Output (table) low value */
int o_low = t->table[i];
/* Input (X-axis) low value */
int i_low = t->columns[i];
/* Spead between the two adjacent input values */
int i_delta = t->columns[i + 1] - t->columns[i];
/* Spread between the two adjacent table output values */
int o_delta = t->table[i + 1] - t->table[i];
/* Prevent division by zero. We could get here if two consecutive
input values in the table are the same. */
if (o_delta == 0)
{
*o = o_low;
return true;
}
*o = o_low + ((ix - i_low) * (long)o_delta) / i_delta;
return false;
}
}
/* Didn't find it (we shouldn't ever get here). */
return true;
}
/**
* 2-D table lookup.
*
* This function performs a 2-D table lookup with interpolation. The output
* value is clamped to either of the table end values when the input value is
* out of bounds.
*
* @param[in] t table data structure
* @param[in] ix input (X-axis) value
* @param[in] iy input (Y-axis) value
* @param[out] o output value
*
* @retval true if the lookup result is suspect due to clipping
* @retval false on successful lookup
*/
bool lookup2d (Table2d *t, int ix, int iy, int *o)
{
/* The lower X and Y coordinates of the interpolation box */
index_t i, j;
/* Set whenever one of the lookups goes off the end of the table */
bool is_fault = false;
/* ------------------------------------------------------------------------ */
/* X axis coordinate lookup */
/* Off the end of the table */
if (ix > t->columns[t->ncols - 1])
{
/* Pretend the input value is right at the table edge so that interpolation
works as expected */
ix = t->columns[t->ncols - 1];
i = t->ncols - 1;
is_fault = true;
}
/* Off beginning of the table */
else if (ix < t->columns[0])
{
ix = t->columns[0];
i = 0;
is_fault = true;
}
/* Within the bounds of the table */
else
{
for (i = 0; i < t->ncols - 1; ++i)
{
if ( ix >= t->columns[i]
&& ix <= t->columns[i + 1])
{
break;
}
}
}
/* ------------------------------------------------------------------------ */
/* Y axis coordinate lookup */
/* Off the bottom of the table */
if (iy > t->rows[t->nrows - 1])
{
iy = t->rows[t->nrows - 1];
j = t->nrows - 1;
is_fault = true;
}
/* Off the top of the table */
else if (iy < t->rows[0])
{
iy = t->rows[0];
j = 0;
is_fault = true;
}
/* Within the bounds of the table */
else
{
for (j = 0; j < t->nrows - 1; ++j)
{
if ( iy >= t->rows[j]
&& iy <= t->rows[j + 1])
{
break;
}
}
}
/* ------------------------------------------------------------------------ */
/* 2-D interpolation */
/* At this point we know that the input X value is between
column[i] and column[i+1] and that the input Y value is between
row[j] and row[j+1]. Therefore we have a rectangle in which we need
to interpolate.
To do the interpolation, we first interpolate between column i and
column i+1 on the upper row j. Then, we interpolate between the same
columns on row j+1. Finally, we interpolate vertically between the two
rows based on the input Y value.
row0 is the upper row data and row1 is the lower (higher subscript) row
data. */
{
const int *row0 = &t->table[j * t->ncols];
const int *row1 = &row0[t->ncols];
/* Difference between the two adjacent column values */
int i_delta = t->columns[i + 1] - t->columns[i];
/* Difference between the two adjacent row values */
int j_delta = t->rows[j + 1] - t->rows[j];
/* Low column value */
int i_low = t->columns[i];
/* Low row value */
int j_low = t->rows[j];
/* Interpolation results for the upper and lower rows */
int o0, o1;
/* Prevent division by zero if the input values aren't increasing.
If no division by zero, interpolate between columns in the upper and
lower row. */
if (i_delta == 0)
{
o0 = row0[i];
o1 = row1[i];
is_fault = true;
}
else
{
/* Interpolate the upper row */
{
int o_low = row0[i]; /* Row value at low column # */
int o_delta = row0[i + 1] - row0[i]; /* Difference from next column */
o0 = o_low + ((ix - i_low) * (long)o_delta) / i_delta;
}
/* Interpolate the lower (higher subscript) row */
{
int o_low = row1[i]; /* Row value at low column # */
int o_delta = row1[i + 1] - row1[i]; /* Difference from next column */
o1 = o_low + ((ix - i_low) * (long)o_delta) / i_delta;
}
}
/* Guard against division by zero in the row axis. If all is well,
interpolate between the two row interpolation results from earlier. */
if (j_delta == 0)
{
*o = o0;
is_fault = true;
}
else
{
*o = o0 + ((iy - j_low) * (long)(o1 - o0)) / j_delta;
}
}
return is_fault;
}
/* Header file */
#if !defined(_LOOKUP_H)
#define _LOOKUP_H
/**
* @file
* Table lookup with interpolation (1-D and 2-D) header file.
*/
#include <stdbool.h>
/** One dimensional lookup table. */
typedef const struct
{
/** Number of elements in the table. This must be at least 2. */
unsigned char ncols;
/** List of input values. */
int *columns;
/** Table data (output values). The output values list must have the same
length as the input list. */
int *table;
} Table1d;
/** Two dimensional lookup table. */
typedef const struct
{
/** Number of columns (X values) in the table. Must be at least 2. */
unsigned char ncols;
/** Number of rows (Y values) in the table. Must be at least 2. */
unsigned char nrows;
/** X-axis input values list. */
int *columns;
/** Y-axis input values list. */
int *rows;
/** Table data. This is an array of <code>columns</code>X<code>rows</code>,
arranged in rows. For example, <code>table[1]</code> is the second
column in the first row. */
int *table;
} Table2d;
/* Prototypes */
bool lookup1d (Table1d *t, int ix, int *o);
bool lookup2d (Table2d *t, int ix, int iy, int *o);
#endif
Software UART receiver
/**
* @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
A simple software timer system
/**
* @file
* Software timer facility.
*
* This module implements an unlimited number of 8-bit down-counting 10ms and
* 100ms timers. Timers are actually held in various places by the application
* code and are registered with this module for service from the system's
* timekeeping interrupt.
*
* A down-counting timer starts out set to a time interval and is
* automatically decremented via the system's periodic interrupt. Check for a
* zero value to know when the timer has expired:
*
* <pre>uint8_t my_timer = 10;
* timer_register_100ms(&my_timer);
*
* for (;;)
* {
* if (my_timer == 0)
* {
* do_something();
* my_timer = 10;
* }
* }</pre>
*
* Down-counting timers are restricted to 8 bits so that they can be
* atomically manipulated outside interrupt code on 8-bit architectures
* without resorting to disable interrupts.
*
* @warning All variables used as timers must be declared
* <code>volatile</code>, because they are modified from an interrupt
* context that may not be understood by the compiler. GCC in
* particular is known to optimize away timer variables that aren't
* declared <code>volatile</code>.
*
* <h2>Configuration</h2>
* The number of available 10ms and 100ms timer slots is set using
* {@link MAX_100MS_TIMERS} and {@link MAX_10MS_TIMERS}.
*/
#include <stdlib.h> /* for NULL */
#include <stdint.h> /* uint8_t, etc. */
#include <stdbool.h> /* bool type, true, false */
#include "timer.h"
/** Maximum number of 100ms timers that can be registered. */
#define MAX_100MS_TIMERS 10
/** Maximum number of 10ms timers that can be registered. */
#define MAX_10MS_TIMERS 10
/** The polling frequency for the 10ms timers is scaled by this factor to
service the 100ms timers. */
#define PRESCALE_100MS 10
/* ------------------------------------------------------------------------ */
/** 10ms timer array. These are pointers to the actual timers elsewhere in
the application code. */
static volatile uint8_t *timers_10ms [MAX_10MS_TIMERS];
/** 100ms timer array. These are pointers to the actual timers elsewhere in
the application code. */
static volatile uint8_t *timers_100ms [MAX_100MS_TIMERS];
bool timer_register_10ms (volatile uint8_t *t)
{
uint8_t k;
for (k = 0; k < MAX_10MS_TIMERS; ++k)
{
if (NULL == timers_10ms[k])
{
/* Success--found an unused slot */
timers_10ms[k] = t;
return false;
}
}
/* Failure */
return true;
}
bool timer_register_100ms (volatile uint8_t *t)
{
uint8_t k;
for (k = 0; k < MAX_100MS_TIMERS; ++k)
{
if (NULL == timers_100ms[k])
{
/* Success--found an unused slot */
timers_100ms[k] = t;
return false;
}
}
/* Failure */
return true;
}
void timer_poll (void)
{
static uint8_t prescaler = PRESCALE_100MS;
volatile uint8_t *t;
uint8_t k;
/* Service the 10ms timers */
for (k = 0; k < MAX_10MS_TIMERS; ++k)
{
t = timers_10ms[k];
/* First NULL entry marks the end of the registered timers */
if (t == NULL)
{
break;
}
if (*t > 0)
{
-- *t;
}
}
/* Now divide the frequency by 10 and service the 100ms timers every 10th
time through. */
if (--prescaler == 0)
{
prescaler = PRESCALE_100MS;
for (k = 0; k < MAX_100MS_TIMERS; ++k)
{
t = timers_100ms[k];
if (t == NULL)
{
break;
}
if (*t > 0)
{
-- *t;
}
}
}
}
/* Header file */
#if !defined(TIMER_H)
#define TIMER_H
/**
* @file
*/
#include <stdbool.h>
#include <stdlib.h>
/**
* Registers a 10-millisecond timer for service.
*
* @param[in] t pointer to the variable used for timing
*
* @retval true if registration failed
* @retval false if registration succeeded (normal return)
*/
bool timer_register_10ms (volatile uint8_t *t);
/**
* Registers a 100-millisecond timer for service.
*
* @param[in] t pointer to the variable used for timing
*
* @retval true if registration failed
* @retval false if registration succeeded (normal return)
*/
bool timer_register_100ms (volatile uint8_t *t);
/**
* Maintains all registered timers.
*
* This function should be called from a stable 10-millisecond time base,
* preferably from an interrupt.
*/
void timer_poll (void);
#endif /* TIMER_H */
Integer PI control with integrator anti-windup
/**
* @file
* Proportional-integral (PI) control law.
*
* This module implements a simple position-type PI controller:
* <pre>
* u = [ kp * e + ki * sum(e) ] >> shift
* </pre>
* <tt>shift</tt> is a right bit shift used to scale the output of the
* controller down from the 32-bit intermediate result.
*
* An anti-windup provision is implemented on the PI integrator to prevent
* deep saturation (aka integrator windup):
* - The new control output with the latest integrator value is computed.
* - If the control output exceeds either output limit, <i>and</i> the latest
* change in the integrator is in the same direction, then the new integrator
* value is not saved for the next call.
* - Otherwise, the integrator is saved for the next call.
*/
#include <stdbool.h>
#include "pi_control.h"
/**
* Proportional-integral (PI) control law.
*
* @param[in,out] p control parameter and state structure
* @param[in] e error signal
*
* @return control output <code>u</code>
*/
int pi_control (struct PIControl *p, int e)
{
bool int_ok; /* Whether or not the integrator should update */
long new_i; /* Proposed new integrator value */
long u; /* Control output */
/* Compute new integrator and the final control output. */
new_i = p->i + e;
u = (p->kp * (long)e + p->ki * new_i) >> p->shift;
/* Check for saturation. In the event of saturation in any one direction,
inhibit saving the integrator if doing so would deepen the saturation. */
int_ok = true;
/* Positive saturation? */
if (u > p->max)
{
/* Clamp the output */
u = p->max;
/* Error is the same sign? Inhibit integration. */
if (e > 0)
{
int_ok = false;
}
}
/* Repeat for negative sign */
else if (u < p->min)
{
u = p->min;
if (e < 0)
{
int_ok = false;
}
}
/* Update the integrator if allowed. */
if (int_ok)
{
p->i = new_i;
}
return (int)u;
}
/**
* Initializes the PI control.
*
* This function resets the PI integrator to zero.
*
* @param[in,out] p control parameter structure
*/
void pi_control_init (struct PIControl *p)
{
p->i = 0L;
}
/* Header file */
#if !defined(_PI_CONTROL_H)
#define _PI_CONTROL_H
/**
* @file
* Proportional-integral (PI) control law header file.
*/
/** PI control data structure. This structure contains configuration (the
proportional and integral gain, plus a final divisor), output limits, and
an integration accumulator (the PI controller's state variable). */
struct PIControl
{
int kp; /**< Proportional gain constant */
int ki; /**< Integral gain constant */
unsigned char shift; /**< Right shift to divide */
int max; /**< Maximum value */
int min; /**< Minimum value */
long i; /**< Current integrator value */
};
/* Prototypes */
int pi_control (struct PIControl *p, int e);
void pi_control_init (struct PIControl *p);
#endif /* _PI_CONTROL_H */
1D and 2D table lookup
/**
* @file
* Table lookup with interpolation (1-D and 2-D).
*
* This is a 1/2-D table lookup facility. Each routine looks up data in a table
* structure, interpolating as needed between data points. The 2-D version
* looks up along 2 axes and interpolates in two dimensions.
*
* <h2>Limitations</h2>
* - The table axes (input values) must monotonically increase, or the lookup
* will fail.
* - The index data type is nominally 8 bits, limiting the table length to
* 256 elements. Change <code>index_t</code> if larger tables are needed.
*/
#include <stdint.h>
#include <stdbool.h>
#include "lookup.h"
/** Index data type */
typedef uint8_t index_t;
/**
* 1-D table lookup.
*
* This function performs a 1-D table lookup with interpolation. The output
* value is clamped to either of the table end values when the input value is
* out of bounds.
*
* @param[in] t table data structure
* @param[in] ix input (X-axis) value
* @param[out] o output data
*
* @retval true if the lookup result is suspect due to clipping
* @retval false on successful lookup
*/
bool lookup1d (Table1d *t, int ix, int *o)
{
index_t i;
/* ------------------------------------------------------------------------ */
/* Off the end of the table */
if (ix > t->columns[t->ncols - 1])
{
*o = t->table[t->ncols - 1];
return true;
}
/* Off beginning of the table */
else if (ix < t->columns[0])
{
*o = t->table[0];
return true;
}
/* Within the bounds of the table */
for (i = 0; i < t->ncols - 1; ++i)
{
if ( ix >= t->columns[i]
&& ix <= t->columns[i + 1])
{
/* Output (table) low value */
int o_low = t->table[i];
/* Input (X-axis) low value */
int i_low = t->columns[i];
/* Spead between the two adjacent input values */
int i_delta = t->columns[i + 1] - t->columns[i];
/* Spread between the two adjacent table output values */
int o_delta = t->table[i + 1] - t->table[i];
/* Prevent division by zero. We could get here if two consecutive
input values in the table are the same. */
if (o_delta == 0)
{
*o = o_low;
return true;
}
*o = o_low + ((ix - i_low) * (long)o_delta) / i_delta;
return false;
}
}
/* Didn't find it (we shouldn't ever get here). */
return true;
}
/**
* 2-D table lookup.
*
* This function performs a 2-D table lookup with interpolation. The output
* value is clamped to either of the table end values when the input value is
* out of bounds.
*
* @param[in] t table data structure
* @param[in] ix input (X-axis) value
* @param[in] iy input (Y-axis) value
* @param[out] o output value
*
* @retval true if the lookup result is suspect due to clipping
* @retval false on successful lookup
*/
bool lookup2d (Table2d *t, int ix, int iy, int *o)
{
/* The lower X and Y coordinates of the interpolation box */
index_t i, j;
/* Set whenever one of the lookups goes off the end of the table */
bool is_fault = false;
/* ------------------------------------------------------------------------ */
/* X axis coordinate lookup */
/* Off the end of the table */
if (ix > t->columns[t->ncols - 1])
{
/* Pretend the input value is right at the table edge so that interpolation
works as expected */
ix = t->columns[t->ncols - 1];
i = t->ncols - 1;
is_fault = true;
}
/* Off beginning of the table */
else if (ix < t->columns[0])
{
ix = t->columns[0];
i = 0;
is_fault = true;
}
/* Within the bounds of the table */
else
{
for (i = 0; i < t->ncols - 1; ++i)
{
if ( ix >= t->columns[i]
&& ix <= t->columns[i + 1])
{
break;
}
}
}
/* ------------------------------------------------------------------------ */
/* Y axis coordinate lookup */
/* Off the bottom of the table */
if (iy > t->rows[t->nrows - 1])
{
iy = t->rows[t->nrows - 1];
j = t->nrows - 1;
is_fault = true;
}
/* Off the top of the table */
else if (iy < t->rows[0])
{
iy = t->rows[0];
j = 0;
is_fault = true;
}
/* Within the bounds of the table */
else
{
for (j = 0; j < t->nrows - 1; ++j)
{
if ( iy >= t->rows[j]
&& iy <= t->rows[j + 1])
{
break;
}
}
}
/* ------------------------------------------------------------------------ */
/* 2-D interpolation */
/* At this point we know that the input X value is between
column[i] and column[i+1] and that the input Y value is between
row[j] and row[j+1]. Therefore we have a rectangle in which we need
to interpolate.
To do the interpolation, we first interpolate between column i and
column i+1 on the upper row j. Then, we interpolate between the same
columns on row j+1. Finally, we interpolate vertically between the two
rows based on the input Y value.
row0 is the upper row data and row1 is the lower (higher subscript) row
data. */
{
const int *row0 = &t->table[j * t->ncols];
const int *row1 = &row0[t->ncols];
/* Difference between the two adjacent column values */
int i_delta = t->columns[i + 1] - t->columns[i];
/* Difference between the two adjacent row values */
int j_delta = t->rows[j + 1] - t->rows[j];
/* Low column value */
int i_low = t->columns[i];
/* Low row value */
int j_low = t->rows[j];
/* Interpolation results for the upper and lower rows */
int o0, o1;
/* Prevent division by zero if the input values aren't increasing.
If no division by zero, interpolate between columns in the upper and
lower row. */
if (i_delta == 0)
{
o0 = row0[i];
o1 = row1[i];
is_fault = true;
}
else
{
/* Interpolate the upper row */
{
int o_low = row0[i]; /* Row value at low column # */
int o_delta = row0[i + 1] - row0[i]; /* Difference from next column */
o0 = o_low + ((ix - i_low) * (long)o_delta) / i_delta;
}
/* Interpolate the lower (higher subscript) row */
{
int o_low = row1[i]; /* Row value at low column # */
int o_delta = row1[i + 1] - row1[i]; /* Difference from next column */
o1 = o_low + ((ix - i_low) * (long)o_delta) / i_delta;
}
}
/* Guard against division by zero in the row axis. If all is well,
interpolate between the two row interpolation results from earlier. */
if (j_delta == 0)
{
*o = o0;
is_fault = true;
}
else
{
*o = o0 + ((iy - j_low) * (long)(o1 - o0)) / j_delta;
}
}
return is_fault;
}
/* Header file */
#if !defined(_LOOKUP_H)
#define _LOOKUP_H
/**
* @file
* Table lookup with interpolation (1-D and 2-D) header file.
*/
#include <stdbool.h>
/** One dimensional lookup table. */
typedef const struct
{
/** Number of elements in the table. This must be at least 2. */
unsigned char ncols;
/** List of input values. */
int *columns;
/** Table data (output values). The output values list must have the same
length as the input list. */
int *table;
} Table1d;
/** Two dimensional lookup table. */
typedef const struct
{
/** Number of columns (X values) in the table. Must be at least 2. */
unsigned char ncols;
/** Number of rows (Y values) in the table. Must be at least 2. */
unsigned char nrows;
/** X-axis input values list. */
int *columns;
/** Y-axis input values list. */
int *rows;
/** Table data. This is an array of <code>columns</code>X<code>rows</code>,
arranged in rows. For example, <code>table[1]</code> is the second
column in the first row. */
int *table;
} Table2d;
/* Prototypes */
bool lookup1d (Table1d *t, int ix, int *o);
bool lookup2d (Table2d *t, int ix, int iy, int *o);
#endif
Software UART receiver
/**
* @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