cancel
Showing results for 
Search instead for 
Did you mean: 

How to implement the USB host in STM32 using the Azure USBX package

B.Montanari
ST Employee

Implementing the USB host in STM32 using Azure USBX

Summary

Step-by-step tutorial on how to implement the AzureRTOS's USBX package for CDC host class based on the NUCLEO-H723ZG board.

Introduction

This article shows how to implement the USB host CDC in the STM32 using the Azure USBX package. USBX is a USB stack developed by Microsoft® that offers a wide range of classes to be implemented both for host and device applications.

For this tutorial, we use the NUCLEO-H723ZG board, which has a USB OTG_FS connector onboard. This is ideal to crate the host interface for the CDC class and interact with a virtual COM Port. This board has an STM32H723ZGT6 microcontroller, and the steps shown here can be easily tailored to any other STM32. The STM32CubeIDE 1.13.2, the STM32CubeH7 1.11.1, and the X-CUBE-AZRTOS-H7 3.1.0 releases were used to build this tutorial.

BMontanari_0-1700765529807.jpeg

1. Development

1.1 Creating the new project

Start by creating a new project for the STM32H723ZGT6 in the STM32CubeIDE. The MPU settings are not mandatory for this demo. Once the project is created, configure the USART3 in the PD8 (USART3_RX) and PD9 (USART3_TX). You can remap the pins by pressing Ctrl and dragging the default pins allocated to the ones needed. These pins are connected to the embedded STLINK. It allows to use the debugger’s virtual COM Port to log and debug our application. While this step is not mandatory, it is used in the demonstration code for this article. A clock issue may appear, but do not worry, we will solve it later.

BMontanari_1-1700765529871.png

Configure the PD10 as GPIO_Output and set its label to “USB_FS_PWR_EN.” This pin is used to turn ON the 5V in the VBUS, as shown in the schematic portion below:

BMontanari_2-1700765529876.png

We have a switch (STMPS2141STR) with a pull-up in the /EN pin that is connected to the PD10. Applying a low level in this pin feeds the VBUS, so the default configuration works:

BMontanari_3-1700765529906.png

Under the Connectivity tab, enable the USB_OTG_HS peripheral in Host_Only mode for the Internal FS Phy. The standard peripheral settings are used for this example, so it is not necessary to change them.

BMontanari_4-1700765529914.png

Go to the NVIC settings tab and enable the USB On The Go HS global interrupt.

BMontanari_5-1700765529921.png

Now, change the HAL Timebase source to Timer 6, since we use the Azure RTOS, and its kernel uses the SysTick for the time base.

BMontanari_6-1700765529926.png

In the next step, we will adjust the project clock settings. The first step is to locate the RCC, under System Core, and enable the HSE in BYPASS Clock Source. This board receives an 8MHz clock from the debugger probe. A stable clock is required for USB Host applications.

BMontanari_7-1700765529931.png

Then, going to the Clock Configuration tab, adjust the HSE input frequency to 8 MHz, and adjust the PLLs to achieve the most convenient frequency according to your project. In this example, set the value to 260 MHz. Also, take care with the USART Clock source.

BMontanari_8-1700765529956.png

          Adjust the following peripherals to achieve 48 MHz in the USB peripheral.

BMontanari_9-1700765529973.png

1.2 Add the AzureRTOS and USBX

          Now, we can add the X-CUBE-AZRTOS-H7 package with the Azure RTOS and the USBX. For that, go to the Middleware and Software Packs menu, and click over the X-CUBE-AZRTOS-H7 package. By doing that, the Software Packs Component Selector menu pops up. There, it is necessary to add the following components:

 

BMontanari_10-1700765529984.png

Once selected close the menu and go back to the X-CUBE-AZRTOS-H7, there you will have to enable both RTOS ThreadX and the USB USBX packages.

BMontanari_11-1700765529994.png

For the next step, we will adjust the amount of data to Host Application Stack. You may find a useful list with the recommended memory footprint needed for each class. This is done by enabling the information menu as shown below:

BMontanari_12-1700765530008.png

