USB Device Vendor Class#

The Vendor class allows you to build vendor-specific devices that can implement a proprietary protocol. It relies on a pair of bulk endpoints to transfer data between the host and the device. Bulk transfers are convenient for transferring large amounts of unstructured data and provide a reliable exchange of data by using an error detection and retry mechanism.

In addition to bulk endpoints, the Vendor class can also use an optional pair of interrupt endpoints. Any operating system (OS) can work with the Vendor class provided that the OS has a driver to handle the Vendor class. Depending on the OS, the driver can be native or vendor-specific. For instance, under Microsoft Windows®, your application interacts with the WinUSB driver provided by Microsoft to communicate with the vendor device.

USB Device Vendor Class Overview#

Figure - General Architecture Between Windows Host and Vendor Class shows the general architecture between the host and the device using the Vendor class. In this example, the host operating system is MS Windows.

Figure - General Architecture Between MS Windows Host and Vendor Class#

Figure 13 General Architecture Between Windows Host and Vendor ClassFigure 13 General Architecture Between Windows Host and Vendor Class

On the MS Windows side, the application communicates with the vendor device by interacting with a USB library. Libraries, like libusb, offer an API to manage a device and its associated pipes, and to communicate with the device through control, bulk and interrupt endpoints.

On the device side, the Vendor class is composed of the following endpoints:

  • A pair of control IN and OUT endpoints called the default endpoint.

  • A pair of bulk IN and OUT endpoints.

  • A pair of interrupt IN and OUT endpoints. This pair is optional.

The table below indicates the usage of the different endpoints:

Table - Vendor Class Endpoints Usage#

Endpoint

Direction

Usage

Control IN

Device-to-host

Standard requests for enumeration and vendor-specific requests.

Control OUT

>Host-to-device

Standard requests for enumeration and vendor-specific requests.

Bulk IN

Device-to-host

Raw data communication. Data can be structured according to a proprietary protocol.

Bulk OUT

Host-to-device

Raw data communication. Data can be structured according to a proprietary protocol.

Interrupt IN

Device-to-host

Raw data communication or notification. Data can be structured according to a proprietary protocol.

Interrupt OUT

Host-to-device

Raw data communication or notification. Data can be structured according to a proprietary protocol.

The device application can use bulk and interrupt endpoints to send or receive data to or from the host. It can only use the default endpoint to decode vendor-specific requests sent by the host. The standard requests are managed internally by the Core layer of Silicon Labs USB Device.

USB Device Vendor Class Resource Needs from Core#

Each time you add a vendor class instance to a configuration via the function sl_usbd_vendor_add_to_configuration(), the following resources will be allocated from the core.

Resource

Quantity

Interfaces

1

Alternate interfaces

1

Endpoints

2 (4 if you enabled interrupt endpoints)

Interface groups

0

Note that those numbers are per configuration. When setting up your SL_USBD_INTERFACE_QUANTITY, SL_USBD_ALT_INTERFACE_QUANTITY, SL_USBD_INTERFACE_GROUP_QUANTITY and SL_USBD_DESCRIPTOR_QUANTITY configuration values, don't forget to take into account on how many configurations the class will be added. For the SL_USBD_OPEN_ENDPOINTS_QUANTITY configuration value, since endpoints are opened only when a configuration is set by the host, you just need to take into account the number of needed endpoints for a class instance.

USB Device Vendor Class Configuration#

Two groups of configuration parameters are used to configure the Vendor class:

USB Device Vendor Class Application-Specific Configurations#

First, to use the Silicon Labs USB device Vendor class module, adjust the Vendor compile-time configuration defines according to your application needs. They are regrouped inside the sl_usbd_core_config.h header file under the Vendor section. The quantity configurations purpose is to inform the USB device module about how many USB Vendor objects to allocate.

The table below describes each configuration define.

Table - USB Device Vendor Configuration Defines#

Configuration Name

Description

Default Value

