cancel
Showing results for 
Search instead for 
Did you mean: 

How to implement USBX in standalone mode

B.Montanari
ST Employee

Summary

The following article is a tutorial on how to implement the USB device, HID mouse class, using the Azure RTOS USBX package in standalone mode (without the RTOS).

1. Introduction

Hello developer and welcome to this article! For this tutorial, we use the NUCLEO-H723ZG board, which has a USB OTG_FS connector interface to open an HID class to work as a mouse. This board has an STM32H723ZGT6 microcontroller, and the steps shown here can be easily adapted to any other STM32. The STM32CubeIDE 1.13.1, the STM32CubeH7 1.11.1, and the X-CUBE-AZRTOS-H7 3.1.0 releases were used to build this tutorial. Standalone support is not present for all STM32 series yet. Refer to the proper release note of each series for more details on the current support.

BMontanari_0-1701201503044.jpeg

2 Development

2.1 Project creation

Let us start creating a project for the STM32H723ZGT6 in the STM32CubeIDE and enabling the following pins to get access to the LEDs available on the board.

PB0 as GPIO Output Push Pull, labeled as LD1_GREEN.

PE1 as GPIO Output Push Pull, labeled as LD2_YELLOW.

PB14 as GPIO Output Push Pull, labeled as LD3_RED.

 

BMontanari_1-1701201503083.png

Then configure the PC13 pin as GPIO_Input with the “USER_BUTTON” as label to allow us to read the state of the User Button available on the Nucleo board.

Down to the USB_OTG_HS menu, under the "Connectivity" tab. Once you have selected the USB peripheral tab, select the "Device_Only" option in the "Internal FS Phy." Afterwards, enable the "USB On the Go" HS global interrupt under the NVIC Settings tab. Doing that, the Clock Settings tab will signalize an error, but do not worry, we will correct it in the next step.

BMontanari_2-1701201503094.png

Go to the Clock Configuration tab. We suggest increasing the frequency of HCLK to achieve the best application performance. See the clock example settings used for this application.

BMontanari_3-1701201503100.png

 

Most importantly, change the USB Clock Mux source to the HSI48.

BMontanari_4-1701201503101.png

 

2.2 Configure the CUBE-AZRTOS-H7 package and USBX

After doing that, go back to the "Pinout & Configuration tab", navigate until the X-CUBE-AZRTOS-H7 under the "Middleware and Software Packs" menu. By clicking in this item, the "Software Packs Component Selector" is opened. If you do not have the USBX package, click the install button to download it.

Once the package is installed, enable the following required components:

  • USB USBX -> USBX -> CoreSystem
  • USB USBX -> USBX -> UX Device CoreStack
  • USB USBX -> USBX -> UX Device Controller
  • USB USBX -> USBX -> UX Device Class HID Core
  • USB USBX -> USBX -> UX Device Class HID Mouse

 BMontanari_5-1701201503105.png

Note: a warning message might appear, but it can be ignored. 

You can select different classes depending on your application. Make sure to have the CoreSystem, CoreStack, and Controller enabled. These components are required for the application. After selecting the packages, press OK.

Now, back to the X-CUBE-AZRTOS-H7 under the "Middleware and Software Packs" and enable USB USBX components:

BMontanari_6-1701201503110.png

Once the USB USBX package is enabled, a configuration menu is shown. In the AzureRTOS application, we need to increase the UXDevice memory buffer size and the USBX Device System Stack Size. For the HID class, 5KB is sufficient for our application. Enabling the information menu, you are able to see the suggested amount of memory needed for each class when selecting the USBX Device System Stack Size:

BMontanari_7-1701201503114.png

It is necessary to set a timebase for our application. Since we are not using RTOS, we can use the default SysTick to be our timebase. So, go to System Core->SYS and select the SysTick as the timebase source:

BMontanari_8-1701201503116.png

2.3 Code generation

Now, go to the "Project Manager" tab and then the "Code Generator" section. Enable the “Generate peripheral initialization as pair of ‘.c/.h’ files per peripheral”. This facilitates the access to the peripheral resources in the code.

 

BMontanari_9-1701201503144.png

The final last step is to disable the USB_OTG_HS peripheral initialization code in the Advanced Settings also in the Project Manager.

 

BMontanari_10-1701201503165.png

Now, that all the settings are configured you can generate the code. For that you can use the alt + K shortcut or press the generate button.

 

BMontanari_11-1701201503168.png

 

3. Code editing

Once the code is generated, we need to call the USBX Init function. For that, first include the app_usbx_device.h header in the main.c file:

/* Private includes ----------------------------------------------------------*/

/* USER CODE BEGIN Includes */

