doc: usb: add initial USB device configuraiton howto

Add initial documentation how to configure and enable new USB device
support. Use literalinclude to pull code snippets from the samples.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
This commit is contained in:
Johann Fischer 2024-06-06 18:33:12 +02:00 committed by Alberto Escolar
commit 8739efe0fc
3 changed files with 134 additions and 0 deletions

View file

@ -76,3 +76,112 @@ configuration ``-DCONF_FILE=usbd_next_prj.conf`` either directly or via
set the configuration overlay file
``-DDEXTRA_CONF_FILE=overlay-usbd_next_ecm.conf`` and devicetree overlay file
``-DDTC_OVERLAY_FILE="usbd_next_ecm.overlay`` either directly or via ``west``.
How to configure and enable USB device support
**********************************************
For the USB device support samples in the Zephyr project repository, we have a
common file for instantiation, configuration and initialization,
:zephyr_file:`samples/subsys/usb/common/sample_usbd_init.c`. The following code
snippets from this file are used as examples. USB Samples Kconfig options used
in the USB samples and prefixed with ``SAMPLE_USBD_`` have default values
specific to the Zephyr project and the scope is limited to the project samples.
In the examples below, you will need to replace these Kconfig options and other
defaults with values appropriate for your application or hardware.
The USB device stack requires a context structure to manage its properties and
runtime data. The preferred way to define a device context is to use the
:c:macro:`USBD_DEVICE_DEFINE` macro. This creates a static
:c:struct:`usbd_context` variable with a given name. Any number of contexts may
be instantiated. A USB controller device can be assigned to multiple contexts,
but only one context can be initialized and used at a time. Context properties
must not be directly accessed or manipulated by the application.
.. literalinclude:: ../../../../samples/subsys/usb/common/sample_usbd_init.c
:language: c
:start-after: doc device instantiation start
:end-before: doc device instantiation end
Your USB device may have manufacturer, product, and serial number string
descriptors. To instantiate these string descriptors, the application should
use the appropriate :c:macro:`USBD_DESC_MANUFACTURER_DEFINE`,
:c:macro:`USBD_DESC_PRODUCT_DEFINE`, and
:c:macro:`USBD_DESC_SERIAL_NUMBER_DEFINE` macros. String descriptors also
require a single instantiation of the language descriptor using the
:c:macro:`USBD_DESC_LANG_DEFINE` macro.
.. literalinclude:: ../../../../samples/subsys/usb/common/sample_usbd_init.c
:language: c
:start-after: doc string instantiation start
:end-before: doc string instantiation end
String descriptors must be added to the device context at runtime before
initializing the USB device with :c:func:`usbd_add_descriptor`.
.. literalinclude:: ../../../../samples/subsys/usb/common/sample_usbd_init.c
:language: c
:start-after: doc add string descriptor start
:end-before: doc add string descriptor end
USB device requires at least one configuration instance per supported speed.
The application should use :c:macro:`USBD_CONFIGURATION_DEFINE` to instantiate
a configuration. Later, USB device functions are assigned to a configuration.
.. literalinclude:: ../../../../samples/subsys/usb/common/sample_usbd_init.c
:language: c
:start-after: doc configuration instantiation start
:end-before: doc configuration instantiation end
Each configuration instance for a specific speed must be added to the device
context at runtime before the USB device is initialized using
:c:func:`usbd_add_configuration`. Note :c:enumerator:`USBD_SPEED_FS` and
:c:enumerator:`USBD_SPEED_HS`. The first full-speed or high-speed
configuration will get ``bConfigurationValue`` one, and then further upward.
.. literalinclude:: ../../../../samples/subsys/usb/common/sample_usbd_init.c
:language: c
:start-after: doc configuration register start
:end-before: doc configuration register end
Although we have already done a lot, this USB device has no function. A device
can have multiple configurations with different set of functions at different
speeds. A function or class can be registered on a USB device before
it is initialized using :c:func:`usbd_register_class`. The desired
configuration is specified using :c:enumerator:`USBD_SPEED_FS` or
:c:enumerator:`USBD_SPEED_HS` and the configuration number. For simple cases,
:c:func:`usbd_register_all_classes` can be used to register all available
instances.
.. literalinclude:: ../../../../samples/subsys/usb/common/sample_usbd_init.c
:language: c
:start-after: doc functions register start
:end-before: doc functions register end
The last step in the preparation is to initialize the device with
:c:func:`usbd_init`. After this, the configuration of the device cannot be
changed. A device can be deinitialized with :c:func:`usbd_shutdown` and all
instances can be reused, but the previous steps must be repeated. So it is
possible to shutdown a device, register another type of configuration or
function, and initialize it again. At the USB controller level,
:c:func:`usbd_init` does only what is necessary to detect VBUS changes. There
are controller types where the next step is only possible if a VBUS signal is
present.
A function or class implementation may require its own specific configuration
steps, which should be performed prior to initializing the USB device.
.. literalinclude:: ../../../../samples/subsys/usb/common/sample_usbd_init.c
:language: c
:start-after: doc device init start
:end-before: doc device init end
The final step to enable the USB device is :c:func:`usbd_enable`, after that,
if the USB device is connected to a USB host controller, the host can start
enumerating the device. The application can disable the USB device using
:c:func:`usbd_disable`.
.. literalinclude:: ../../../../samples/subsys/usb/hid-keyboard/src/main.c
:language: c
:start-after: doc device enable start
:end-before: doc device enable end

