From 63d755835adff11984a70718010ca5bf8a4bc5ea Mon Sep 17 00:00:00 2001
From: Anders Blomdell <anders.blomdell@control.lth.se>
Date: Thu, 11 Oct 2018 18:42:12 +0200
Subject: [PATCH] Add pcio processes, enhance avr target handling with multiple
 targets for one project

---
 .gitignore               |   1 +
 Makefile                 |  20 ++-
 pcio/avr/Makefile        |  19 +++
 pcio/avr/pcio.c          | 217 +++++++++++++++++++++++++++++
 pcio/avr/pcio_w_usb.c    | 218 ++++++++++++++++++++++++++++++
 pcio/linux/Makefile      |  21 +++
 pcio/linux/pciotest.c    | 285 +++++++++++++++++++++++++++++++++++++++
 tools/find_avr_targets   |  32 +++++
 tools/find_linux_targets |  33 +++++
 9 files changed, 841 insertions(+), 5 deletions(-)
 create mode 100644 pcio/avr/Makefile
 create mode 100644 pcio/avr/pcio.c
 create mode 100644 pcio/avr/pcio_w_usb.c
 create mode 100644 pcio/linux/Makefile
 create mode 100644 pcio/linux/pciotest.c
 create mode 100755 tools/find_avr_targets
 create mode 100755 tools/find_linux_targets

diff --git a/.gitignore b/.gitignore
index 0ee7f02..9e1f2d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 */avr/compiled
+*/linux/compiled
 *~
\ No newline at end of file
diff --git a/Makefile b/Makefile
index a95c907..9fee9e6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,24 @@
 all:
+	@echo "###" >&2
+	@echo "### Atmel AVR targets ###" >&2
+	@echo "###" >&2
 	@echo "Do 'make PROCESS.LOAD' or 'make PROCESS.FUSE'" >&2
 	@echo "where PROCESS is one of" >&2
-	@for m in */*/Makefile ; do \
-	    echo "  $$(dirname $$(dirname $$m))" >&2 \
-	; done
+	@./tools/find_avr_targets
+	@echo "###" >&2
+	@echo "### Linux targets ###" >&2
+	@echo "###" >&2
+	@echo "Do 'make PROGRAM.LINUX'" >&2
+	@echo "where PROGRAM is one of" >&2
+	@./tools/find_linux_targets
 	@echo >&2
 	@exit 1
 
 %.LOAD:
-	make -C $*/avr $@
+	make -C $$(./tools/find_avr_targets $*) $@
 
 %.FUSE:
