| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- /*
- * Minimal polled USB CDC-ACM stack for ATmega8U2 @ 16 MHz crystal.
- *
- * 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 <avr/io.h>
- #include <avr/pgmspace.h>
- #include <util/delay.h>
- #include "usb.h"
- #define USB_VID 0x1209 /* pid.codes test VID/PID - replace for production */
- #define USB_PID 0x0001
- #define EP0_SIZE 32
- #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 */
- 0x02, 0x00, 0x00, /* CDC device class */
- 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, 67, 0, 2, 1, 0, 0x80, 50, /* config, 2 ifaces, bus powered 100 mA */
- /* 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
- };
- 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 */
- static 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;
- }
- static void ep0_status_in(void) /* zero-length IN status stage */
- {
- if (ep0_wait(1 << TXINI)) UEINTX = ~(1 << TXINI);
- }
- static void ep0_stall(void) { UECONX |= (1 << STALLRQ); }
- static 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 */
- }
- 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;
- }
- 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;
- (void)wI;
- 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 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;
- 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));
- }
|