SL_USBD_VENDOR_CLASS_INSTANCE_QUANTITY

Number of class instances you will allocate via a call to the function sl_usbd_vendor_create_instance().

2

SL_USBD_VENDOR_CONFIGURATION_QUANTITY

Number of configurations. Vendor class instances can be added to one or more configurations via a call to the function sl_usbd_vendor_add_to_configuration().

1

USB Device Vendor Class Instance Configurations#

This section defines the configurations related to the Vendor class instances.

Class Instance Creation#

Creating a Vendor class instance is done by calling the function sl_usbd_vendor_create_instance(), which takes three configuration arguments that are described below.

intr_en#

Boolean that indicates if a pair of interrupt endpoints should be added or not.

Value

Description

true

A pair of IN/OUT endpoints will be added and made available to the embedded application.

false

No interrupt endpoint will be added. Only a pair of Bulk IN/OUT endpoint will be available.

interval#
  • If you set intr_en to true, you can specify the interrupt endpoints polling interval (in milliseconds).

  • If you set intr_en to false, you can set interval to 0 as it will be ignored by the class.

p_vendor_callbacks#

p_vendor_callbacks is a pointer to a callback functions structure variable. that you can specify to handle the class specific control requests. If you don't use any class specific requests or needs enable/disable notification, you can set this to NULL.

The example below provides the expected signature of your class specific requests handler.

Example - Signature of Class-Specific Request Function#
void  app_usbd_vendor_req_handle(uint8_t                    class_nbr,       (1)
                                 const sl_usbd_setup_req_t  *p_setup_req);   (2)

sl_usbd_vendor_callbacks_t  app_usbd_vendor_callback_functions =
{
  .enable = NULL,
  .disable = NULL,
  .setup_req = app_usbd_vendor_req_handle,
};

(1) Vendor class instance number.

(2) Pointer to a received setup request from host.

USB Device Vendor Class Programming Guide#

This section explains how to use the Vendor class.

Initializing the USB Device Vendor Class#

To add a Vendor Class functionality to your device, first initialize the class by calling the function USBD_Vendor_Init().

The example below shows how to call sl_usbd_vendor_init().

Example - Calling sl_usbd_vendor_init()#
sl_status_t  status;

status = sl_usbd_vendor_init();
if (status != SL_STATUS_OK) {
  /* An error occurred. Error handling should be added here. */
}

Adding a USB Device Vendor Class Instance to your Device#

To add vendor class functionality to your device, you must first create an instance, then add it to your device's configuration(s).

Creating a Vendor Class Instance#

Create a Vendor class instance by calling the function sl_usbd_vendor_create_instance().

The example below shows how to call sl_usbd_vendor_create_instance() using default arguments. For more information about the configuration arguments to pass to sl_usbd_vendor_create_instance(), see USB Device Vendor Class Instance Configurations .

Example - Calling sl_usbd_vendor_create_instance()#
uint8_t      class_nbr;
sl_status_t  status;

status = sl_usbd_vendor_create_instance(false,                              (1)
                                        0u,                                 (2)
                                        app_usbd_vendor_callback_functions, (3)
                                        &class_nbr);
if (status != SL_STATUS_OK) {
  /* An error occurred. Error handling should be added here. */
}

(1) No Interrupt endpoints with this class instance.

(2) Interval is ignored since Interrupt endpoints are disabled.

(3) Callback function that is part of your application that handles vendor-specific class requests. See Communicating using the USB Device Vendor Class for more information.

Adding the Vendor Class Instance to Your Device's Configuration(s)#

After you have created a vendor class instance, you can add it to a configuration by calling the function USBD_Vendor_ConfigAdd().

The example below shows how to call sl_usbd_vendor_add_to_configuration() using default arguments.

Example - Calling sl_usbd_vendor_add_to_configuration()#
sl_status_t  status;

status = sl_usbd_vendor_add_to_configuration(class_nbr,                         (1)
                                             config_nbr_fs);                    (2)