#include "app_usbx_device.h"

/* USER CODE END Includes */

Then call the MX_USBX_Device_Init() function after the peripheral initialization. We recommend doing that within User Code section 2:

/* USER CODE BEGIN 2 */

MX_USBX_Device_Init();

/* USER CODE END 2 */

 

Finally, call the USBX_Device_Process() within the while(1) statement. This function does not exist yet, and we will implement it later. This function must be constantly called for the USBX application works:

  /* Infinite loop */

  /* USER CODE BEGIN WHILE */

  while (1)

  {

    /* USER CODE END WHILE */



    /* USER CODE BEGIN 3 */

            USBX_Device_Process();

  }

  /* USER CODE END 3 */

 

You can save and close the main.c file and open the ../USBX/App/app_usbx_device.c. In this file we need to start the USB peripheral and link the HAL drivers with the USBX DCD (device controller drivers). For that include the following files in the app_usbx_device.c file:

/* Private includes ----------------------------------------------------------*/

/* USER CODE BEGIN Includes */

#include "main.h"

#include "usb_otg.h"

#include "ux_dcd_stm32.h"

/* USER CODE END Includes */

Then, navigate until the MX_USBX_Device_Init function and locate the USER CODE MX_USBX_Device_Init1 section. Between this section, add the following code:

/* USER CODE BEGIN MX_USBX_Device_Init1 */

/* Initialize the USB Peripheral */

MX_USB_OTG_HS_PCD_Init();



/* Set the RX Fifo */

HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_HS, 0x200);



/* Set the TX Fifo for the Control EP 0 */

HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_HS, 0, 0x40);



/* Set the TX Fifo for the HID Mouse EP 1 */

HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_HS, 1, 0x100);



/* Link the USB drivers with the USBX DCD and check if it return error */

if(ux_dcd_stm32_initialize((ULONG)USB_OTG_HS, (ULONG)&hpcd_USB_OTG_HS) != UX_SUCCESS)

{

          HAL_GPIO_WritePin(LD3_RED_GPIO_Port, LD3_RED_Pin, GPIO_PIN_SET);

          Error_Handler();

}



/* Start the PCD Peripheral */

HAL_PCD_Start(&hpcd_USB_OTG_HS);

/* USER CODE END MX_USBX_Device_Init1 */

 

In the next step, we need to pass the current tick to the application. Scroll down until the _ux_utility_time_get function and pass the current tick to the local variable. The HAL Tick can be used, as the code below:


ULONG _ux_utility_time_get(VOID)

{

          ULONG time_tick = 0U;



          /* USER CODE BEGIN _ux_utility_time_get */

          time_tick = HAL_GetTick();

          /* USER CODE END _ux_utility_time_get */



          return time_tick;

}

 

The last step is to implement the USBX_Device_Process function. Navigate to the app_usbx_device.c file and scroll down until the User Code Section 1. Implement this function as follows:


/* USER CODE BEGIN 1 */

VOID USBX_Device_Process(VOID)

{

          ux_device_stack_tasks_run();

}

/* USER CODE END 1 */

Then add its prototype in the app_usbx_device.h file:

/* USER CODE BEGIN EFP */

VOID USBX_Device_Process(VOID);

/* USER CODE END EFP */

Doing that you have all set for using the USBX. If you flash this code and connect a USB cable in the boards user USB connector, it should act as a HID Mouse:

BMontanari_12-1701201503175.png

In the next steps, we will guide you through an example of how to use this class and send the mouse reports to the computer.

Open the ../USBX/App/ux_device_mouse.c file and include the main.h header file to get access to some resources that allows the GPIOs control:

/* Private includes ----------------------------------------------------------*/

/* USER CODE BEGIN Includes */

#include "main.h"

/* USER CODE END Includes */

Then create this variable to store the address of the HID instance:

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

UX_SLAVE_CLASS_HID *hid_mouse;

/* USER CODE END PV */

We need to use the USBD_HID_Mouse_Activate to save the address of the hid instance in the variable we have created before:


VOID USBD_HID_Mouse_Activate(VOID *hid_instance)

{

          /* USER CODE BEGIN USBD_HID_Mouse_Activate */

          hid_mouse = (UX_SLAVE_CLASS_HID *)hid_instance;

          /* USER CODE END USBD_HID_Mouse_Activate */



          return;

}

 

We should also clear the instance when the device is deactivated:

VOID USBD_HID_Mouse_Deactivate(VOID *hid_instance)
{

          /* USER CODE BEGIN USBD_HID_Mouse_Deactivate */

          hid_mouse = UX_NULL;

          /* USER CODE END USBD_HID_Mouse_Deactivate */

          return;

}

 

