Wednesday 9 June 2010

Bidirectional communication with an Arduino

I now have transparent communication with an Arduino (programming the device work, as well as serial communication with the loaded program). You can find the proxy driver code here, or fetch the whole branch here.

Basically, the Arduino appears as a USB->serial converter to the host, with 2 bulk endpoints, with directions IN and OUT (for both directions of the serial transfers):

Bus 007 Device 120: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x0403 Future Technology Devices International, Ltd
idProduct 0x6001 FT232 USB-Serial (UART) IC
bcdDevice 6.00
iManufacturer 1 FTDI
iProduct 2 FT232R USB UART
iSerial 3 A800evGn
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 32
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 90mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 255 Vendor Specific Subclass
bInterfaceProtocol 255 Vendor Specific Protocol
iInterface 2 FT232R USB UART
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Device Status: 0x0000
(Bus Powered)

The first endpoint (EP 1 IN), for communication from the Arduino to the host, worked almost immediately: apart from the transfer type, this is the same as for HID devices.

Getting the second endpoint (EP 2 OUT) to be configured properly on the gadget side was a little tricky. In fact, the infrastucture provided in drivers/usb/gadget/epautoconf.c, namely the function usb_ep_autoconfig, does not allow to choose an endpoint address: This function looks at the list of endpoints provided by the gadget controller driver, and simply selects the first endpoint that supports the request transfer type, direction and maximum packet size (here, Bulk OUT, 64 bytes). In our case, the function selects EP 1 OUT, while the host still thinks transfers should be done on EP 2 OUT.

To get it to work, I had to rewrite usb_ep_autoconfig (see find_gadget_endpoint), and I was a little horrified to see the way gadget controllers endpoints are advertised: Each endpoint structure has a char* name field, which tells the endpoint address, direction, and possibly the supported transfer types. To find EP 2 OUT, the function has to go through all the endpoints, until it finds one whose name is ep2out. A bit kludgy, but well, it works, at least for the MUSB controller...

Data transfers are handled differently, whether they happens on a IN, or OUT, endpoint. For IN endpoints, we follow the following procedure:
1. Initialization (see bridge_endpoint): We submit an URB (USB request block) to the device, asking for data.
2. Device callback (see device_epin_irq): We got some data. Copy it to a request (the gadget equivalent of an URB), and submit it.
3. Gadget callback (see gadget_epin_complete): The request was submitted correctly. Resubmit the URB, so we can receive the next packet.

OUT endpoints are handled similarly: Upon initialization, a request is submitted (bridge_endpoint), the gadget callback (gadget_epout_complete) submits an URB. When the URB completes (device_epout_irq), the request is resubmitted.

This method, that is, with only one URB/request "flying" at the time, seems to work well for low throughput applications, but buffering may be required for higher throughput.