Skip to main content

Understanding USB Errors

How To Debug USB Issues

impOS™ 34 and above support the connection of a single USB 1.1/2.0 Full Speed (12Mbit/s) device to an imp005 or impC001 It provides a usb object (a property of the hardware object) through which the imp005’s universal serial bus can be managed: the bus configured, a path to the device (the endpoint) established, and control signals and bulk data transferred between the device and the imp005 (the host).

While developing an application that makes use of USB, the developer may encounter common categories of error. Each of these is discussed below. Errors are reported through the callback function registered when the bus is configured with usb.configure(): the eventDetails table returned to the callback as a USB_TRANSFER_COMPLETED action includes a state key which you can use to determine whether an error has taken place and, if so, of what kind. 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.

The following code portion provides an example of how this status information might be used. In the USB event callback, we trap non-zero non-error status codes and set them to ‘OK’ (0). The follow-on function checks the value of state passed into the callback: stall errors are handled by first attempting to clear the stall (see ‘Halt Conditions’, below) and if that fails, resetting the bus — which is also the procedure for all other types of error:

// This is the callback function passed into usb.configure()
function usbEventCallback(eventType, eventDetails) {
    if (eventType == USB_TRANSFER_COMPLETED) {
        if (eventDetails.state == 15 || eventDetails.state == 16) {
            // Trap non-error codes so they are processed as 'OK'
            // by the callback completion function
            completeCallback(USB_OK, 
                             eventDetails.endpoint, 
                             eventDetails.length);
        } else {
            // Just pass all other codes for callback completion
            completeCallback(eventDetails.state, 
                             eventDetails.endpoint, 
                             eventDetails.length);
        }
    }
}

function completeCallback(state, endpoint, length) {
    if (state != USB_OK) {
        if (state == USB_TYPE_STALL_ERROR) {
            // Try to clear the stall...
            // clearStall() returns 'true' if it has succeeded
            if (!clearStall(endpoint)) {
                // Unable to clear the stall, so reset the bus
                resetBus();
            } else {
                // Stall cleared - do any extra work required
            }
        } else {
            // The error is not a stalled, so reset the bus to clear
            resetBus();
        }
    }
}

Generic USB Error Types

Only two types of error are generic: halt conditions (in which data flow stops prematurely) and unending transfers (in which data flow is formally not acknowledged (NAK’d) by the device). These are discussed below from the perspective of both bulk data transfers and control data transfers.

Other errors are application-specific (or device-specific) and so are not discussed here. Developers encountering these errors should check the documentation provided with the device itself, or their own application documentation.

Halt Conditions

A halt condition is signalled by the transmission by the peripheral device of a STALL handshake packet — signalled by the state key in the eventDetails table returned to your USB_TRANSFER_COMPLETED callback having the value USB_TYPE_STALL_ERROR (4). The device has found itself in a state that requires intervention from the host; the host has to perform some form of recovery action before communication to that endpoint can begin again. An example of an event that can trigger a STALL is an attempt to transfer more data to the device than was specified.

Bulk Transfers

A stalled endpoint can be cleared by sending a standard clear feature request; you specify the endpoint address to clear. The clear feature request (USB_REQUEST_CLEAR_FEATURE) must be sent to the default endpoint (0) via a control transfer:

function clearStall(endpoint) {
    // Attempt to clear the stall
    try {
        usb.controltransfer(_speed,
                            _devAddress,
                            0,
                            USB_SETUP_RECIPIENT_ENDPOINT | USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD,
                            USB_REQUEST_CLEAR_FEATURE,
                            0,    // Feature selector: 0 = ENDPOINT_HALT
                            endpoint,
                            _maxPacketSize);
    } catch(error) {
        // Attempt failed
        server.error("Control transfer failed. Error: " + error);
        return false;
    }

    // Attempt successful
    return true;
}

Control Transfers

The USB 2.0 specification states that after a halt condition is encountered or an error is detected by the host, a control endpoint is allowed to recover by simply waiting for and accepting the next setup packet ID (initiating a control transfer). If the next setup packet ID can’t be accepted, the control endpoint must initiate a device reset to clear the halt or error condition.

In these circumstances, the imp API can be used to reset the device by calling usb.disable() and then calling usb.configure() to reconfigure the connected device. The following code shows this implemented as a function:

function resetBus() {
    // We can't clear the stall, so reset the bus
    // This is overkill - more sophisticated code would examine
    // exact error state and adapt accordingly

    // Reset the bus: disable it...
    usb.disable();

    // ...then reconfigure it
    usb.configure(usbEventCallback);
}

Unending Transfers

Bulk transfers

Although the USB 2.0 specification sets an upper limit of five seconds for any command to be processed, impOS does not abort any transfer. Instead, the Squirrel programmer can set a timer and act when it fires. Currently, the only recovery action available is to disable (with usb.disable()) and then (re)configure the entire USB interface (with usb.configure()) as shown in the example code above. Note that this will abort all ongoing transactions.

Control Transfers

Because the usb.controltransfer() method is blocking, a timeout of five seconds is imposed on its use. A USB_TYPE_TIMEOUT error will be thrown if the timeout period is exceeded.

Other Errors

Other conditions are device and/or application specific and are normally cleared via control transfers. The actions to take are specific to the connected device and must be known by the Squirrel programmer. For common classes of devices, details are provided in standard USB specifications.