if (status != SL_STATUS_OK) {
  /* An error occurred. Error handling should be added here. */
}

(1) Class number to add to the configuration returned by sl_usbd_vendor_create_instance().

(2) Configuration number (here adding it to a Full-Speed configuration).

Communicating Using the USB Device Vendor Class#

General#

The Vendor class offers the following functions to communicate with the host. For more details about the parameters of the function, see USB Device Vendor API.

Table - Vendor Communication API Summary#

Function name

Operation

sl_usbd_vendor_read_bulk_sync()

Receives data from host through bulk OUT endpoint. This function is blocking.

sl_usbd_vendor_write_bulk_sync()

Sends data to host through bulk IN endpoint. This function is blocking.

sl_usbd_vendor_read_bulk_async()

Receives data from host through bulk OUT endpoint. This function is non-blocking.

sl_usbd_vendor_write_bulk_async()

Sends data to host through bulk IN endpoint. This function is non-blocking.

sl_usbd_vendor_read_interrupt_sync()

Receives data from host through interrupt OUT endpoint. This function is blocking.

sl_usbd_vendor_write_interrupt_sync()

Sends data to host through interrupt IN endpoint. This function is blocking.

sl_usbd_vendor_read_interrupt_async()

Receives data from host through interrupt OUT endpoint. This function is non-blocking.

sl_usbd_vendor_write_interrupt_async()

Sends data to host through interrupt IN endpoint. This function is non-blocking.

The vendor requests are also another way to communicate with the host. When managing vendor requests sent by the host, the application can receive or send data from or to the host using the control endpoint; you will need to provide an application callback passed as a parameter of sl_usbd_vendor_create_instance().

Synchronous Communication#

Synchronous communication means that the transfer is blocking. When a function is called, the application blocks until the transfer completes with or without an error. A timeout can be specified to avoid waiting forever.

The example below shows a read and write that receives data from the host using the bulk OUT endpoint and sends data to the host using the bulk IN endpoint.

Example - Synchronous Bulk Read and Write#
__ALIGNED(4) uint8_t  rx_buf[2];
__ALIGNED(4) uint8_t  tx_buf[2];
uint32_t              xfer_len;
sl_status_t           status;

status = sl_usbd_vendor_read_bulk_sync(class_nbr,                                      (1)
                                       (void *)&rx_buf[0],                             (2)
                                       2u,
                                       0u,                                             (3)
                                       &xfer_len);
if (status != SL_STATUS_OK) {
  /* $$$$ Handle the error. */
}

status = sl_usbd_vendor_write_bulk_sync( class_nbr,                                    (1)
                                        (void *)&tx_buf[0],                            (4)
                                        2u,
                                        0u,                                            (3)
                                        false,                                         (5)
                                        &xfer_len);
if (status != SL_STATUS_OK) {
  /* $$$$ Handle the error. */
}

(1) The class instance number created with sl_usbd_vendor_create_instance() provides an internal reference to the Vendor class to route the transfer to the proper bulk OUT or IN endpoint.

(2) The application must ensure that the buffer provided to the function is large enough to accommodate all the data. Otherwise, synchronization issues might happen.

(3) In order to avoid an infinite blocking situation, a timeout expressed in milliseconds can be specified. A value of ‘0’ makes the application task wait forever.

(4) The application provides the initialized transmit buffer.

(5) If this flag is set to true, and the transfer length is multiple of the endpoint maximum packet size, the device stack will send a zero-length packet to the host to signal the end of the transfer.

The use of interrupt endpoint communication functions, sl_usbd_vendor_read_interrupt_sync() and sl_usbd_vendor_write_interrupt_sync(), is similar to bulk endpoint communication functions presented in Example - Synchronous Bulk Read and Write.

Asynchronous Communication#

Asynchronous communication means that the transfer is non-blocking. When a function is called, the application passes the transfer information to the device stack and does not block. Other application processing can be done while the transfer is in progress over the USB bus. Once the transfer has completed, a callback function is called by the device stack to inform the application about the transfer completion. The example below shows asynchronous read and write.

