nRF5 SDK v17.1.0
USB device library
This information applies to the following SoCs: nRF52820, nRF52833, and nRF52840.

This chapter explains the details of the USB device (USBD) library implementation in this SDK.

Class brick concept

usb_low_level.svg
USB device architecture overview

The library allows to compose the whole USB device using class bricks:

Controller module

The main task of the controller is to distribute events to the correct blocks. You can also use it to access a class instance, if you know the interface or endpoint number.

The design of this module assumes that it is easy for a higher level module to know the endpoint number for read/write operations. However, for the information that comes from bottom to top, an additional module is required that will direct all the events to the right higher level module.

Every class instance needs a pointer to the data processing function. No matter what type of class it is, this pointer must be in a constant position.

One of the tasks of the controller module is clock management - clock is requested only when required by the peripheral to work. Clock is released when the library is stopped or when the USB traffic is suspended.

Core module

The main task of the core module is to process all system events and manage the current USB bus state. It also manages the enumeration process gathering all the descriptors from a class instance.

All transfers to control endpoint 0 are directed to the core module. This means that this module processes all SETUP transfers. The graph below shows how events are processed:

usb_setup_ev_workflow.svg
USB SETUP events workflow

Notice that all events that are targeted directly to the class instance (to the endpoint or interface that is used by the instance) are processed by the class first. Only if the class cannot process them (returns NRF_ERROR_NOT_SUPPORTED), then the standard handler is called.

The opposite order is implemented when it comes to SETUP messages that are targeted to the device. The standard handler is called first, and when it cannot process the message, it is sent to subsequent classes until one of them returns a value different than NRF_ERROR_NOT_SUPPORTED.

A specific handler path is implemented when a different target than endpoint, interface, or device is specified. This kind of SETUP requests does not have any default handler, so an attempt is made to process it through any available class instance.

Driver

For detailed driver documentation, refer to USBD driver.

Strings

This is a highly customizable string description module. For detailed documentation, refer to Configurable string descriptor module.

Predefined classes

This library provides a number of standard predefined classes that are ready to just instantiate them into the code. For more documentation on these classes, see USB Device library classes implementation.

Using the library

USBD library uses the USBD hardware driver. If you decide to use the USBD library, then endpoints should be accessed only via the API of this library. No functions from the driver should be called directly.

Initialization

Before configuring and using the library, it must be initialized. You can do it by calling app_usbd_init in the following way:

static const app_usbd_config_t m_usbd_config = {
.ev_handler = usbd_user_ev_handler
};
ret = app_usbd_init(&m_usbd_config);

The usbd_user_ev_handler function is provided here to process all the internal library events. It is called on every event coming from the hardware driver after the USB library processes it. It can be set to NULL if no events need global processing. In most cases, it is used to process SUSPEND/RESUME and APP_USBD_EVT_POWER_ events.

Example of an event processing function:

static void usbd_user_ev_handler(app_usbd_event_type_t event)
{
switch (event)
{
bsp_board_led_off(LED_USB_RESUME);
break;
bsp_board_led_on(LED_USB_RESUME);
break;
break;
bsp_board_leds_off();
break;
{
}
break;
m_usb_connected = false;
break;
m_usb_connected = true;
break;
default:
break;
}
}

Adding class instances

After the USBD library is initialized, you can add class instances. For information on how to create a class instance, see the documentation for the selected class. Basically, every class implementation provides a macro for creating the required instance.

Example of creating a CDC class instance:

static void cdc_acm_user_ev_handler(app_usbd_class_inst_t const * p_inst,
#define CDC_ACM_COMM_INTERFACE 0
#define CDC_ACM_COMM_EPIN NRF_DRV_USBD_EPIN2
#define CDC_ACM_DATA_INTERFACE 1
#define CDC_ACM_DATA_EPIN NRF_DRV_USBD_EPIN1
#define CDC_ACM_DATA_EPOUT NRF_DRV_USBD_EPOUT1
/**
@brief CDC_ACM class instance
*/
cdc_acm_user_ev_handler,
CDC_ACM_COMM_INTERFACE,
CDC_ACM_DATA_INTERFACE,
CDC_ACM_COMM_EPIN,
CDC_ACM_DATA_EPIN,
CDC_ACM_DATA_EPOUT,
);

In the code above, a class instance named m_app_cdc_acm was created. This code will be placed in global scope - outside any function.

Now, the created instance can be added to the USBD library:

app_usbd_class_inst_t const * class_cdc_acm = app_usbd_cdc_acm_class_inst_get(&m_app_cdc_acm);
ret = app_usbd_class_append(class_cdc_acm);

The USB stack is now initialized and has one CDC class implemented. It uses interface numbers 0 and 1 and endpoints IN2, IN1, and OUT1.

Another CDC instance may be created in a similar way. Remember that another CDC class instance would have to use different interface numbers and endpoints. However, after adding it to the library, all the descriptors will be updated and ready to work.

Starting

Now, you can enable and start the USBD library:

This may be done inside USB event handler, but requires enabling power events. Use the code below after USBD library is initialized and all the class instances are appended:

USBD library should be enabled on APP_USBD_EVT_POWER_DETECTED and started on APP_USBD_EVT_POWER_READY. See the source code in the examples directory for more details.

After starting the library, the whole stack starts to work. All processing required by the standard is done in the interrupts. It means that no more processing in any main loop is required for the USB to work.

Any other actions that may be required depend on the events processed in the class event handler and should be consulted with documentation of the implemented class.

Configurable string descriptor module

USB Device library classes implementation


Documentation feedback | Developer Zone | Subscribe | Updated