cancel
Showing results for 
Search instead for 
Did you mean: 

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

B.Montanari
ST Employee

HOW TO IMPLEMENT A USB DEVICE CUSTOM HID CLASS ON STM32?
 

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 END 6 */ 
} 
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

3. CONCLUSIONS:


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!
4. REFERENCE LINKS:
Introduction to USB with STM32: https://wiki.st.com/stm32mcu/wiki/Introduction_to_USB_with_STM32#What_is_the_Universal_Serial_Bus_-28USB-29-3F
Simple HID Write:
http://janaxelson.com/files/SimpleHIDWrite3.zip
32F072BDISCOVERY Main Page:
https://www.st.com/en/evaluation-tools/32f072bdiscovery.html
32F072BDISCOVERY User Manual:
https://www.st.com/resource/en/user_manual/um1690-discovery-kit-for-stm32f0-series-microcontrollers-with-stm32f072rb-stmicroelectronics.pdf
STM32F072RB Main Page:
https://www.st.com/en/microcontrollers-microprocessors/stm32f072rb.html
STM32F072RB Datasheet:
https://www.st.com/resource/en/datasheet/stm32f072rb.pdf
STM32F0x2 Reference Manual:
https://www.st.com/resource/en/reference_manual/rm0091-stm32f0x1stm32f0x2stm32f0x8-advanced-armbased-32bit-mcus-stmicroelectronics.pdf
STM32CubeIDE:
https://www.st.com/en/development-tools/stm32cubeide.html
 
Comments
gbm
Lead II

AFAIK, the example is incorrect. Calling USBD_CUSTOM_HID_SendReport() from main() will sooner or later cause an error in USB device stack operation.

MMyar.1
Associate II

I have problems with this code. I have exactly the demo board mentionned here. I followed it exactly, except I changed a bit the code from the main to experiment various things :

  • if I only send data from PC to STM32, everything works
  • if I only send data from STM32 to PC, everything works

if I want to play like send/receive sessions, I experience USB device error after 2 exchanges, exactly as @gbm mentions it. If restart the PC side software, no message then continues. It seems the USB is dead

I also tried to use the USD HID Demonstrator by ST (can be downloaded). I want to mention I had to install the visual studio redist 2005 in 32 bit in order to make it work. Again, after 2 "send", the USB freezes. It was to be sure it is not my code on the PC side that was broken.

More fun : I installed the USB HID demonstrator in a virtual machine : this allows to "unmount" the USB interface from PC side without powering down the STM32. In this case, after a USB crash, I can unmount and mount again the STM32 card, and then I have each time 2 messages before crash.

Please notice I made these tests with the 2 lasts versions of STM32cube, thus with the 2 last versions of the framework. My code on the PC side was written in Python with PyUSB, and I tried with 2 versions of the underlying libusb (libusb0 and libusb1) on linux. Other tests with USB HID demonstrator of course made with windows.

So I think there is something that does not work, I think it is somewhere in the USB driver on the STM32. Perhaps is this specific to code related to the STM32F072 chip

Is there a solution for this ?

Best Regards,

Mikhaël

MMyar.1
Associate II

Tried the same tuto with a STM32F407-disc1 Board and it works like a charm. I guess there is a bug somewhere in the HID framework for the STM32F072 chip, in the specific case of send/receive sequences on USB.

Eurous
Associate

I got the tutorial running on a STM32F412G-DISCO Board, successfully.

But I had to modify the following function differently, as in the tutorial described, located in the file: “usbd_customhid.c�?

static uint8_t USBD_CUSTOM_HID_EP0_RxReady(USBD_HandleTypeDef *pdev)

{

 USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassDataCmsit[pdev->classId];

 if (hhid->IsReportAvailable == 1U)

 {

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

                                                     

   hhid->IsReportAvailable = 0U;

 }

 return (uint8_t)USBD_OK;

}

This war more or less the default code which was generated by the STM32CubeIDE

Otherwise it will stuck in the HardFault_Handler(void)

Best regards

Markus

MMyar.1
Associate II

I have seen various problems with custom HID with various versions, notified at many places :

https://community.st.com/s/question/0D53W00000ttTa2SAE/what-is-the-issue-with-the-usb-custom-hid-low-level-driver?t=1672355984794

https://community.st.com/s/question/0D53W00000vQ4iASAS/issue-with-stm32wb55-usb-custom-hid-using-v1111-firmware-pack

https://community.st.com/s/article/how-to-implement-a-usb-device-custom-hid-class-on-stm32-part2

etc. It seems Custom HID implementation is not really taken seriously at ST.