Example - Asynchronous Bulk Read and Write#
void app_usbd_vendor_comm (uint8_t  class_nbr)
{
  __ALIGNED(4) uint8_t  rx_buf[2];
  __ALIGNED(4) uint8_t  tx_buf[2];
  sl_status_t           status;

  status = sl_usbd_vendor_read_bulk_async(class_nbr,                                (1)
                                          (void *)&rx_buf[0],                       (2)
                                          2u,
                                          app_usbd_vendor_rx_completed,             (3)
                                          NULL);                                    (4)
  if (status != SL_STATUS_OK) {
    /* $$$$ Handle the error. */
  }
  status = sl_usbd_vendor_write_bulk_async(class_nbr,                               (1)
                                           (void *)&tx_buf[0],                      (5)
                                           2u,
                                           app_usbd_vendor_tx_completed,            (3)
                                           NULL,                                    (4)
                                           false);                                  (6)
  if (status != SL_STATUS_OK) {
    /* $$$$ Handle the error. */
  }
}

static void app_usbd_vendor_rx_completed(uint8_t      class_nbr,                    (3)
                                         void         *p_buf,
                                         uint32_t     buf_len,
                                         uint32_t     xfer_len,
                                         void         *p_callback_arg,
                                         sl_status_t  status)
{
  if (status != SL_STATUS_OK) {
    /* $$$$ Do some processing. */
  } else {
    /* $$$$ Handle the error. */
  }
}

static void app_usbd_vendor_tx_completed(uint8_t      class_nbr,                    (3)
                                         void         *p_buf,
                                         uint32_t     buf_len,
                                         uint32_t     xfer_len,
                                         void         *p_callback_arg,
                                         sl_status_t  status)
{
  if (status != SL_STATUS_OK) {
    /* $$$$ Do some processing. */
  } else {
    /* $$$$ Handle the error. */
  }
}

(1) The class instance number provides an internal reference to the Vendor class to route the transfer to the proper bulk OUT or IN endpoint.

(2) The application must ensure that the buffer provided is large enough to accommodate all the data. Otherwise, there may be synchronization issues.

(3) The application provides a callback function pointer passed as a parameter. Upon completion of the transfer, the device stack calls this callback function so that the application can finalize the transfer by analyzing the transfer result. For instance, on completion of a read operation, the application might perform processing on the received data. Upon write completion, the application can indicate if the write was successful and how many bytes were sent.

(4) An argument associated with the callback can be also passed. Then in the callback context, some private information can be retrieved.

(5) The application provides the initialized transmit buffer.

(6) If this flag is set to true, and the transfer length is a multiple of the endpoint maximum packet size, the device stack will send a zero-length packet to the host to signal the end of transfer.

The use of interrupt endpoint communication functions, sl_usbd_vendor_read_interrupt_async() and sl_usbd_vendor_write_interrupt_async(), is similar to the bulk endpoint communication functions presented in Example - Asynchronous Bulk Read and Write.

Vendor Request#

The USB 2.0 specification defines three types of requests: standard, class, and vendor. All standard requests are handled directly by the core layer, while class requests are managed by the proper associated class.

Vendor requests can be processed by the vendor class. To process vendor requests, you must provide an application callback as a parameter of sl_usbd_vendor_create_instance(). After a vendor request is received by the USB device, it must be decoded properly. The example below shows vendor request decoding.

Certain requests may be required to receive from or send to the host during the data stage of a control transfer. If no data stage is present, you only have to decode the Setup packet. This example shows the three types of data stage management: no data, data OUT and data IN.

Example - Vendor Request Decoding#
#define  APP_VENDOR_REQ_NO_DATA                     0x01u
#define  APP_VENDOR_REQ_RECEIVE_DATA_FROM_HOST      0x02u
#define  APP_VENDOR_REQ_SEND_DATA_TO_HOST           0x03u

