/* * Minimal polled USB CDC-ACM stack for ATmega8U2 @ 16 MHz crystal. * * Composite device: * IAD -> iface 0 (CDC comm) + iface 1 (CDC data): virtual COM port (cdc_acm / usbser) * iface 2: vendor class, no endpoints - i2c-tiny-usb protocol on EP0 (see tiny.c) * * Endpoints (176 B DPRAM total on 8U2): * EP0 control 32 B * EP1 bulk OUT 64 B (host -> device) * EP2 bulk IN 64 B (device -> host) * EP3 interrupt IN 8 B (CDC notification, never used) */ #include #include #include #include "usb.h" /* i2c-tiny-usb (EZPrototypes) VID/PID, matched by the mainline i2c-tiny-usb driver. * Not ours to ship - private/development use only. */ #define USB_VID 0x1C40 #define USB_PID 0x0534 #define EP_OUT 1 #define EP_IN 2 #define EP_NOTIF 3 #ifndef USE_VBUS_DETECT #define USE_VBUS_DETECT 0 #endif /* ---------------------------------------------------------------- descriptors */ static const uint8_t dev_desc[] PROGMEM = { 18, 1, 0x00, 0x02, /* USB 2.0 */ 0xEF, 0x02, 0x01, /* misc class, IAD composite */ EP0_SIZE, USB_VID & 0xFF, USB_VID >> 8, USB_PID & 0xFF, USB_PID >> 8, 0x00, 0x01, /* bcdDevice */ 1, 2, 0, /* strings: mfr, product, no serial */ 1 /* configurations */ }; static const uint8_t cfg_desc[] PROGMEM = { 9, 2, 84, 0, 3, 1, 0, 0x80, 50, /* config, 3 ifaces, bus powered 100 mA */ 8, 0x0B, 0, 2, 0x02, 0x02, 0x00, 0, /* IAD: ifaces 0-1 are one CDC function */ /* Communication interface */ 9, 4, 0, 0, 1, 0x02, 0x02, 0x00, 0, /* CDC, ACM, no protocol */ 5, 0x24, 0x00, 0x10, 0x01, /* header, CDC 1.10 */ 5, 0x24, 0x01, 0x00, 1, /* call management, data iface 1 */ 4, 0x24, 0x02, 0x02, /* ACM: line coding + break */ 5, 0x24, 0x06, 0, 1, /* union */ 7, 5, 0x80 | EP_NOTIF, 3, 8, 0, 0xFF, /* notification EP */ /* Data interface */ 9, 4, 1, 0, 2, 0x0A, 0x00, 0x00, 0, 7, 5, EP_OUT, 2, USB_PACKET, 0, 0, 7, 5, 0x80 | EP_IN, 2, USB_PACKET, 0, 0, /* i2c-tiny-usb interface: vendor class, control endpoint only */ 9, 4, 2, 0, 0, 0xFF, 0x00, 0x00, 0 }; static const char str_mfr[] PROGMEM = "Open"; static const char str_prod[] PROGMEM = "USB-I2C Bridge"; /* ---------------------------------------------------------------- state */ static uint8_t configured; static uint8_t line_coding[7] = { 0x00, 0xC2, 0x01, 0x00, 0, 0, 8 }; /* 115200 8N1 */ bool usb_ready(void) { return configured; } /* ---------------------------------------------------------------- EP0 helpers */ bool ep0_wait(uint8_t mask) { uint16_t t = 40000; /* ~100 ms */ while (!(UEINTX & mask)) { if ((UEINTX & (1 << RXSTPI)) || (UDINT & (1 << EORSTI))) return false; _delay_us(2); if (!--t) return false; } return true; } void ep0_status_in(void) /* zero-length IN status stage */ { if (ep0_wait(1 << TXINI)) UEINTX = ~(1 << TXINI); } void ep0_stall(void) { UECONX |= (1 << STALLRQ); } void ep0_send(const void *p, uint16_t len, uint16_t wlen, bool pgm) { const uint8_t *s = p; bool zlp = len < wlen; if (len > wlen) len = wlen; for (;;) { if (!ep0_wait((1 << TXINI) | (1 << RXOUTI))) return; if (UEINTX & (1 << RXOUTI)) { UEINTX = ~(1 << RXOUTI); return; } /* host aborted */ uint8_t n = len > EP0_SIZE ? EP0_SIZE : (uint8_t)len; for (uint8_t i = 0; i < n; i++) UEDATX = pgm ? pgm_read_byte(s++) : *s++; UEINTX = ~(1 << TXINI); len -= n; if (n < EP0_SIZE) break; /* short packet ends transfer */ if (len == 0 && !zlp) break; } if (ep0_wait(1 << RXOUTI)) UEINTX = ~(1 << RXOUTI); /* status stage */ } uint8_t ep0_rx(uint8_t *buf) /* one OUT data-stage packet */ { if (!ep0_wait(1 << RXOUTI)) return 0xFF; uint8_t n = UEBCLX; for (uint8_t i = 0; i < n; i++) buf[i] = UEDATX; UEINTX = ~(1 << RXOUTI); return n; } bool ep0_tx(const uint8_t *buf, uint8_t n) /* one IN data-stage packet */ { if (!ep0_wait((1 << TXINI) | (1 << RXOUTI))) return false; if (UEINTX & (1 << RXOUTI)) { UEINTX = ~(1 << RXOUTI); return false; } /* host aborted */ for (uint8_t i = 0; i < n; i++) UEDATX = buf[i]; UEINTX = ~(1 << TXINI); return true; } void ep0_status_out(void) /* zero-length OUT status stage after IN data */ { if (ep0_wait(1 << RXOUTI)) UEINTX = ~(1 << RXOUTI); } static uint8_t build_string(uint8_t *out, const char *pgm_s) { uint8_t n = 0, c; while (n < 24 && (c = pgm_read_byte(pgm_s++))) { out[2 + 2 * n] = c; out[3 + 2 * n] = 0; n++; } out[0] = 2 + 2 * n; out[1] = 3; return out[0]; } static void eps_configure(void) { for (uint8_t e = EP_OUT; e <= EP_NOTIF; e++) { UENUM = e; UECONX = 0; UECFG1X = 0; } /* allocation must be ascending */ UENUM = EP_OUT; UECONX = (1 << EPEN); UECFG0X = (2 << EPTYPE0); UECFG1X = (3 << EPSIZE0) | (1 << ALLOC); UENUM = EP_IN; UECONX = (1 << EPEN); UECFG0X = (2 << EPTYPE0) | (1 << EPDIR); UECFG1X = (3 << EPSIZE0) | (1 << ALLOC); UENUM = EP_NOTIF; UECONX = (1 << EPEN); UECFG0X = (3 << EPTYPE0) | (1 << EPDIR); UECFG1X = (0 << EPSIZE0) | (1 << ALLOC); UERST = 0x0E; UERST = 0; UENUM = 0; /* back to EP0: the SET_CONFIGURATION status stage follows */ } static void ep0_setup(void) { uint8_t bmRT = UEDATX, bReq = UEDATX; uint16_t wV = UEDATX; wV |= (uint16_t)UEDATX << 8; uint16_t wI = UEDATX; wI |= (uint16_t)UEDATX << 8; uint16_t wL = UEDATX; wL |= (uint16_t)UEDATX << 8; UEINTX = ~(1 << RXSTPI); uint8_t type = bmRT & 0x60; if (type == 0x00) { /* standard */ switch (bReq) { case 0: /* GET_STATUS */ { static const uint8_t z[2] = {0, 0}; ep0_send(z, 2, wL, false); break; } case 1: case 3: /* CLEAR/SET_FEATURE */ ep0_status_in(); break; case 5: /* SET_ADDRESS */ UDADDR = wV & 0x7F; ep0_status_in(); if (ep0_wait(1 << TXINI)) UDADDR |= (1 << ADDEN); break; case 6: /* GET_DESCRIPTOR */ { uint8_t t = wV >> 8, idx = wV & 0xFF; uint8_t buf[2 + 48]; if (t == 1) ep0_send(dev_desc, sizeof dev_desc, wL, true); else if (t == 2 && idx == 0) ep0_send(cfg_desc, sizeof cfg_desc, wL, true); else if (t == 3 && idx == 0) { buf[0] = 4; buf[1] = 3; buf[2] = 0x09; buf[3] = 0x04; ep0_send(buf, 4, wL, false); } else if (t == 3 && idx == 1) ep0_send(buf, build_string(buf, str_mfr), wL, false); else if (t == 3 && idx == 2) ep0_send(buf, build_string(buf, str_prod), wL, false); else ep0_stall(); break; } case 8: ep0_send(&configured, 1, wL, false); break; /* GET_CONFIGURATION */ case 9: /* SET_CONFIGURATION */ if ((wV & 0xFF) <= 1) { configured = wV & 0xFF; if (configured) eps_configure(); ep0_status_in(); } else ep0_stall(); break; case 10: { static const uint8_t z = 0; ep0_send(&z, 1, wL, false); break; } /* GET_INTERFACE */ case 11: ep0_status_in(); break; /* SET_INTERFACE */ default: ep0_stall(); } } else if (type == 0x20) { /* CDC class */ switch (bReq) { case 0x20: /* SET_LINE_CODING */ if (ep0_wait(1 << RXOUTI)) { for (uint8_t i = 0; i < 7; i++) line_coding[i] = UEDATX; UEINTX = ~(1 << RXOUTI); ep0_status_in(); } break; case 0x21: ep0_send(line_coding, 7, wL, false); break; /* GET_LINE_CODING */ case 0x22: case 0x23: ep0_status_in(); break; /* CONTROL_LINE_STATE, SEND_BREAK */ default: ep0_stall(); } } else if (type == 0x40) { /* vendor */ usb_vendor_request(bmRT, bReq, wV, wI, wL); } else ep0_stall(); } /* ---------------------------------------------------------------- public API */ void usb_init(void) { REGCR &= ~(1 << REGDIS); /* enable 3.3 V USB pad regulator */ USBCON = (1 << USBE) | (1 << FRZCLK); PLLCSR = (1 << PLLP0) | (1 << PLLE); /* PLLP=001: 16 MHz /2 = 8 MHz -> x6 = 48 MHz (as LUFA) */ while (!(PLLCSR & (1 << PLOCK))); USBCON = (1 << USBE); /* unfreeze */ UDCON = (1 << DETACH); #if !USE_VBUS_DETECT UDCON = 0; /* attach, full speed */ #else DDRB &= ~(1 << PB6); #endif } void usb_task(void) { #if USE_VBUS_DETECT if (PINB & (1 << PB6)) UDCON &= ~(1 << DETACH); else { UDCON |= (1 << DETACH); configured = 0; return; } #endif if (UDINT & (1 << EORSTI)) { UDINT = ~(1 << EORSTI); configured = 0; usb_reset_hook(); UENUM = 0; UECONX = (1 << EPEN); UECFG0X = 0; /* control */ UECFG1X = (2 << EPSIZE0) | (1 << ALLOC); /* 32 B */ } UENUM = 0; if (UEINTX & (1 << RXSTPI)) ep0_setup(); } uint8_t usb_rx(uint8_t *buf) { if (!configured) return 0; UENUM = EP_OUT; if (!(UEINTX & (1 << RXOUTI))) return 0; uint8_t n = UEBCLX; for (uint8_t i = 0; i < n; i++) buf[i] = UEDATX; UEINTX = (uint8_t)~((1 << RXOUTI) | (1 << FIFOCON)); return n; } void usb_tx(const uint8_t *buf, uint8_t n) { if (!configured || !n) return; UENUM = EP_IN; uint16_t t = 20000; /* ~50 ms: host not reading -> drop */ while (!(UEINTX & (1 << TXINI))) { if (!--t) return; _delay_us(2); } for (uint8_t i = 0; i < n; i++) UEDATX = buf[i]; UEINTX = (uint8_t)~((1 << TXINI) | (1 << FIFOCON)); }