The software USB stack we want to use is called V-USB:
http://www.obdev.at/products/vusb
I downloaded V-USB and extracted it:
[mjd@onza src]$ wget http://www.obdev.at/downloads/vusb/vusb-20100715.tar.gz
--2010-11-23 18:17:39-- http://www.obdev.at/downloads/vusb/vusb-20100715.tar.gz
Resolving www.obdev.at... 78.46.114.187
Connecting to www.obdev.at|78.46.114.187|:80... connected.
HTTP request sent, awaiting response... 302 Found
Location: http://www.obdev.at/ftp/pub/Products/vusb/vusb-20100715.tar.gz [following]
--2010-11-23 18:17:40-- http://www.obdev.at/ftp/pub/Products/vusb/vusb-20100715.tar.gz
Reusing existing connection to www.obdev.at:80.
HTTP request sent, awaiting response... 200 OK
Length: 417848 (408K) [application/x-gzip]
Saving to: “vusb-20100715.tar.gz”
100%[======================================>] 417,848 94.8K/s in 4.3s
2010-11-23 18:17:45 (94.8 KB/s) - “vusb-20100715.tar.gz” saved [417848/417848]
[mjd@onza src]$ tar -xzf vusb-20100715.tar.gz
[mjd@onza src]$
--2010-11-23 18:17:39-- http://www.obdev.at/downloads/vusb/vusb-20100715.tar.gz
Resolving www.obdev.at... 78.46.114.187
Connecting to www.obdev.at|78.46.114.187|:80... connected.
HTTP request sent, awaiting response... 302 Found
Location: http://www.obdev.at/ftp/pub/Products/vusb/vusb-20100715.tar.gz [following]
--2010-11-23 18:17:40-- http://www.obdev.at/ftp/pub/Products/vusb/vusb-20100715.tar.gz
Reusing existing connection to www.obdev.at:80.
HTTP request sent, awaiting response... 200 OK
Length: 417848 (408K) [application/x-gzip]
Saving to: “vusb-20100715.tar.gz”
100%[======================================>] 417,848 94.8K/s in 4.3s
2010-11-23 18:17:45 (94.8 KB/s) - “vusb-20100715.tar.gz” saved [417848/417848]
[mjd@onza src]$ tar -xzf vusb-20100715.tar.gz
[mjd@onza src]$
V-USB comes with several demos. I chose the mouse demo because it doesn't require any extra hardware such as switches or chips.
I have made some changes to the mouse demo source to reflect that we're using different pins for the USB, and also to make the LEDs blink. I've also changed the MCU type, the fuses, and the right command to run avrdude for my USB programmer:
--- vusb-20100715-orig/examples/hid-mouse/firmware/main.c
+++ vusb-20100715/examples/hid-mouse/firmware/main.c
@@ -124,9 +124,19 @@
/* ------------------------------------------------------------------------- */
+static const unsigned char portCMask = _BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3);
+
+static void setLED(const unsigned char v)
+{
+ PORTC &= ~portCMask;
+ PORTC |= (v & portCMask);
+}
+
int __attribute__((noreturn)) main(void)
{
-uchar i;
+ uchar i;
+ DDRC |= _BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3) | _BV(PC4) | _BV(PC5);
+ PORTC |= _BV(4);
wdt_enable(WDTO_1S);
/* Even if you don't use the watchdog, turn it off here. On newer devices,
@@ -148,12 +158,16 @@
usbDeviceConnect();
sei();
DBG1(0x01, 0, 0); /* debug output: main loop starts */
+ unsigned char a = 0;
for(;;){ /* main event loop */
DBG1(0x02, 0, 0); /* debug output: main loop iterates */
wdt_reset();
usbPoll();
if(usbInterruptIsReady()){
/* called after every poll of the interrupt endpoint */
+ ++a;
+ a &= 0b111111;
+ setLED(a >> 2);
advanceCircleByFixedAngle();
DBG1(0x03, 0, 0); /* debug output: interrupt report prepared */
usbSetInterrupt((void *)&reportBuffer, sizeof(reportBuffer));
--- vusb-20100715-orig/examples/hid-mouse/firmware/Makefile+++ vusb-20100715/examples/hid-mouse/firmware/Makefile
@@ -7,11 +7,11 @@
# License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt)
# This Revision: $Id: Makefile 692 2008-11-07 15:07:40Z cs $
-DEVICE = atmega168
-F_CPU = 16000000 # in Hz
-FUSE_L = # see below for fuse values for particular devices
-FUSE_H =
-AVRDUDE = avrdude -c usbasp -p $(DEVICE) # edit this line for your programmer
+DEVICE = atmega328p
+F_CPU = 16000000 # in Hz
+FUSE_L = 0xff# see below for fuse values for particular devices
+FUSE_H = 0xd9
+AVRDUDE = avrdude -c avrisp2 -P usb -p $(DEVICE) # edit this line for your programmer
CFLAGS = -Iusbdrv -I. -DDEBUG_LEVEL=0
OBJECTS = usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o
@@ -125,6 +125,8 @@
clean:
rm -f main.hex main.lst main.obj main.cof main.list main.map main.eep.hex main.elf *.o usbdrv/*.o main.s usbdrv/oddebug.s usbdrv/usbdrv.s
+main.o: main.c usbconfig.h
+
# Generic rule for compiling C files:
.c.o:
$(COMPILE) -c $< -o $@
diff -x usbdrv -X lufa-dontdiff.txt -u -r vusb-20100715-orig/examples/hid-mouse/firmware/usbconfig.h vusb-20100715/examples/hid-mouse/firmware/usbconfig.h
--- vusb-20100715-orig/examples/hid-mouse/firmware/usbconfig.h 2010-07-16 02:43:47.000000000 +1000
+++ vusb-20100715/examples/hid-mouse/firmware/usbconfig.h 2010-11-22 09:07:33.000000000 +1100
@@ -27,7 +27,7 @@
/* This is the port where the USB bus is connected. When you configure it to
* "B", the registers PORTB, PINB and DDRB will be used.
*/
-#define USB_CFG_DMINUS_BIT 4
+#define USB_CFG_DMINUS_BIT 3
/* This is the bit number in USB_CFG_IOPORT where the USB D- line is connected.
* This may be any bit in the port.
*/
@@ -116,7 +116,7 @@
/* Define this to 1 if the device has its own power supply. Set it to 0 if the
* device is powered from the USB bus.
*/
-#define USB_CFG_MAX_BUS_POWER 20
+#define USB_CFG_MAX_BUS_POWER 400
/* Set this variable to the maximum USB bus power consumption of your device.
* The value is in milliamperes. [It will be divided by two since USB
* communicates power requirements in units of 2 mA.]
+++ vusb-20100715/examples/hid-mouse/firmware/main.c
@@ -124,9 +124,19 @@
/* ------------------------------------------------------------------------- */
+static const unsigned char portCMask = _BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3);
+
+static void setLED(const unsigned char v)
+{
+ PORTC &= ~portCMask;
+ PORTC |= (v & portCMask);
+}
+
int __attribute__((noreturn)) main(void)
{
-uchar i;
+ uchar i;
+ DDRC |= _BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3) | _BV(PC4) | _BV(PC5);
+ PORTC |= _BV(4);
wdt_enable(WDTO_1S);
/* Even if you don't use the watchdog, turn it off here. On newer devices,
@@ -148,12 +158,16 @@
usbDeviceConnect();
sei();
DBG1(0x01, 0, 0); /* debug output: main loop starts */
+ unsigned char a = 0;
for(;;){ /* main event loop */
DBG1(0x02, 0, 0); /* debug output: main loop iterates */
wdt_reset();
usbPoll();
if(usbInterruptIsReady()){
/* called after every poll of the interrupt endpoint */
+ ++a;
+ a &= 0b111111;
+ setLED(a >> 2);
advanceCircleByFixedAngle();
DBG1(0x03, 0, 0); /* debug output: interrupt report prepared */
usbSetInterrupt((void *)&reportBuffer, sizeof(reportBuffer));
--- vusb-20100715-orig/examples/hid-mouse/firmware/Makefile+++ vusb-20100715/examples/hid-mouse/firmware/Makefile
@@ -7,11 +7,11 @@
# License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt)
# This Revision: $Id: Makefile 692 2008-11-07 15:07:40Z cs $
-DEVICE = atmega168
-F_CPU = 16000000 # in Hz
-FUSE_L = # see below for fuse values for particular devices
-FUSE_H =
-AVRDUDE = avrdude -c usbasp -p $(DEVICE) # edit this line for your programmer
+DEVICE = atmega328p
+F_CPU = 16000000 # in Hz
+FUSE_L = 0xff# see below for fuse values for particular devices
+FUSE_H = 0xd9
+AVRDUDE = avrdude -c avrisp2 -P usb -p $(DEVICE) # edit this line for your programmer
CFLAGS = -Iusbdrv -I. -DDEBUG_LEVEL=0
OBJECTS = usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o
@@ -125,6 +125,8 @@
clean:
rm -f main.hex main.lst main.obj main.cof main.list main.map main.eep.hex main.elf *.o usbdrv/*.o main.s usbdrv/oddebug.s usbdrv/usbdrv.s
+main.o: main.c usbconfig.h
+
# Generic rule for compiling C files:
.c.o:
$(COMPILE) -c $< -o $@
diff -x usbdrv -X lufa-dontdiff.txt -u -r vusb-20100715-orig/examples/hid-mouse/firmware/usbconfig.h vusb-20100715/examples/hid-mouse/firmware/usbconfig.h
--- vusb-20100715-orig/examples/hid-mouse/firmware/usbconfig.h 2010-07-16 02:43:47.000000000 +1000
+++ vusb-20100715/examples/hid-mouse/firmware/usbconfig.h 2010-11-22 09:07:33.000000000 +1100
@@ -27,7 +27,7 @@
/* This is the port where the USB bus is connected. When you configure it to
* "B", the registers PORTB, PINB and DDRB will be used.
*/
-#define USB_CFG_DMINUS_BIT 4
+#define USB_CFG_DMINUS_BIT 3
/* This is the bit number in USB_CFG_IOPORT where the USB D- line is connected.
* This may be any bit in the port.
*/
@@ -116,7 +116,7 @@
/* Define this to 1 if the device has its own power supply. Set it to 0 if the
* device is powered from the USB bus.
*/
-#define USB_CFG_MAX_BUS_POWER 20
+#define USB_CFG_MAX_BUS_POWER 400
/* Set this variable to the maximum USB bus power consumption of your device.
* The value is in milliamperes. [It will be divided by two since USB
* communicates power requirements in units of 2 mA.]
I applied this patch:
[mjd@onza src]$ cd vusb-20100715
[mjd@onza vusb-20100715]$ patch -p1 < ../vusb-20100715-mjd-1.diff
patching file examples/hid-mouse/firmware/main.c
patching file examples/hid-mouse/firmware/Makefile
patching file examples/hid-mouse/firmware/usbconfig.h
[mjd@onza vusb-20100715]$
[mjd@onza vusb-20100715]$ patch -p1 < ../vusb-20100715-mjd-1.diff
patching file examples/hid-mouse/firmware/main.c
patching file examples/hid-mouse/firmware/Makefile
patching file examples/hid-mouse/firmware/usbconfig.h
[mjd@onza vusb-20100715]$
Then compiled the software:
[mjd@onza vusb-20100715]$ cd examples/hid-mouse/firmware
[mjd@onza firmware]$ make hex
cp -r ../../../usbdrv .
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -c usbdrv/usbdrv.c -o usbdrv/usbdrv.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -x assembler-with-cpp -c usbdrv/usbdrvasm.S -o usbdrv/usbdrvasm.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -c usbdrv/oddebug.c -o usbdrv/oddebug.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -c main.c -o main.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -o main.elf usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o
rm -f main.hex main.eep.hex
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avr-size main.hex
text data bss dec hex filename
0 1872 0 1872 750 main.hex
[mjd@onza firmware]$ make hex
cp -r ../../../usbdrv .
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -c usbdrv/usbdrv.c -o usbdrv/usbdrv.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -x assembler-with-cpp -c usbdrv/usbdrvasm.S -o usbdrv/usbdrvasm.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -c usbdrv/oddebug.c -o usbdrv/oddebug.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -c main.c -o main.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -o main.elf usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o
rm -f main.hex main.eep.hex
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avr-size main.hex
text data bss dec hex filename
0 1872 0 1872 750 main.hex
And now I can program the Doodad:
[mjd@onza firmware]$ su
Password:
[root@onza firmware]# make flash
avrdude -c avrisp2 -P usb -p atmega328p -U flash:w:main.hex:i
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: writing flash (1872 bytes):
Writing | ################################################## | 100% 0.65s
avrdude: 1872 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex contains 1872 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 0.52s
avrdude: verifying ...
avrdude: 1872 bytes of flash verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza firmware]# make fuse
avrdude -c avrisp2 -P usb -p atmega328p -U hfuse:w:0xd9:m -U lfuse:w:0xff:m
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude: reading input file "0xd9"
avrdude: writing hfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of hfuse written
avrdude: verifying hfuse memory against 0xd9:
avrdude: load data hfuse data from input file 0xd9:
avrdude: input file 0xd9 contains 1 bytes
avrdude: reading on-chip hfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of hfuse verified
avrdude: reading input file "0xff"
avrdude: writing lfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of lfuse written
avrdude: verifying lfuse memory against 0xff:
avrdude: load data lfuse data from input file 0xff:
avrdude: input file 0xff contains 1 bytes
avrdude: reading on-chip lfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of lfuse verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza firmware]#
Password:
[root@onza firmware]# make flash
avrdude -c avrisp2 -P usb -p atmega328p -U flash:w:main.hex:i
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: writing flash (1872 bytes):
Writing | ################################################## | 100% 0.65s
avrdude: 1872 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex contains 1872 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 0.52s
avrdude: verifying ...
avrdude: 1872 bytes of flash verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza firmware]# make fuse
avrdude -c avrisp2 -P usb -p atmega328p -U hfuse:w:0xd9:m -U lfuse:w:0xff:m
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude: reading input file "0xd9"
avrdude: writing hfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of hfuse written
avrdude: verifying hfuse memory against 0xd9:
avrdude: load data hfuse data from input file 0xd9:
avrdude: input file 0xd9 contains 1 bytes
avrdude: reading on-chip hfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of hfuse verified
avrdude: reading input file "0xff"
avrdude: writing lfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of lfuse written
avrdude: verifying lfuse memory against 0xff:
avrdude: load data lfuse data from input file 0xff:
avrdude: input file 0xff contains 1 bytes
avrdude: reading on-chip lfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of lfuse verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza firmware]#
As a prelude to trying it I also filed away the end of the board so it can fit into a USB port.
Well after that, I power cycled the Doodad, and the second last LED is lit. And if that wasn't impressive enough, After I plugged it into my computer's USB port, the mouse cursor started moving around in a large circle, and the LEDs are counting!
Here's what lsusb shows:
[root@onza mjd]# lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 1a40:0101 TERMINUS TECHNOLOGY INC.
Bus 001 Device 003: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 001 Device 004: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 001 Device 005: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 002: ID 16c0:03e8 VOTI free for internal lab use 1000
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 1a40:0101 TERMINUS TECHNOLOGY INC.
Bus 001 Device 003: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 001 Device 004: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 001 Device 005: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 002: ID 16c0:03e8 VOTI free for internal lab use 1000
The mouse is the "VOTI" entry. The USB vendor ID and product ID can be set in the software.
Now to see the details:
[root@onza mjd]# lsusb -v
Bus 003 Device 002: ID 16c0:03e8 VOTI free for internal lab use 1000
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x16c0 VOTI
idProduct 0x03e8 free for internal lab use 1000
bcdDevice 1.00
iManufacturer 1 obdev.at
iProduct 2 Mouse
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 400mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.01
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 52
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 100
Device Status: 0x0000
(Bus Powered)
⋮
[root@onza mjd]#
Bus 003 Device 002: ID 16c0:03e8 VOTI free for internal lab use 1000
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x16c0 VOTI
idProduct 0x03e8 free for internal lab use 1000
bcdDevice 1.00
iManufacturer 1 obdev.at
iProduct 2 Mouse
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 400mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.01
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 52
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 100
Device Status: 0x0000
(Bus Powered)
⋮
[root@onza mjd]#
I'm not sure why it says the report descriptors are unavailable. But at least it works.
Now I'll start working on the bootloader. If I can get the bootloader working, I can take the three pins we're currently using for ISP, and make them LED outputs.
The success continues!