/* * USB (CDC-ACM) <-> I2C bridge, ATmega8U2 @ 16 MHz. * * Request frame (host -> device), several frames may be concatenated freely: * A5 01 addr wlen rlen wdata[wlen] I2C transaction (7-bit addr, wlen/rlen <= 32) * A5 02 n set SCL delay (0 = fastest, default 4 ~ 400 kHz) * A5 03 ping * A5 04 bus recovery * Reply (device -> host), one per request, in order: * status [payload] * status: 0 OK, 1 NACK on address, 2 NACK on data, 3 bus error/timeout, * 0x10 unknown op, 0x11 bad length * payload: XFER -> rlen bytes (only if status==0); PING -> proto, maxlen * Bytes outside a frame are ignored (so ModemManager "AT" probes are harmless). * * The same device also speaks the i2c-tiny-usb protocol on EP0 (tiny.c), so the * Linux i2c-tiny-usb driver can register it as a kernel I2C adapter. */ #include #include #include #include "usb.h" #include "i2c.h" #include "tiny.h" #define MAGIC 0xA5 #define MAXLEN 32 #define PROTO 1 enum { OP_XFER = 1, OP_SPEED = 2, OP_PING = 3, OP_RECOVER = 4, OP_LINES = 5 }; enum { ST_BAD_OP = 0x10, ST_BAD_LEN = 0x11 }; static uint8_t cdc_delay = 4; /* own SCL setting, ~400 kHz */ static uint8_t txb[USB_PACKET]; static uint8_t txn; static void flush(void) { if (txn) { usb_tx(txb, txn); txn = 0; } } static uint8_t *reply_begin(uint8_t len) /* reserve len bytes in the tx buffer */ { if (txn + len > USB_PACKET) flush(); uint8_t *p = &txb[txn]; txn += len; return p; } static void reply1(uint8_t st) { *reply_begin(1) = st; } /* ---- frame parser ---- */ static uint8_t fbuf[4 + MAXLEN]; static uint8_t fn; static bool in_frame; static void frame_done(void) { in_frame = false; fn = 0; } static void feed(uint8_t b) { if (!in_frame) { if (b == MAGIC) { in_frame = true; fn = 0; } return; } fbuf[fn++] = b; uint8_t need; switch (fbuf[0]) { case OP_XFER: if (fn < 4) return; if (fbuf[2] > MAXLEN || fbuf[3] > MAXLEN) { reply1(ST_BAD_LEN); frame_done(); return; } need = 4 + fbuf[2]; break; case OP_SPEED: case OP_LINES: need = 2; break; case OP_PING: case OP_RECOVER: need = 1; break; default: reply1(ST_BAD_OP); frame_done(); return; } if (fn < need) return; switch (fbuf[0]) { case OP_XFER: { uint8_t rlen = fbuf[3]; uint8_t *p = reply_begin(1 + rlen); /* reserve worst case, trim on error */ uint8_t st = i2c_xfer(fbuf[1] & 0x7F, &fbuf[4], fbuf[2], p + 1, rlen); p[0] = st; if (st != I2C_OK) txn -= rlen; break; } case OP_SPEED: cdc_delay = fbuf[1]; i2c_set_delay(cdc_delay); reply1(0); break; case OP_PING: { uint8_t *p = reply_begin(3); p[0] = 0; p[1] = PROTO; p[2] = MAXLEN; break; } case OP_RECOVER: reply1(i2c_recover()); break; case OP_LINES: { uint8_t *p = reply_begin(2); p[0] = 0; p[1] = i2c_lines(fbuf[1]); break; } } frame_done(); } int main(void) { MCUSR = 0; wdt_disable(); CLKPR = 0x80; CLKPR = 0; /* full 16 MHz, no prescaler */ i2c_init(); usb_init(); TCCR0B = (1 << CS02) | (1 << CS00); /* clk/1024: overflow every ~16.4 ms */ uint8_t idle = 0; uint8_t rx[USB_PACKET]; for (;;) { usb_task(); uint8_t n = tiny_busy() ? 0 : usb_rx(rx); /* don't interleave with a kernel transaction */ if (n) { idle = 0; i2c_set_delay(cdc_delay); /* the kernel interface has its own speed */ for (uint8_t i = 0; i < n; i++) feed(rx[i]); flush(); /* one IN packet for the whole OUT packet */ } if (TIFR0 & (1 << TOV0)) { /* resync if a frame stalls ~100 ms */ TIFR0 = (1 << TOV0); tiny_tick(); if (in_frame && ++idle > 6) frame_done(); } } }