March 3, 2025

Black Pill boards - F411 USB -- more callbacks!

So, we have talked about the two callback structures that get passed to USBD_Init(), namely the cdc callbacks and the vcp callbacks. We learned that the vcp callbacks do essentially nothing. The cdc callbacks are very important.

Consider, before we move on, that we could have presented some different pair of callback structures (along with code to go with them). Such a change would be the way we could implement a different USB class, such as an HID or mass storage device. Being able to do this is exactly why things are done this way. Often such a scheme is called "registering a driver" and is important to know about.

Yet more callbacks

Whoever wrote this code liked this way of doing things. I have disovered several other structures full of callbacks while studying the code.

The VCP_fops structure

This is set up in vcp/usbd_cdc_vcp.c as follows:
CDC_IF_Prop_TypeDef VCP_fops =
{
  VCP_Init,
  VCP_DeInit,
  VCP_Ctrl,
  VCP_DataTx,
  VCP_DataRx
};
The structure (and corresponding typedef) are set up in library/usbd_cdc_core.h as follows:
typedef struct _CDC_IF_PROP
{
  uint16_t (*pIf_Init)     (void *pdev);
  uint16_t (*pIf_DeInit)   (void);
  uint16_t (*pIf_Ctrl)     (uint32_t Cmd, uint8_t* Buf, uint32_t Len);
  uint32_t (*pIf_DataTx)   (const uint8_t* Buf, uint32_t Len);
  uint16_t (*pIf_DataRx)   (uint8_t* Buf, uint32_t Len);
}
CDC_IF_Prop_TypeDef;

extern USBD_Class_cb_TypeDef  USBD_CDC_cb;
This thing is referenced via the following alias (which is in vcp/usbd_conf.h
#define APP_FOPS                        VCP_fops
This is referenced only in library/usbd_cdc_core.c, as follows:
./library/usbd_cdc_core.c: #include 
./library/usbd_cdc_core.c:extern CDC_IF_Prop_TypeDef  APP_FOPS;
./library/usbd_cdc_core.c:  APP_FOPS.pIf_Init(pdev);
./library/usbd_cdc_core.c:  APP_FOPS.pIf_DeInit();
./library/usbd_cdc_core.c:          APP_FOPS.pIf_Ctrl(req->bRequest, CmdBuff, req->wLength);
./library/usbd_cdc_core.c:        APP_FOPS.pIf_Ctrl(req->bRequest, (uint8_t*)&req->wValue, sizeof(req->wValue));
./library/usbd_cdc_core.c:    APP_FOPS.pIf_Ctrl(cdcCmd, CmdBuff, cdcLen);
./library/usbd_cdc_core.c:  if ( APP_FOPS.pIf_DataRx(USB_Rx_Buffer, USB_Rx_Cnt)==USBD_OK )
Note the include that pulls in usb_conf.h from the vcp directory. This is what makes the connection between the generic CDC code in usbd_cdc_core.c and the VCP specific code. The connection between CDC_ACM and the VCP code is "wired in" by this arrangement, yet localized in such a way that changes could be easily made.

The USBD_DCD_INT_fops structure

This one is tucked away in library/usbd_core.h. Here we have a bunch of callbacks that are not CDC or VCP specific. These are generic USB operations.
USBD_DCD_INT_cb_TypeDef USBD_DCD_INT_cb =
{
  USBD_DataOutStage,
  USBD_DataInStage,
  USBD_SetupStage,
  USBD_SOF,
  USBD_Reset,
  USBD_Suspend,
  USBD_Resume,
  USBD_IsoINIncomplete,
  USBD_IsoOUTIncomplete,
#ifdef VBUS_SENSING_ENABLED
  USBD_DevConnected,
  USBD_DevDisconnected,
#endif
};

USBD_DCD_INT_cb_TypeDef  *USBD_DCD_INT_fops = &USBD_DCD_INT_cb;
The above loads values (specific functions) into the structure. The structure (and typedef) are defined in driver/usb_dcd_int.h
typedef struct _USBD_DCD_INT
{
  uint8_t (* DataOutStage) (USB_OTG_CORE_HANDLE *pdev , uint8_t epnum);
  uint8_t (* DataInStage)  (USB_OTG_CORE_HANDLE *pdev , uint8_t epnum);
  uint8_t (* SetupStage) (USB_OTG_CORE_HANDLE *pdev);
  uint8_t (* SOF) (USB_OTG_CORE_HANDLE *pdev);
  uint8_t (* Reset) (USB_OTG_CORE_HANDLE *pdev);
  uint8_t (* Suspend) (USB_OTG_CORE_HANDLE *pdev);
  uint8_t (* Resume) (USB_OTG_CORE_HANDLE *pdev);
  uint8_t (* IsoINIncomplete) (USB_OTG_CORE_HANDLE *pdev);
  uint8_t (* IsoOUTIncomplete) (USB_OTG_CORE_HANDLE *pdev);

  uint8_t (* DevConnected) (USB_OTG_CORE_HANDLE *pdev);
  uint8_t (* DevDisconnected) (USB_OTG_CORE_HANDLE *pdev);

}USBD_DCD_INT_cb_TypeDef;

extern USBD_DCD_INT_cb_TypeDef *USBD_DCD_INT_fops;
Note that the structure and typedef are defined in the driver layer. The driver needs to be able to call these to send data "up" to the general USB code in the "library" layer. Indeed when we search on USBD_DCD_INT_fops we find that all of the calls are in one file: driver/usb_dcd_int.c
./driver/usb_dcd_int.c:--    USBD_DCD_INT_fops->DataOutStage(pdev , 1);
./driver/usb_dcd_int.c:--    USBD_DCD_INT_fops->DataInStage(pdev , 1);
./driver/usb_dcd_int.c:  USBD_DCD_INT_fops->DevConnected (pdev);
./driver/usb_dcd_int.c:    USBD_DCD_INT_fops->DevDisconnected (pdev);
./driver/usb_dcd_int.c:  USBD_DCD_INT_fops->Resume (pdev);
./driver/usb_dcd_int.c:  USBD_DCD_INT_fops->Suspend (pdev);      
./driver/usb_dcd_int.c:        USBD_DCD_INT_fops->DataInStage(pdev , epnum);
./driver/usb_dcd_int.c:        USBD_DCD_INT_fops->DataOutStage(pdev , epnum);
./driver/usb_dcd_int.c:        USBD_DCD_INT_fops->SetupStage(pdev);
./driver/usb_dcd_int.c:  USBD_DCD_INT_fops->SOF(pdev);
./driver/usb_dcd_int.c:  USBD_DCD_INT_fops->Reset(pdev);
./driver/usb_dcd_int.c:  USBD_DCD_INT_fops->IsoINIncomplete (pdev); 
./driver/usb_dcd_int.c:  USBD_DCD_INT_fops->IsoOUTIncomplete (pdev); 
All of this is in response to USB events that generate interrupts. The file usb_dcd_int.c has one entry point exposed to the outside world (for our hardware), namely:
STM32_USBF_OTG_ISR_Handler () --- Handles all USB Interrupts

Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org