You will have to increase the USBX host memory pool size. This number should be higher than Host system Stack size, since the pool comports the stack. You may also increase this value if your application needs to have some threads. For this example, the selected value was arbitrarily chosen as 18KB, but you can optimize this value according to your application.

BMontanari_13-1700765530018.png

Go to the Project Manager tab, and in the Code Generator section, enable the “Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral” option.

BMontanari_14-1700765530027.png

The last step in the STM32CubeMX is to disable the USB Peripheral initialization code by HAL, as this is handled by the RTOS. For that, go to Advanced Settings and enable the “Do Not Generate Function Call” option for the USB_OTG_HS.

BMontanari_15-1700765530036.png

1.3 Code editing

All the steps in the STM32CubeMX are done. You can press the alt + K shortcut, or just press the Device Configuration Tool Code Generation to generate our code.

BMontanari_16-1700765530039.png

Once the code is generated, it is time to write some code. Start by including the stdio.h header in the main.c file along with the following code, responsible for redirecting our printfs to the UART peripheral:

 /* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int __io_putchar(int ch)
{
          HAL_UART_Transmit(&huart3, (uint8_t*)&ch, 1, 0xFF);
          return ch;
}
/* USER CODE END 0 */

With that, all the needed changes in the main.c file are completed and you can save and close this file.

The next file to be edited is the ../USBX/App/app_usbx_host.c. The first step is to add these include files:

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "main.h"
#include "usb_otg.h"
#include "ux_hcd_stm32.h"
#include "ux_host_cdc_acm.h"
/* USER CODE END Includes */

Now, we must link the HAL driver to the USBX Host Driver Controller and initialize the USB peripheral. For that, locate the app_ux_host_thread_entry function and change it to the following code:

/**
 * @brief  Function implementing app_ux_host_thread_entry.
 *   thread_input: User thread input parameter.
 * @retval none
 */
static VOID app_ux_host_thread_entry(ULONG thread_input)
{
          /* USER CODE BEGIN app_ux_host_thread_entry */
          /* Initialize the USB Peripheral */
          MX_USB_OTG_HS_HCD_Init();
          /* Link the USB Peripheral to the USBX Host Controller Driver */
ux_host_stack_hcd_register(_ux_system_host_hcd_stm32_name, _ux_hcd_stm32_initialize, USB_OTG_HS_PERIPH_BASE, (ULONG)&hhcd_USB_OTG_HS);
          /* Start the Peripheral */
          HAL_HCD_Start(&hhcd_USB_OTG_HS);
          /* USER CODE END app_ux_host_thread_entry */
}

Now that the connection is done, we need to create the following variables, still in the app_usbx_host.c file:

/* USER CODE BEGIN PV */
static TX_THREAD cdc_acm_send_thread;
static TX_THREAD cdc_acm_receive_thread;
TX_EVENT_FLAGS_GROUP cdc_acm_eventflag;
TX_MUTEX cdc_acm_uart_mutex;
UX_HOST_CLASS_CDC_ACM *cdc_acm;
/* USER CODE END PV */

Then add the callback code as shown below. It handles when a device is attached and detached from the host and print a log in the terminal through the UART.

UINT ux_host_event_callback(ULONG event, UX_HOST_CLASS *current_class, VOID *current_instance)
{
          UINT status = UX_SUCCESS;
          /* USER CODE BEGIN ux_host_event_callback0 */
          /* USER CODE END ux_host_event_callback0 */
          switch (event)
          {
                    case UX_DEVICE_INSERTION:
                              /* USER CODE BEGIN UX_DEVICE_INSERTION */
                              if(current_class->ux_host_class_entry_function == ux_host_class_cdc_acm_entry)
                              {
                                        /* Check if the CDC class is empty */
                                        if(cdc_acm == UX_NULL)
                                        {
                                                   /* Save the Class */
                                                   cdc_acm = (UX_HOST_CLASS_CDC_ACM *)current_instance;

                                                   /* Check if this is CDC DATA instance */
                                                   if(cdc_acm->ux_host_class_cdc_acm_bulk_in_endpoint == UX_NULL)
                                                   {
                                                             cdc_acm = UX_NULL;
                                                   }
                                                   else
                                                   {
                                                           tx_mutex_get(&cdc_acm_uart_mutex, TX_WAIT_FOREVER);
                                                             printf("Device Connected \r\n");
                                                             printf("PID: %#x ", (UINT)cdc_acm -> ux_host_class_cdc_acm_device -> ux_device_descriptor.idProduct);
                                                             printf("VID: %#x \r\n", (UINT)cdc_acm -> ux_host_class_cdc_acm_device -> ux_device_descriptor.idVendor);
                                                            tx_mutex_put(&cdc_acm_uart_mutex);
                                                   }
                                        }
                              }
                              /* USER CODE END UX_DEVICE_INSERTION */
                              break;
                    case UX_DEVICE_REMOVAL:
                              /* USER CODE BEGIN UX_DEVICE_REMOVAL */
                              if((VOID*)cdc_acm == current_instance)
                              {
                                        /* Clear the cdc instance */
                                        cdc_acm = UX_NULL;
                                        tx_mutex_get(&cdc_acm_uart_mutex, TX_WAIT_FOREVER);
                                        printf("Device Disconnected \r\n");
                                        tx_mutex_put(&cdc_acm_uart_mutex);
                                        tx_event_flags_set(&cdc_acm_eventflag, 0x01, TX_OR);
                              }
                              /* USER CODE END UX_DEVICE_REMOVAL */
                              break;
                    case UX_DEVICE_CONNECTION:
                              /* USER CODE BEGIN UX_DEVICE_CONNECTION */
                              /* USER CODE END UX_DEVICE_CONNECTION */
                              break;
                    case UX_DEVICE_DISCONNECTION:
                              /* USER CODE BEGIN UX_DEVICE_DISCONNECTION */
                              /* USER CODE END UX_DEVICE_DISCONNECTION */
                              break;
                    default:
                              /* USER CODE BEGIN EVENT_DEFAULT */
                              /* USER CODE END EVENT_DEFAULT */
                              break;
          }
          /* USER CODE BEGIN ux_host_event_callback1 */
          /* USER CODE END ux_host_event_callback1 */
          return status;
}

Create two threads, one for sending and another for receiving data. Then create an event flag to handle the data reception and one mutex to manage the UART resource in the logger. These resources should be created within the MX_USBX_Host_Init function, still in the app_usbx_host.c.

/* USER CODE BEGIN MX_USBX_Host_Init1 */
/* Create the CDC Receive Thread */
tx_byte_allocate(byte_pool, (VOID **)&pointer, 1024, TX_NO_WAIT);
tx_thread_create(&cdc_acm_receive_thread, "CDC Receive Thread", cdc_acm_receive_thread_entry, 0, pointer, 1024, 30, 30, TX_NO_TIME_SLICE, TX_AUTO_START);
/* Create the CDC Send Thread */
tx_byte_allocate(byte_pool, (VOID **)&pointer, 1024, TX_NO_WAIT);
tx_thread_create(&cdc_acm_send_thread, "CDC Send Thread", cdc_acm_send_thread_entry, 0, pointer, 1024, 15, 15, TX_NO_TIME_SLICE, TX_AUTO_START);
/* Create the Event Flags and the UART Mutex */
tx_event_flags_create(&cdc_acm_eventflag, "Event Flag");
tx_mutex_create(&cdc_acm_uart_mutex, "UART Mutex", TX_INHERIT);
/* USER CODE END MX_USBX_Host_Init1 */

Now, you can save and close this file and proceed with openning the ../USBX/App/ux_host_cdc_acm.c, where the thread functions are created to handle the CDC application. Start including the main.h and a define for the Receiver Buffer size:

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "main.h"
/* USER CODE END Includes */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define APP_RX_DATA_SIZE                         1024
/* USER CODE END PD */

