This week, my goal was to take the arcade button input board I created last week, and get it to behave as a USB keyboard using the V-USB library. While the fab inventory includes microcontrollers with dedicated USB controllers, I wanted to learn how to use V-USB to provide virtual USB support using cheaper microcontrollers for my own future projects.
This week started out incredibly slowly. Compilation errors were abound, even with out-of-the-box example projects, and documentation was scarce. I'm going to start by giving an overview of what I did to get V-USB examples working with an ATTiny44 and a schematic based on the FabISP programmer.
examples/hid-mouse
example.usbconfig.h
file, specifying the appropriate IO port ("A"), D minus bit (0) and D plus bit (7), as well as setting USB_CFG_HAVE_INTRIN_ENDPOINT
to 1, USB_CFG_INTR_POLL_INTERVAL
to 10, and USB_CFG_MAX_BUS_POWER
to 40I reached those specific options based on a combination of common sense (my D- and D+ lines were in fact connected to PA0 and PA7, on my ATTiny44 with a 20Hz clock), and reading configuration options from the FabISP usbconfig.h file (which is itself based on one of the V-USB example usbconfig.h files).
The first thing I did was get V-USB's included "hid-mouse" example working. This just causes the device to present itself as a standard HID mouse that repeatedly moves in a circle on the screen.
Often, compiling a third-party example would yield errors that looked like the following:
➜ firmware make avr-gcc -Wall -Os -Iusbdrv -I. -mmcu=attiny45 -DF_CPU=16500000 -DDEBUG_LEVEL=0 -c usbdrv/usbdrv.c -o usbdrv/usbdrv.o In file included from usbdrv/usbdrv.c:12:0: usbdrv/usbdrv.h:455:6: error: variable 'usbDescriptorDevice' must be const in order to be put into read-only section by means of '__attribute__((progmem))' char usbDescriptorDevice[]; ^
This means that you're using an outdated version of V-USB. Download the latest version (the absolute latest code can be found on GitHub, but the 2012-12-06 release on the official downloads page should suffice as well), and replace the usbdrv
folder within your project with the latest version included in the official library. If you're using the code from GitHub, you may need to run an included shell script to generate the appropriate output.
If you take one of the example projects and simply change the Makefile to use an ATTiny44 (ATTiny45, etc) microcontroller instead of the default ATMega328, you'll get a series of compilation errors that look like the following:
➜ firmware git:(master) make hex avr-gcc -Wall -Os -DF_CPU=20000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=attiny44 -c usbdrv/usbdrv.c -o usbdrv/usbdrv.o In file included from usbdrv/usbdrv.c:10:0: usbdrv/usbdrv.c: In function 'usbPoll': usbdrv/usbdrv.h:528:48: error: 'PIND' undeclared (first use in this function) #define USB_INPORT(name) USB_CONCAT(PIN, name) ^ usbdrv/usbdrv.h:524:37: note: in definition of macro 'USB_CONCAT' #define USB_CONCAT(a, b) a ## b ^
In the usbconfig.h
file, you specify which letter-named IO port your device has its D- and D+ pins connected to. Using the ATMega-based "megaboard" layout the V-USB creators use, this is pin 'D'; the ATTiny44 only has "A" and "B" IO pins. To fix this, you'll want to open usbconfig.h and change the #define USB_CFG_IOPORTNAME D
line to refer to the appropriate port.
One time I compiled, I received the following error:
➜ firmware git:(master) make hex avr-gcc -Wall -Os -DF_CPU=20000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=attiny44 -c usbdrv/usbdrv.c -o usbdrv/usbdrv.o avr-gcc -Wall -Os -DF_CPU=20000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=attiny44 -x assembler-with-cpp -c usbdrv/usbdrvasm.S -o usbdrv/usbdrvasm.o avr-gcc -Wall -Os -DF_CPU=20000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=attiny44 -c usbdrv/oddebug.c -o usbdrv/oddebug.o avr-gcc -Wall -Os -DF_CPU=20000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=attiny44 -c main.c -o main.o main.c: In function 'main': main.c:155:9: warning: implicit declaration of function 'usbInterruptIsReady' [-Wimplicit-function-declaration] if(usbInterruptIsReady()){ ^
The solution is to find the USB_CFG_HAVE_INTRIN_ENDPOINT
#define, and change it from 0 to 1. I don't know exactly what it does, other than change something with how interrupts are dealt with, but it is in fact the magic incantation that causes it to work.
The first time I was able to successfully flash the board, nothing happened. It turned out this was two separate problems.
The first was because my usbconfig.h
file didn't specify the appropriate D+ and D- pins. These configuration #defines
come immediately after the IO port letter. in the case of the FabISP board and my board, D- was on pin 0 and D+ was on pin 7, with the IO port being A. If you're designing your own board, either D+ or D- needs to be connected to INT0 as part of the USB spec; on an ATTiny44, that's pin PA7.
Even after fixing that, though, I still didn't see the expected behavior. A hint to my issue could be found in checking out my OS's system message bus (sudo dmesg
on OS X) and seeing the following errors when plugging in my programmed board:
46844.679120 AppleUSB20HubPort@14114000: AppleUSBHostPort::resetGated: retrying enumeration in 100 ms 146844.836259 AppleUSB20HubPort@14114000: AppleUSBHostPort::resetGated: retrying enumeration in 200 ms 146845.111013 AppleUSB20HubPort@14114000: AppleUSBHostPort::resetGated: retrying enumeration in 400 ms 146845.626993 AppleUSB20HubPort@14114000: AppleUSBHostPort::resetGated: retrying enumeration in 800 ms 146846.622163 AppleUSB20HubPort@14114000: AppleUSBHostPort::resetGated: retrying enumeration in 1600 ms 146848.578016 AppleUSB20HubPort@14114000: AppleUSBHostPort::resetGated: retrying enumeration in 3200 ms 146852.454121 AppleUSB20HubPort@14114000: AppleUSBHostPort::resetGated: retrying enumeration in 6400 ms 146860.170380 AppleUSB20HubPort@14114000: AppleUSBHostPort::resetGated: unrecoverable enumeration failure after 8 attempts
Looking at the FabISP's Makefile and usbconfig.h file (which were both themselves modified from one of the V-USB example projects), the main difference was a variable called USB_CFG_INTR_POLL_INTERVAL
. The default value was 100; the FabISP's config file specified 10. While I don't exactly know the reason for this failure, I'm guessing it has to do with modern OSes expecting a more frequent polling rate.