main.c 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. /*
  2. * USB (CDC-ACM) <-> I2C bridge, ATmega8U2 @ 16 MHz.
  3. *
  4. * Request frame (host -> device), several frames may be concatenated freely:
  5. * A5 01 addr wlen rlen wdata[wlen] I2C transaction (7-bit addr, wlen/rlen <= 32)
  6. * A5 02 n set SCL delay (0 = 400 kHz, the default)
  7. * A5 03 ping
  8. * A5 04 bus recovery
  9. * Reply (device -> host), one per request, in order:
  10. * status [payload]
  11. * status: 0 OK, 1 NACK on address, 2 NACK on data, 3 bus error/timeout,
  12. * 0x10 unknown op, 0x11 bad length
  13. * payload: XFER -> rlen bytes (only if status==0); PING -> proto, maxlen
  14. * Bytes outside a frame are ignored (so ModemManager "AT" probes are harmless).
  15. *
  16. * The same device also speaks the i2c-tiny-usb protocol on EP0 (tiny.c), so the
  17. * Linux i2c-tiny-usb driver can register it as a kernel I2C adapter.
  18. */
  19. #include <avr/io.h>
  20. #include <avr/wdt.h>
  21. #include <string.h>
  22. #include "usb.h"
  23. #include "i2c.h"
  24. #include "tiny.h"
  25. #define MAGIC 0xA5
  26. #define MAXLEN 32
  27. #define PROTO 1
  28. enum { OP_XFER = 1, OP_SPEED = 2, OP_PING = 3, OP_RECOVER = 4, OP_LINES = 5 };
  29. enum { ST_BAD_OP = 0x10, ST_BAD_LEN = 0x11 };
  30. static uint8_t cdc_delay = 0; /* own SCL setting, 400 kHz */
  31. static uint8_t txb[USB_PACKET];
  32. static uint8_t txn;
  33. static void flush(void)
  34. {
  35. if (txn) { usb_tx(txb, txn); txn = 0; }
  36. }
  37. static uint8_t *reply_begin(uint8_t len) /* reserve len bytes in the tx buffer */
  38. {
  39. if (txn + len > USB_PACKET) flush();
  40. uint8_t *p = &txb[txn];
  41. txn += len;
  42. return p;
  43. }
  44. static void reply1(uint8_t st) { *reply_begin(1) = st; }
  45. /* ---- frame parser ---- */
  46. static uint8_t fbuf[4 + MAXLEN];
  47. static uint8_t fn;
  48. static bool in_frame;
  49. static void frame_done(void) { in_frame = false; fn = 0; }
  50. static void feed(uint8_t b)
  51. {
  52. if (!in_frame) {
  53. if (b == MAGIC) { in_frame = true; fn = 0; }
  54. return;
  55. }
  56. fbuf[fn++] = b;
  57. uint8_t need;
  58. switch (fbuf[0]) {
  59. case OP_XFER:
  60. if (fn < 4) return;
  61. if (fbuf[2] > MAXLEN || fbuf[3] > MAXLEN) { reply1(ST_BAD_LEN); frame_done(); return; }
  62. need = 4 + fbuf[2];
  63. break;
  64. case OP_SPEED:
  65. case OP_LINES: need = 2; break;
  66. case OP_PING:
  67. case OP_RECOVER: need = 1; break;
  68. default: reply1(ST_BAD_OP); frame_done(); return;
  69. }
  70. if (fn < need) return;
  71. switch (fbuf[0]) {
  72. case OP_XFER: {
  73. uint8_t rlen = fbuf[3];
  74. uint8_t *p = reply_begin(1 + rlen); /* reserve worst case, trim on error */
  75. uint8_t st = i2c_xfer(fbuf[1] & 0x7F, &fbuf[4], fbuf[2], p + 1, rlen);
  76. p[0] = st;
  77. if (st != I2C_OK) txn -= rlen;
  78. break; }
  79. case OP_SPEED: cdc_delay = fbuf[1]; i2c_set_delay(cdc_delay); reply1(0); break;
  80. case OP_PING: { uint8_t *p = reply_begin(3); p[0] = 0; p[1] = PROTO; p[2] = MAXLEN; break; }
  81. case OP_RECOVER: reply1(i2c_recover()); break;
  82. case OP_LINES: { uint8_t *p = reply_begin(2); p[0] = 0; p[1] = i2c_lines(fbuf[1]); break; }
  83. }
  84. frame_done();
  85. }
  86. int main(void)
  87. {
  88. MCUSR = 0;
  89. wdt_disable();
  90. CLKPR = 0x80; CLKPR = 0; /* full 16 MHz, no prescaler */
  91. i2c_init();
  92. usb_init();
  93. TCCR0B = (1 << CS02) | (1 << CS00); /* clk/1024: overflow every ~16.4 ms */
  94. uint8_t idle = 0;
  95. uint8_t rx[USB_PACKET];
  96. for (;;) {
  97. usb_task();
  98. uint8_t n = tiny_busy() ? 0 : usb_rx(rx); /* don't interleave with a kernel transaction */
  99. if (n) {
  100. idle = 0;
  101. i2c_set_delay(cdc_delay); /* the kernel interface has its own speed */
  102. for (uint8_t i = 0; i < n; i++) feed(rx[i]);
  103. flush(); /* one IN packet for the whole OUT packet */
  104. }
  105. if (TIFR0 & (1 << TOV0)) { /* resync if a frame stalls ~100 ms */
  106. TIFR0 = (1 << TOV0);
  107. tiny_tick();
  108. if (in_frame && ++idle > 6) frame_done();
  109. }
  110. }
  111. }