February 28, 2025

Black Pill boards - USB experiments with Hydra - part 4, circa 2021

What about reading data?

We have seen that usb_printf() does the job for writing data. We should dig into it in detail eventually. But what about reading data? When we type a character into picocom, we see the following chatter on our console:

USBint - Rx level
Endpoint 0, read packet 1 bytes from FIFO
USBint - Rx level
USBint = OUT Endpoint 1 xfer complete
VCP_DataRx 1
- EP 1 StartXfer 64 bytes
All of these are debug messages, generated by printf statements that I sprinkled (back in 2021) through the source code.

The first -- USBint - Rx level -- comes from usbF4/driver/usb_dcd_int.c in the routine USBD_OTG_ISR_Handler(). We have seen a USB interrupt and the status register has "rxstsqlvl" set. This will be handled by a call to DCD_HandleRxStatusQueueLevel_ISR().

The next -- Endpoint 0, read packet 1 bytes from FIFO -- comes from usbF4/driver/usb_core.c in the routine USB_OTG_ReadPacket(). Are we sure this is on endpoint 0? I think this could be any endpoint and is almost surely endpoint 1 in this case.

The next -- USBint - Rx level -- is just like the first, another interrupt indicating data.

The next -- USBint = OUT Endpoint 1 xfer complete -- is from usbF4/driver/usb_dcd_int.c in the routine DCD_HandleOutEP_ISR()

Then we get: -- VCP_DataRx 1 -- from usbF4/vcp/usbd_cdc_vcp.c in the routine VCP_DataRx(). Our single byte of data has been passed up to the higher VCP layer to be handled. It goes into a receive buffer (unless a read_hook exists to claim it). If the buffer is full, reception on the endpoint will be disabled.

Last of all, we get: -- - EP 1 StartXfer 64 bytes -- from usbF4/driver/usb_core.c in the routine USB_OTG_EPStartXfer() This looks like it is preparing the endpoint to receive more data?

In more detail

So, we get a rxstsqlvl interrupt. This goes to the ISR handler USBD_OTG_ISR_Handler() and then gets dispatched to DCD_HandleRxStatusQueueLevel_ISR() in the same file. This examines the status (from the top of the FIFO) and sees (probably) STS_DATA_UPDT and then calls USB_OTG_ReadPacket() in usb_core.c. This is a simple routine and just copies data from the fifo into the buffer given in the call.

Now I don't understand why, but we get a second level interrupt.

Now we get yet another interrupt, but not a level interupt this time. This one is "outepintr" and gets handled by DCD_HandleOutEP_ISR(). The situation is "transfer complete" and the path to VCP_DataRx() is far from simple.

What about that call to usb_read()

I coded up usb_read() in usb.c to simply be a call to: VCPGetBytes ( buf, len ), but noted that it never blocks and will return 0 if there is no data. The variable checked is "rx_unread" which is UsbRecWrite - usbRxRead.

What about usb_read_hook? This is something I added so that Hydra can ask for an upcall when data is received. It can be set via "usb_hookup ( func )". I set a hook, and when I type an "x" I now see this:

- EP 1 StartXfer 64 bytes
USBint - Rx level
Endpoint ??, read packet 1 bytes from FIFO
USBint - Rx level
USBint - OUT Endpoint
USBint = OUT Endpoint 1 xfer complete
VCP_DataRx 1
Hydra USB got: 1
x

Note the message: "Endpoint ??, read packet 1 bytes from FIFO". I don't think this read is for any specific endpoint (and it must be for endpoint 1 in this case). I'll need to study the manual more, but I think there is only one Rx FIFO for all endpoints.

Conclusion

We can both read and write to the VCP device from the F411 using Hydra.

We can also use the stm32duino USB driver to learn things. However we need a more in depth understanding both of the F411 USB hardware and the structure of the stm32duino USB driver to get much out of this.


Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org