| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- /*
- * 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 <avr/io.h>
- #include <avr/wdt.h>
- #include <string.h>
- #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();
- }
- }
- }
|