[Code] USB Device FS driver on GD32 (bare metal) - Page 1
Forum section: electronics / general

author: Saimoun
author_rank: Frequent Contributor
timestamp: October 22, 2022, 11:19:00 am
content: Hi allJust started to write USB Device FS driver for GD32 (it will probably work similarly on STM32 since the registers are literally the same).The device is a MIDI device, able to both send and receive MIDI over USB.I am not using any library, only GD32 registers.I thought I will write and share my journey here.Simon

author: pcprogrammer
author_rank: Super Contributor
timestamp: October 22, 2022, 11:25:21 am
content: Hi Simon,this might be of some help.https://github.com/pecostm32/STM32F103C8_USB_CH340I wrote this to emulate a CH340 on a STM32F103 as bare metal implementation. For midi replace the descriptor with the correct one and fill in the needed in and out points.Success,Peter

author: Saimoun
author_rank: Frequent Contributor
timestamp: October 22, 2022, 11:34:10 am
content: Starting now - I am using Alex's USB analyzer for... well, analyzing.=>https://github.com/ataradov/usb-sniffer-litefor those who do not know about it(By the way amazing work Alex @ataradov - this is super usefulI guess all it's missing is a nice GUI looking at the data and presenting it in a "nicer" way instead of having to manually read a bunch of numbersIf anybody feels like making a GUI simply reading the data it would already be a huge plus - then at least being able to quickly check the "fields" in the descriptors would be awesome)So, looking at a similar device (a USB-MIDI keyboard), I can see:* The host starts by giving it an address* Then asks for a bunch of stuff, device descriptor, strings, config descriptorSlightly strange thing the "bmAttributes" in the config descriptor is set to zero (while I though bit 7 should always be 1).Then the host sends a "set configuration" - makes sense.After that I can see regular IN packets on endpoint 4 which is the MIDI IN endpoint - so it seems it's operational from there on.

author: Saimoun
author_rank: Frequent Contributor
timestamp: October 22, 2022, 11:36:41 am
content: Quote from: pcprogrammer on October 22, 2022, 11:25:21 amGosh man that is amazing - I have been looking for something like this for a while!! Thanks!

author: pcprogrammer
author_rank: Super Contributor
timestamp: October 22, 2022, 11:41:08 am
content: You are welcome.Here is another that you might find helpful.https://www.beyondlogic.org/usbnutshell/usb1.shtmlFor checking USB I use wireshark with USB enabled. But I use it on linux. No idea if it works on windows.

author: Saimoun
author_rank: Frequent Contributor
timestamp: October 22, 2022, 02:30:59 pm
content: Ok here is something I do not understand from the enumeration. After setting the configuration, the host starts requesting MIDI Data as I wrote before.But then this shows up:Code:664 : SETUP: 0x0b/0667 : DATA0 (8): 81 06 00 22 00 00 34 00676 : ACK678 : IN: 0x0b/4681 : NAK690 : IN: 0x0b/0693 : NAK699 : IN: 0x0b/4702 : NAK704 : IN: 0x0b/0707 : DATA1 (32): 06 a0 ff 09 01 a1 01 09 02 a1 00 06 a1 ff 09 03 09 04 15 18 25 7f 35 00 45 ff 75 08 95 20 81 02732 : ACK735 : IN: 0x0b/4738 : NAK771 : IN: 0x0b/0774 : DATA0 (20): 09 05 09 06 15 80 25 7f 35 00 45 ff 75 08 95 20 91 02 c0 c0791 : ACK794 : IN: 0x0b/4797 : NAK805 : OUT: 0x0b/0808 : DATA1: ZLP811 : ACKI am confused, what is the host asking for?"81 06 00 22 00 00 34 00" means get descriptor (06) sent to "interface" (81)? and the descriptor type is 0x22=34?Clearly I must be missing something because the device sent the 0x34=52 bytes of whatever that descriptor is.

author: pcprogrammer
author_rank: Super Contributor
timestamp: October 22, 2022, 03:54:21 pm
content: It has been a while, but the host wants to know more about the interface.Within the device descriptor you probably listed one or more interfaces, and for these you have to supply the host with what they are for.Try and play with my code to see how things work for a simple USB to serial device. There are lots of comment in the code about what is for what.

author: ataradov
author_rank: Super Contributor
timestamp: October 22, 2022, 05:01:26 pm
content: It looks like your device also has HID interface. 0x22 is HID report descriptor and your data parses well as a report descriptor (usinghttps://eleccelerator.com/usbdescreqparser/):Code:0x06, 0xA0, 0xFF, // Usage Page (Vendor Defined 0xFFA0)0x09, 0x01, // Usage (0x01)0xA1, 0x01, // Collection (Application)0x09, 0x02, // Usage (0x02)0xA1, 0x00, // Collection (Physical)0x06, 0xA1, 0xFF, // Usage Page (Vendor Defined 0xFFA1)0x09, 0x03, // Usage (0x03)0x09, 0x04, // Usage (0x04)0x15, 0x18, // Logical Minimum (24)0x25, 0x7F, // Logical Maximum (127)0x35, 0x00, // Physical Minimum (0)0x45, 0xFF, // Physical Maximum (-1)0x75, 0x08, // Report Size (8)0x95, 0x20, // Report Count (32)0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)0x09, 0x05, // Usage (0x05)0x09, 0x06, // Usage (0x06)0x15, 0x80, // Logical Minimum (-128)0x25, 0x7F, // Logical Maximum (127)0x35, 0x00, // Physical Minimum (0)0x45, 0xFF, // Physical Maximum (-1)0x75, 0x08, // Report Size (8)0x95, 0x20, // Report Count (32)0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)0xC0, // End Collection0xC0, // End Collection// 52 bytes

author: Saimoun
author_rank: Frequent Contributor
timestamp: October 22, 2022, 05:45:17 pm
content: Quote from: ataradov on October 22, 2022, 05:01:26 pmSpot on, it does indeed have an HID interface (no idea why a MIDI keyboard would have an HID interface but I am sure they have their reasons... the device is an AKAI MPK mini).I did not know the host could ask for interface specific stuff on EP 0 as "standard" requests (bmRequestType bits 6-5 = 00), I would have expected a class specific request (bmRequestType bits 6-5 = 01).Unrelated to this - really cool tool thishttps://eleccelerator.com/usbdescreqparser/. I was precisely looking for something like this use with your sniffer. Maybe you should put that link on the readme on github

author: ataradov
author_rank: Super Contributor
timestamp: October 22, 2022, 06:08:52 pm
content: Quote from: simonlasnier on October 22, 2022, 05:45:17 pmHID reports are always requested as standard. No idea why. HID is the worst class in USB. Probably because it was the first one and they had no good structure in place yet.Quote from: simonlasnier on October 22, 2022, 05:45:17 pmThis is a very well known tool. But I've added the link, it can't hurt.

author: Saimoun
author_rank: Frequent Contributor
timestamp: October 22, 2022, 09:35:33 pm
content: Quote from: pcprogrammer on October 22, 2022, 11:25:21 amThanks so much Peter for sharing that code. I've been looking at tons of other code examples, libraries, which tend to be long and complex because they try to catch every single usage. So really happy to have found your code, much more similar to my way of coding: bare bone, simple, and only sticking to the essential. SO much easier to understand, and great comments by the wayBut I would say you even simplified too much - wondering why you decided not to handle these requests?USB_REQUEST_GET_STATUSUSB_REQUEST_CLEAR_FEATUREUSB_REQUEST_SET_FEATUREUSB_REQUEST_SET_DESCRIPTORUSB_REQUEST_GET_CONFIGURATIONUSB_REQUEST_GET_INTERFACEUSB_REQUEST_SET_INTERFACEUSB_REQUEST_SYNC_FRAMEAnd more importantly, what will happen if the device receives one of them? Wouldn't the hardware just send NAK forever? Isn't it better to configure to send STALL when one of these happens? (then at least the host knows this is not implemented)Thanks!

author: ataradov
author_rank: Super Contributor
timestamp: October 22, 2022, 10:25:43 pm
content: Quote from: simonlasnier on October 22, 2022, 09:35:33 pmCompliant device would send STALL indicating that request is not supported and the host should be able to handle it.I don't implement most of this stuff in any of my stacks and it works fine.I implement those things when necessary. For example if you actually have a device with multiple interfaces, you will have to implement get/set interface. Otherwise, why bother?

author: pcprogrammer
author_rank: Super Contributor
timestamp: October 23, 2022, 04:49:56 am
content: Like ataradov wrote, why bother?The host won't ask for them if the device descriptor does not list them as being used. Part of it also depends on the driver on the host. But like I wrote in my other post it has been a while that I wrote the code. I do know that it took some effort to distill it from the code generated by MXcube and other examples I found on the net. It also being my first experience with USB it might well be cut a bit to much.I removed as much clutter as possible and still have it work. Developing an USB device is not easy due to a lot of rules to follow and timing that can't be stretched to much, so no debugging by single stepping.

author: Saimoun
author_rank: Frequent Contributor
timestamp: October 23, 2022, 11:46:25 am
content: Quote from: pcprogrammer on October 23, 2022, 04:49:56 amYou did, and did greatThanks to you both, and yes that makes sense. All I needed to know was what a compliant device would do (stall as I understood), as I do intend to be compliant (eventually).Regarding the USB_REQUEST_SET_DESCRIPTOR - such a weird command which I guess no host will ever send >_< - what would the compliant way be? Because the GD32 hardware always replies ACK to a SETUP packet, so it seems the only way I can "refuse" it is by sending STALL when the hosts tries to send the descriptor? Or should I just ACK the descriptor and then ignore it?Thanks

author: pcprogrammer
author_rank: Super Contributor
timestamp: October 23, 2022, 11:58:07 am
content: It is a strange command indeed. Your device would need to be programmable in some way to be able to start using a new descriptor. Have not really thought about it thoughI would ignore it and just implement what is needed for MIDI. Try to find a description for the USB standard for it if it exists, and stick with implementing that.

author: ataradov
author_rank: Super Contributor
timestamp: October 23, 2022, 05:17:02 pm
content: Quote from: simonlasnier on October 23, 2022, 11:46:25 amThis is the standard. You can't send anything else.Quote from: simonlasnier on October 23, 2022, 11:46:25 amThis is also the standard. You are supposed to send STALL for any command you don't support.

author: Saimoun
author_rank: Frequent Contributor
timestamp: October 23, 2022, 05:50:39 pm
content: True about the ACK response to a setup packet - my bad the GD manual actually does say it is from the standardQuote from: ataradov on October 23, 2022, 05:17:02 pmRegarding stalling, you mean send stall on the OUT packet coming from the host, or accepting those OUT, and then when the host is requesting the zero-length packet, at that moment send STALL?

author: ataradov
author_rank: Super Contributor
timestamp: October 23, 2022, 06:07:05 pm
content: STALL is a response token, it can't be sent by itself. In the hardware you just set a bit telling the hardware that it should send STALL next time something is requested. So, the sequence for OUT transfers would be like this: SETUP, ACK, <here you set stall request> OUT (DATA), STALL. And for IN: SETUP, ACK, <here you set stall request> IN, STALL.

author: Saimoun
author_rank: Frequent Contributor
timestamp: October 23, 2022, 06:28:40 pm
content: Undestood - thanksMy question was for the SET Descriptor, whether I should doSETUP, ACK, OUT (descriptor), STALL (like you said)orSETUP, ACK, OUT (descriptor), ACK, OUT (descriptor), ... ACK, IN, STALLAnyways - it's clear now thanks

author: Saimoun
author_rank: Frequent Contributor
timestamp: October 24, 2022, 09:31:44 am
content: Posting my progress of the day: now got the enumeration workingThe complex library codes and the countless people on the internet saying "oh nobody writes their own USB stack it's too complicated" make it look impossible. But actually it's really not that hard, it just takes a bit of time understand the protocol and the way the USB peripheral on the MCU handles it.If anybody wants to write their own USB stack, I would recommend:get a Raspberry Pi and install Alex's amazing snifferreadhttps://www.usbmadesimple.co.uk/read your MCU's datasheet/user manualif you are using ST or GD, you can look how Peter (pcprogrammer) is handling the enumeration here:https://github.com/pecostm32/STM32F103C8_USB_CH340(thank you so much for that)Then start writing the code and debug with Alex's snifferOnce this is working if you want to be USB compliant you need to start reading the USB specsThings I had to fight with during my learnings (GD32/STM32 specific):Remember that weird addressing (skip every 16 bits) on the USB internal buffer: to keep in mind everytime you need to read or write an endpoint buffer, and read or write an endpoint status registerRemember those bloody toggle bits in the endpoint status registers which make changing any value in those more complicatedI found this part of the manual (this is from ST - GD says something similar) very confusing:Quote from: ST manual RM0008 section 23.4.2I found that confusing because FRES is reset by default. So I thought maybe we need to set it before resetting PDWN?What they do not tell you is that the FRES bit is set automatically when you reset the PDWN bit.