Skip to main content
B.Montanari
ST Technical Moderator
October 3, 2022

How to implement a USB device custom HID class on STM32 part2

  • October 3, 2022
  • 19 replies
  • 47746 views

Hello and welcome back to the second and last portion!

1. Recap

We resume from the point we left off, just to give a brief recap, in part 1 we made all the way up to the code generation with STM32CubeIDE:

516.png
Among the several files created automatically, the highlighted ones are modified during this tutorial, so you might want to open them preemptively:
517.png
 The first step in the code is to increase the IN and OUT Endpoint size. Just to do a small recap, the Endpoint is the source or destination data buffer that must be implemented on the device side, each amount of data that is received from or sent to the host will be placed into an endpoint. In the usbd_customhid.h file, modify the CUSTOM_HID_EPIN_SIZE and CUSTOM_HID_EPOUT_SIZE defines to 0x40 (64 bytes): 

#define CUSTOM_HID_EPIN_SIZE 0x40U
#define CUSTOM_HID_EPOUT_SIZE 0x40U

 

Modify the USBD_CUSTOM_HID_ItfTypeDef to get the entire message buffer:

typedef struct _USBD_CUSTOM_HID_Itf
{
 uint8_t *pReport;
 int8_t (* Init)(void);
 int8_t (* DeInit)(void);
 int8_t (* OutEvent)(uint8_t* state);

} USBD_CUSTOM_HID_ItfTypeDef;	

Save and close the file.
NOTE: All the modifications on files inside the middlewares folder may be overwritten in case of code generation from STM32CubeMX. So, create a copy of the files when the modifications were finished.
Open the usbd_customhid.c file and modify the following functions:

static uint8_t USBD_CUSTOM_HID_DataOut(USBD_HandleTypeDef *pdev,
 uint8_t epnum)
{

 USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData;

 ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(hhid->Report_buf);

 USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR, hhid->Report_buf,
 USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);

 return USBD_OK;
}
static uint8_t USBD_CUSTOM_HID_EP0_RxReady(USBD_HandleTypeDef *pdev)
{
 USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData;

 if (hhid->IsReportAvailable == 1U)
 {
 ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(hhid->Report_buf);
 hhid->IsReportAvailable = 0U;
 }

 return USBD_OK;
}


Save and close the file.

          Open the usbd_custom_hid_if.c and add the following information in Usb HID report descriptor (if you need more information regarding the report descriptor, refer the documentation available in Information for Developers | USB-IF:(

/** Usb HID report descriptor. */
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
		/* USER CODE BEGIN 0 */
		0x06, 0x00, 0xff, // Usage Page(Undefined )
		0x09, 0x01, // USAGE (Undefined)
		0xa1, 0x01, // COLLECTION (Application)
		0x15, 0x00, // LOGICAL_MINIMUM (0)
		0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
		0x75, 0x08, // REPORT_SIZE (8)
		0x95, 0x40, // REPORT_COUNT (64)
		0x09, 0x01, // USAGE (Undefined)
		0x81, 0x02, // INPUT (Data,Var,Abs)
		0x95, 0x40, // REPORT_COUNT (64)
		0x09, 0x01, // USAGE (Undefined)
		0x91, 0x02, // OUTPUT (Data,Var,Abs)
		0x95, 0x01, // REPORT_COUNT (1)
		0x09, 0x01, // USAGE (Undefined)
		0xb1, 0x02, // FEATURE (Data,Var,Abs)
		/* USER CODE END 0 */
		0xC0 /* END_COLLECTION */

};	

 

In the same file, modify both the CUSTOM_HID_OutEvent_FS prototype and function structure (this function is called when a full package data is received through the USB):

static int8_t CUSTOM_HID_OutEvent_FS(uint8_t* state);
static int8_t CUSTOM_HID_OutEvent_FS(uint8_t* state)
{
	/* USER CODE BEGIN 6 */
	return (USBD_OK);
	/* USER CODE END 6 */
}

 

Open the main.c file, extern the USB handler, add the transmission and reception buffers and the following flags variables. Use the USER CODE comments to locate the proper place to copy and paste the code snippets below:

/* USER CODE BEGIN PV */ 
uint8_t tx_buffer[64];		//Variable to store the output data 
uint8_t report_buffer[64];		//Variable to receive the report buffer 
uint8_t flag = 0;			//Variable to store the button flag 
uint8_t flag_rx = 0;			//Variable to store the reception flag 
 
//extern the USB handler 
extern USBD_HandleTypeDef hUsbDeviceFS; 
/* USER CODE END PV */ 

 
/* USER CODE BEGIN 2 */ 
 //To fill the buffer 
 for (uint8_t i=0; i<64; i++) 
 { 
tx_buffer[i] = i; 
 } 
/* USER CODE END 2 */ 

 
 /* Infinite loop */ 
 /* USER CODE BEGIN WHILE */ 
 while (1) 
 { 
 if (flag_rx == 1) 
 { 
 //Check if the first byte of the report buffer equals 1 
 if (report_buffer[0] == 1) 
 { 
//Turn the user LED on 
HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, SET); 
 } 
 
 //Check if the first byte of the report buffer equals 2 
 else if (report_buffer[0] == 2) 
 { 
//Turn the user LED off 
HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, RESET); 
 } 
 
 flag_rx = 0; 
 } 
 
 //To send the output data when the button is pressed 
 if (flag==1) 
 { 
 USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, tx_buffer, 64); 
 
 Flag = 0; 
 } 
 