#define  APP_VENDOR_REQ_DATA_BUF_SIZE                 50u

static uint8_t app_vendor_req_buf[APP_VENDOR_REQ_DATA_BUF_SIZE];

static bool app_usbd_vendor_req (uint8_t                     class_nbr,
                                 const  sl_usbd_setup_req_t  *p_setup_req)              (1)
{
  bool         valid;
  sl_status_t  status;
  uint16_t     req_len;
  uint32_t     xfer_len;

  (void)class_nbr;

  switch(p_setup_req->bRequest) {                                                       (2)
    case APP_VENDOR_REQ_NO_DATA:                                                        (3)
      valid = true;
      break;

    case APP_VENDOR_REQ_RECEIVE_DATA_FROM_HOST:                                         (4)
      req_len = p_setup_req->wLength;
      if (req_len > APP_VENDOR_REQ_DATA_BUF_SIZE) {
        // Not enough room to receive data.
        return (false);
      }

      // Receive data via Control OUT EP.
      // Wait transfer completion forever.
      status = sl_usbd_core_read_control_sync((void *)&app_vendor_req_buf[0u],
                                              req_len,
                                              0u,
                                              &xfer_len);
      if (status != SL_STATUS_OK) {
        valid = false;
      } else {
        valid = true;
      }
      break;

    case APP_VENDOR_REQ_SEND_DATA_TO_HOST:                                              (5)
      req_len = APP_VENDOR_REQ_DATA_BUF_SIZE;

      // Fill buf with a pattern.
      Mem_Set((void *)&AppVendorReqBuf[0u],
                             'A',
                             req_len);

      // Send data via Control IN EP.
      // Wait transfer completion forever.
      status = sl_usbd_core_write_control_sync((void *)&app_vendor_req_buf[0u],
                                               req_len,
                                               0u,
                                               false,
                                               &xfer_len);
      if (status != SL_STATUS_OK) {
        valid = DEF_FAIL;
      } else {
        valid = DEF_OK;
      }
      break;

    default:                                                                            (6)
      // Request is not supported.
      valid = DEF_FAIL;
      break;
  }
  return (valid);
}

(1) The core will pass the Setup packet content to your application. The structure sl_usbd_setup_req_t contains the same fields as defined by the USB 2.0 specification (refer to section "9.3 USB Device Requests" of the specification for more details):

typedef  struct {
    uint8_t   bmRequestType;  /* Characteristics of request.                          */
    uint8_t   bRequest;       /* Specific request.                                    */
    uint16_t  wValue;         /* Varies according to request.                         */
    uint16_t  wIndex;         /* Varies according to request; typically used as index.*/
    uint16_t  wLength;        /* Transfer length if data stage present.               */
} sl_usbd_setup_req_t;

(2) Determine the request. You may use a switch statement if you are using different requests. In this example, there are three different requests corresponding to the three types of the data stage: APP_VENDOR_REQ_NO_DATA, APP_VENDOR_REQ_RECEIVE_DATA_FROM_HOST, and APP_VENDOR_REQ_SEND_DATA_TO_HOST.

(3) If no data stage is present, you only need to decode the other fields. The presence of a data stage or not is indicated by the field wLength being non-null or null.

(4) If the host sends data to the device, you must call the function sl_usbd_core_read_control_sync() . The buffer provided should be able to contain up to wLength bytes. If any error occurs, return false to the core that will stall the status stage of the control transfer, indicating to the host that the request cannot be processed. true is returned in case of success.

(5) If the host receives data from the device, you must call the function sl_usbd_core_write_control_sync(). If any error occurs, return false to the core that will stall the status stage of the control transfer, indicating to the host that the request cannot be processed. true is returned in case of success.

(6) In this example, all requests not recognized are marked by returning false to the core. This one will stall the data or status stage of the control transfer indicating to the host that the request is not supported.

The host sends vendor requests through a host vendor application. USb libraries, such as libusb, can be used to help you develop your custom host vendor application.