Then scroll down until the User Code Section 1 and create the following function to send the mouse report through the USB communication:

/* USER CODE BEGIN 1 */

VOID HID_Mouse_Send(VOID)
{
          /* Local Variables */
          UX_SLAVE_DEVICE *device;

          UX_SLAVE_CLASS_HID_EVENT hid_event;

          device = &_ux_system_slave->ux_system_slave_device;

          /* Check if the device is configured */

          if((device->ux_slave_device_state == UX_DEVICE_CONFIGURED) && hid_mouse != UX_NULL)
          {

                    /* Mouse event. Length is fixed to 4 */
                    hid_event.ux_device_class_hid_event_length = 4;

                    /* Set select position */
                    hid_event.ux_device_class_hid_event_buffer[0] = 0;

                    /* Set X position */
                    hid_event.ux_device_class_hid_event_buffer[1] = 2;

                    /* Set Y position */
                    hid_event.ux_device_class_hid_event_buffer[2] = 2;

                    /* Set wheel position */
                    hid_event.ux_device_class_hid_event_buffer[3] = 0;

                    /* Send the report */
                    ux_device_class_hid_event_set(hid_mouse, &hid_event);

          }

}

/* USER CODE END 1 */

 

Add its function prototype into the ../USBX/App/ux_device_mouse.h file:

/* USER CODE BEGIN EFP */

VOID HID_Mouse_Send(VOID);

/* USER CODE END EFP */

Save and close both the ux_device_mouse.c and its header file. The last step is to call the HID_Mouse_Send function within the USBX process. So, open the ../USBX/App/app_usbx_device.c file and include the ux_device_mouse.h. Only the last include in the list is added, as the other ones were already present:

/* Private includes ----------------------------------------------------------*/

/* USER CODE BEGIN Includes */
#include "main.h"
#include "usb_otg.h"
#include "ux_dcd_stm32.h"
#include "ux_device_mouse.h"
/* USER CODE END Includes */

Finally, add the following code within the USBX_Device_Process function:

/* USER CODE BEGIN 1 */

VOID USBX_Device_Process(VOID)
{

          /* Call the device stack task */
          ux_device_stack_tasks_run();

          /* Check if the user button was pressed */
          if(HAL_GPIO_ReadPin(USER_BUTTON_GPIO_Port, USER_BUTTON_Pin) == GPIO_PIN_SET)
          {

                    /* Turn the Yellow LED on */
                    HAL_GPIO_WritePin(LD2_YELLOW_GPIO_Port, LD2_YELLOW_Pin, GPIO_PIN_SET);


                    /* Send the HID report to move the Mouse */
                    HID_Mouse_Send();

          }

          else

          {

                    /* Turn the Yellow LED off */
                    HAL_GPIO_WritePin(LD2_YELLOW_GPIO_Port, LD2_YELLOW_Pin, GPIO_PIN_RESET);

          }

}

/* USER CODE END 1 */

 

All code implementation is done! You can build and flash the code into your device and try it!

 

4. Results

After compiling and flashing this code onto the microcontroller, connect a Mini-USB cable to the user USB connector (CN13). When the USB is connected, pressing the user button makes the mouse to move in the diagonal direction and turn on the yellow LED. The yellow LED stays on as long as the user button is pressed.

 

5. Conclusion

And that concludes our article. You now have the needed knowledge to implement a class in the STM32 using the Azure USBX package in standalone mode. Here we presented the step-by-step to construct an HID mouse class, but the steps for opening other classes should be similar.

For more details on how to use/implement the other device classes, refer to our GitHub page and also the X-CUBE-AZRTOS package for your MCU family. Within that you may find a great set of examples showing the usage of several different classes available.

Hope you enjoyed this how to guide!

 

Reference Links:

 

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

 

Comments
jea74
Associate III

Dear Mr. Montanari,

I noticed that this code does not work with wireless transmitters, while it is OK with wired keyboards and mice, it fails with wireless transmitters - I tried to adapt without any success... Will it possible to have an updated version, more generic ? My final goal, since a very long time, is to get a gamepad controller (in this case Logitech wireless F710) working with the H723ZG board.  I tried to adapt both the USBX and the HID_standalone versions for this gamepad controller again without any success...

Would you have some code snippets which could get me started with this gamepad controller ?

If I run the HID_standalone example, I get this output:

**** USB OTG HS in FS MSC Host ****

USB Host library started.

Starting HID Application
Connect your HID Device