/* USER CODE END WHILE */ 
 
 
/* USER CODE BEGIN 4 */ 
 
//If the button is pressed, set button flag variable 
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) 
{ 
Flag = 1; 
} 
 
/* USER CODE END 4 */ 

 

Open the usbd_custom_hid_if.c file and extern the variables created in main.c. 

/* USER CODE BEGIN PV */ 
/* Private variables --------------------------------------------------*/ 
//To extern the report_buffer variable 
extern uint8_t report_buffer[64]; 
extern uint8_t flag_rx; 
/* USER CODE END PV */

In CUSTOM_HID_OutEvent_FS function, add the memcpy command to copy the data stored in the reception variable of the USB (“state”) and place it in the variable report_buffer previously created

static int8_t CUSTOM_HID_OutEvent_FS(uint8_t* state) 
{ 
 /* USER CODE BEGIN 6 */ 
 
//To copy the reception buffer into the report_buffer variable 
memcpy(report_buffer, state, 64);
 
flag_rx = 1; 
 
return (USBD_OK); 
 /* USER CODE

 

Done! By building the application the dream of all programmers should be achieved, 0 errors and 0 warnings. Now let us see if it also works.

2. Validation

Start a debug session with the created project with STM32CubeIDE by clicking in the Debug button:
518.png
        
A window may appear to ask for some settings about the Debugger, for this example the standard settings work well. So, just click on Ok:
519.png
Run the execution clicking Resume button or pressing F8:
523.png
Open the SimpleHIDWrite software, select the STM32 Custom Human interface in the list, then click on Clear button to clear the terminal:
527.png
Press the User Button on the Discovery Board and check that the buffer sent to host on terminal. It is the buffer with the sequence from 0 to 63U (0x3F):
531.png
Fill the first field with the value 1, click on Set Report button and check that the Red LED turned ON (change the first value to 2 and send the report again to turn OFF the LED):533.png
Finally, insert a breakpoint on some line between the if statement of main.c that checks the flag_rx, fill the fields of the SimpleHIDWrite with random data, set a report and check the value of the report_buffer on STM32CubeIDE:
535.png

Conclusion


All the steps to implement a USB Device as Custom HID Class on STM32 are done! And now you have a project that can transmit a package through the USB, receive and manage a data package from a Host in your code.  Enjoy it!


Related links

 

19 replies

Robmar
Senior II
September 24, 2023

That's a lot of fussing about to add a bog standard HID device which should really be automated.

So how now to add an audio device, and a virtual com port, that would be a useful walk through which so far I can't find anywhere.

BRonc.1
Associate III
December 12, 2024

@B.Montanari  In the first part of this article you speak about another article with USBX management, can you post the link? I really need it :-))

Robmar
Senior II
January 3, 2025

I'd avoid USBX, STM will be dropping it.

BRonc.1
Associate III
January 7, 2025

@Robmarthanks for reply... are you sure STM will drop USBX? At the moment is the only way to manage USB for several devices in the STM IDE.

Robmar
Senior II
January 7, 2025

Not the only way, we use TinyUSB and an extended USB driver we developed from the original 2015 USB driver.

Are you using USBC with Threadex?

Solved: Standalone implementation of USBX ,MTP Support, ST... - STMicroelectronics Community

BRonc.1
Associate III
January 7, 2025

I'm using a STM32C071, I need to connect it to a PC and get connection/disconnection and read/write 64 bytes from/to device. I've done before (for another uC) with standard USB HID lib, but in the STM32 CubeIDE I can enable only USBX for STM32C071.

Robmar
Senior II
January 7, 2025

The 32C071 has the standard Synopsis USB controller (1x) like STM32F4/7 and H7 which have a dual USB, so you will be able to drop in the standard driver, it takes a while to go through the code, but its not too difficult.  If CubeIDE won't let you add the standard code, just copy it from another project.
STM seem to like to force clients in the direct of their choice which isn't often in our interests.

Visitor II
January 22, 2025

I am working on the IR keyboard . The project include a remote controller and, ir receiver, stm32f103 .
I want to use the device it on a win11 based mediaplayer. (mini PC)

The win11 recognizes it as a keyboard when i plug.

I can read the remote controler ir keys , I can sent normal keys ('1','2'....'ENTER','ESC'... etc)

My problem is i can not sent FN keys , VOLUME_UP , VOLUME_DOWN,VOLUME_MUTE etc.

How can i continou?

Please note: I used  'HID keyboard report descriptor' for keyboard. 187 byte as desctipted on ST   

 
*************************
typedef  struct
{
uint8_t MODIFIER;
uint8_t RESERVED;
uint8_t OEMKEY;
uint8_t KEYCODE1;
uint8_t KEYCODE2;
uint8_t KEYCODE3;
uint8_t KEYCODE4;
uint8_t KEYCODE5;
} TkeyboardHID ;
 
union 
{
 TkeyboardHID  keyboardHID;
 uint8_t tx_buf[8];
}  keyData;

 

 
 
keyData.keyboardHID.MODIFIER=0x00; 
keyData.keyboardHID.RESERVED=0x00;
keyData.keyboardHID.OEMKEY=0x00;
keyData.keyboardHID.KEYCODE1=0x7F;   // 0X7f is Mute key.
keyData.keyboardHID.KEYCODE2=0;
keyData.keyboardHID.KEYCODE3=0;
keyData.keyboardHID.KEYCODE4=0;
keyData.keyboardHID.KEYCODE5=0;
 USBD_HID_SendReport(&hUsbDeviceFS,keyData.tx_buf,8);
 
HAL_Delay(10);
keyData.keyboardHID.MODIFIER=0; 
....
 
USBD_HID_SendReport(&hUsbDeviceFS,keyData.tx_buf,8);
 HAL_Delay(10);
 
Could someone please  guide me.
Thanks.
 
 

 

Visitor II
September 2, 2025

It works!

Works fine with FreeRTOS on STM32G connected to PC or Android. Should work fine just with while(1) superloop. Just be careful when changing the code. I was missing changing arguments from uint8_t to uint8_t* were it was is needed.

By changing USB HID report descriptor in usbd_custom_hid_if.c I can achieve desired communication.

Thanks!