nRF5 SDK v13.0.0
Experimental: USB device library
This information applies to the nRF52840 SoC only.

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.

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.

Driver

For detailed driver documentation, refer to Experimental: USBD driver.

Strings

This is a highly customizable string description module. For detailed documentation, refer to USBD library 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 events. This function is of little significance to the library itself and is provided here only to globally react to some events in the application.

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;
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 Interfaces list passed to @ref APP_USBD_CDC_ACM_GLOBAL_DEF
*/
#define CDC_ACM_INTERFACES_CONFIG() \
APP_USBD_CDC_ACM_CONFIG(CDC_ACM_COMM_INTERFACE, \
CDC_ACM_COMM_EPIN, \
CDC_ACM_DATA_INTERFACE, \
CDC_ACM_DATA_EPIN, \
CDC_ACM_DATA_EPOUT)
static const uint8_t m_cdc_acm_class_descriptors[] = {
APP_USBD_CDC_ACM_DEFAULT_DESC(CDC_ACM_COMM_INTERFACE,
CDC_ACM_COMM_EPIN,
CDC_ACM_DATA_INTERFACE,
CDC_ACM_DATA_EPIN,
CDC_ACM_DATA_EPOUT)
};
/**
@brief CDC_ACM class instance
*/
CDC_ACM_INTERFACES_CONFIG(),
cdc_acm_user_ev_handler,
m_cdc_acm_class_descriptors
);

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 power event handler. USBD library should be enabled on NRF_DRV_POWER_USB_EVT_DETECTED and started on NRF_DRV_POWER_USB_EVT_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.

USBD library configurable string descriptor module

USB Device library classes implementation


Documentation feedback | Developer Zone | Subscribe | Updated