2024-10-23 04:48 AM
Hi there,
I am trying to implement 3 CDC interfaces over USB. I'm using the USB stack generated by CubeMX, I enabled composite devices and call 3 times "USBD_RegisterClassComposite" in my "MX_USB_Device_Init".
I am having troubles with endpoint numbering as if I choose the simple sequence:
CDC1: 0x81/0x01 + CMD1: 0x82
CDC2: 0x83/0x03 + CMD2: 0x84
CDC3: 0x85/0x05 + CMD3: 0x86
Then CDC 1 and 3 work as expected but I get problems on CDC 2. Testing a bunch of numbers for the endpoints there seams to be a lot of limitations but one sequence that works for my applications is:
CDC1: 0x81/0x01 + CMD1: 0x82
CDC2: 0x87/0x07 + CMD2: 0x84
CDC3: 0x85/0x05 + CMD3: 0x86
For some reason, this configuration works. This fixes my problem but I would like to know why. Many combo of endpoints do not work but the number 3 seams to be especially cursed in my tests. Is there a reason for that? I cannot find anything in the doc that would relate to limitations or logics to follow when it comes to endpoint numbering.
Thanks a lot
Have a nice day
PS: The PMA memory in my application is allocated as follow in all my testing (this is part of "USBD_LL_Init"):
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x18);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x58);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN1_EP , PCD_SNG_BUF, 0x100);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT1_EP , PCD_SNG_BUF, 0x140);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_CMD1_EP , PCD_SNG_BUF, 0x180);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN2_EP , PCD_SNG_BUF, 0x200);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT2_EP , PCD_SNG_BUF, 0x240);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_CMD2_EP , PCD_SNG_BUF, 0x280);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN3_EP , PCD_SNG_BUF, 0x300);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT3_EP , PCD_SNG_BUF, 0x340);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_CMD3_EP , PCD_SNG_BUF, 0x380);
Solved! Go to Solution.
2024-10-23 06:26 AM
All the endpoint pairs are the same, so maybe there is a problem with config descriptor. Other thoughts:
-Since you use almost all the endpoints, the starting address of EP0 buffer should be 0x40, not 0x18. This might be the problem - control EP0 transfers destroy buffer descriptors for other endpoints!!!
- The interrupt endpoints are not used at all and if they were used, they would be used for status information. No idea why anyone would name them "CMD".
- If they were used, their size should be 10 bytes, so it's enough to reserve 16 bytes for them instead of 128.
2024-10-23 06:26 AM
All the endpoint pairs are the same, so maybe there is a problem with config descriptor. Other thoughts:
-Since you use almost all the endpoints, the starting address of EP0 buffer should be 0x40, not 0x18. This might be the problem - control EP0 transfers destroy buffer descriptors for other endpoints!!!
- The interrupt endpoints are not used at all and if they were used, they would be used for status information. No idea why anyone would name them "CMD".
- If they were used, their size should be 10 bytes, so it's enough to reserve 16 bytes for them instead of 128.
2024-10-23 08:49 AM
Haaaa! Thanks a lot @gbm , you are right. Moving the memory address of EP0 did solve my problem.
Interestingly, this address of 0x18 is the one generated by CubeMx itself.
For anyone playing at home, Figure 402 of RM0444 should clarify the memory mapping and why configuring the PMA of EP0 at address 0x18 will overwrite what is configured for the endpoint 3.
2024-10-24 06:18 AM - edited 2024-10-24 09:14 AM
Thank you for reporting the issue. It seems a common issue in CubeMX code generation! Internal ticket number 194991
You can find also this step by step guide How to select suitable endpoints for your STM32 US... - STMicroelectronics Community
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2024-10-24 08:11 AM
The real issue is the use of hardcoded values in HAL and associated code.
The hardcoded values in USB hardware init routines must match other hardcoded values in USB descriptors and yet another ones used in endpoint-related stuff.
In my opinion, a better solution is to read endpoint sizes from the configuration descriptor. This reduces the possibility of mistakes in USB code.
2024-10-25 02:50 AM
I appreciate your feedback @gbm
New ticket is submitted as change request to dedicated team. How about this proposal to set PMA offset properly?
// Function to read endpoint sizes from the configuration descriptor
void ReadEndpointSizesFromDescriptor(USB_ConfigDescriptorTypeDef *configDesc) {
for (int i = 0; i < configDesc->bNumEndpoints; i++) {
USB_EndpointDescriptorTypeDef *epDesc = &configDesc->endpoint[i];
uint16_t epSize = epDesc->wMaxPacketSize;
// Configure the PMA based on the endpoint size
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData, epDesc->bEndpointAddress, PCD_SNG_BUF, CalculatePMAOffset(epSize));
}
}
// Example calculation of PMA offset based on endpoint size
uint16_t CalculatePMAOffset(uint16_t epSize) {
// Implement logic to calculate PMA offset based on endpoint size
// This is a placeholder example
return epSize * 2;
}
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2024-10-25 12:35 PM
Getting the EP descriptor from device's config descriptor is slightly more complex than shown in the above code. ;) (And will be even more complex when HS support is added, cause there will be two config descriptors to choose from.)
After you get the EP descriptor, the EP size read from EP descriptor must be rounded up to the multiple of 4 (so that's compatible with all the USB peripherals found in various STM32s). Then PMA offset should be calculated based on the adjusted EP size in the loop setting all the PMA buffer descriptors. The relevant code fragment from my USB stack is shown below (fragment of usb_hw_g0.c file). Note that in order to make the code easier to read & write, I used my own declaration of structure representing the USB peripheral - registers and PMA as single entity, with unions/structures representing the EP descriptors in PMA.
// convert endpoint size to endpoint buffer size
static inline uint16_t epbufsize(uint16_t s)
{
return (s + 3) & ~3;
}
// setup and enable app endpoints on set configuration request
static void USBhw_SetCfg(const struct usbdevice_ *usbd)
{
USBh_TypeDef *usb = (USBh_TypeDef *)usbd->usb;
USBreg *epr = usb->EPR;
const struct usbdcfg_ *cfg = usbd->cfg;
uint16_t addr = 0x40 /*cfg->numeppairs * 8*/ + cfg->devdesc->bMaxPacketSize0 * 2;
// enable app endpoints
struct USB_BufDesc_ *bufdesc = usb->BUFDESC;
for (uint8_t i = 1; i < cfg->numeppairs; i++)
{
bufdesc[i].TxAddressCount.v = addr;
const struct USBdesc_ep_ *ind = USBdev_GetEPDescriptor(usbd, i | EP_IS_IN);
uint16_t txsize = ind ? epbufsize(getusb16(&ind->wMaxPacketSize)) : 0;
addr += txsize;
const struct USBdesc_ep_ *outd = USBdev_GetEPDescriptor(usbd, i);
uint16_t rxsize = outd ? epbufsize(getusb16(&outd->wMaxPacketSize)) : 0;
// do not remove .v from the line below!
bufdesc[i].RxAddressCount.v = (union USB_BDesc_){.num_block = SetRxNumBlock(rxsize), .addr = addr, .count = CNT_INVALID}.v;
addr += rxsize;
epr[i] = i | USB_EPR_EPTYPE((ind ? ind->bmAttributes : 0) | (outd ? outd->bmAttributes : 0));
uint32_t epstate = (rxsize && usbd->outep[i].ptr ? USB_EPR_STATRX(USB_EPSTATE_VALID) : USB_EPR_STATRX(USB_EPSTATE_NAK))
| USB_EPR_STATTX(USB_EPSTATE_NAK);
SetEPRState(usbd, i, USB_EP_RX_STRX | USB_EP_TX_STTX | USB_EP_DTOG_TX | USB_EP_DTOG_RX, epstate);
}
}
The complete code is available at the link in my signature.