Skip to main content

USB Application Development Guide

How To Integrate USB Drivers Into Your Code

This guide is intended for those developers who are going to integrate one or more existing USB drivers into their applications. If you are developing a driver itself, please see the USB Driver Development Guide.

Before you use a driver, please read its documentation carefully to fully understand its limitations and requirements.

Including The USB Drivers Framework And Drivers

To include Electric Imp’s USB Drivers Framework in your application, add #require "USB.device.lib.nut:1.1.0" to the top of your device code.

The base USB Drivers Framework does not itself provide any device drivers. Application developers will therefore need to include in their code any drivers they need.

Statements to import specific USB drivers and other, related libraries should be placed immediately after the USB Drivers Framework import statement. For example, here we include a generic driver provided as a library by Electric Imp:

#require "USB.device.lib.nut:1.1.0"
#require "GenericUsbDriver.device.lib.nut:1.0.0"

Not all drivers may be offered this way; some you may need to declare within your code:

#require "USB.device.lib.nut:1.1.0"

class GenericUsbDriver extends USB.Driver {
    // Your driver code goes here
}

Or you may load the driver code in using Builder or some other tool:

#require "USB.device.lib.nut:1.1.0"

@include "GenericUsbDriver.nut"

Initializing The USB Drivers Framework

Once the necessary driver libraries are included in your application code, the USB Drivers Framework should be configured to use them.

The main entry point into the USB Drivers Framework is the USB.Host class. This class is responsible for driver registration and instantiation; device and driver event notifications; and driver lifecycle management.

The following code example shows how to initialize the USB Drivers Framework with a single FT232RL FTDI USB driver. It runs on an imp005-based board:

#require "USB.device.lib.nut:1.1.0"
#require "FT232RLFtdiUsbDriver.device.lib.nut:1.0.0"

ft232Driver <- null;

function driverStatusListener(eventType, driver) {
    if (eventType == USB_DRIVER_STATE_STARTED) {
        if (typeof driver == "FT232RLFtdiUsbDriver") ft232Driver = driver;

        // Start work with FT232RL driver API here...

    } else if (eventType == USB_DRIVER_STATE_STOPPED) {
        // Immediately stop all interaction with FT232RL driver API
        // and cleanup the driver reference
        ft232Driver = null;
    }
}

host <- USB.Host(hardware.usb, [FT232RLFtdiUsbDriver]);
host.setDriverListener(driverStatusListener);

The example creates an instance of the USB.Host class. The constructor takes two parameters: the imp API USB object representing your board’s USB bus, and an array of device-driver classes — in this case, an array containing a single class, FT232RLFtdiUsbDriver.

The final line shows how to register a driver-state listener function by calling the USB.Host.setDriverListener() method. Please refer to the method documentation for more details.

Using Multiple Drivers

It is possible to register any number of drivers with the USB Drivers Framework: just add further device-driver class references to the array parameter in the USB.Host constructor:

#require "USB.device.lib.nut:1.1.0"

#require "ACustomDriver2.nut:1.2.0"
#require "ACustomDriver1.nut:1.0.0"
#require "ACustomDriver3.nut:0.1.0"

host <- USB.Host(hardware.usb, [ACustomDriver1, ACustomDriver2, ACustomDriver3]);

Whenever a device is plugged or unplugged, the corresponding drivers that match this device will be respectively started or stopped automatically by the USB Drivers Framework. Upon the connection of a device, the Framework attempts to match it to each of the registered drivers. If one of the drivers matches the device being connected, then the driver will be instantiated.

Some devices provide multiple interfaces and these interfaces could be implemented via a single driver or multiple drivers. The USB Drivers Framework instantiates all drivers which match the connected device. If all the registered drivers match device interfaces, then they all are instantiated and started by the USB Drivers Framework.

Application developers are responsible for including required device-driver libraries into the application code.

Accessing A Driver’s API