View file

@ -15,27 +15,40 @@ LOG_MODULE_REGISTER(usbd_sample_config);
#define ZEPHYR_PROJECT_USB_VID 0x2fe3
/* doc device instantiation start */
/*
* Instantiate a context named sample_usbd using the default USB device
* controller, the Zephyr project vendor ID, and the sample product ID.
* Zephyr project vendor ID must not be used outside of Zephyr samples.
*/
USBD_DEVICE_DEFINE(sample_usbd,
DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)),
ZEPHYR_PROJECT_USB_VID, CONFIG_SAMPLE_USBD_PID);
/* doc device instantiation end */
/* doc string instantiation start */
USBD_DESC_LANG_DEFINE(sample_lang);
USBD_DESC_MANUFACTURER_DEFINE(sample_mfr, CONFIG_SAMPLE_USBD_MANUFACTURER);
USBD_DESC_PRODUCT_DEFINE(sample_product, CONFIG_SAMPLE_USBD_PRODUCT);
USBD_DESC_SERIAL_NUMBER_DEFINE(sample_sn);
/* doc string instantiation end */
/* doc configuration instantiation start */
static const uint8_t attributes = (IS_ENABLED(CONFIG_SAMPLE_USBD_SELF_POWERED) ?
USB_SCD_SELF_POWERED : 0) |
(IS_ENABLED(CONFIG_SAMPLE_USBD_REMOTE_WAKEUP) ?
USB_SCD_REMOTE_WAKEUP : 0);
/* Full speed configuration */
USBD_CONFIGURATION_DEFINE(sample_fs_config,
attributes,
CONFIG_SAMPLE_USBD_MAX_POWER);
/* High speed configuration */
USBD_CONFIGURATION_DEFINE(sample_hs_config,
attributes,
CONFIG_SAMPLE_USBD_MAX_POWER);
/* doc configuration instantiation end */
/*
* This does not yet provide valuable information, but rather serves as an
@ -73,6 +86,7 @@ struct usbd_context *sample_usbd_init_device(usbd_msg_cb_t msg_cb)
{
int err;
/* doc add string descriptor start */
err = usbd_add_descriptor(&sample_usbd, &sample_lang);
if (err) {
LOG_ERR("Failed to initialize language descriptor (%d)", err);
@ -96,6 +110,7 @@ struct usbd_context *sample_usbd_init_device(usbd_msg_cb_t msg_cb)
LOG_ERR("Failed to initialize SN descriptor (%d)", err);
return NULL;
}
/* doc add string descriptor end */
if (usbd_caps_speed(&sample_usbd) == USBD_SPEED_HS) {
err = usbd_add_configuration(&sample_usbd, USBD_SPEED_HS,
@ -114,21 +129,26 @@ struct usbd_context *sample_usbd_init_device(usbd_msg_cb_t msg_cb)
sample_fix_code_triple(&sample_usbd, USBD_SPEED_HS);
}
/* doc configuration register start */
err = usbd_add_configuration(&sample_usbd, USBD_SPEED_FS,
&sample_fs_config);
if (err) {
LOG_ERR("Failed to add Full-Speed configuration");
return NULL;
}
/* doc configuration register end */
/* doc functions register start */
err = usbd_register_all_classes(&sample_usbd, USBD_SPEED_FS, 1);
if (err) {
LOG_ERR("Failed to add register classes");
return NULL;
}
/* doc functions register end */
sample_fix_code_triple(&sample_usbd, USBD_SPEED_FS);
/* doc message callback register start */
if (msg_cb != NULL) {
err = usbd_msg_register_cb(&sample_usbd, msg_cb);
if (err) {
@ -136,6 +156,7 @@ struct usbd_context *sample_usbd_init_device(usbd_msg_cb_t msg_cb)
return NULL;
}
}
/* doc message callback register end */
if (IS_ENABLED(CONFIG_SAMPLE_USBD_20_EXTENSION_DESC)) {
(void)usbd_device_set_bcd(&sample_usbd, USBD_SPEED_FS, 0x0201);
@ -148,11 +169,13 @@ struct usbd_context *sample_usbd_init_device(usbd_msg_cb_t msg_cb)
}
}
/* doc device init start */
err = usbd_init(&sample_usbd);
if (err) {
LOG_ERR("Failed to initialize device support");
return NULL;
}
/* doc device init end */
return &sample_usbd;
}

View file

@ -184,11 +184,13 @@ int main(void)
return -ENODEV;
}
/* doc device enable start */
ret = usbd_enable(sample_usbd);
if (ret) {
LOG_ERR("Failed to enable device support");
return ret;
}
/* doc device enable end */
LOG_INF("HID keyboard sample is initialized");