Project 10: Application Programming

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.

  1. I downloaded the latest V-USB (2012-12-06 at the time of writing) from the official downloads page. Newer code can be found on GitHub, but it doesn't seem necessary.
  2. I chose an example to work with, in my case the examples/hid-mouse example.
  3. I modified the Makefile, specifying an ATTiny44 with a 20000000 clock speed, and the appropriate programmer type in the AVRDude command.
  4. I modified the 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 40
  5. I built the example as normal (`make clean; make hex; make fuse; make flash`)
  6. I 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).

    Success!

    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.

    Common Errors

    Rather than simply telling you what options I ended up with, let me walk through some of the specific errors I ran into and what configuration changes I made to solve them.

    Outdated version of V-USB

    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.

    Wrong IO Port

    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.

    usbInterruptIsReady compilation error?

    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.

    Nothing happens! [Wrong pin version]

    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.

    Nothing happens! [Wrong polling interval version]

    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.