Skip to main content
Associate
May 17, 2026
Question

STM32H750: unexpected behaviour when trying to send multi-packet messages over USB

  • May 17, 2026
  • 1 reply
  • 104 views

Hi,

During the development of a bare-metal driver for USB CDC (virtual com port) in device mode i noticed an unexpected behaviour when trying to configure the USB Core to do a transmission of a multi packet message.

I've got the following code which prepares the transmission over usb for a data array "data" whose size is "dlen" over endpoint number "epNr". "epInBuffers" contain the pointers to the data buffers for each endpoint, "epInBytesTransferred" keep track of how many bytes have already been transmitted and "epInDataCntrs" the total amount of bytes to transmit.

__RAMFUNC
void prepareUSBTransfer(uint8_t epNr,const uint8_t*data,uint16_t dlen)
{
 USB_OTG_INEndpointTypeDef * inEndpoint = ((USB_OTG_INEndpointTypeDef*)(USB2_OTG_FS_PERIPH_BASE + USB_OTG_IN_ENDPOINT_BASE + epNr*0x20));
 uint32_t epCtrl = inEndpoint->DIEPCTL;
 uint16_t npackets;

 if(dlen==0)
 {
 npackets = 1;
 USB2_OTG_FS_DEVICE->DIEPEMPMSK &= ~(1UL << epNr);
 }
 else
 {
 npackets = ((dlen + epInMaxPacketSizes[epNr] - 1) / epInMaxPacketSizes[epNr]);
 USB2_OTG_FS_DEVICE->DIEPEMPMSK |= (1UL << epNr);
 }
 
 epInBuffers[epNr] = (uint8_t*)data;
 epInBytesTransferred[epNr]=0;
 epInDataCntrs[epNr] = dlen;

 inEndpoint->DIEPTSIZ = (npackets << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | dlen;

 epCtrl &= ~(1 << USB_OTG_DIEPCTL_STALL_Pos);
 epCtrl |= (1 << USB_OTG_DIEPCTL_EPENA_Pos) | (1 << USB_OTG_DIEPCTL_CNAK_Pos);
 inEndpoint->DIEPCTL = epCtrl;

}

 The function essentially computes of how many packages the message consist and stores this in inEndpoint->DIEPTSIZ. Before that the interrupt in empty transmit buffer is unmasked, at the end the endpoint is enabled.

Then the transmit fifo buffer empty is fired, the code block below executes. The driver make sure that a maximum the max packet size is fed into the fifo, although the fifo is configured to allow more, then the amount of word to put into the fifo is calculated, the the words are transferred into the fifo.

 #ifdef USB_DBG
 sendStringBlocking("USB TX Fifo empty\r\n");
 #endif
 upperLimit=0;
 if ((epInDataCntrs[epNr] - epInBytesTransferred[epNr]) > (epInMaxPacketSizes[epNr]))
 {
 upperLimit = epInMaxPacketSizes[epNr];
 }
 else
 {
 upperLimit = epInDataCntrs[epNr] - epInBytesTransferred[epNr];
 }
 uint16_t wordCount = (upperLimit + 3)/4;
 uint16_t c=0;
 while (c< wordCount && ((inEndpoint->DTXFSTS& 0xFFFF)!=0))
 {
 *((volatile uint32_t *)(USB_OTG_FS_PERIPH_BASE + USB_OTG_FIFO_BASE + epNr * USB_OTG_FIFO_SIZE)) = (((const struct T_UINT32_READ *)(const void *)(epInBuffers[epNr] + epInBytesTransferred[epNr]))->v);
 epInBytesTransferred[epNr]+=4;
 c++;
 } 
 if (epInBytesTransferred[epNr] >= epInDataCntrs[epNr])
 {
 USB2_OTG_FS_DEVICE->DIEPEMPMSK &= ~(1 << epNr);
 }
 inEndpoint->DIEPINT = (1 << USB_OTG_DIEPINT_TXFE_Pos);

 The unexpected behavior happens at line 18. With a maximum packet size of 64 bytes and a message containing  140 bytes i expected the following to happen (according from what i understood from the reference manual):

Upon the copying 64 bytes (16 words)) into the transmit buffer the size in DIEPTSIZ decreases by 64 and the number of packets by one. What really happens is that the size in DIEPTSIZ is set to zero and the packet number is decreased by one. 

When the transmit fifo buffer interrupt is fired a second time no data in transmitted. Also the tranmission complete interrupt isn't called resulting in the device not recognizing the end of transfer. The host sees a single packet of maximum packet size, therefore expecting more (zero- or not zero length packages) to arrive.

When transmitting less than the maximum packet size the packet count is decreased by one and the size decreased to zero at the expected moment (when transmitting 20 bytes when c is 4). 

What did is miss here?

Of course the driver work fine when splitting the message into maximum packet size chunks beforehand.

1 reply

Technical Moderator
May 20, 2026

Hello @Stonerose35 
I compared your implementation to the ST classic middleware implementation.
With the information provided, I still cannot determine a precise root cause.
However, the main difference I found, which can cause your issue, is that you enable TXFE before making the USB endpoint ready.

f(dlen==0)
 {
 npackets = 1;
 USB2_OTG_FS_DEVICE->DIEPEMPMSK &= ~(1UL << epNr);
 }
 else
 {
 npackets = ((dlen + epInMaxPacketSizes[epNr] - 1) / epInMaxPacketSizes[epNr]);
 USB2_OTG_FS_DEVICE->DIEPEMPMSK |= (1UL << epNr); // <-- TXFE enabled here
 }

then 

 epCtrl |= (1 << USB_OTG_DIEPCTL_EPENA_Pos) | (1 << USB_OTG_DIEPCTL_CNAK_Pos);

Enabling the Tx FIFO Empty interrupt too early can cause the ISR to run while the endpoint transfer is not fully armed. That can lead to premature FIFO feeding or inconsistent internal endpoint state:
can you try this order:

epInBuffers[epNr] = (uint8_t*)data;
epInBytesTransferred[epNr] = 0;
epInDataCntrs[epNr] = dlen;

inEndpoint->DIEPTSIZ = (npackets << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | dlen;

epCtrl &= ~(1 << USB_OTG_DIEPCTL_STALL_Pos);
epCtrl |= (1 << USB_OTG_DIEPCTL_EPENA_Pos) | (1 << USB_OTG_DIEPCTL_CNAK_Pos);
inEndpoint->DIEPCTL = epCtrl;

if (dlen != 0)
{
 USB2_OTG_FS_DEVICE->DIEPEMPMSK |= (1UL << epNr);
}
else
{
 USB2_OTG_FS_DEVICE->DIEPEMPMSK &= ~(1UL << epNr);
}

 BR
Gyessine

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.
Associate
May 22, 2026

Hi @Gyessine 

 

Thanks for your detailed explanation, that might be it! I'll try it out as soon as i have time.