Latest Version: 1.1.0
Electric Imp’s USB Driver Framework is an easily extensible foundation for the delivery of USB device drivers. It is implemented as a code library, but comprises a number of classes and data structures, which are described below.
If you are developing an application which makes use of one or more existing drivers, please see the USB Application Development Guide for instructions on how to make use of the USB Driver Framework in your code.
If you are developing a driver, please see the USB Driver Development Guide.
You can view the latest version of the library’s source code on GitHub. Click here to see information on other versions of this library.
To include this library in your project, add #require "USB.device.lib.nut:1.1.0" at the top of your device code.
This is the main interface you use to start working with USB devices and drivers.
If you have more then one USB port in your product or development board, you should create a USB.Host instance for each of them.
This method instantiates the USB.Host class. USB.Host is a wrapper over the native imp API USB implementation.
It should be instantiated only once per physical port for any application. There are some Electric Imp boards which do not have a USB port, therefore an exception will be thrown on any attempt to instantiate USB.Host in code running on these boards.
Note When using the USB Drivers Framework, do not access the imp API hardware.usb object directly.
Parameter | Type | Required | Description |
---|---|---|---|
usb | Object | Yes | The imp API usb object representing a Universal Serial Bus (USB) interface |
drivers | Array of USB.Driver objects | Yes | An array of pre-defined driver classes |
autoConfigPins | Boolean | No | Indicate whether to configure imp005 pins R and W, which must be configured for USB to work on the imp005. Default: true |
#require "USB.device.lib.nut:1.1.0"
class MyCustomDriver1 extends USB.Driver {
...
}
class MyCustomDriver2 extends USB.Driver {
...
}
// Instantiate the USB host and register our drivers
usbHost <- USB.Host(hardware.usb, [MyCustomDriver1, MyCustomDriver2]);
This method instructs the host to begin listening for driver events. The application will then be notified via the supplied listener function if a driver is started or stopped.
Passing in null
clears any previously assigned listener.
Parameter | Type | Required | Description |
---|---|---|---|
listener | Function | Yes | A function to be called when a driver event occurs |
Parameter | Type | Description |
---|---|---|
eventType | String | The driver event type: USB_DRIVER_STATE_STARTED or USB_DRIVER_STATE_STOPPED |
driver | USB.Driver instance | The driver triggering the event |
Nothing.
usbHost.setDriverListener(function (eventType, driver) {
switch (eventType) {
case USB_DRIVER_STATE_STARTED:
server.log("Driver found and started " + (typeof driver));
break;
case USB_DRIVER_STATE_STOPPED:
server.log("Driver stopped " + (typeof driver));
break;
}
});
This method instructs the host to begin listening for runtime device events, such as plugging or unplugging a peripheral. The application will then be notified via the supplied listener function.
Passing in null
clears any previously assigned listener.
Parameter | Type | Required | Description |
---|---|---|---|
listener | Function | Yes | A function to be called when a device event occurs |
Parameter | Type | Description |
---|---|---|
eventType | String | The device event type: USB_DEVICE_STATE_CONNECTED or USB_DEVICE_STATE_DISCONNECTED |
device | USB.Device instance | The device triggering the event |
Nothing.
// Subscribe to USB connection events
usbHost.setDeviceListener(function (eventType, device) {
switch (eventType) {
case USB_DEVICE_STATE_CONNECTED:
server.log("New device found");
break;
case USB_DEVICE_STATE_DISCONNECTED:
server.log("Device detached");
break;
}
});
This method resets the USB host. The effect of this action is similar to the physical reconnection of all connected devices. It disables USB, and cleans up all drivers and devices. This results in the execution of any corresponding driver and device listeners. All devices will have a new device object instances and different addresses after a reset.
It can be used by a driver or application in response to an unrecoverable error, such as a timed out bulk transfer, or a halt condition encountered during control transfers.
Nothing.
class MyCustomDriver extends USB.Driver {
...
}
host <- USB.Host(hardware.usb, [MyCustomDriver]);
host.setDeviceListener(function(eventName, eventDetails) {
if (eventName == USB_DEVICE_STATE_CONNECTED && host.getAttachedDevices().len() != 1) {
server.log("Only one device could be attached");
}
});
// Reset after two seconds
imp.wakeup(2, function() {
host.reset();
}.bindenv(this));
This method returns a list of attached devices.
Array of USB.Device objects.
This class represents attached USB devices. Please refer to the USB specification for details of USB devices' descriptions.
Typically, applications don't use device objects directly, but they can use by drivers to acquire required endpoints. Neither applications nor drivers should explicitly create USB.Device objects — they are instantiated by the USB Drivers Framework automatically.
When using the USB Drivers Framework, all management of USB device configurations, interfaces and endpoints must go through the device object rather than via the imp API hardware.usb object.
This method returns the device’s descriptor. It throws an exception if the device is not currently connected.
Table — the device descriptor.
This method returns the device vendor ID. It throws an exception if the device is not currently connected.
String — the device’s vendor ID.
This method returns the device product ID. It throws an exception if the device is not currently connected.
String — the device’s product ID.
This method returns an array of drivers for the attached device. It throws an exception if the device is not currently connected.
Each USB device may provide a number of interfaces which could be supported by a one or more drivers. For example, a keyboard with an integrated touchpad could have keyboard and touchpad drivers assigned.
Array — a set of USB.Driver objects.
This method returns a proxy for the device’s Control Endpoint 0. The endpoint 0 is a special type of endpoint that implicitly exists for every device. The method throws an exception if the device is not currently connected.
USB.ControlEndpoint — the zero endpoint.
This method returns a reference to the parent USB host.
USB.Host — the host to which the device is connected.
This class represents USB control endpoints. Class instances are managed by instances of USB.Device and should be acquired by calling getEndpointZero() on a USB.Device instance.
Note Neither applications nor drivers should explicitly create USB.ControlEndpoint objects — they are instantiated by the USB Drivers Framework automatically.
The following code sends a request to the control endpoint 0 to reset the state (clear error codes) of a functional endpoint specified by functionalEndpointAddress:
device
.getEndpointZero()
.transfer(USB_SETUP_RECIPIENT_ENDPOINT | USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD,
USB_REQUEST_CLEAR_FEATURE,
0,
functionalEndpointAddress);
This is a generic method for transferring data over a control endpoint.
Parameter | Type | Required | Description |
---|---|---|---|
requestType | Integer | Yes | USB request type. Please see Control Endpoint Request Types for more details |
request | Integer | Yes | The specific USB request. Please see Control Endpoint Requests for more details |
value | Integer | Yes | A value determined by the specific USB request |
index | Integer | Yes | An index value determined by the specific USB request |
data | Blob | No | Optional storage for incoming or outgoing payload. Default: null |
This method returns the endpoint address, which is required by a device control operation performed over control endpoint 0.
Integer — the endpoint address.
This class represents all non-control endpoints, ie. bulk, interrupt and isochronous endpoints. It is managed by the USB.Device class and should be acquired only through USB.Device instances. Neither applications nor drivers should explicitly create USB.FuncEndpoint objects — they are instantiated by the USB Drivers Framework automatically.
This method asynchronously writes data through the endpoint. It throws an exception if the endpoint is closed or doesn't support USB_DIRECTION_OUT.
Parameter | Type | Required | Description |
---|---|---|---|
data | Blob | Yes | Payload data to be sent through this endpoint |
onComplete | Function | Yes | A function to be called when the transfer is complete |
Parameter | Type | Description |
---|---|---|
endpoint | USB.FuncEndpoint instance | The endpoint used |
state | Integer | USB transfer state. Please see USB Transfer States for more details |
data | Blob | The payload data being sent |
length | Integer | The length of the written data |
Nothing.
class MyCustomDriver extends USB.Driver {
constructor(device, interfaces) {
try {
local payload = blob(16);
local endpoint = interfaces[0].endpoints[1].get();
endpoint.write(payload, function(ep, state, data, len) {
if (len > 0) server.log(len + " bytes sent");
}.bindenv(this));
} catch(err) {
server.error(err);
}
}
function match(device, interfaces) {
return MyCustomDriver(device, interfaces);
}
}
This method asynchronously reads data from the endpoint. It throws an exception if the endpoint is closed, has an incompatible type, or is already busy.
The method sets an upper limit of five seconds for any command for the bulk endpoint to be processed.
Parameter | Type | Required | Description |
---|---|---|---|
data | Blob | Yes | Blob to read data into |
onComplete | Function | Yes | A function to be called when the transfer is complete |
Parameter | Type | Description |
---|---|---|
endpoint | USB.FuncEndpoint instance | The endpoint used |
state | Integer | USB transfer state. Please see USB Transfer States for more details |
data | Blob | The payload data being received |
length | Integer | The length of the written data |
Nothing.
class MyCustomDriver extends USB.Driver {
constructor(device, interfaces) {
try {
local payload = blob(16);
local endpoint = interfaces[0].endpoints[0].get();
endpoint.read(payload, function(ep, state, data, len) {
if (len > 0) server.log(len + " bytes read");
}.bindenv(this));
} catch(err) {
server.error(err);
}
}
function match(device, interfaces) {
return MyCustomDriver(device, interfaces);
}
}
This method returns the endpoint address, which is required by a device control operation performed over control endpoint 0.
Integer — the endpoint address.
This class is the base for all drivers that are developed using the USB Drivers Framework. It contains one method, match() that must be be implemented by every USB driver, and two further methods, release() and _typeof(), which are optional but recommended.
Note Applications should not explicitly create USB.Driver objects — they are instantiated by the USB Drivers Framework automatically.
This method is used to check if the driver can support the specified device and its exposed interfaces.
If the driver can support the device, the method should return a new driver instance or an array of driver instances (when multiple interfaces are supported and a driver instance is created for each of them).
If the driver can’t support the device, return null
.
The driver-device matching procedure can be based on checking Vendor ID, Product ID, device class and/or subclass, and/or interfaces.
Parameter | Type | Required | Description |
---|---|---|---|
device | USB.Device instance | Yes | The object representing the attached device |
interfaces | Array of tables | Yes | A set of tables each of which describes the interfaces supported by the attached device |
USB.Driver instance, array of
USB.Driver instances, or null
.
This method releases all of the resources instantiated by the driver. It should be used by the driver to clean up its resources and free external resources if necessary.
It is called by the USB Drivers Framework when a USB device is disconnected.
Do not access USB.Driver or endpoint instances from any callback once release() is called, as they may have already been partially released. Any attempts to access these objects and their members from the callback may therefore throw exceptions.
This metamethod is used to return a class name when typeof <instance>
is invoked. It can be used to identify the driver instance type as runtime: for example, for debugging purposes.
local usbHost = USB.Host(hardware.usb, [MyCustomDriver1, MyCustomDriver2]);
usbHost.setDriverListener(function(eventName, eventDetails) {
if (eventName == "started" && typeof eventDetails == "MyCustomDriver2") {
server.log("MyCustomDriver2 initialized");
}
});
These are constants that may be useful for endpoint search functions.
Constant Name | Value | Description |
---|---|---|
USB_ENDPOINT_CONTROL | 0x00 | Control Endpoint type value |
USB_ENDPOINT_ISOCHRONOUS | 0x01 | Isochronous Endpoint type value |
USB_ENDPOINT_BULK | 0x02 | Bulk Endpoint type value |
USB_ENDPOINT_INTERRUPT | 0x03 | Interrupt Endpoint type value |
USB_ENDPOINT_TYPE_MASK | 0x03 | A mask value that covers all endpoint types |
USB_DIRECTION_OUT | 0x00 | A bit value that indicates OUTPUT endpoint direction |
USB_DIRECTION_IN | 0x80 | A bit value that indicates INPUT endpoint direction |
USB_DIRECTION_MASK | 0x80 | A mask to extract the endpoint direction from an endpoint address |
These are possible values for the ControlEndpoint.transfer() method’s parameter requestType.
Constant Name | Value | Description |
---|---|---|
USB_SETUP_HOST_TO_DEVICE | 0x00 | Transfer direction: host to device |
USB_SETUP_DEVICE_TO_HOST | 0x80 | Transfer direction: device to host |
USB_SETUP_TYPE_STANDARD | 0x00 | Type: standard |
USB_SETUP_TYPE_CLASS | 0x20 | Type: class |
USB_SETUP_TYPE_VENDOR | 0x40 | Type: vendor |
USB_SETUP_RECIPIENT_DEVICE | 0x00 | Recipient: device |
USB_SETUP_RECIPIENT_INTERFACE | 0x01 | Recipient: interface |
USB_SETUP_RECIPIENT_ENDPOINT | 0x02 | Recipient: endpoint |
USB_SETUP_RECIPIENT_OTHER | 0x03 | Recipient: other |
These are possible values for the ControlEndpoint.transfer() method’s parameter request.
Constant Name | Value | Description |
---|---|---|
USB_REQUEST_GET_STATUS | 0 | Get status |
USB_REQUEST_CLEAR_FEATURE | 1 | Clear feature |
USB_REQUEST_SET_FEATURE | 3 | Set feature |
USB_REQUEST_SET_ADDRESS | 5 | Set address |
USB_REQUEST_GET_DESCRIPTOR | 6 | Get descriptor |
USB_REQUEST_SET_DESCRIPTOR | 7 | Set descriptor |
USB_REQUEST_GET_CONFIGURATION | 8 | Get configuration |
USB_REQUEST_SET_CONFIGURATION | 9 | Set configuration |
USB_REQUEST_GET_INTERFACE | 10 | Get interface |
USB_REQUEST_SET_INTERFACE | 11 | Set interface |
USB_REQUEST_SYNCH_FRAME | 12 | Sync frame |
These are possible values for the ControlEndpoint.transfer() method’s parameter state.
Not all of the non-zero state values indicate errors. For example, USB_TYPE_FREE (15) and USB_TYPE_IDLE (16) are not errors. The range of possible error values you may encounter will depend on which type of USB device you are connecting. More details here.
Constant Name | Value |
---|---|
OK | 0 |
USB_TYPE_CRC_ERROR | 1 |
USB_TYPE_BIT_STUFFING_ERROR | 2 |
USB_TYPE_DATA_TOGGLE_MISMATCH_ERROR | 3 |
USB_TYPE_STALL_ERROR | 4 |
USB_TYPE_DEVICE_NOT_RESPONDING_ERROR | 5 |
USB_TYPE_PID_CHECK_FAILURE_ERROR | 6 |
USB_TYPE_UNEXPECTED_PID_ERROR | 7 |
USB_TYPE_DATA_OVERRUN_ERROR | 8 |
USB_TYPE_DATA_UNDERRUN_ERROR | 9 |
USB_TYPE_UNKNOWN_ERROR | 10 |
USB_TYPE_UNKNOWN_ERROR | 11 |
USB_TYPE_BUFFER_OVERRUN_ERROR | 12 |
USB_TYPE_BUFFER_UNDERRUN_ERROR | 13 |
USB_TYPE_DISCONNECTED | 14 |
USB_TYPE_FREE | 15 |
USB_TYPE_IDLE | 16 |
USB_TYPE_BUSY | 17 |
USB_TYPE_INVALID_ENDPOINT | 18 |
USB_TYPE_TIMEOUT | 19 |
USB_TYPE_INTERNAL_ERROR | 20 |
The USB Drivers Framework uses tables named descriptors which contain a description of the attached device, its interfaces and endpoint. endpoint and interface descriptors are used only at the driver probing stage, while device descriptors may be acquired from a USB.Device instance.
A device descriptor contains whole the device specification in addition to the Vendor ID and Product Id. The descriptor table has the following keys:
Key | Type | Description |
---|---|---|
usb | Integer | The USB specification to which the device conforms. It is a binary coded decimal value. For example, 0x0110 is USB 1.1 |
class | Integer | The USB class assigned by the USB-IF. If 0x00, each interface specifies its own class. If 0xFF, the class is vendor specific |
subclass | Integer | The USB subclass (assigned by the USB-IF) |
protocol | Integer | The USB protocol (assigned by the USB-IF) |
vendorid | Integer | The vendor ID (assigned by the USB-IF) |
productid | Integer | The product ID (assigned by the vendor) |
device | Integer | The device version number as BCD |
manufacturer | Integer | Index to a string descriptor containing the manufacturer string |
product | Integer | Index to a string descriptor containing the product string |
serial | Integer | Index to a string descriptor containing the serial number string |
numofconfigurations | Integer | The number of possible configurations |
When probed, a driver’s match() method receives two objects: a USB.Device instance and an array of the interfaces exposed by this device. Each interface is presented as a descriptor table with the following keys:
Key | Type | Description |
---|---|---|
interfacenumber | Integer | The number representing this interface |
altsetting | Integer | The alternative setting of this interface |
class | Integer | The interface class |
subclass | Integer | The interface subclass |
protocol | Integer | The interface class protocol |
interface | Integer | The index of the string descriptor describing this interface |
endpoints | Array of table | The endpoint descriptors |
find | Function | Auxiliary function to search for endpoints with specified attributes |
getDevice | Function | Returns the USB.Device instance that is the owner of this interface |
The interface descriptor’s find() function signature is as follows:
function(endpointType, endpointDirection) {
...
}
Parameter | Type | Accepted Values |
---|---|---|
endpointType | Integer | USB_ENDPOINT_CONTROL, USB_ENDPOINT_BULK, USB_ENDPOINT_INTERRUPT |
endpointDirection | Integer | USB_DIRECTION_IN, USB_DIRECTION_OUT |
It returns an instance of either the ControlEndpoint class or the FuncEndpoint class, or null
if no endpoints were found.
Each endpoints table contains the following keys:
Key | Type | Description |
---|---|---|
address | Integer bitfield | The endpoint address: D0-3: Endpoint number D4-6: Reserved D7: Direction (0 out, 1 in) |
attributes | Integer bitfield | Transfer type: 00: control 01: isochronous 10: bulk 11: interrupt |
maxpacketsize | Integer | The maximum size of packet this endpoint can send or receive |
interval | Integer | Only relevant for Interrupt In endpoints |
get | Function | A function that returns an instance of either USB.FuncEndpoint or USB.ControlEndpoint depending on information stored in the attributes and address fields |
The Electric Imp Dev Center documents the latest version of the library. For past versions, please see the Electric Imp public GitHub repos listed below.
Version | Source Code | Notes |
---|---|---|
0.1.0 | GitHub | Initial pre-release |
1.0.0 | GitHub | Initial release |
1.0.1 | GitHub | Remove bulk endpoint timeout |
1.1.0 | GitHub | Add USB.Device.getHost() method; add USB Hub driver example |
This library is licensed under the MIT License.