Nanduino » USB Messaging
The AT90USB162 microcontroller on the Nanduino incorporates a full-speed USB peripheral allowing it to enumerate as any of the standard USB device classes or as a vendor-specific device like the bootloader you saw earlier. The standard device types include virtual serial port, mass-storage device, keyboard, mouse, MIDI port, audio input and output. They usually do not require any drivers on the host side; the host recognises their device class and assumes certain capabilities as a result. Conversely, the vendor-specific devices need specific drivers to be loaded on the host side to facilitate communication with the device.
In some cases it's possible to (ab)use some standard device class for what you want to do, but often it's better to just drop down to the more fundamental messaging layer. There are two great libraries out there to facilitate this approach:
- LUFA provides a nice framework for implementing USB device firmware on the AVR8 microcontrollers.
- LibUSB provides a similar framework for cross-platform host-side USB programming, crucially avoiding the need to learn the details of driver development on each platform.
My aim here was to put together a very simple example of a vendor firmware running on a Nanduino and a simple host-side application. And here it is:
avrcalc.tar.gz
There's a README with instructions on how to build on Windows and Linux. Pay particular attention to the directory structure, which follows the build infrastructure described in this blog post.
Code Walkthrough
If you have never done any USB programming before, it would be wise at this stage to familiarise yourself with its fundamental concepts. I learned all I needed from this great tutorial on BeyondLogic.
The firmware is about as simple as it can be. It accepts requests for IN transfers (from device to host) on EP0 having bRequest=0x80, and returns eight bytes or four words: the four arithmetic operations on the wValue and wIndex parameters:
void EVENT_USB_Device_UnhandledControlRequest(void) {
switch ( USB_ControlRequest.bRequest ) {
case 0x80:
if ( USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_VENDOR) ) {
uint16 arith[4];
arith[0] = USB_ControlRequest.wValue + USB_ControlRequest.wIndex;
arith[1] = USB_ControlRequest.wValue - USB_ControlRequest.wIndex;
arith[2] = USB_ControlRequest.wValue * USB_ControlRequest.wIndex;
arith[3] = USB_ControlRequest.wValue / USB_ControlRequest.wIndex;
Endpoint_ClearSETUP();
Endpoint_Write_Control_Stream_LE(arith, 8);
Endpoint_ClearOUT();
}
break;
}
}
The host side code is just a small utility called ucm ("USB Control Messager") that is bundled with fx2tools. It too is fairly straightforward - most of the code is for accepting command-line arguments. And as you probably guessed, it is generic too - it allows you to send and receive control data from any USB device. The core of the code makes use of this libusb function:
int usb_control_msg(
usb_dev_handle *dev, // the device we're talking to
int bRequestType, // whether IN or OUT (amongst other things)
int bRequest, // the request code, 0x80 in this example
int wValue, // the first parameter
int wIndex, // the second parameter
char *buffer, // a buffer with/for the data
int size, // the number of bytes to send/request
int timeout // the timeout in milliseconds
);
From the firmware code above you can see that this firmware only accepts IN requests (i.e data sent from the device to the host). Any attempt to initiate an OUT request results in an error.
Putting It All Together
Once the calculator firmware is loaded into the Nanduino, either with make flip on Windows or make dfu on Linux (and installed the supplied libusb driver on Windows), you can interrogate it with the ucm utility from fx2tools:
>../fx2tools/ucm/ucm -i -v 0x03EB -p 0x3002 0x80 0x0010 0x0002 0x0008 | ../fx2tools/hxd/hxd
00000000 12 00 0E 00 20 00 08 00 .... ...
The Nanduino takes the two numbers passed to it 0x0010 (16) and 0x0002 (2) and returns four words:
- Their sum: 16 + 2 = 18 (0x0012)
- Their difference: 16 - 2 = 14 (0x000E)
- Their product: 16 * 2 = 32 (0x0020)
- Their quotient: 16 / 2 = 8 (0x0008)
OK, so I admit that a Nanduino makes a poor math coprocessor, but this simple calculator example demonstrates a number of important concepts, which will hopefully serve to get you off the ground quicker.