diff --git a/china_io-2019/avr/Makefile b/china_io-2019/avr/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..b658ee4533bfaf5f61963942e28951740aa1a367
--- /dev/null
+++ b/china_io-2019/avr/Makefile
@@ -0,0 +1,21 @@
+TARGETS=china-io china-io-test
+
+china-io.ARCH=avr
+china-io.CHIP=atmega32
+# 14.7456 MHz crystal, brown out
+china-io.FUSE=--wr_fuse_l=0x1f --wr_fuse_h=0xd9 --wr_fuse_e=0xff
+china-io.FUSE_L=0x1f
+china-io.FUSE_H=0xd9
+china-io.C=china-io
+china-io.H=../lib/serialio i2c_master
+
+china-io-test.ARCH=avr
+china-io-test.CHIP=atmega32
+# 14.7456 MHz crystal, brown out
+china-io-test.FUSE=--wr_fuse_l=0x1f --wr_fuse_h=0xd9 --wr_fuse_e=0xff
+china-io-test.FUSE_L=0x1f
+china-io-test.FUSE_H=0xd9
+china-io-test.C=china-io-test
+china-io-test.H=i2c_master
+
+include ../../lib/avr/Makefile.common
diff --git a/china_io-2019/avr/china-io-test.c b/china_io-2019/avr/china-io-test.c
new file mode 100644
index 0000000000000000000000000000000000000000..bb0f11424c49f535490cf41fe7b06ac5efbf876a
--- /dev/null
+++ b/china_io-2019/avr/china-io-test.c
@@ -0,0 +1,224 @@
+#include <avr/interrupt.h>
+#include <avr/io.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <util/twi.h>
+#include "i2c_master.h"
+
+#define DDR_SPI DDRB
+#define DD_MISO PB6
+#define DD_MOSI PB5
+#define DD_SCK PB7
+#define DD_SS PB4
+#define PORT_SPI PORTB
+
+static void putch(unsigned char ch) 
+{
+  
+  while ((UCSRA & 0x20) == 0) {};
+  UDR =ch;
+}
+
+static void putonehex(char i) {
+  if (i < 10) {
+    putch(i + '0');
+  } else if (i < 16) {
+    putch(i - 10 + 'a');
+  }
+}
+
+static void puthex(char value) {
+  putonehex((value >> 4) & 0x0f);
+  putonehex(value & 0x0f);
+}
+
+void putstr(char *message)
+{
+  for (int i = 0 ; message[i] ; i++) {
+    putch(message[i]);
+  }
+}
+
+void putint32(int32_t value)
+{
+  char buf[20];
+  snprintf(buf, sizeof(buf), "%8ld", value);
+  putstr(buf);
+}
+
+void putint16(int16_t value)
+{
+  char buf[20];
+  snprintf(buf, sizeof(buf), "%8d", value);
+  putstr(buf);
+}
+
+
+static uint8_t ADS8694_W(
+  uint8_t length,
+  const __flash uint8_t *data)
+{
+  DDR_SPI |= (1<<DD_MOSI) | (1<<DD_SCK) | (1<<DD_SS);
+  SPCR &= (1<<SPIE);
+  SPCR = (1<<SPE) | (1<<MSTR) | (1<<CPHA);
+  SPSR = (1<<SPI2X);
+  PORT_SPI |= (1<<DD_MISO); // Pull up
+  
+  PORT_SPI &= ~(1<<DD_SS);
+  for (uint8_t i = 0 ; i < length ; i++) {
+    SPDR = data[i];
+    while ((SPSR & (1<<SPIF)) == 0) {}
+  }
+  uint8_t dummy = SPDR; // Clear SPIF flag
+  PORT_SPI |= (1<<DD_SS);
+  return dummy;
+}
+
+/*
+ * DAC8574 4-channel D/A convert
+ */
+
+#define DAC8574ADDR 0x4c
+
+static volatile struct dac8574 {
+  union dac8574_channel {
+    uint8_t bytes[3];
+    struct {
+      uint8_t control;
+      uint8_t data[2];
+    } reg;
+  } a, b, c, d;
+} dac8574 = {
+  .a={ .reg={ .control=0x10 } },
+  .b={ .reg={ .control=0x12 } },
+  .c={ .reg={ .control=0x14 } },
+  .d={ .reg={ .control=0x16 } },
+};
+
+static void dac8574_set_chan(
+  volatile union dac8574_channel *channel,
+  int value)
+{
+  channel->reg.data[0] = (value & 0xff00) >> 8;
+  channel->reg.data[1] = (value & 0x00ff);
+}
+
+static const __flash struct i2c_transcation write_all[] = {
+  I2C_WRITE_RAM(DAC8574ADDR, 3, dac8574.a.bytes),
+  I2C_WRITE_RAM(DAC8574ADDR, 3, dac8574.b.bytes),
+  I2C_WRITE_RAM(DAC8574ADDR, 3, dac8574.c.bytes),
+  I2C_WRITE_RAM(DAC8574ADDR, 3, dac8574.d.bytes),
+  I2C_END()
+};
+
+static volatile int32_t ADS8694_value[4] = { 0, 0, 0, 0};
+
+SIGNAL(TIMER1_COMPA_vect)
+{
+  unsigned char data[5];
+
+  SPCR = (1<<SPE) | (1<<MSTR) | (1<< CPHA);
+  PORT_SPI &= ~(1<<DD_SS); // Chip select
+  SPDR = 0x00; // NOP
+  for (int i = 0 ; i < 5 ; i++) {
+    while ((SPSR & 0x80) == 0) { }
+    data[i] = SPDR;
+    if (i < 4) {
+      // More to read
+      SPDR = 0;
+    }
+  }
+  PORT_SPI |= (1<<DD_SS); // Chip deselect
+
+  union {
+    int32_t i;
+    unsigned char b[4];
+  } v;
+  uint8_t chan;
+  v.i = 0;
+  v.b[2] = data[2];
+  v.b[1] = data[3];
+  v.b[0] = data[4];
+  chan = (v.i>>2) & 0xf;
+  ADS8694_value[chan] = (v.i>>6) - 0x20000;
+}
+
+static int16_t v = 0;
+SIGNAL(USART_RXC_vect)
+{
+  char ch = UDR;
+  
+  puthex(ch);
+  putstr("RX Status:");
+  puthex(TWSR);
+  puthex(TWCR);
+  if (ch == '+') {
+    v += 1024;
+  } else if (ch == '-') {
+    v -= 1024;
+  }
+  putstr("\r\n");
+}
+
+int main()
+{
+  /* Setup serial port */
+  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 
+
+  /* Setup timer1 */
+  TCCR1A = 0x00;    // Normal port mode (no output)
+  TCCR1B = 0x09;    // Clock / 1
+  OCR1A = 0x3fff;
+  TIMSK =  0x10;    // Enable Timer1 compare A interrupts 
+
+  /* Setup I2C (TWI), used by DAC8574 */
+  TWBR = 0x10;
+  TWSR = 0x00;
+
+  /* Setup DAC8574 (SPI setup is done in ADS8694_W) */
+  static const __flash uint8_t RST[] = { 0x85, 0x00, 0x00 };
+  static const __flash uint8_t SEQUENCE[] = { (0x01<<1)|0x01, 0x0f, 0x00 };
+  static const __flash uint8_t FEATURES[] = { (0x03<<1)|0x01, 0x03, 0x00 };
+  static const __flash uint8_t AUTO_RST[] = { 0xa0, 0x00, 0x00, 0x00, 0x00 };
+  ADS8694_W(3, RST);
+  ADS8694_W(3, SEQUENCE);
+  ADS8694_W(3, FEATURES);
+  ADS8694_W(5, AUTO_RST);
+
+  sei();      // Global interrupt enable
+  
+  putstr("\r\n\r\n");
+  i2c_start(write_all);
+
+  while (1) {
+    struct dac8574 tmp;
+    dac8574_set_chan(&tmp.a, 0x8000);
+    dac8574_set_chan(&tmp.b, 0x8000);
+    dac8574_set_chan(&tmp.c, v+0x0000);
+    dac8574_set_chan(&tmp.d, v+0x8000);
+//    v = (v + 1024) & 0xffff;
+    putstr("i2c status=");
+    putint16(i2c_ctx.status);
+    putstr(" v=");
+    putint16(v);
+    putint16(v+0x8000);
+    for (int i = 0 ; i < 4 ; i++) {
+      putint32(ADS8694_value[i]);
+    }
+    putstr("\r");
+    if (i2c_idle()) {
+      cli();
+      *dac8574.a.reg.data = *tmp.a.reg.data;
+      *dac8574.b.reg.data = *tmp.b.reg.data;
+      *dac8574.c.reg.data = *tmp.c.reg.data;
+      *dac8574.d.reg.data = *tmp.d.reg.data;
+      i2c_start(write_all);
+      sei();
+      putstr("\r\n");
+    }
+  }
+}
diff --git a/china_io-2019/avr/i2c_master.h b/china_io-2019/avr/i2c_master.h
new file mode 100644
index 0000000000000000000000000000000000000000..51ee51d54880779d7e227fc2b4230970b6abbe3b
--- /dev/null
+++ b/china_io-2019/avr/i2c_master.h
@@ -0,0 +1,335 @@
+#ifndef __I2C_MASTER_H__
+#define __I2C_MASTER_H__
+
+#include <util/atomic.h>
+
+enum i2c_op {
+  I2C_R_OP=0x01,
+  I2C_W_FLASH_OP,
+  I2C_W_RAM_OP,
+  I2C_W_1_OP,
+  I2C_W_2_OP,
+  I2C_W_3_OP,
+  I2C_CALL_OP,
+  I2C_CHAIN_OP,
+  I2C_END_OP,
+};
+
+struct i2c_transcation {
+  enum i2c_op op:8;
+  union {
+    struct i2c_read {
+      unsigned char address;
+      unsigned char count;
+      void volatile *data;
+    } read;
+    struct i2c_write_flash {
+      unsigned char address;
+      unsigned char count;
+      const volatile unsigned __flash char *data;
+    } write_flash;
+    struct i2c_write_ram {
+      unsigned char address;
+      unsigned char count;
+      const volatile unsigned char *data;
+    } write_ram;
+    struct i2c_write {
+      unsigned char address;
+      const volatile unsigned char data[3];
+    } write;
+    struct i2c_callback {
+      unsigned char (*function)(const volatile void *context);
+      const volatile void *context;
+    } callback;
+    struct i2c_chain {
+      const __flash struct i2c_transcation *transaction;
+    } chain;
+  };
+};
+
+static volatile struct i2c_ctx {
+  const __flash struct i2c_transcation *transaction;
+//  unsigned char length;
+  unsigned char pos;
+  char status;
+} i2c_ctx;
+
+#define I2C_READ(ADDR, COUNT, DATA)                                     \
+  {.op=I2C_R_OP,{.read={.address=ADDR,.count=COUNT, .data=DATA}}}
+#define I2C_WRITE_FLASH(ADDR, COUNT, DATA) \
+  {.op=I2C_W_FLASH_OP,{.write_flash={.address=ADDR,.count=COUNT,.data=DATA}}}
+#define I2C_WRITE_RAM(ADDR, COUNT, DATA) \
+  {.op=I2C_W_RAM_OP,{.write_ram={.address=ADDR,.count=COUNT,.data=DATA}}}
+#define I2C_WRITE_1(ADDR, DATA0) \
+  {.op=I2C_W_1_OP,{.write={.address=ADDR,       \
+                           .data[0]=DATA0}}}
+#define I2C_WRITE_2(ADDR, DATA0, DATA1)         \
+  {.op=I2C_W_2_OP,{.write={.address=ADDR,       \
+                           .data[0]=DATA0,      \
+                           .data[1]=DATA1}}}
+#define I2C_WRITE_3(ADDR, DATA0, DATA1, DATA2)  \
+  {.op=I2C_W_3_OP,{.write={.address=ADDR,       \
+                           .data[0]=DATA0,      \
+                           .data[1]=DATA1,      \
+                           .data[2]=DATA2}}}
+#define I2C_CALLBACK(FUNC, CONTEXT) \
+  {.op=I2C_CALL_OP,{.callback={.function=FUNC,.context=CONTEXT}}}
+#define I2C_CHAIN(TRANSACTION) \
+  {.op=I2C_CHAIN_OP,{.chain={.transaction=TRANSACTION}}}
+#define I2C_END()                                     \
+  {.op=I2C_END_OP,{}}
+
+static unsigned char i2c_chain(
+  const  __flash struct i2c_transcation *transaction)
+{
+  i2c_ctx.transaction = transaction; 
+  i2c_ctx.pos = 0;
+  i2c_ctx.status = 0;
+  return 0;
+}
+
+static unsigned char i2c_start(
+  const  __flash struct i2c_transcation *transaction)
+{
+  i2c_chain(transaction);
+  TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN) | (1<<TWIE);
+  return 0;
+}
+
+/* Return number of bytes in current transaction */
+static unsigned char i2c_count()
+{
+  struct i2c_transcation tr = i2c_ctx.transaction[i2c_ctx.pos];
+  switch (tr.op) {
+    case I2C_CALL_OP:    return 0; // ERROR
+    case I2C_R_OP:       return tr.read.count;
+    case I2C_W_FLASH_OP: return tr.write_flash.count;
+    case I2C_W_RAM_OP:   return tr.write_ram.count;
+    case I2C_W_1_OP:     return 1;
+    case I2C_W_2_OP:     return 2;
+    case I2C_W_3_OP:     return 3;
+    case I2C_CHAIN_OP:   return 0; // ERROR
+    case I2C_END_OP:     return 0; // ERROR
+  }
+  return 0;
+}
+
+static unsigned char i2c_get_data(unsigned char index)
+{
+  struct i2c_transcation tr = i2c_ctx.transaction[i2c_ctx.pos];
+  switch (tr.op) {
+    case I2C_CALL_OP:    return 0; // ERROR
+    case I2C_R_OP:       return 0; // ERROR 
+    case I2C_W_FLASH_OP: return tr.write_flash.data[index];
+    case I2C_W_RAM_OP:   return tr.write_ram.data[index];
+    case I2C_W_1_OP:     return tr.write.data[index];
+    case I2C_W_2_OP:     return tr.write.data[index];
+    case I2C_W_3_OP:     return tr.write.data[index];
+    case I2C_CHAIN_OP:   return 0; // ERROR
+    case I2C_END_OP:     return 0; // ERROR
+  }
+  return 0;
+}
+
+static void i2c_put_data(unsigned char index, unsigned char data)
+{
+  struct i2c_transcation tr = i2c_ctx.transaction[i2c_ctx.pos];
+  switch (tr.op) {
+    case I2C_CALL_OP:    break;
+    case I2C_R_OP: {
+      if (index < tr.read.count) {
+        ((unsigned char *)tr.read.data)[index] = data;
+      }
+    } break;
+    case I2C_W_FLASH_OP: break;
+    case I2C_W_RAM_OP:   break;
+    case I2C_W_1_OP:     break;
+    case I2C_W_2_OP:     break;
+    case I2C_W_3_OP:     break;
+    case I2C_CHAIN_OP:   break;
+    case I2C_END_OP:     break;
+  }
+}
+static char i2c_callback()
+{
+  while (i2c_ctx.status == 0) {
+    enum i2c_op op = i2c_ctx.transaction[i2c_ctx.pos].op;
+    if (op == I2C_END_OP) {
+      break;
+    } else if (i2c_ctx.transaction[i2c_ctx.pos].op == I2C_CALL_OP) {
+      struct i2c_callback cb = i2c_ctx.transaction[i2c_ctx.pos].callback;
+      i2c_ctx.status = cb.function(cb.context);
+      if (i2c_ctx.status == 0) {
+        i2c_ctx.pos++;
+        continue;
+      }
+    } else if (i2c_ctx.transaction[i2c_ctx.pos].op == I2C_CHAIN_OP) {
+      i2c_chain(i2c_ctx.transaction[i2c_ctx.pos].chain.transaction);
+      continue;
+    } else {
+      break;
+    } 
+  } 
+  return i2c_ctx.status;
+}
+
+static char i2c_next()
+{
+#if 0
+  putstr("POS: ");
+  puthex(i2c_ctx.pos);
+  putstr(" OP: ");
+  puthex( i2c_ctx.transaction[i2c_ctx.pos].op);
+  putstr("\r\n");
+#endif
+  if (i2c_ctx.status != 0) {
+    return i2c_ctx.status;
+  }
+
+  if (i2c_ctx.transaction[i2c_ctx.pos].op != I2C_END_OP) {
+    i2c_ctx.pos++;
+    i2c_callback();
+  }
+  
+  if (i2c_ctx.status != 0) {
+    return i2c_ctx.status;
+  }
+  return i2c_ctx.transaction[i2c_ctx.pos].op != I2C_END_OP;
+}
+
+static char i2c_more(unsigned char index)
+{
+  if (i2c_ctx.status != 0) {
+    return i2c_ctx.status;
+  }
+  return index < i2c_count();
+}
+
+static char i2c_idle()
+{
+  char result = 1;
+  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+    if (i2c_ctx.transaction &&
+        i2c_ctx.transaction[i2c_ctx.pos].op != I2C_END_OP) {
+      result = 0;
+    }
+  }
+  return result;
+}
+
+SIGNAL(TWI_vect)
+{
+//  unsigned char twsr = TWSR;
+  unsigned char twcr = 0x00;
+  static unsigned char data_pos = 0x00;
+
+  // Consume callback operations
+  if (i2c_callback() != 0) {
+//    putstr("Error\r\n");
+  }
+
+  switch (TW_STATUS) {
+    case TW_START:
+    case TW_REP_START:
+    {
+      switch (i2c_ctx.transaction[i2c_ctx.pos].op) {
+        case I2C_CALL_OP:
+        case I2C_CHAIN_OP:
+        case I2C_END_OP:
+        {
+          // ERROR
+        } break;
+        case I2C_R_OP: {
+          // send SLA+R
+          TWDR = (i2c_ctx.transaction[i2c_ctx.pos].read.address << 1) | 1;
+        } break;
+        case I2C_W_FLASH_OP: {
+          // send SLA+W
+          TWDR = (i2c_ctx.transaction[i2c_ctx.pos].write_flash.address << 1);
+        } break;
+        case I2C_W_RAM_OP: {
+          // send SLA+W
+          TWDR = (i2c_ctx.transaction[i2c_ctx.pos].write_ram.address << 1);
+        } break;
+        case I2C_W_1_OP:
+        case I2C_W_2_OP:
+        case I2C_W_3_OP: {
+          // send SLA+W
+          TWDR = (i2c_ctx.transaction[i2c_ctx.pos].write.address << 1);
+        } break;
+      }
+      twcr = 0;
+      data_pos = 0;
+    } break;
+    case TW_MT_SLA_ACK:
+    case TW_MT_DATA_ACK:
+    {
+      // Send next byte
+      if (i2c_more(data_pos)) {
+        TWDR = i2c_get_data(data_pos);
+        twcr = 0;
+        data_pos++;
+      } else {
+        if (i2c_next()) {
+          // Repeated start
+          twcr = (1 << TWSTA);
+        } else {
+          // Done
+          twcr = (1 << TWSTO);          
+        }
+      }
+    } break;
+    case TW_MR_SLA_ACK:
+    {
+      if (i2c_more(data_pos + 1)) {
+        // Receive byte, send ACK
+        twcr = (1<<TWEA);
+      } else {
+        // Receive last byte, send NOT ACK
+        twcr = 0;
+      }
+    } break;
+    case TW_MR_DATA_ACK: 
+    case TW_MR_DATA_NACK:
+    {
+      // Data received:
+      i2c_put_data(data_pos, TWDR);
+      data_pos++;
+      if (i2c_more(data_pos+1)) {
+        // Receive byte, send ACK
+        twcr = (1<<TWEA);
+      } else if (i2c_more(data_pos)) {
+        // Receive last byte, send NOT ACK
+        twcr = 0;
+     } else if (i2c_next()) {
+        // Send  repeated START
+        twcr = (1 << TWSTA);
+      } else {
+        // Send STOP
+        twcr = (1<<TWSTO);
+      }
+    } break;
+    default: {
+      i2c_ctx.status = TW_STATUS | 0x01;
+      twcr = (1<<TWSTO);
+      // Done
+#if 0
+  putstr("TWSR: ");
+  puthex(TW_STATUS);
+  putstr(" TWCR: ");
+  puthex(twcr);
+  putstr("\r\n");
+#endif
+    } break;
+  }
+#if 0
+  putstr("TWSR: ");
+  puthex(TW_STATUS);
+  putstr(" TWCR: ");
+  puthex(twcr);
+  putstr("\r\n");
+#endif
+  TWCR = twcr | (1<<TWINT) | (1<<TWEN) |(1<<TWIE);
+}
+
+#endif