Each driver provides its own public API to allow the application to interact with USB devices. Application developers should therefore read carefully the driver documentation and follow its instructions.

Driver public APIs are neither limited nor enforced by the USB Drivers Framework in any way. It is the responsibility of the driver developer to decide which APIs to expose to application developers.

Configuring Hardware Pins For USB

The reference hardware for the USB Drivers Framework is the imp005. This imp module requires a special pin configuration in order to enable USB: pinR (USB active high load control) and pinW (active low USB fault indication) must both be set high. The USB Driver Framework does this for you by default. Please see documentation on the USB.Host constructor for more details.

If your application is targeting a custom board based on a different Electric Imp module, you may need to set the constructor’s autoConfigPins parameter to false in order to prevent configuration issues. You will also need to configure it from within the application according to the module’s specification.

Working With Attached Devices

The recommended way to interact with an attached device is to use one of the drivers that support that device. However, it may be important to access the device directly, for example, to select an alternative configuration or to change its power state.

To provide such access, the USB Drivers Framework creates a proxy USB.Device class for every device attached to the USB interface.

You can retrieve the device’s USB.Device instance from the listener registered using USB.Host.setDeviceListener(), which is executed when a device is connected and/or disconnected to/from USB. You can also retrieve a list of all the attached devices by calling the USB.Host.getAttachedDevices().

The USB.Device class provides a number of methods you can use to interact and manages devices. For example, USB.Device.getEndpointZero() returns a special control endpoint 0 that can be used to configure the device by transferring messages in a special format via this endpoint. The format of such messages is out the scope of this document; please refer to the USB specification for more details.

The following example code shows how to retrieve the endpoint 0 to then use it for device configuration:

#require "USB.device.lib.nut:1.1.0"

const VID = 0x413C;
const PID = 0x2107;

// Endpoint 0 for the required device
endpoint0 <- null;

// Custom driver class
class MyCustomDriver extends USB.Driver {

    constructor() {
        // constructor
    }

    function match(device, interfaces) {
        return MyCustomDriver();
    }

    function _typeof() {
        return "MyCustomDriver";
    }
}

function deviceStatusListener(eventType, device) {
    server.log(device.getVendorId());
    server.log(device.getProductId());
    if (eventType == USB_DEVICE_STATE_CONNECTED) {
        if (device.getVendorId() == VID && device.getProductId() == PID) {
            endpoint0 = device.getEndpointZero();

            // Do device configuration here
        }
    } else if (eventType == "disconnected") {
        endpoint0 = null;
    }
}

host <- USB.Host(hardware.usb, [MyCustomDriver]);
host.setDeviceListener(deviceStatusListener);

Resetting The USB.Host

To reset the USB host, call USB.Host.reset(). This method can be used by an application in response to unrecoverable error, such as a driver not responding. This method should clean up all drivers and devices with corresponding event listener notifications and finally perform a USB reconfiguration.

It is not necessary to configure setDriverListener() or setDeviceListener() again — the same callback should receive all further notifications about re-attached devices and corresponding driver state changes. Please note that as the drivers and devices are instantiated again, they are may have different addresses.

The following example shows reset() being used:

#require "USB.device.lib.nut:1.1.0"

// Custom driver class
class MyCustomDriver extends USB.Driver {

    function match(device, interfaces) {
        return MyCustomDriver();
    }

    function _typeof() {
        return "MyCustomDriver";
    }
}

host <- USB.Host(hardware.usb, [MyCustomDriver]);

host.setDeviceListener(function(event, device) {
    // print all events
    server.log("[APP]: Event: " + event + ", number of connected " + host.getAttachedDevices().len());

    // Check that the number of connected devices is the same after reset
    if (event == USB_DEVICE_STATE_CONNECTED && host.getAttachedDevices().len() != 1) {
        server.log("Expected only one attached device");
    }
});

// Reset bus after 2 seconds
imp.wakeup(2, function() {
    host.reset();
}.bindenv(this));