USB data toggling with DMA
Hey folks,
I've seen many posts from frustrated users of USB OTG HAL, especially when enabling DMA. I created a fix for data toggle errors (DTERR) when using DMA with USB_OTG_HS HCD paired with full speed embedded PHY on a STM32F4xx. Hopefully this fixes more than just F4xx.
The latest stm32f4xx_hal_hcd.c generated by CubeMX fixes a great deal from the previous HAL code, but still has a bug that causes data toggle errors when using DMA.
The interrupt service routine HCD_HC_IN_IRQHandler() handles XFRC interrupts with the following toggling logic:
if (hhcd->Init.dma_enable == 1U)
{
if ((((hhcd->hc[chnum].xfer_count + hhcd->hc[chnum].max_packet - 1U) / hhcd->hc[chnum].max_packet) & 1U) != 0U)
{
hhcd->hc[chnum].toggle_in ^= 1U;
}
}
else
{
hhcd->hc[chnum].toggle_in ^= 1U;
}
Changing it to this fixed my woes:
if (hhcd->Init.dma_enable == 1U)
{
// Toggle if packet count is odd or xfer_count is an even multiple of max_packet
uint32_t pkt_count = (hhcd->hc[chnum].xfer_count + hhcd->hc[chnum].max_packet - 1U) / hhcd->hc[chnum].max_packet;
if (((pkt_count & 1U) != 0U) || ((pkt_count * hhcd->hc[chnum].max_packet) == hhcd->hc[chnum].xfer_count))
{
hhcd->hc[chnum].toggle_in ^= 1U;
}
}
else
{
hhcd->hc[chnum].toggle_in ^= 1U;
}
Essentially, ST got it half correct. With DMA, multiple packets occur within a single XFRC so you have to do extra work to figure out the data toggling after a receive completes. You should still toggle if an odd number of packets were received, but you should also toggle if the total received bytes is an even multiple of the endpoint's max packet size (64 in my case). This is true for all endpoint types, not just BULK and INTR.
Not only does this fix the data toggle error interrupts (which show up as URB_NOTREADY), but it also fixes having to deal with zero-length packets (ZLP's) in the URB ISR callback.
Hope this helps someone.
Cheers
