|
@@ -0,0 +1,233 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ * 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));
|
|
|
|
|
+}
|