/*
 * Digital in/out and poll commands are sent as one byte:
 *
 *   +-+-+-+-+-+-+-+-+
 *   |0|0 0|  chan   | Bit clear
 *   +-+-+-+-+-+-+-+-+
 *
 *   +-+-+-+-+-+-+-+-+
 *   |0|0 1|  chan   | Bit set
 *   +-+-+-+-+-+-+-+-+
 *
 *   +-+-+-+-+-+-+-+-+
 *   |0|1 0|  chan   | Bit get
 *   +-+-+-+-+-+-+-+-+
 *
 *   +-+-+-+-+-+-+-+-+
 *   |0|1 1|  chan   | Channel get
 *   +-+-+-+-+-+-+-+-+
 *
 *
 * Channels are sent as 2 to 6 bytes, depending on resolution:
 *
 *   +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+ 
 * 2 |1| bit8...bit2 |  |0|bit|  chan   |
 *   +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+ 
 *
 *   +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+ 
 * 3 |1|bit15...bit9 |  |1| bit8...bit2 |  |0|bit|  chan   |
 *   +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+ 
 *
 *   ...
 *
 *   +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+     +-+-+-+-+-+-+-+-+ 
 * 6 |1|bit31...bit30|  |1|bit29...bit23| ... |0|bit|  chan   |
 *   +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+     +-+-+-+-+-+-+-+-+ 
 *
 *
 *
 * Channel 31 is special, as it serves as the configuration channel. When
 * reading from it multiple responses are sent with the following layout
 *
 * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
 * |           command specific data           |cmd|kind |conf chan|
 * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
 *
 *  kind: 000 == end of configuration
 *        001 == digital in
 *        010 == digital out
 *        011 == analog in
 *        100 == analog out
 *        101 == counter in
 *
 *cmd == 0 (Resolution)
 *
 * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
 * |                               | # of bits |0 0|kind |conf chan|
 * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
 *
 *  # of bits (1..32) 
 *
 *cmd == 1 (Minimum value)
 *
 * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
 * |              minimum              |S| unit|0 1|kind |conf chan|
 * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
 * 
 *  S (sign): 0 == +
 *            1 == -
 *  unit: 000 == V
 *        001 == mV
 *        010 == uV
 *        100 == A
 *
 *cmd == 2 (Maximum value)
 *
 * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
 * |              maximum              |S| unit|1 0|kind |conf chan|
 * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
 * 
 *  S (sign): 0 == +
 *            1 == -
 *  unit: 000 == V
 *        001 == mV
 *        010 == uV
 *        100 == A
 */

#include <avr/io.h>

static volatile unsigned long serialio_value;
static volatile unsigned char serialio_channel, serialio_length;

static inline void serialio_putchar(unsigned char ch) 
{
  
  while ((UCSRA & 0x20) == 0) {};
  UDR = ch;
}

static inline void serialio_putbit(unsigned char channel, unsigned char value)
{
  if (value) {
    serialio_putchar(0x20 | (channel & 0x1f));
  } else {
    serialio_putchar(0x00 | (channel & 0x1f));
  }
}



static inline void serialio_putchannel(unsigned char channel, 
				       unsigned long value) 
{
  if (value >= (1L<<30)) { serialio_putchar(0x80 | ((value >> 30) & 0x03)); }
  if (value >= (1L<<23)) { serialio_putchar(0x80 | ((value >> 23) & 0x7f)); }
  if (value >= (1L<<16)) { serialio_putchar(0x80 | ((value >> 16) & 0x7f)); }
  if (value >= (1L<< 9)) { serialio_putchar(0x80 | ((value >> 9) & 0x7f)); }
  serialio_putchar(0x80 | ((value >> 2) & 0x7f));
  serialio_putchar(((value << 5) & 0x60) | (channel & 0x1f));
}


#define CONF_DIG_IN(channel) (0x20 | (channel)&0x1f)
#define CONF_DIG_OUT(channel) (0x40 | (channel)&0x1f)

#define CONF_END() serialio_putchannel(31, 0)
#define CONF_DIGITAL_IN(chan, config) \
  serialio_putchannel(31, (0x20|(chan&0x1f)|(config&0xffffff00)))
#define CONF_DIGITAL_OUT(chan, config) \
  serialio_putchannel(31, (0x40|(chan&0x1f)|(config&0xffffff00)))
#define CONF_ANALOG_IN(chan, config) \
  serialio_putchannel(31, (0x60|(chan&0x1f)|(config&0xffffff00)))
#define CONF_ANALOG_OUT(chan, config) \
  serialio_putchannel(31, (0x80|(chan&0x1f)|(config&0xffffff00)))
#define CONF_ENCODER_IN(chan, config) \
  serialio_putchannel(31, (0xa0|(chan&0x1f)|(config&0xffffff00)))
#define CONF_RESOLUTION(bits) (((bits)<<10)|0x000)
#define CONF_MIN(value) ((value)|0x100)
#define CONF_MAX(value) ((value)|0x200)
#define CONF_NEGATIVE_VOLT(volt) (((long)(volt)<<14)|0x2000)
#define CONF_POSITIVE_VOLT(volt) ((long)(volt)<<14)
#define CONF_NEGATIVE_MILLIVOLT(millivolt) (((long)(millivolt)<<14)|0x2400)
#define CONF_POSITIVE_MILLIVOLT(millivolt) ((long)(millivolt)<<14|0x400)
#define CONF_POSITIVE_AMPERE(ampere) (((long)(ampere)<<14)|0x1000)

static inline void serialio_init() 
{
  serialio_value = 0;
  serialio_channel = 255;
  serialio_length = 0;
}

typedef enum { 
  serialio_error, serialio_more, serialio_clearbit, serialio_setbit, 
  serialio_setchannel, serialio_pollbit, serialio_pollchannel 
} serialio_rxc_status;


static inline serialio_rxc_status serialio_RXC(unsigned char ch) {
  unsigned char result = serialio_error;

  if (serialio_length == 0) { serialio_value = 0; }
  serialio_length++;
  if ((ch & 0x80) == 0x80) {
    // Collect yet another byte for later processing
    serialio_value = (serialio_value << 7) | (ch & 0x7f);
    result = serialio_more;
  } else {
    serialio_value = (serialio_value << 2) | ((ch & 0x60) >> 5);
    serialio_channel = ch & 0x1f;
    if (serialio_length == 1) {
      switch (serialio_value & 0x03) {
	// Digital output buffer (ULN2803A) is inverting
	case 0: { result = serialio_clearbit; } break;
	case 1: { result = serialio_setbit; } break;
	case 2: { result = serialio_pollbit; } break;
	case 3: { result = serialio_pollchannel; } break;
      }
    } else {
      result = serialio_setchannel;
    }
    serialio_length = 0;
  }
  return result;
}