Skip to main content

USB Driver Development Guide

How To Create New USB Drivers

This document is intended for those developers who are going to create new drivers for USB devices. If you are developing an application which makes use of one or more existing drivers, please see the USB Application Development Guide.

Driver development leverages Electric Imp’s USB Driver Framework, an easily extensible foundation for USB device drivers which is embodied in a class, USB.Driver.

The USB Driver Framework is implemented as code library, which applications making use of your driver will need to import using the standard #require directive. For more information on the USB Driver Framework’s core classes and data structures, please see the library page.

New Driver Development Step-by-Step Instructions

1. Extend The Basic USB.Driver Class

The USB Drivers Framework includes a basic USB.Driver implementation. You create your new driver class by extending USB.Driver as follows:

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

2. Implement The Standard Driver Matching Procedure

Every driver class must implement a match() method which will be called automatically when a device is connected to the host. This method is responsible for checking that the attached device is supported by the driver and, if so, instantiating a suitable driver. Once match() returns, the USB Drivers Framework will then check any other drivers registered with it.

If your driver is able to work with the attached device, it should return a new instance of the driver class. The method may also return an array of instances if the driver decides to work with each of the device’s interfaces individually.

For example, you might wish to instantiate a driver only for devices with a known product and vendor ID:

class MyCustomDriver extends USB.Driver {

    static VENDOR_ID = 0x46D;
    static PRODUCT_ID = 0xC31C;

    function match(device, interfaces) {
        if (device.getVendorId() == VENDOR_ID && device.getProductId() == PRODUCT_ID) {
            server.log("Device matched, creating the driver instance...");
            return MyCustomDriver();
        }

        // Device not supported
        server.log("Device didn't match");
        return null;
    }
}

Alternatively, your driver might support a whole class of devices, such as keyboards and mice, which should not have vendor specific attributes. The following example shows how to match all HID and Boot keyboards:

class MyCustomDriver extends USB.Driver {
    function match(device, interfaces) {
        foreach (interface in interfaces) {
            if (interface["class"] == 3 &&  /* HID      */
              interface.subclass == 1 &&    /* BOOT     */
              interface.protocol == 1 ) {   /* Keyboard */
                server.log("Device matched");
                return MyCustomDriver();
            }
        }

        // Device not supported
        server.log("Device didn't match");
        return null;
    }
}

Note There are no limits placed on the driver’s constructor arguments as it is always called from within the driver’s own match() method.

Getting Access To USB Interfaces

Every driver receives interfaces that it may work with via the second parameter of the match() method. To start working with these interfaces, the driver needs to select the correct endpoint by parsing information from each interface descriptor’s endpoints array. When a suitable endpoint is found, the driver should call the endpoint descriptor’s get() function to retrieve the endpoint instance.

Note An endpoint’s get() function may result in an exception being thrown when the number of open endpoints exceeds certain limits set by the imp API. For example, there currently can be only one Interrupt In endpoint open at any one time:

function findEndpoint(interfaces) {
    foreach(interface in interfaces) {
        if (interface["class"] == 3) {  /* HID */
            local endpoints = interface.endpoints;
            foreach(endpoint in endpoints) {
                if (endpoint.attributes == USB_ENDPOINT_INTERRUPT) return endpoint.get();
            }
        }
    }

    return null;
}

To simplify new driver code, every interface descriptor instance includes a find() method that searches for the endpoint with the given attributes and returns the one found first. So we can update the above code to:

function findEndpoint(interfaces) {
    foreach(interface in interfaces) {
        if (interface["class"] == 3) {  /* HID */
            local endpoint = interface.find(USB_ENDPOINT_INTERRUPT, USB_DIRECTION_IN);
            if (endpoint) return endpoint;
        }
    }

    return null;
}

Concurrent Access To Resources

The USB Drivers Framework does not prevent drivers from accessing any interface resources the driver receives through match(). It is the responsibility of the driver’s author to manage situations where several drivers may be trying to access the same device concurrently.

