This chapter explains the details of the USB device (USBD) library implementation in this SDK.
The library allows to compose the whole USB device using class bricks:
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.
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.
For detailed driver documentation, refer to Experimental: USBD driver.
This is a highly customizable string description module. For detailed documentation, refer to USBD library configurable string descriptor module.
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.
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.
Before configuring and using the library, it must be initialized. You can do it by calling app_usbd_init in the following way:
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:
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:
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:
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.
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.