on
2022-10-03
07:31 AM
- edited on
2024-06-17
08:01 AM
by
Laurids_PETERSE
Hello and welcome back to the second and last portion!
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:
Among the several files created automatically, the highlighted ones are modified during this tutorial, so you might want to open them preemptively:
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.
Start a debug session with the created project with STM32CubeIDE by clicking in the Debug button:
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:
Run the execution clicking Resume button or pressing F8:
Open the SimpleHIDWrite software, select the STM32 Custom Human interface in the list, then click on Clear button to clear the terminal:
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):
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):
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:
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!
AFAIK, the example is incorrect. Calling USBD_CUSTOM_HID_SendReport() from main() will sooner or later cause an error in USB device stack operation.
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 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
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.
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
I have seen various problems with custom HID with various versions, notified at many places :
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
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
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 don't know what a OLS ticket is. Can you explain ?
Regards,
Mike
@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
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;
}
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
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.