USB Device Connected
USB Device Reset Completed
PID: c22fh
VID: 46dh
Address (#1) assigned.
Manufacturer : Logitech
Product : Logitech Cordless RumblePad 2
Serial Number : N/A
Enumeration done.
This device has only 1 configuration.
Default configuration set.
Switching to Interface (#0)
Class : 3h
SubClass : 1h
Protocol : 2h
Mouse device found!
HID class started.
USB HID Host Mouse App...
USB Device disconnected
ERROR: USB device disconnected !!!

USB Device Connected
USB Device Reset Completed
PID: c21fh
VID: 46dh
Address (#1) assigned.
Manufacturer : Logitech
Product : Wireless Gamepad F710
Serial Number : 19619ECF
Enumeration done.
This device has only 1 configuration.
Default configuration set.
No registered class for this device.

Strangely the code sees the 2 modes (rumblepad2 & gamepad F710) of the gamepad while these modes are normally set via a switch on the gamepad ? But that's a detail... If I could read the buttons and gimbals values in any mode it will be amazing...  I think I must first have something working in "HID standalone" before going more complex with USBX

If I could get some help with some code snippets to get the gamepad correctly registered and being able to read the button states, I will be very grateful, I would like to use this H723ZG-gamepad to drive an industrial welding machine (in teaching mode) while all the code is ready for the application, I'm really stuck with this USB problem (and I'm not an USB expert)

Thank you,

Jerome

Misterxyz83
Associate II

Thanks for this article!

I'm working with an STM32U073 micro and i'd need to implement a virtual com with USBX in standalone mode.

I've looked on the source examples but i'm not sure about the low level read/write functions. There are two API:

  • _ux_device_class_cdc_acm_read_run: it seems to be a non-blocking read function to get data from bulk endpoint;
  • _ux_device_class_cdc_acm_write_run: it seems to be a non-blocking write function to put data to bulk endpoint

I'm not sure on how to use them...do i have to write a loop function where to call "read_run" API to retreive data and, when needed, call the "write_run" API to send data? Is there some basic operation to start the USB cdc stack? 

Have you any example?

Thanks a lot and sorry for my english

HH_42
Associate II

Hi @B.Montanari 

Thank you for the effort in publishing this, I would really like to make use of it, but I have a problem

I am using an NUCLEO-U5A5ZJ-Q board and I want to create a VCP using the CDC class in USBX

However, I am faced with some issues

I have to avoid using THREADX, or any other RTOS functionality and there also appears to be some kind of bug when I initialize the USB_OTG_HS peripheral in Device Only mode. The USB Core does not want to reset for some reason, I am a bit new at this so please forgive me if I am being confusing

All I want is to create a VCP using the SMR32U5A5 chip, for a low power application. Can you perhaps provide me with steps to do so? I already tried following your article on Classic USB Middleware, but the PCD drivers would not work, or perhaps I missed something?

Thanks so much

pkapt
Associate II

@Misterxyz83 were you able to figure out to use those APIs for CDC VPC?

Misterxyz83
Associate II

@pkapt yes sorry for late, i've solved. 

I've found how to use reading the functions' source code but there is something strange behavior on the write API. If i don't send a bunch of data of 64B (the size of the endpoint) the API seems to stall in WAIT state (no NEXT state to provide fresh data) and the host stall the connection. 

Solved sending always a block multiple of 64B

Simo_Sappo
Associate III

Hi @B.Montanari  I am trying to implement this example into the NUCLEO-U5A5ZJ-Q. Some steps need to be different because the options are not the same in STM32CubeIDE. While the program compiles after doing some debugging I found that it hangs at this function in Drivers/STM32U5xx_HAL_Driver/stm32u5xx_II_usb.c

static HAL_StatusTypeDef USB_CoreReset(USB_OTG_GlobalTypeDef *USBx)
{
  __IO uint32_t count = 0U;

  /* Wait for AHB master IDLE state. */
  do
  {
    count++;

    if (count > HAL_USB_TIMEOUT)
    {
      return HAL_TIMEOUT;
    }
  } while ((USBx->GRSTCTL & USB_OTG_GRSTCTL_AHBIDL) == 0U);

  /* Core Soft Reset */
  count = 0U;
  USBx->GRSTCTL |= USB_OTG_GRSTCTL_CSRST;

  do
  {
    count++;

    if (count > HAL_USB_TIMEOUT)
    {
      return HAL_TIMEOUT;
    }
  } while ((USBx->GRSTCTL & USB_OTG_GRSTCTL_CSRST) == USB_OTG_GRSTCTL_CSRST);

  return HAL_OK;
}

more specifically it stops in the second do/while so I am sure that it's a problem coming from the clock configuration but I really don't know what to change to make it work. Can you please help me?

Version history
Last update:
‎2023-12-07 04:43 AM
Updated by: