Skip to main content
Visitor II
June 13, 2024
Solved

Bare metal USB HID

  • June 13, 2024
  • 8 replies
  • 3274 views

Hello,

I am trying to implement a USB HID keyboard on an STM32F042 without HAL or LL, just simple register defs. I took an example code generated in CubeIDE with HAL and a few other examples. I extended the example with HAL to also implement the LEDs which all works fine. 

I understand how it basically works. I define the descriptors and configure the Endpoints etc. Then I need to transfer the descriptors when the host requires them. But what I can not figure out is how exactly the descriptors are actually transferred. The HAL is so overloaded that it seems impossible to find the exact lines of code where the descriptors are transferred to the host and how that is done exactly. Also where do I put the report descriptor to send some key presses? Can anyone give me some info on how that is done?

    This topic has been closed for replies.
    Best answer by gbm

    Looks like you have some error in the code setting the endpoint buffer pointers. In F0, PMA content may be displayed incorrectly. It must be read and written as 8-bit or 16-bit but unlike in F1 both halfwords in every word are meaningful. You may try to compare your code with mine - link in the signature.

    8 replies

    Visitor II
    June 15, 2024

    Maybe someone from ST can point me to some additional documentation for the USB peripheral and the PMA?

    Technical Moderator
    June 15, 2024

    Dear @Nickelgrass ,

     

    Full description of USB and PMA SRAMs is here :https://www.st.com/resource/en/reference_manual/rm0444-stm32g0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf#page1262

     

    IMG_8430.jpeg

    Not  an easy task to handle , all the processing should be interrupt based .Let us know

    Hope it helps you .

    Ciao

    STOne-32

    Visitor II
    June 15, 2024

    Thanks for the reply. I read the RM (of F042). But I somewhat hoped there was a more detailed explanation to the PMA. Something like a concrete map of what goes where.

    Is it correct that the PMA is 16 bit registers but 32 bit aligned?

    So from the logic of how it works is that I get an interrupt. The host requests a specific descriptor. I load the descriptor in the PMA and then send it of. Is that basically how it works?

    Technical Moderator
    June 15, 2024

    Hi @Nickelgrass,

    Here is a legacy STM32F1 series library documentation which was the first software implementation before F0 series , there are some similarities.

    https://www.st.com/resource/en/user_manual/um0424-stm32-usbfsdevice-development-kit-stmicroelectronics.pdf

    PMA is a kind optimized dual port SRAM where the Core and USB are able to write and read , endpoints should be well programmed with the right size .

    Hope it helps ,

    STOne-32

    Visitor II
    June 15, 2024

    Thanks! I had not found that yet.

    Visitor II
    September 6, 2025

    Hello again,

    its been over a year since I posted first. After a few things got in the way today finally I got round to actually start implementing it. So far all is setup and I am debugging receiving packets. 

    It seems I am misunderstanding one detail. With BTABLE=0 I have set my control endpoint 0 ADDR_TX to 32 and COUNT_TX to 0. Then ADDR_RX to 96 and COUNT_RX to 64 which translates to 0x8800 with the NUM_BLOCK value 2<<10 and bit 15 set. I checked in the debugger that the values in the USB RAM are actually correct which seems to be the case. Note that the USB RAM is 16 bit wide, so the upper 16 bits of each 32 bit word are just a mirror garbage. 

    Nickelgrass_0-1757184588848.png

    Now if I start the program I do receive the first two bytes of the first request. But the bytes are written to address 0 and not 96. If I change the COUNT_TX to some value, the data is stored at the address that COUNT_TX points to. I also tried simply using values that the Cube HID example uses. But it still results in rubbish. The request data is simply not stored. My endpoint 0 register is set to 0x3210 just like in Cube. Note that the value at address 0 are actually the first two bytes from the request 0x80 0x06. The 0xDDDDDDDD seems to be some strange garbage. 

    Nickelgrass_1-1757184955713.png

    Does anyone have an idea what I am doing wrong?

    Thanks

    kind regards

     

    Super User
    September 7, 2025

    As you've found out yourself, the device-only USB module bears significant 16-bit legacy, i.e. it has been originally built for 16-bit mcus. This is reflected not only in the access to its registers, but also to the memory buffer, which does not allow other than 16-bit access. Thus, what Cube displays when reading it as 32-bit words, may be and most probably also is incorrect.

    I have no insight on why the addressing failed, I don't use that USB actively. However, generally, I strongly recommend reading out and checking content of the registers when debugging, not just relying on the code which wrote into them.

    JW

    gbmAnswer
    Graduate
    September 7, 2025

    Looks like you have some error in the code setting the endpoint buffer pointers. In F0, PMA content may be displayed incorrectly. It must be read and written as 8-bit or 16-bit but unlike in F1 both halfwords in every word are meaningful. You may try to compare your code with mine - link in the signature.

    Visitor II
    September 7, 2025

    Thanks to gbm and JW! It works now. I had some screw ups in my addressing. My biggest mistake was using 32bit pointers and taking in account the wrong addressing like in F1 series.

    Visitor II
    September 12, 2025

    So all "seems" to work correctly. I can see on a logic analyzer that the descriptor requests are received and served correctly. But my EP1 is never polled and the USB bus is reset in intervals of 3 to 5 seconds and enumeration starts new. So there must be something wrong in my descriptors. Also it seems that the report descriptor is never requested. 

    const uint8_t USB_DeviceDescriptor[] =
    {
    	0x12, 				// lenght
    	0x01, 					// descriptor type device
    	0x00, 0x02, 				// usb 2.0
    	0x00, 				// device class 0 = defined in interface
    	0x00, 				// subclass
    	0x00, 					// protocoll
    	0x40, 				// packetsize 64 byte
    	0x83, 0x04, 				// vendor ST
    	0x52, 0x57, 				// product id
    	0x00, 0x01, 				// device
    	0x01, 				// Manufacturer
    	0x02, 				// Product
    	0x03, 				// SerialNumber
    	0x01 				// bNumConfigurations
    };
    
    const uint8_t USB_ConfigDescriptor[] =
    {
    	0x09, // length
    	0x02, // descriptor type configuration
    	0x22, 0x00, // total length 34 bytes
    	0x01, // interfaces 1
    	0x01, // config value
    	0x00, // config
    	0x80, // attributes bus powered remote wakeup
    	0x32, // power 100 mA
    	// interface descriptor
    	0x09, // length
    	0x04, // descriptor type interface
    	0x00, // interface number
    	0x00, // alternate setting
    	0x01, // endpoints
    	0x03, // interface class HID
    	0x01, // subclass (boot = 1, extended = 0)
    	0x01, // protocol keyboard
    	0x00, // interface
    	// HID descriptor
    	0x09, // length
    	0x21, // descriptor type HID
    	0x11, 0x01, // HID 1.11
    	0x00, // country code 0
    	0x01, // num descriptors
    	0x22, // descriptor type report
    	45, 0x00, 					// length of USB_HID_KeyboardReportDescriptor (set to 0 for only boot mode) 45
    	// endpoint descriptor in interrupt
    	0x07, // length
    	0x05, // descriptor type endpoint
    	0x81, // endpoint address 1
    	0x03, // attributes interrupt
    	0x08, 0x00, // packet size 8
    	0x0A 						// polling interval in ms
    };
    
    // standard report descriptor without media keys
    const uint8_t USB_HID_KeyboardReportDescriptor[] =
    {
    	0x05, 0x01, 			// usage page generic desktop
    	0x09, 0x06, 			// usage keyboard
    	0xA1, 0x01, 			// collection application
    	0x05, 0x07, 			// usage page key codes
    	0x19, 0xE0, 			// usage min 224
    	0x29, 0xE7, 			// usage max 231
    	0x15, 0x00, 			// logical min 0
    	0x25, 0x01, 			// logical max 1
    	0x75, 0x01, 			// report size 1
    	0x95, 0x08, 			// report count 8
    	0x81, 0x02, 			// input data, var, abs modifier byte
    	0x95, 0x01, 			// report count 1
    	0x75, 0x08, 			// report size 8
    	0x81, 0x01, 			// input const, array, abs reserved byte
    	0x95, 0x06, 			// report count 6
    	0x75, 0x08, 			// report size 8
    	0x15, 0x00, 			// logical min 0
    	0x25, 0x65, 			// logical max 101 keys
    	0x05, 0x07, 			// usage page key codes
    	0x19, 0x00, 			// usage min 0
    	0x29, 0x65, 			// usage max 101
    	0x81, 0x00, 			// input data, array
    	0xC0 			// end collection
    };

    The request for qualifier descriptor is stalled. 

    I have compared and tried all sorts of different examples but it just wont get past the enumeration.

    Edit: the point where it stops is when the host requests the clear feature. What is the response to that? Now I just Ack it.

    Visitor II
    September 13, 2025

    Found the issue, cant edit the post. Now the USB driver works and I have my HID running. It was EP1 not configured in combination with missing the token.