What I find is that it depends a lot on the version of the Cube framework. People say that it works well with the 5.2 (but I was not able to install it on recent linux distribution), I found that it works on CubeMX 6.7 with Framework v. 1.11.0 but not with 1.11.3 for example. So you should not use "Use last version" but a given version. For me on a STM32F3-discovery it works well with 1.11.0 in custom HID.

Hope this helps,

Mike

B.Montanari
ST Employee

Hi @MMyar.1​ 

Sorry for the hiatus in the comment session here. I saw that you had some problems with this before with other boards/STM32 series and they can definitely be explored in depth by issuing an OLS ticket

I tried to recreate the latest problem you've mentioned, related to the 1.11.3 not working but I couldn't reproduce it. I've tried the 2 variants with the latest HAL release: STM32CubeMX 6.7 with v1.11.3 and CubeMX6.6.1 with v1.11.3, both worked as expected considering this particular article content, which is a very simplistic implementation just to provide a head start in the topic. I've attached the two codes (in a single zip) in case you need them, but overall, my suggestion is to issue an OLS ticket if you are facing any issues

Hope this helps

MMyar.1
Associate II

Hello @B.Montanari​ . Thanks for your time.

I looked at your project and I have no possibility to try it for now as my STM32 boards are on a prototype that is used for now. However I can speak about what I did and what happend.

  • I started with this tutorial : https://community.st.com/s/article/how-to-implement-a-usb-device-custom-hid-class-on-stm32-part2
  • I followed it exactly, step by step, on a new project, using exactly the same board as mentionned in the tutorial (STM32F072 discovery). On the PC side, I used a popular Python library, pyUSB, in Linux environment, which is simply a wrapper on a C USB library on Linux. At this time, when I performed only send or only receive actions, as many as I want, it worked like a charm. However, when I performed send/receive cycles, it worked twice, then crashed. I did not know if it comes from PC or STM32 side, and so I installed, on a windows computer, a tool provided by ST to send USB data. I obtained the same behaviour. At this time, I believed the problem came from the board itself, or from the HAL support for the board. I worked on a older version of CubeMX (I guess 5.x), but I did not think about updating. So I bought another one, a STM32F3 discovery.
  • When I receive my new board, I update CubeMX as well as the framework of course, then follow the tutorial again, and it works. When I change the code and add my updates, it works. So it validates the use of pyUSB as well as the STM32 framework and board. OK
  • But a few month later, I work again on the code, and I obtain the same problems as on the initial STM32F072 board, but on the STM32F3 ! I was so disappointed of course, and I restarted from zero, following again the tutorial, and I still obtain the same behaviour. On the internet, I understand that people obtain similar problems, and I start to understand it can come from versions of the framework. At this time, I unclick the "use last version of the framework" in the CubeMX code generation options, and downgrade the framework to 11.1.0 and then it works. Then I update a lot my code and it still works, very well and exactly as I want. I did not try to go again to 11.1.3 (I want my project to work, I can help debugging a few but I already lost a lot of time with that).
  • If it is useful for you I can send my STM32 code as well as my python code, just ask.
  •  

I don't know what a OLS ticket is. Can you explain ?

Regards,

Mike

MMyar.1
Associate II

@B.Montanari​  : A last comment. At a time, I made the STM32F072 board work BUT I had to add a "reset" of the connection (on PC side with pyUSB) after each send-receive sequence. I was not satisfied by this behaviour, it was a bit hacky, and I started then to use the STM32F3 board

rkari
Associate

This was a nice tutorial. Thank you @B.Montanari . If it helps someone else out, I was testing this with a Nucleo-F446ZE. I found I needed the change pointed out by @Eurous along with a call to USBD_LL_PrepareReceive within the  _DataOut function to repeatedly receive data into EP1. I was testing this with pyusb and calling dev.write(0x1,string,timeout=100) versus set report within SimpleHID. I was able to reproduce the set report function of SimpleHID with dev.ctrl_transfer within Python. 

 

static uint8_t USBD_CUSTOM_HID_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum)

{

UNUSED(epnum);

USBD_CUSTOM_HID_HandleTypeDef *hhid;

if (pdev->pClassDataCmsit[pdev->classId] == NULL)

{

return (uint8_t)USBD_FAIL;

}

hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassDataCmsit[pdev->classId];

/* USB data will be immediately processed, this allow next USB traffic being

NAKed till the end of the application processing */

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

USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR , hhid->Report_buf,

USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);

return (uint8_t)USBD_OK;

}

RhSilicon
Lead

I would suggest that USB device posts have complementary USB host posts. So that the STM32 Host can handle the STM32 Device.

About Custom USB Host HID, maybe below link post is related:

https://github.com/STMicroelectronics/stm32_mw_usb_host/issues/14

Robmar
Senior III

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.

Version history
Last update:
‎2022-10-03 07:31 AM
Updated by: