USB & Android deep dive: theory and practical examples
Modern Android smartphones and tablets support USB technology allowing users the ability to connect peripherals without using a computer. The possibilities for USB use seem somewhat limitless. However, there is a lack of documentation on just how to use it.
In an effort to remedy this, our delivery team is sharing its knowledge on how USB works with Android devices. Here’s what we know.
USB and Android: the essentials
USB (Universal Serial Bus) is a serial interface for connecting peripheral devices to computers. The interface allows for the exchange of data and provides a power supply for peripheral devices. Network architecture enables the connection of a large number of peripherals, even to a device that has only one USB slot.
Current uses of USB technology:
- Internet of Things for connection to various wireless proprietary interface protocols: ZigBee, 6LoWPAN, RFID, etc.
- Tokens and electronic keys. By using USB, it’s possible to prevent MitM attacks which occur often in cryptography.
- Various devices that do not have their own power supply, such as thermometers, carbo meters, barometers, hygrometers, radios, etc.
All USB devices have a hierarchy of descriptors (standardized data) that describes information to the host, i.e, device type, vendor, supported USB version, how the device can be configured, the number of endpoints, their types, etc.
The most common USB descriptors are: Device Descriptors, Interface Descriptors, Endpoint Descriptors.
When connecting, the host requests Device Descriptors in order to decide how to work with the device. Then the host changes speed (if it’s a high-speed device) and assigns the address to the device.
USB devices can have only one Device Descriptor. It contains Vendor and Product ID, Class and Subclass, Protocol, Interface Count (the number of device interfaces), Manufacturer and Product Name, and Serial Number.
The Interface Descriptor can be considered as a header or endpoints grouping into a functional group that performs a single feature of the device. For example, you have a multifunction fax/scanner/printer. The Interface Descriptor 1 can describe the endpoints of the fax function, the Interface Descriptor 2 can describe the function of the scanner, and the Interface Descriptor 3 can describe the printer function. Unlike the Device Descriptor, there are no restrictions on the number of simultaneously allowed interfaces. The Interface Descriptor contains the Interface number and name, Class and Subclass, Protocol, and Endpoints Count.
A couple of words about Classes of USB interface. The type of USB devices can be determined by code classes that are sent to the USB host to download the necessary drivers. Class codes help unify the work with similar devices of different manufacturers. A device can support one or several classes, the maximum number of which is determined by the number of available endpoints.
Now let’s return to USB Descriptors. The last thing to mention here is the Endpoint Descriptor. Each Endpoint Descriptor is used to specify transfer type, direction, polling interval and maximum packet size for each endpoint. By default, Endpoint 0 is always implied to be a control point and that’s why a Descriptor is never configured automatically, even before requesting information from all descriptors. The host will use the information obtained from these Endpoint Descriptors to determine the bandwidth requirements of the bus.
Endpoint descriptor includes:
- Endpoint address. Bits 0-3 stand for the endpoint number, bits 4-6 are reserved (set to 0), bit 7 stands for the direction (0 = Out, 1 = In);
- Transfer type (Type of transfer: Control, Isochronous, Bulk, Interrupt);
- Max packet size (endpoint max packet size available for sending or receiving, value range from 1 to 1024);
- Interval (serves to poll the endpoint data transfer and valued in frame counts). The field is ignored by Bulk and Control endpoints. For Isochronous endpoints,it should be 1 and for interrupt endpoints it can range from 1 to 255.
Here is where we get to more information about Endpoint Transfer types. For device connection, USB specification provides four different transfer types for each endpoint. The purpose of such division is to differentiate transfer types according to their bandwidth, delivery guarantee, and time spent on delivery.
Control Transfers are used by the host to configure the device during the connection, control the device and obtain status information during operation. The protocol ensures the guaranteed delivery. The length of the data field of the control parcel can not exceed 64 bytes for full speed and 8 bytes for low-speed devices. For such packets, the host allocates 10% of bandwidth.
Bulk Data Transfers are used when it’s necessary to ensure guaranteed delivery of data from the host to the function or from this function to the host, with the condition that there is no limit on delivery time. This transfer occupies all available bus bandwidth. The size of the packets data field can be 8, 16, 32 or 64 bytes. They have the lowest priority and can be stopped during peak busloads. Such parcels are used, for example, by printers or scanners.
Interrupt Transfers are used when there is a need to transfer small single data packets. Each packet should be sent within a limited time frame. Transfer operations are spontaneous and shouldn’t be serviced slower than the device requires. The data field can contain up to 64 bytes for high-speed and up to 8 bytes for low-speed devices. The service time limit is set in the range of 1-255 ms for full and 10-255 ms for low speed. Such transfers are used in input devices such as a mouse and keyboard.
Isochronous Transfers are used for "real time" data exchange when necessary to transfer a certain amount of data at each time interval. But the delivery of information is not guaranteed (data transfer is carried out without repetition in case of failures, packet loss is allowed). Such transfers occupy a primarily agreed bus bandwidth and have a predetermined timed delay. Isochronous transfers are commonly used in multimedia devices to transfer audio and video data, for example, digital transfer of voice. Thus, there are asynchronous, synchronous and adaptive classes of devices, each of which has its own type of USB channel.
All data transfer operations are initiated only by the host, regardless of whether it receives data or sends it forward to a peripheral device. All uncompleted operations are stored in lists according to the type of transfers. Lists are constantly updated with new requests.
The host performs the planning of information transfer operations, which are organized in accordance with ordered lists of requests at intervals of one frame. The requests are processed in accordance with the following rules:
- Isochronous transfers have the highest priority.
- After processing all Isochronous Transfers, the system proceeds to the Interrupt Transfers.
- Bulk Data Transfers are serviced last.
- On the expiry of 90% of the specified interval, the host automatically proceeds to Control Transfers, regardless of whether it has fully serviced the other three lists or not.
Following these rules ensures that Control Transfers will always have at least 10% of USB bus bandwidth. If the transfer of Control packets is completed before the expiration of allocated scheduling interval, the remaining time will be used by the host for Bulk Data Transfers. Thus we have:
- Isochronous Transfers are guaranteed get 90% of bus bandwidth.
- Interrupt Transfers allocate the rest capacity remaining after 90% occupied by Isochronous Transfers.
- For Bulk Data Transfers, allocate the time left after Isochronous Transfers and Interrupt Transfers (within the frame of 90% brandwidth)
- Control Transfers are guaranteed 10% of the bus bandwidth
- If the transfer of Control packets is finished before the time allocated for it 10% interval, the rest time will be used for Bulk Data Transfers.
Now, we’re done with the theory, let’s look how it can be implemented on Android OS.
USB use and Android: general information
A few words about the existing Android limitations you need to bear in when mind developing an application with USB device support. The first one is that, for now, Android does not support isochronous data transfer type; the second is that the minimum version of the Android SDK should be 12.
Traditionally, in order to debug an app, a wired connection is used. But to perform Android USB debugging, you should use a wireless device connection. To do this, you must:
- Switch the device into wireless debugging mode using the adb tcpip 5555 command.
- Disconnect the device from USB.
- Connect to the device via: adb connect : 5555
To switch a phone to USB debug mode, use the adb usb command.
Setting up an Android project
1. Add feature:
2. Add permission:
3. To the intent-filter of main Activity should be added:
4. Add meta-data to the main Activity section:
Add «device_filter.xml» file to the folder «/res/xml/». This file is used to specify USB device filters. The filtration is usually carried out in accordance with the device VID and PID. Additional filtering by device class is also possible. It is possible to leave the file empty. In this case, the filtering will not be applied and the application will be launched upon the connection with any USB device. Example of the contents of the file "device_filter.xml":
The device VID and PID should be specified in decimal format only!
Working with USB devices in Android
Before getting started, it’s necessary to get a UsbManager object. To do this you can use the following code line:
usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
We highly recommend you add this variable to class scope, since you’ll need it in the future.
The next step is to find available devices. It can be done using getDeviceList() method of usbManager variable. This method returns Map. To get the device, we need, we should filter this map.
After receiving the device, we can get the number of interfaces from it using the device.getInterfaceCount() method. A particular interface can be obtained using the device.getInterface (int) method, where it is necessary to transfer the interface number as a parameter. The interface type can be obtained using the interface.getInterfaceClass() method, which returns int. The list of constants can be found in the class UsbConstants.
By analogy, using the interface.getEndpointCount() method we get the number of interface endpoints. A specific endpoint is obtained using the interface.getEndpoint (int) method. Since we have a one way (in or out) endpoint, it’s possible to get information using the endpoint.getDirection() method.
To connect the devices, follow the next sequencing:
1. UsbDeviceConnection connection = usbManager.openDevice(device); - to create a connection.
2. connection.claimInterface(interface, isForceClaim); - to grab the interface we need.
Data transfer can be carried out in 3 ways depending on the type of data need to be transferred:
1. connection.controlTransfer(int requestType, int request, int value, int index, byte  buffer, int offset, int length, int timeout) - for Control Transfer.
2. connection.bulkTransfer(UsbEndpoint endpoint, byte  buffer, int offset, int length, int timeout) - for Bulk Data Transfer.
3. connection.queue(ByteBuffer buffer) - for Interrupt Transfers. This method works asynchronously.
To receive data, the same methods as for data transfer are used. The only difference is that the endpoint with the "out" direction is used. The exception is for the transfer of data by interrupts. To receive data of this type UsbRequest method request = connection.requestWait() is applied.
After finishing the work with the device, it’s necessary to close the connection. It can be done using a sequential method call:
1. connection.releaseInterface(interface); - release the interface;
2. connection.close(); - close the connection.
The lack of documentation on how to develop Android apps with USB device support forces us to really consider how it all works together. By utilizing a test and try approach, we found all the keys we needed that help us overcome this challenge.
Hopefully, now it will be easier for you when you need to connect a USB device to Android.
Created by Dmitry Protasov