# USB-I2C bridge (ATmega8U2) USB to I2C master for ATmega8U2. No LUFA, no interrupts: ~3.6 KB flash. Composite device with two independent ways in: * **CDC-ACM virtual COM port** with a fast batched binary protocol (below, `host/usbi2c.py`). * **i2c-tiny-usb vendor interface** (EP0 control requests only): the mainline Linux `i2c-tiny-usb` driver registers it as a normal kernel I2C adapter (`/dev/i2c-N`, i2c-tools, kernel client drivers). * Windows 10/11: binds inbox `usbser.sys` automatically (no driver/INF). Windows 7/8 need a stock CDC INF. * Windows: the vendor (i2c-tiny-usb) interface has no driver and shows up as an unknown device; harmless. * Linux: `cdc_acm` -> `/dev/ttyACMx`. Add the udev rule below so ModemManager does not probe it. * I2C is bit-banged (the 8U2 has no TWI): SCL=PD3, SDA=PD2, open drain. **External pull-ups (2.2-4.7 k to 5 V) are required.** Default ~400 kHz (`set_delay(4)`), `set_delay(0)` is ~1 MHz (TCA9539 supports Fm+ if your pull-ups/capacitance allow). * Clock stretching supported; stuck-SDA bus recovery runs automatically before a transaction. ## Build / flash (USBasp) ``` cd firmware make make fuses # once: external crystal, CKDIV8 off (lfuse FF hfuse D9 efuse F4 - same as Arduino Uno 8U2) make flash ``` Without the crystal fuse the chip runs from the 8 MHz/8 RC and USB will not enumerate. Flashing over ISP overwrites any DFU bootloader. Use `make VBUS=1` if PB6 carries a VBUS sense (attach to USB only while PB6 high); not needed for a bus-powered board. ## Protocol (binary, 0xA5-framed; frames may be concatenated) | Request | Reply | |---|---| | `A5 01 addr wlen rlen wdata..` (I2C xfer, 7-bit addr, <=32 B per phase; wlen&rlen>0 = repeated START; both 0 = probe) | `status [rdata]` | | `A5 02 n` set SCL delay | `status` | | `A5 03` ping | `00 proto maxlen` | | `A5 04` bus recovery | `status` | Status: 0 OK, 1 NACK addr, 2 NACK data, 3 bus error, 0x10 bad op, 0x11 bad length. Bytes outside a frame are ignored and a stalled frame is dropped after ~100 ms. **Speed:** a USB bulk round trip is ~1 ms, so unbatched transactions are ~1000/s. Pipeline many frames in one write (`Bridge.batch`): the device answers a whole 64-byte OUT packet with one IN packet. ## Host ``` pip install pyserial python host/usbi2c.py COM5 # or /dev/ttyACM0 : scan, blink port 0, benchmark ``` ```python from usbi2c import Bridge, TCA9539 io = TCA9539(Bridge("COM5"), 0x74) # A1/A0 select 0x74..0x77 io.set_direction(0xFF00); io.write(0x00AA); print(hex(io.read())) ``` Linux udev (`/etc/udev/rules.d/99-usbi2c.rules`): ``` SUBSYSTEM=="tty", ATTRS{idVendor}=="1c40", ATTRS{idProduct}=="0534", ENV{ID_MM_DEVICE_IGNORE}="1", MODE="0666" SUBSYSTEM=="i2c-dev", ATTRS{idVendor}=="1c40", ATTRS{idProduct}=="0534", MODE="0666" ``` VID/PID 1c40:0534 is the EZPrototypes i2c-tiny-usb ID, used so the stock kernel driver binds without changes. It is not ours: development use only, change it in `usb.c` for anything shipped (then bind with `echo VID PID > /sys/bus/usb/drivers/i2c-tiny-usb/new_id`). ## Linux kernel I2C adapter (i2c-tiny-usb) Kernel config: `CONFIG_I2C_TINY_USB=m` (Device Drivers > I2C > I2C Hardware Bus support > Tiny-USB adapter) and `CONFIG_I2C_CHARDEV` for `/dev/i2c-N`. ``` modprobe i2c-tiny-usb # optional: delay=N (SCL half period in us, default 10 = ~100 kHz) i2cdetect -l # "i2c-tiny-usb at bus 00X device 00Y" i2cdetect -y N ``` `delay`: 0 = ~1 MHz, 1 = ~570 kHz, 2 = ~250 kHz, 10 = ~100 kHz (approximate, verify with a scope). It is sent when the driver binds. The kernel interface and the CDC `set_delay` command keep separate settings. Kernel client drivers work too, e.g. TCA9539 (needs `CONFIG_GPIOLIB` + `CONFIG_GPIO_PCA953X`): `echo tca9539 0x74 > /sys/bus/i2c/devices/i2c-N/new_device`. Notes: * `cdc_acm` keeps interfaces 0/1 (grouped by an IAD, which `i2c-tiny-usb` refuses); `i2c-tiny-usb` gets interface 2. Both can be used at the same time: while a kernel transaction is open (START sent, no STOP yet) CDC requests wait; an abandoned transaction is dropped after ~100 ms. * Each `i2c_msg` costs two control transfers, so a register read is ~4 ms. Use the CDC path for bulk work. * Address NAK -> `-ENXIO`; data NAK or bus error -> `-EIO`. No 10-bit addresses, no protocol mangling.