March 3, 2025

Black Pill boards - F411 USB -- Enumeration part 2

A note up front. I try to resist the temptation to go back and augment or polish previous pages in this sequence. I know I have at least one reader who follows this daily, and I can hardly expect him to go reread everthing he has already looked at to see if I have added material

More about enumeration

We learned that the file vcp/usbd_desc.c collects a bunch of routines into a struct:
USBD_DEVICE USR_desc =
{
  USBD_USR_DeviceDescriptor,
  USBD_USR_LangIDStrDescriptor,
  USBD_USR_ManufacturerStrDescriptor,
  USBD_USR_ProductStrDescriptor,
  USBD_USR_SerialStrDescriptor,
  USBD_USR_ConfigStrDescriptor,
  USBD_USR_InterfaceStrDescriptor,
};
This gets passed to USBD_Init() when everything starts up, and this routine copies the pointer to this structure into the pdev structure:
pdev->dev.usr_device = pDevice;
We can search for "usr_device" to discover where these get used:
./library/usbd_req.c:    pbuf = pdev->dev.usr_device->GetDeviceDescriptor(pdev->cfg.speed, &len);
./library/usbd_req.c:     pbuf = pdev->dev.usr_device->GetLangIDStrDescriptor(pdev->cfg.speed, &len);        
./library/usbd_req.c:      pbuf = pdev->dev.usr_device->GetManufacturerStrDescriptor(pdev->cfg.speed, &len);
./library/usbd_req.c:      pbuf = pdev->dev.usr_device->GetProductStrDescriptor(pdev->cfg.speed, &len);
./library/usbd_req.c:      pbuf = pdev->dev.usr_device->GetSerialStrDescriptor(pdev->cfg.speed, &len);
./library/usbd_req.c:      pbuf = pdev->dev.usr_device->GetConfigurationStrDescriptor(pdev->cfg.speed, &len);
./library/usbd_req.c:      pbuf = pdev->dev.usr_device->GetInterfaceStrDescriptor(pdev->cfg.speed, &len);
Note that all these routines are defined in the VCP subsystem, but they are getting used in the general USB library. This is just the arrangement that would allow a different USB class to replace these with its own collection.

Within usbd_req.c these are all used in one routine -- USBD_GetDescriptor().
Let's find out why and from where this gets called.

It is called from USBD_StdDevReq() in the same file (which calls other things of great interest, which we will investigate later). The routine USBD_StdDevReq() is called by USBD_SetupStage() in the file usbd_core.c

Notice how helpful and explanatory these routine names are. We remember from another project that Setup packets are what are being exchanged during enumeration. I find myself itching to set aside the source code for a bit an revisit some of the USB basics, but we will stick with the source code for now.

USBD_SetupStage() is the third callback in USBD_DCD_INT_fops, so the actual call will use just "SetupStage". A search finds this:

./driver/usb_dcd_int.c:        USBD_DCD_INT_fops->SetupStage(pdev);
This is in the routine DCD_HandleOutEP_ISR(). What has happened is an interrupt has occured related to an OUT endpoint (we receive data on an OUT endpoint).
The code looks like this:
      /* Setup Phase Done (control EPs) */
      if ( doepint.b.setup ) {
    printf ( "USBint = OUT Endpoint %d setup done\n", epnum );

        /* inform the upper layer that a setup packet is available */
        /* SETUP COMPLETE */
        USBD_DCD_INT_fops->SetupStage(pdev);
        CLEAR_OUT_EP_INTR(epnum, setup);
      }
The printf was added by me. The comments tell the story. The comment say "a setup packet is available", so we naturally ask, "where is it". We need to roll back through the routines we have already examined to find out about that.

At some point we need to write a page or two about reading from OUT endpoints and writing to IN endpoints. At some point the question will arise about how many endpoints a VCP uses. It ought to just be one IN and one OUT on some endpoint other than 0, but we shall see.

At some point we should write a page just about the VCP and discuss whether we can identify an API for adding different classes.

At some point we should do the same for the driver, which we hope is the only place F411 specific code exists. How hard would it be to substitute code for the F103 and use the same vcp and library code?

But where does that setup packet get read?

I am skipping over quite a few details, but it happens like this:
  case STS_SETUP_UPDT:
    /* Copy the setup packet received in FIFO into the setup buffer in RAM */
    USB_OTG_ReadPacket(pdev , pdev->dev.setup_packet, 8);
    ep->xfer_count += status.b.bcnt;
There is an area set aside just for setup packets in the pdev structure (which itself is worth looking at sometime) and it apparently is big enough for any setup packet. The above code is in the routine DCD_HandleRxStatusQueueLevel_ISR() in driver/usb_dcd_int.c

An interrupt has happened and it is telling us that the entire setup packet has been received and is waiting for us.


Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org