-	make -C $*/avr $@
+	make -C $$(./tools/find_avr_targets $*) $@
+
+%.LINUX:
+	make -C $$(./tools/find_linux_targets $*) $@
diff --git a/pcio/avr/Makefile b/pcio/avr/Makefile
new file mode 100644
index 0000000..4ead37a
--- /dev/null
+++ b/pcio/avr/Makefile
@@ -0,0 +1,19 @@
+TARGETS=pcio pcio_w_usb
+
+pcio.ARCH=avr
+pcio.CHIP=atmega8
+# 16 MHz crystal, brown out
+pcio.FUSE_L=0x1f
+pcio.FUSE_H=0xd9
+pcio.C=pcio
+pcio.H=../lib/serialio
+
+pcio_w_usb.ARCH=avr
+pcio_w_usb.CHIP=atmega16
+# 16 MHz crystal, brown out
+pcio_w_usb.FUSE_L=0x1f
+pcio_w_usb.FUSE_H=0xd9
+pcio_w_usb.C=pcio_w_usb
+pcio_w_usb.H=../lib/serialio
+
+include ../../lib/avr/Makefile.common
diff --git a/pcio/avr/pcio.c b/pcio/avr/pcio.c
new file mode 100644
index 0000000..a045fba
--- /dev/null
+++ b/pcio/avr/pcio.c
@@ -0,0 +1,217 @@
+#include <avr/io.h>
+#include <avr/interrupt.h>
+//#include <avr/signal.h>
+#include "serialio.h"
+
+volatile unsigned char error;
+
+
+volatile unsigned char serial_readbits;
+volatile unsigned char serial_readchannels;
+volatile unsigned char serial_readconfig;
+volatile unsigned int ai0;
+volatile unsigned int ai1;
+volatile unsigned int ai2;
+volatile unsigned int ai3;
+
+SIGNAL(ADC_vect)
+{
+  unsigned char channel = ADMUX & 0x0f;
+  unsigned int value = ADCW;
+
+  switch (channel) {
+    case 0: { 
+      channel = 1; 
+      ai2 = value;
+    } break;
+    case 1: { 
+      channel = 2; 
+      ai3 = value;
+    } break;
+    case 2: { 
+      channel = 3; 
+      ai0 = value;
+    } break;
+    case 3: { 
+      channel = 0; 
+      ai1 = value;
+    } break;
+    default: { 
+      channel = 0; 
+    } break;
+  }
+  ADMUX = 0xc0 | channel; // Internal Vref, right adjust
+  ADCSR = 0xcf;           // Enable ADC interrupts, Clock/128 
+
+}
+
+typedef enum { cmd_clear_bit, cmd_set_bit, 
+	       cmd_read_bit, cmd_read_chan } command;
+
+SIGNAL(USART_RXC_vect)
+{
+  char ch = UDR;
+
+  switch (serialio_RXC(ch)) {
+    case serialio_clearbit: {
+      switch (serialio_channel) {
+	// Digital output buffer (ULN2803A) is inverting
+	case 0: { PORTB = PORTB | 0x01; } break;
+	case 1: { PORTB = PORTB | 0x10; } break;
+	case 2: { PORTB = PORTB | 0x20; } break;
+	case 3: { PORTC = PORTC | 0x10; } break;
+	case 4: { PORTC = PORTC | 0x20; } break;
+	case 5: { PORTB = PORTB | 0x08; } break;
+      }
+    } break;
+    case serialio_setbit: {
+      switch (serialio_channel) {
+	// Digital output buffer (ULN2803A) is inverting
+	case 0: { PORTB = PORTB & ~0x01; } break;
+	case 1: { PORTB = PORTB & ~0x10; } break;
+	case 2: { PORTB = PORTB & ~0x20; } break;
+	case 3: { PORTC = PORTC & ~0x10; } break;
+	case 4: { PORTC = PORTC & ~0x20; } break;
+	case 5: { PORTB = PORTB & ~0x08; } break;
+      }
+    } break;
+    case serialio_pollbit: {
+      if (serialio_channel <= 7) { 
+	  serial_readbits |= (1<<serialio_channel); 
+      }
+    } break;
+    case serialio_pollchannel: { 
+     if (serialio_channel <= 7) { 
+        serial_readchannels |= (1<<serialio_channel); 
+      } else if (serialio_channel == 31) { 
+        serial_readconfig = 1; 
+      }
+    } break;
+    case serialio_setchannel: {
+      switch (serialio_channel) {
+	case 0: { 
+	  OCR1A = serialio_value & 0x3ff;
+	} break;
+	case 1: { 
+	  OCR1B = serialio_value & 0x3ff;
+	} break;
+      } break;
+    } break;
+    case serialio_error: {
+    } break;
+    case serialio_more: {
+    } break;
+  }
+}
+
+/*
+ * PB0  DO5
+ * PB1  AO0 / DO4
+ * PB2  AO1 / DO3
+ * PB3  DO2
+ * PB4  DO1
+ * PB5  DO0
+ *
+ * PC0  AI2
+ * PC1  AI3
+ * PC2  AI0
+ * PC3  AI1
+ *
+ * PD2  DI0
+ * PD3  DI2
+ * PD4  DI4
+ * PD5  DI5
+ * PD6  DI3
+ * PD7  DI1
+ *
+ */
+int main() 
+{
+  serialio_init();
+  serial_readbits = 0;
+  serial_readchannels = 0;
+  serial_readconfig = 0;
+  ai0 = 0;
+  ai1 = 0;
+  ai2 = 0;
+  ai3 = 0;
+  PORTD = 0xfc;     // PortD, pull up 
+  DDRD = 0x00; 	    // PortD, all input
+  PORTB = 0x00;     // PortB, no pull up
+  DDRB = 0xff; 	    // PortB, all outputs 
+  PORTC = 0x00;     // PortC, no pull up
+  DDRC = 0x30; 	    // PortC, bit 4 & 5 outputs 
+  TCCR0 = 0x05;	    // Timer0, Clock / 1024 
+  TCCR1A = 0xa3;    // OC1A & OC1B 10 bit PWM (PC), clear on upcounting
+  TCCR1B = 0x01;    // Clock / 1 
+  UCSRA = 0x00;     // USART: 
+  UCSRB = 0x98;     // USART: RxIntEnable|RxEnable|TxEnable 
+  UCSRC = 0x86;     // USART: 8bit, no parity
+  UBRRH = 0;        // USART: 115200 @ 14.7456MHz
+  UBRRL = 7;        // USART: 115200 @ 14.7456MHz
+  ADMUX = 0xc0;     // Internal Vref, right adjust
+  ADMUX = 0xce;     // Internal Vref, right adjust, read 1.22V (Vbg)
+  ADCSR = 0xcf;     // Enable ADC interrupts, Clock/128 
+  SREG = 0x80;	    // Global interrupt enable 
+  OCR1A = 512 & 0x3ff;
+  OCR1B = 512 & 0x3ff;
+
+  while (1) {
+    unsigned char bits, channels, config;
+    SREG = 0x00;    // Global interrupt disable 
+    bits = serial_readbits;
+    serial_readbits = 0;
+    channels = serial_readchannels;
+    serial_readchannels = 0;
+    config = serial_readconfig;
+    serial_readconfig = 0;
+    SREG = 0x80;    // Global interrupt enable 
+    if (bits & 0x01) { serialio_putbit(0, PIND & 0x04); }
+    if (bits & 0x02) { serialio_putbit(1, PIND & 0x08); }
+    if (bits & 0x04) { serialio_putbit(2, PIND & 0x10); }
+    if (bits & 0x08) { serialio_putbit(3, PIND & 0x20); }
+    if (bits & 0x10) { serialio_putbit(4, PIND & 0x40); }
+    if (bits & 0x20) { serialio_putbit(5, PIND & 0x80); }
+    if (channels & 0x01) { serialio_putchannel(0, 1023 - ai0); }
+    if (channels & 0x02) { serialio_putchannel(1, 1023 - ai1); }
+    if (channels & 0x04) { serialio_putchannel(2, 1023 - ai2); }
+    if (channels & 0x08) { serialio_putchannel(3, 1023 - ai3); }
+    if (config) {
+      CONF_DIGITAL_IN(0, CONF_RESOLUTION(1));			// DI0
+      CONF_DIGITAL_IN(1, CONF_RESOLUTION(1));			// DI1
+      CONF_DIGITAL_IN(2, CONF_RESOLUTION(1));			// DI2
+      CONF_DIGITAL_IN(3, CONF_RESOLUTION(1));			// DI3
+      CONF_DIGITAL_IN(4, CONF_RESOLUTION(1));			// DI4
+      CONF_DIGITAL_IN(5, CONF_RESOLUTION(1));			// DI5
+
+      CONF_DIGITAL_OUT(0, CONF_RESOLUTION(1));			// DO0
+      CONF_DIGITAL_OUT(1, CONF_RESOLUTION(1));			// DO1
+      CONF_DIGITAL_OUT(2, CONF_RESOLUTION(1));			// DO2
+      CONF_DIGITAL_OUT(3, CONF_RESOLUTION(1));			// DO3
+      CONF_DIGITAL_OUT(4, CONF_RESOLUTION(1));			// DO4
+      CONF_DIGITAL_OUT(5, CONF_RESOLUTION(1));			// DO5
+
+      CONF_ANALOG_IN(0, CONF_RESOLUTION(10));			// AI0
+      CONF_ANALOG_IN(0, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10400)));
+      CONF_ANALOG_IN(0, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10300)));
+      CONF_ANALOG_IN(1, CONF_RESOLUTION(10));			// AI1
+      CONF_ANALOG_IN(1, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10400)));
+      CONF_ANALOG_IN(1, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10300)));
+      CONF_ANALOG_IN(2, CONF_RESOLUTION(10));			// AI2
+      CONF_ANALOG_IN(2, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10400)));
+      CONF_ANALOG_IN(2, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10300)));
+      CONF_ANALOG_IN(3, CONF_RESOLUTION(10));			// AI3
+      CONF_ANALOG_IN(3, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10400)));
+      CONF_ANALOG_IN(3, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10300)));
+
+      CONF_ANALOG_OUT(0, CONF_RESOLUTION(10));			// AO0
+      CONF_ANALOG_OUT(0, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(9800)));
+      CONF_ANALOG_OUT(0, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10170)));
+      CONF_ANALOG_OUT(1, CONF_RESOLUTION(10));			// AO1
+      CONF_ANALOG_OUT(1, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(9800)));
+      CONF_ANALOG_OUT(1, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10106)));
+
+      CONF_END();
+    }
+  }
+}
diff --git a/pcio/avr/pcio_w_usb.c b/pcio/avr/pcio_w_usb.c
new file mode 100644
index 0000000..3d68869
--- /dev/null
+++ b/pcio/avr/pcio_w_usb.c
@@ -0,0 +1,218 @@
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include "serialio.h"
+
+volatile unsigned char error;
+
+
+volatile unsigned char serial_readbits;
+volatile unsigned char serial_readchannels;
+volatile unsigned char serial_readconfig;
+volatile unsigned int ai0;
+volatile unsigned int ai1;
+volatile unsigned int ai2;
+volatile unsigned int ai3;
+
+SIGNAL(ADC_vect)
+{
+  unsigned char channel = ADMUX & 0x0f;
+  unsigned int value = ADCW;
+
+  switch (channel) {
+    case 0: { 
+      channel = 1; 
+      ai0 = value;
+    } break;
+    case 1: { 
+      channel = 2; 
+      ai1 = value;
+    } break;
+    case 2: { 
+      channel = 3; 
+      ai2 = value;
+    } break;
+    case 3: { 
+      channel = 0; 
+      ai3 = value;
+    } break;
+    default: { 
+      channel = 0; 
+    } break;
+  }
+  ADMUX = 0xc0 | channel; // Internal Vref, right adjust
+  ADCSRA = 0xcf;           // Enable ADC interrupts, Clock/128 
+
+}
+
+typedef enum { cmd_clear_bit, cmd_set_bit, 
+	       cmd_read_bit, cmd_read_chan } command;
+
+SIGNAL(USART_RXC_vect)
+{
+  char ch = UDR;
+
+  switch (serialio_RXC(ch)) {
+    case serialio_clearbit: {
+      switch (serialio_channel) {
+	// Digital output buffer (ULN2803A) is inverting
+	case 0: { PORTC |= 0x04; } break;
+	case 1: { PORTC |= 0x08; } break;
+	case 2: { PORTC |= 0x10; } break;
+	case 3: { PORTC |= 0x20;  } break;
+	case 4: { PORTC |= 0x40; } break;
+	case 5: { PORTC |= 0x80; } break;
+      }
+    } break;
+    case serialio_setbit: {
+      switch (serialio_channel) {
+	// Digital output buffer (ULN2803A) is inverting
+	case 0: { PORTC &= ~0x04; } break;
+	case 1: { PORTC &= ~0x08; } break;
+	case 2: { PORTC &= ~0x10; } break;
+	case 3: { PORTC &= ~0x20;  } break;
+	case 4: { PORTC &= ~0x40; } break;
+	case 5: { PORTC &= ~0x80; } break;
+      }
+    } break;
+    case serialio_pollbit: {
+      if (serialio_channel <= 7) { 
+	  serial_readbits |= (1<<serialio_channel); 
+      }
+    } break;
+    case serialio_pollchannel: { 
+     if (serialio_channel <= 7) { 
+        serial_readchannels |= (1<<serialio_channel); 
+      } else if (serialio_channel == 31) { 
+        serial_readconfig = 1; 
+      }
+    } break;
+    case serialio_setchannel: {
+      switch (serialio_channel) {
+	case 0: { 
+	  OCR1B = serialio_value & 0x3ff;
+	} break;
+	case 1: { 
+	  OCR1A = serialio_value & 0x3ff;
+	} break;
+      } break;
+    } break;
+    case serialio_error: {
+    } break;
+    case serialio_more: {
+    } break;
+  }
+}
+
+/*
+ * PA0  AI0
+ * PA1  AI1
+ * PA2  AI2
+ * PA3  AI3
+ * PA4  DI0
+ * PA5  DI1
+ * PA6  DI2
+ * PA7  DI3
+ *
+ * PC2  DO0
+ * PC3  DO1
+ * PC4  DO2
+ * PC5  DO3
+ * PC6  DO4
+ * PC7  DO5
+ *
+ * PD3  DI5
+ * PD4  AO0
+ * PD5  AO1
+ * PD7  DI4
+ *
+ */
+int main() 
+{
+  serialio_init();
+  serial_readbits = 0;
+  serial_readchannels = 0;
+  serial_readconfig = 0;
+  ai0 = 0;
+  ai1 = 0;
+  ai2 = 0;
+  ai3 = 0;
+  PORTA = 0xf0;     // PortA, pull-ups
+  DDRA = 0x00;      // PortA, all inputs
+  PORTC = 0x00;     // PortC, initial data
+  DDRC = 0xfc;      // PortC, 2-7 outputs
+  PORTD = 0x88;     // PortD, pull-ups
+  DDRD = 0x30;      // PortD, 4-5 outputs
+  TCCR0 = 0x05;	    // Timer0, Clock / 1024 
+  TCCR1A = 0xa3;    // OC1A & OC1B 10 bit PWM (PC), clear on upcounting
+  TCCR1B = 0x01;    // Clock / 1 
+  UCSRA = 0x00;     // USART: 
+  UCSRB = 0x98;     // USART: RxIntEnable|RxEnable|TxEnable 
+  UCSRC = 0x86;     // USART: 8bit, no parity
+  UBRRH = 0;        // USART: 115200 @ 14.7456MHz
+  UBRRL = 7;        // USART: 115200 @ 14.7456MHz
+  ADMUX = 0xc0;     // Internal Vref, right adjust
+  ADMUX = 0xce;     // Internal Vref, right adjust, read 1.22V (Vbg)
+  ADCSRA = 0xcf;    // Enable ADC interrupts, Clock/128 
+  SREG = 0x80;	    // Global interrupt enable 
+  OCR1A = 512 & 0x3ff;
+  OCR1B = 512 & 0x3ff;
+
+  while (1) {
+    unsigned char bits, channels, config;
+    SREG = 0x00;    // Global interrupt disable 
+    bits = serial_readbits;
+    serial_readbits = 0;
+    channels = serial_readchannels;
+    serial_readchannels = 0;
+    config = serial_readconfig;
+    serial_readconfig = 0;
+    SREG = 0x80;    // Global interrupt enable 
+    if (bits & 0x01) { serialio_putbit(0, PINA & 0x10); }
+    if (bits & 0x02) { serialio_putbit(1, PINA & 0x20); }
+    if (bits & 0x04) { serialio_putbit(2, PINA & 0x40); }
+    if (bits & 0x08) { serialio_putbit(3, PINA & 0x80); }
+    if (bits & 0x10) { serialio_putbit(4, PIND & 0x80); }
+    if (bits & 0x20) { serialio_putbit(5, PIND & 0x08); }
+    if (channels & 0x01) { serialio_putchannel(0, 1023 - ai0); }
+    if (channels & 0x02) { serialio_putchannel(1, 1023 - ai1); }
+    if (channels & 0x04) { serialio_putchannel(2, 1023 - ai2); }
+    if (channels & 0x08) { serialio_putchannel(3, 1023 - ai3); }
+    if (config) {
+      CONF_DIGITAL_IN(0, CONF_RESOLUTION(1));			// DI0
+      CONF_DIGITAL_IN(1, CONF_RESOLUTION(1));			// DI1
+      CONF_DIGITAL_IN(2, CONF_RESOLUTION(1));			// DI2
+      CONF_DIGITAL_IN(3, CONF_RESOLUTION(1));			// DI3
+      CONF_DIGITAL_IN(4, CONF_RESOLUTION(1));			// DI4
+      CONF_DIGITAL_IN(5, CONF_RESOLUTION(1));			// DI5
+
+      CONF_DIGITAL_OUT(0, CONF_RESOLUTION(1));			// DO0
+      CONF_DIGITAL_OUT(1, CONF_RESOLUTION(1));			// DO1
+      CONF_DIGITAL_OUT(2, CONF_RESOLUTION(1));			// DO2
+      CONF_DIGITAL_OUT(3, CONF_RESOLUTION(1));			// DO3
+      CONF_DIGITAL_OUT(4, CONF_RESOLUTION(1));			// DO4
+      CONF_DIGITAL_OUT(5, CONF_RESOLUTION(1));			// DO5
+
+      CONF_ANALOG_IN(0, CONF_RESOLUTION(10));			// AI0
+      CONF_ANALOG_IN(0, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10400)));
+      CONF_ANALOG_IN(0, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10300)));
+      CONF_ANALOG_IN(1, CONF_RESOLUTION(10));			// AI1
+      CONF_ANALOG_IN(1, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10400)));
+      CONF_ANALOG_IN(1, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10300)));
+      CONF_ANALOG_IN(2, CONF_RESOLUTION(10));			// AI2
+      CONF_ANALOG_IN(2, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10400)));
+      CONF_ANALOG_IN(2, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10300)));
+      CONF_ANALOG_IN(3, CONF_RESOLUTION(10));			// AI3
+      CONF_ANALOG_IN(3, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10400)));
+      CONF_ANALOG_IN(3, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10300)));
+
+      CONF_ANALOG_OUT(0, CONF_RESOLUTION(10));			// AO0
+      CONF_ANALOG_OUT(0, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(9800)));
+      CONF_ANALOG_OUT(0, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10170)));
+      CONF_ANALOG_OUT(1, CONF_RESOLUTION(10));			// AO1
+      CONF_ANALOG_OUT(1, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(9800)));
+      CONF_ANALOG_OUT(1, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10106)));
+
+      CONF_END();
+    }
+  }
+}
diff --git a/pcio/linux/Makefile b/pcio/linux/Makefile
new file mode 100644
index 0000000..7a3863d
--- /dev/null
+++ b/pcio/linux/Makefile
@@ -0,0 +1,21 @@
+CC=gcc
+CFLAGS.pciotest=-Wall -lpthread
+
+.SUFFIXES: .c
+
+all: pciotest.LINUX
+
+.PHONY: %.LINUX
+%.LINUX: compiled/% | compiled
+	@/bin/true
+
+.PRECIOUS: compiled/%
+compiled/%:	%.c | compiled
+	echo $*
+	$(CC) $(CFLAGS.$*) -o $@ $<
+
+compiled:
+	mkdir -p $@
+
+clean:
+	rm -rf compiled
diff --git a/pcio/linux/pciotest.c b/pcio/linux/pciotest.c
new file mode 100644
index 0000000..093dde7
--- /dev/null
+++ b/pcio/linux/pciotest.c
@@ -0,0 +1,285 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <string.h>
+#include <poll.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <unistd.h>
+#include <signal.h>
+
+static volatile int OK = 1;
+
+static pthread_t reader, writer;
+static int digital_in = 0;
+static int digital_out = 0;
+static int analog_in = 0;
+static int analog_out = 0;
+static int encoder_in = 0;
+static int maxdata_in[32];
+static int maxdata_out[32];
+
+static int millivolts(int value) {
+  int val = value >> 4;
+  int unit = value & 0x7;
+  int sign = value & 0x8;
+  if (sign != 0) { val = -val; }
+  switch (unit) {
+    case 0: { val = val * 1000; } break;
+    case 1: {} break;
+    case 2: {} break;
+  }
+  return val;
+}
+
+void *Reader(void *argument) {
+  int *fd, length, value;
+
+  fd = argument;
+  length = 0;
+  value = 0;
+  for (;;) {
+    unsigned char ch;
+
+    if (read(*fd, &ch, 1) <= 0) {
+      fprintf(stderr, "Read failed\n");
+      OK = 0;
+      break;
+    }
+    length++;
+    if ((ch & 0x80) == 0x80) {
+      value = (value << 7) | (ch & 0x7f);
+    } else {
+      unsigned int channel = ch & 0x1f;
+      value = (value << 2) | ((ch & 0x60) >> 5);
+      if (length == 1) {
+	unsigned char cmd = (ch & 0x60) >> 5;
+	switch (cmd) {
+	  case 0: { // Clear bit
+	    printf("#%d:0 ", channel);
+	  } break;
+	  case 1: { // Set bit
+	    printf("#%d:1 ", channel);
+	  } break;
+	  case 2: { // Read bit
+	    // Currently unused
+	  } break;
+	  case 3: { // Read channel
+	    // Currently unused
+	  } break;
+	}
+      } else {
+	switch (channel) {
+	  case 0:{ printf("I0=%3d ", value); } break;
+	  case 1:{ printf("I1=%3d ", value); } break;
+	  case 2:{ printf("I2=%3d ", value); } break;
+	  case 3:{ printf("I3=%3d ", value); } break;
+	  case 4:{ printf("I4=%3d ", value); } break;
+	  case 5:{ printf("I5=%3d ", value); } break;
+	  case 6:{ printf("I6=%3d ", value); } break;
+	  case 31:{
+	    int kind = (value>>5)& 0x7;
+	    int cmd = (value>>8)& 0x3;
+	    int conf_channel = value & 0x1f;
+	    int *maxdata = 0;
+	    switch(kind) {
+	      case 1: { 
+		printf("digital in "); 
+		digital_in |= 1 << conf_channel;
+	      } break; 
+	      case 2: { 
+		printf("digital out "); 
+		digital_out |= 1 << conf_channel;
+	      } break; 
+	      case 3: { 
+		printf("analog in "); 
+		analog_in |= 1 << conf_channel;
+		maxdata = &maxdata_in[conf_channel];
+	      } break; 
+	      case 4: { 
+		printf("analog out "); 
+		analog_out |= 1 << conf_channel;
+		maxdata = &maxdata_out[conf_channel];
+	      } break; 
+	      case 5: { 
+		printf("encoder in "); 
+		encoder_in |= 1 << conf_channel;
+		maxdata = &maxdata_in[conf_channel];
+	      } break; 
+	    }
+	    printf("#%d ", conf_channel);
+	    switch (cmd) {
+              case 0: { 
+		printf("maxdata %d\n", (1<<(value>>10))-1); 
+		if (maxdata) { *maxdata = (1<<(value>>10))-1; }
+	      } break;
+              case 1: { printf("min %d\n", millivolts(value>>10)); } break;
+              case 2: { printf("max %d\n", millivolts(value>>10)); } break;
+	      default: { printf("cmd %d %d\n", cmd, (value>>10)); } break;
+	    }
+	  } break;
+	  default: {
+	    printf("%d=%x (%d)\n", channel, value, length); 
+	  } break;
+	}
+      }
+      value = 0;
+      length = 0;
+    }
+  }
+  return NULL;
+}
+
+void poll_io(int fd) 
+{
+  char ch;
+  int i;
+
+  // Poll digital in
+  for (i = 0 ; i < 32 ; i++) {
+    if (digital_in & (1 << i)) {
+      ch = 0x40 | i;
+      write(fd, &ch, 1);
+    }
+  }
+  // Poll analog in
+  for (i = 0 ; i < 31 ; i++) {
+    if (analog_in & (1 << i)) {
+      ch = 0x60 | i;
+      write(fd, &ch, 1);
+    }
+  }
+  // Poll encoder in
+  for (i = 0 ; i < 31 ; i++) {
+    if (encoder_in & (1 << i)) {
+      ch = 0x60 | i;
+      write(fd, &ch, 1);
+    }
+  }
+}
+
+static void put_char(int fd, unsigned char value) {
+  write(fd, &value, 1);
+}
+
+static void put_channel(int fd, int channel, int value) {
+  if (value >= (1L<<30)) { put_char(fd, 0x80 | ((value >> 30) & 0x03)); }
+  if (value >= (1L<<23)) { put_char(fd, 0x80 | ((value >> 23) & 0x7f)); }
+  if (value >= (1L<<16)) { put_char(fd, 0x80 | ((value >> 16) & 0x7f)); }
+  if (value >= (1L<< 9)) { put_char(fd, 0x80 | ((value >> 9) & 0x7f)); }
+  put_char(fd, 0x80 | ((value >> 2) & 0x7f));
+  put_char(fd, ((value << 5) & 0x60) | channel);
+}
+
+void *Writer(void *argument) {
+  int *fd = argument;
+
+  // Read configuration 
+  char ch = 0x60 | 31;
+  write(*fd, &ch, 1);
+  usleep(1000000);
+
+  while (OK) {
+    int i, j;
+    for (i = 0 ; i < 32 ; i++) {
+      for (j = 0 ; j < 32 ; j++) {
+	if (digital_out & (1 << j)) {
+	  if (i == j) {
+	    printf("1");
+	    ch = 0x20 | j;
+	  } else {
+	    printf("0");
+	    ch = 0x00 | j;
+	  }
+	  write(*fd, &ch, 1);
+	}
+      }
+      printf(" ");
+      for (j = 0 ; j < 31 ; j++) {
+	if (analog_out & (1 << j)) {
+	  int value = (((i + j) % 32) * maxdata_out[j]) / 32;
+//	  printf("%d = %d\n", j, value);
+	  put_channel(*fd, j, value);
+	}
+      }
+      // Let analog values settle
+      usleep(10000);
+      poll_io(*fd);
+      usleep(100000);
+      printf("\n");
+    }
+  }
+  return NULL;
+}
+
+
+int main(int argc, char *argv[]) {
+  int avr;
+  struct termios old_settings, new_settings;
+  
+  if (argc <= 1) {
+    avr = open("/dev/ttyS0", O_RDWR);
+  } else {
+    avr = open(argv[1], O_RDWR);
+  }
+  if (avr < 0) {
+    printf("Couldn't open terminal line\n");
+    exit(1);
+  }
+
+  if (lockf(avr, F_TLOCK, 0)) {
+    printf("Couldn't lock terminal line\n");
+    exit(1);
+  }
+  
+  tcgetattr(avr, &old_settings);
+  new_settings = old_settings;
+  new_settings.c_iflag = 0;
+  new_settings.c_oflag = 0;
+  new_settings.c_lflag = 0;
+  new_settings.c_cflag = CLOCAL | CS8 | CREAD;
+  new_settings.c_cc[VMIN] = 1;
+  new_settings.c_cc[VTIME] = 0;
+  if (argc <= 2) {
+    cfsetispeed(&new_settings, B115200);
+    cfsetospeed(&new_settings, B115200);
+  } else {
+    if (strcmp(argv[2], "2400") == 0) {
+      cfsetispeed(&new_settings, B2400);
+      cfsetospeed(&new_settings, B2400);
+    } else if (strcmp(argv[2], "9600") == 0) {
+      cfsetispeed(&new_settings, B9600);
+      cfsetospeed(&new_settings, B9600);
+    } else if (strcmp(argv[2], "38400") == 0) {
+      cfsetispeed(&new_settings, B38400);
+      cfsetospeed(&new_settings, B38400);
+    } else if (strcmp(argv[2], "57600") == 0) {
+      cfsetispeed(&new_settings, B57600);
+      cfsetospeed(&new_settings, B57600);
+    } else if (strcmp(argv[2], "115200") == 0) {
+      cfsetispeed(&new_settings, B115200);
+      cfsetospeed(&new_settings, B115200);
+    } else {
+      printf("Unknown speed %s\n", argv[2]);
+      exit(1);
+    }
+  }
+      
+  tcsetattr(avr, TCSADRAIN, &new_settings);
+
+  pthread_create(&reader, NULL, Reader, &avr);
+  pthread_create(&writer, NULL, Writer, &avr);
+  while (OK) {
+    sleep(1);
+  }
+  
+  lockf(avr, F_ULOCK, 0);
+  tcsetattr(avr, TCSADRAIN, &old_settings);
+  close(avr);
+
+}
diff --git a/tools/find_avr_targets b/tools/find_avr_targets
new file mode 100755
index 0000000..2fbe8f8
--- /dev/null
+++ b/tools/find_avr_targets
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+set -e
+
+ROOTDIR=$(dirname $(dirname $0))
+
+err_report() {
+    echo "No AVR target(s) found [line $1]"
+}
+
+trap 'err_report $LINENO' ERR
+
+all_avr_programs() {
+    egrep '^\S+.CHIP=' ${ROOTDIR}/*/avr/Makefile \
+        | sed -re 's|^.*/(\S+)/avr/Makefile:(\S+).CHIP=(\S+)\s*$|\2,\1,\3|'
+}
+
+target_dir() {
+    dirname $(egrep -l "$1.CHIP=" ${ROOTDIR}/*/avr/Makefile)
+}
+
+if [ $# -eq 0 ] ; then
+    # Report all possible AVR target programs
+    for l in $(all_avr_programs | sort) ; do
+        printf "  %-25s %-25s %s\n" \
+               $(echo ${l} | sed -re 's|(.*),(.*),(.*)|\1 (\2) [\3]|')
+    done
+elif [ $# -eq 1 ] ; then
+    target_dir $1
+else
+    exit 1
+fi
diff --git a/tools/find_linux_targets b/tools/find_linux_targets
new file mode 100755
index 0000000..f268553
--- /dev/null
+++ b/tools/find_linux_targets
@@ -0,0 +1,33 @@
+#! /bin/bash
+
+set -e
+
+err_report() {
+    echo "No Linux programs found [line $1]"
+}
+
+trap 'err_report $LINENO' ERR
+
+ROOTDIR=$(dirname $(dirname $0))
+
+all_linux_programs() {
+    egrep -H '^CFLAGS.\S+=' ${ROOTDIR}/*/linux/Makefile \
+        | sed -re 's|^.*/(\S+)/linux/Makefile:CFLAGS.(\S+)=.*$|\2,\1|'
+    exit 1
+}
+
+target_dir() {
+    dirname $(egrep -l "^CFLAGS.$1=" ${ROOTDIR}/*/linux/Makefile)
+}
+
+if [ $# -eq 0 ] ; then
+    # Report all possible AVR target programs
+    for l in $(all_linux_programs | sort) ; do
+        printf "  %-25s %-25s %s\n" \
+               $(echo ${l} | sed -re 's|(.*),(.*)|\1 (\2)|')
+    done             
+elif [ $# -eq 1 ] ; then
+    target_dir $1
+else
+    exit 1
+fi
-- 
GitLab