An example of such a collision is an exception thrown by USB.FuncEndpoint.read() while another driver's call is still pending.

3. Support Releasing Driver Resources (Optional)

When device resources required by the driver are no longer available, the USB Drivers Framework calls the driver’s release() method to give the driver a chance to shut down gracefully and release any other resources that were allocated during its lifetime.

Note All the framework resources should be considered as closed at this point and must not be accessed in the release() function.

The release() method is optional, so implement it only if you need to release resources.

The following example shows a very simple release() implementation:

class MyCustomDriver extends USB.Driver {

    constructor() {
        // Allocating some native resources
        server.log("Allocating resources...")
    }

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

    function release() {
        // Deallocating resources
        server.log("Deallocating resources...")
    }
}

4. Support Accessing Endpoint Zero (Optional)

Endpoint 0 is a special type of control endpoint that implicitly exists for every device (see USB.ControlEndpoint).

Example

class MyCustomDriver {

    _endpoint0 = null;

    constructor(ep0) {
        _endpoint0 = ep0;

        // Get USB descriptor
        local data = blob(16);
        _endpoint0.transfer(USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_DEVICE,
                            USB_REQUEST_GET_DESCRIPTOR,
                            0x01 << 8,
                            0,
                            data);

        // Handle data
    }

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

    function release() {
        _endpoint0 = null;
    }
}

5. Implement The _typeof Metamethod (Optional)

The Squirrel _typeof metamethod can be used to return a unique identifier for the driver class, and so be used to identify the driver at runtime, eg. for diagnostic and/or debugging purposes.

The _typeof() method is optional.

The following example shows a very simple release() implementation:

class MyCustomDriver extends USB.Driver {

    constructor() {
        // Allocating some native resources
        server.log("Allocating resources...")
    }

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

    function release() {
        // Deallocating resources
        server.log("Deallocating resources...")
    }

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

6. Export Public Driver APIs

Each driver should expose its own API to applications. Please supply your driver with suitable documentation that describes its public API, details its limitations and requirements, and includes example code.

Full Driver Example

class MyCustomDriver extends USB.Driver {

    _endpoint0 = null;

    constructor(ep0) {
        _endpoint0 = ep0;

        local data = blob(16);
        _endpoint0.transfer(USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_DEVICE,
                            USB_REQUEST_GET_DESCRIPTOR,
                            0x01 << 8,
                            0,
                            data);

        server.log(data);

        // Allocate some native resources
        server.log("Allocating resources...")
    }

    // Required Core Function
    function match(device, interfaces) {
        if (findEndpoint(interfaces)) {
            server.log("Driver matches the device");
            return MyCustomDriver(device.getEndpointZero());
        }

        server.log("Driver doesn't match the device");
        return null;
    }

    // Optional Core Function
    function release() {
        // Deallocating resources
        server.log("Deallocating resources...")
    }

    // Optional Core Function
    function _typeof() {
        // Return the name of the driver when the application calls 'typeof driver'
        return "MyCustomDriver";
    }

    // Used by match() to check we support the connected device
    function findEndpoint(interfaces) {
        foreach(interface in interfaces) {
            if (interface["class"] == 3) { 
                local endpoint = interface.find(USB_ENDPOINT_INTERRUPT, USB_DIRECTION_IN);
                if (endpoint) return endpoint;
            }
        }

        // No endpoints found
        return null;
    }
}

Generic Development Recommendations

  • When using the USB Drivers Framework, do not access the imp API hardware.usb object directly.

  • Please avoid using the impCentral #require directive or the Builder statement @include within your driver code to avoid compilation issues and to prevent the loading of duplicated code at runtime.

  • If you are writing a USB driver that you intend to share with the community, please follow our guidelines for third-party library submission.

Driver Examples

Electric Imp’s USB Driver Framework source code repository on GitHub contains a number of example drivers that you can use to examine how Framework-based drivers are constructed and used, or as the basis for your own drivers: