diff --git a/doc/connectivity/usb/device_next/usb_device.rst b/doc/connectivity/usb/device_next/usb_device.rst index a21da33fdf8..852da003cef 100644 --- a/doc/connectivity/usb/device_next/usb_device.rst +++ b/doc/connectivity/usb/device_next/usb_device.rst @@ -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 diff --git a/samples/subsys/usb/common/sample_usbd_init.c b/samples/subsys/usb/common/sample_usbd_init.c index 2c559aa789a..f6b2963b212 100644 --- a/samples/subsys/usb/common/sample_usbd_init.c +++ b/samples/subsys/usb/common/sample_usbd_init.c @@ -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; } diff --git a/samples/subsys/usb/hid-keyboard/src/main.c b/samples/subsys/usb/hid-keyboard/src/main.c index 393edfcf06a..b56fe26c0c7 100644 --- a/samples/subsys/usb/hid-keyboard/src/main.c +++ b/samples/subsys/usb/hid-keyboard/src/main.c @@ -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");