In the next step, we import some variables declared in the past file and creating some others. This is used by the application.

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
extern UX_HOST_CLASS_CDC_ACM    *cdc_acm;
extern TX_EVENT_FLAGS_GROUP     cdc_acm_eventflag;
extern TX_MUTEX                                cdc_acm_uart_mutex;
UX_HOST_CLASS_CDC_ACM_RECEPTION cdc_acm_reception;
uint8_t                                                            UserRxBuffer[APP_RX_DATA_SIZE];
ULONG                           block_reception_count;
/* USER CODE END PV */

 

After that, create the send thread to transmit data through the CDC to the device. This can be created inside the USER CODE BEGIN 0 and USER CODE END 0 region:

VOID cdc_acm_send_thread_entry(ULONG thread_input)
{
          /* Local Variables */
          UINT status;
          UCHAR UserTxBuffer[] = "10";
          ULONG tx_actual_length;
          uint8_t nibble = 0;
          /* Infinite Loop */
          while(1)
          {
                    /* Check if the CDC Class is available */
                    if((cdc_acm != UX_NULL) && (cdc_acm->ux_host_class_cdc_acm_state == UX_HOST_CLASS_INSTANCE_LIVE))
                    {
                              /* Start sending data and store it results */
                              status = _ux_host_class_cdc_acm_write(cdc_acm, &UserTxBuffer[nibble], 1, &tx_actual_length);
                              tx_mutex_get(&cdc_acm_uart_mutex, TX_WAIT_FOREVER);
                              if (status == UX_SUCCESS)
                                        printf("Data sent: %c\r\n", UserTxBuffer[nibble]);
                              else
                                        printf("Unable to send data\r\n");
                              tx_mutex_put(&cdc_acm_uart_mutex);
                              /* Invert the nibble variable */
                              nibble ^= 1;
                              /* Wait 2 seconds for the next transmission */
                              tx_thread_sleep(200);
                    }
                    else
                    {
                              tx_thread_sleep(1);
                    }
          }
}

And then, the receive thread. Note that in this part of the code, the receiver starts in interrupt mode and we will declare it later.

VOID cdc_acm_receive_thread_entry(ULONG thread_input)
{
          /* Local Variables */
          UINT status;
          ULONG events;
          /* Infinite Loop */
          while(1)
          {
                    /* Check if the CDC Class is available */
                    if((cdc_acm != UX_NULL) && (cdc_acm->ux_host_class_cdc_acm_state == UX_HOST_CLASS_INSTANCE_LIVE))
                    {
                              /* Check if the reception was already started */
                      if(cdc_acm_reception.ux_host_class_cdc_acm_reception_state != UX_HOST_CLASS_CDC_ACM_RECEPTION_STATE_STARTED)
                              {
                                        /* Configure the reception parameters */
                             cdc_acm_reception.ux_host_class_cdc_acm_reception_block_size = 64;
                             cdc_acm_reception.ux_host_class_cdc_acm_reception_data_buffer = (UCHAR *)UserRxBuffer;
                              cdc_acm_reception.ux_host_class_cdc_acm_reception_data_buffer_size = APP_RX_DATA_SIZE;
                              cdc_acm_reception.ux_host_class_cdc_acm_reception_callback = cdc_acm_reception_callback;
                                        /* Start the recpetion and store it status */
                                        status = ux_host_class_cdc_acm_reception_start(cdc_acm, &cdc_acm_reception);
                                        if (status == UX_SUCCESS)
                                        {
                                                  tx_mutex_get(&cdc_acm_uart_mutex, TX_WAIT_FOREVER);
                                                   printf("Ready to receive data\r\n");
                                                  tx_mutex_put(&cdc_acm_uart_mutex);
                                        }
                                        else
                                        {
                                                  tx_mutex_get(&cdc_acm_uart_mutex, TX_WAIT_FOREVER);
                                                   printf("Unable to start reception\r\n");
                                                  tx_mutex_put(&cdc_acm_uart_mutex);
                                                   /* Wait 100 ms until try to start the reception again */
                                                   tx_thread_sleep(10);
                                        }
                              }
                              /* Reception already started */
                              else if(cdc_acm_reception.ux_host_class_cdc_acm_reception_state == UX_HOST_CLASS_CDC_ACM_RECEPTION_STATE_STARTED)
                              {
                                        /* Wait for a data to be received */
                                        tx_event_flags_get(&cdc_acm_eventflag, 0x01, TX_OR_CLEAR, &events, TX_WAIT_FOREVER);
                                        /* Print the received data through UART */
                                        printf("Data received: ");
                                        for(uint16_t count = 0; count < block_reception_count; count++)
                                        {
                                                   printf("%c", UserRxBuffer[count]);
                                        }
                              }
                    }
                    else
                              tx_thread_sleep(10);
          }
}

       Then add the receive callback function:

VOID cdc_acm_reception_callback(struct UX_HOST_CLASS_CDC_ACM_STRUCT *cdc_acm,
                    UINT status, UCHAR *reception_buffer, ULONG reception_size)
{
          /* Set the reception pointer to the beginning of the Rx Buffer */
cdc_acm_reception.ux_host_class_cdc_acm_reception_data_head = cdc_acm_reception.ux_host_class_cdc_acm_reception_data_buffer;
          /* Store the reception size in a global variable */
          block_reception_count = reception_size;
          /* Set NEW_RECEIVED_DATA flag */
          if (tx_event_flags_set(&cdc_acm_eventflag, 0x01, TX_OR) != TX_SUCCESS)
          {
                    Error_Handler();
          }
}

          Note that in the receive callback, we only reset the pointer to the receive buffer in any reception. This is the simplest way to manage a continuous reception in this library and was selected to make understanding easier. For a more robust reception management, refer to the USBX Host CDC demo in our GitHub.

          After that, add the callback function prototype:

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
VOID cdc_acm_reception_callback(struct UX_HOST_CLASS_CDC_ACM_STRUCT *cdc_acm, UINT status, UCHAR *reception_buffer, ULONG reception_size);
/* USER CODE END PFP */

With all the changes in the usbx_host_cdc_acm.c file are done, you can save and close this file. The last file to be changed is the ../USBX/App/usbx_host_cdc_acm.h file. So, opening that, we may add the thread prototype functions to share the function references with the app_usbx_host.c file.

/* Exported functions prototypes ---------------------------------------------*/
/* USER CODE BEGIN EFP */
VOID cdc_acm_send_thread_entry(ULONG thread_input);
VOID cdc_acm_receive_thread_entry(ULONG thread_input);
/* USER CODE END EFP */

 

Congratulations! We have done our code implementation! Now, you can build and flash the code into your microcontroller and see its results. Remember you need a USB CDC device class to be connected to test it.

2. Results

To try this demonstration project, after flashing the code onto the microcontroller, you can open a terminal on your computer and connect it to the STLINK virtual COM Port. Then, you can connect a USB CDC device to the user USB connector and check the log information.

Once the USB device is connected to the NUCLEO-H723ZG, a message is printed in the terminal showing its PID and VID. After that, the host (STM32H7) will transmit the values 0 and 1 every second to the device, and the received data will also be printed in the terminal.

BMontanari_17-1700765530042.png

To test this demo, we used the code constructed in the How to implement the USB Device CDC (VCOM) in STM32 using the Azure USBX package. We invite you to view that article for further details.

3. Conclusion

That concludes our article! You have the necessary information to start developing a USB host application using the USBX host package. In this article we have shown a tutorial for the STM32H7 family, but the steps for the other families are similar.

We have a set of demonstration projects that can help and address you during a development with the USBX. These are available in our GitHub Repository and demonstrate how to use them for any STM32 Line that contemplates the USB host feature.

Our best wishes for your project, and we hope you enjoyed the material!

 

Useful links:

Here are some links that were used to produce this article. These can be helpful for developing with the USBX Host package:

GitHub - X-CUBE-AZRTOS-H7 (Azure RTOS Software Expansion for STM32Cube)

STMicroelectronics - Introduction to USBX

STMicroelectronics - Azure RTOS

GitHub - STM32H7 USBX Examples

Microsoft Learn - USBX

STMicroelectronics - NUCLEO-H723ZG

STMicroelectronics - STM32H723ZG

 

 

Version history
Last update:
‎2023-12-07 05:25 AM
Updated by: