UAC2 I2S input on STM32F723E-DISCO

I made a simplified version of my STM32F723 firmware for STM32F723E-DISCO board with just I2S input (no I2S output). Should work at least at 192k/32.

It uses this board:
https://www.mouser.fi/ProductDetail/STMicroelectronics/STM32F723E-DISCO?qs=DXv0QSHKF4wBapbNjrzotQ==

Attached is the source code. It runs on FreeRTOS although this version does not really utilize it.
I used STM32F723E-DISCO board's Arduino connectors CN11 and CN13 for all I/O.
I2S signals are at CN11.1 (FS), CN13.4 (BCK) and CN13.7 (SD).
I2C2 is available at CN11.10 (SCL) and CN11.9 (SDA).
4 GPIO outputs at CN13.3, CN13.5, CN13.6 and CN13.8.
GND at CN11.7.

The source code also includes a "driver" for my AK5394 board which uses all 4 GPIO outputs. This can be used as an example of how to connect to ADC board.

STM32F723E-DISCO board is of course not optimal for running I2S as it is meant to be a showcase of various MCU capabilities. For best results a custom board dedicated to I2S should be used.

Have fun!
 

Attachments

  • STM32F723E-DISCO-176k4.PNG
    STM32F723E-DISCO-176k4.PNG
    50.2 KB · Views: 197
  • STM32F723E-DISCO-UAC2.zip
    1.2 MB · Views: 242
  • Like
Reactions: 2 users
@bohrok2610 thanks for sharing!
Have looked through the code. Are you sure the code of "USBD_AUDIO_IsoINIncomplete()" and "USBD_AUDIO_IsoOutIncomplete" in "usbd_audio.c" is included in the firmware and reachable?
uint8_t USBD_AUDIO_IsoINIncomplete (USBD_HandleTypeDef *pdev, uint8_t epnum)
{
// Check all IN-endpoints
for (int i = 1; i <= USBD_AUDIO_MAX_IN_EP; i++) {
if (USB_DIEPCTL(i) & USB_OTG_DIEPCTL_EPENA) {
if (epnum == (AUDIO_IN_EP & EP_ADDR_MSK)) {
// Flip parity if not flipped already
USBD_AUDIO_HandleTypeDef *haudio;
haudio = &((USBD_AUDIO_DeviceTypeDef*)pdev->pClassData)->in;
if (haudio->parity != PARITY_FLIP) {
if (_debug_output) {
printf("diepctl2: %d %08lx %ld\r\n", i, USB_DIEPCTL(epnum), HAL_GetTick());
}
FLIP_PARITY(epnum);
USB_CLEAR_INCOMPLETE_IN_EP(epnum);
USBD_LL_FlushEP(pdev, AUDIO_IN_EP);
USBD_LL_Transmit(pdev, AUDIO_IN_EP, inEmptyBuffer, AUDIO_IN_PACKET_SIZE(haudio->fs, haudio->bytes_per_sample));
haudio->parity = PARITY_FLIP;
}
}
}
}
return (uint8_t)USBD_OK;
}
//---------------------------------------------------------------------------
uint8_t USBD_AUDIO_IsoOutIncomplete(USBD_HandleTypeDef *pdev, uint8_t epnum)
{
if (epnum != 0) {
if (_debug_output) {
printf("IsoOut: %d %ld\r\n", epnum, HAL_GetTick());
}
}
UNUSED(pdev);
UNUSED(epnum);
return (uint8_t)USBD_OK;
}
I believe it's not. Explanation:
1) When InComplete interrupts rise, corresponding functions in "usbd_conf.c" are called from USB ISR "HAL_PCD_IRQHandler()".
#if (USE_HAL_PCD_REGISTER_CALLBACKS == 1U)
static void PCD_ISOOUTIncompleteCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
#else
void HAL_PCD_ISOOUTIncompleteCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
#endif /* USE_HAL_PCD_REGISTER_CALLBACKS */
{
USBD_LL_IsoOUTIncomplete((USBD_HandleTypeDef*)hpcd->pData, epnum);
}
//---------------------------------------------------------------------------
#if (USE_HAL_PCD_REGISTER_CALLBACKS == 1U)
static void PCD_ISOINIncompleteCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
#else
void HAL_PCD_ISOINIncompleteCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
#endif /* USE_HAL_PCD_REGISTER_CALLBACKS */
{
USBD_LL_IsoINIncomplete((USBD_HandleTypeDef*)hpcd->pData, epnum);
}
2) Then from incomplete functions (mentioned in spoiler above) in "usbd_conf.c" functions "USBD_LL_IsoOUTIncomplete" and "USBD_LL_IsoINIncomplete" are called which are located in "usbd_core.c" file of ST USB device library.
3) And from these functions in "usbd_core.c" corresponding InComplete functions of implemented USB class should be called. But these calls are not implemented in "usbd_core.c" file of ST USB device library.
USBD_StatusTypeDef USBD_LL_IsoINIncomplete(USBD_HandleTypeDef *pdev,
uint8_t epnum)
{
/* Prevent unused arguments compilation warning */
UNUSED(pdev);
UNUSED(epnum);

return USBD_OK;
}
//------------------------------------------------------------------------------
USBD_StatusTypeDef USBD_LL_IsoOUTIncomplete(USBD_HandleTypeDef *pdev,
uint8_t epnum)
{
/* Prevent unused arguments compilation warning */
UNUSED(pdev);
UNUSED(epnum);

return USBD_OK;
}
One can compare that functions with for example "USBD_LL_SOF", which is correct.
USBD_StatusTypeDef USBD_LL_SOF(USBD_HandleTypeDef *pdev)
{
if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
if (pdev->pClass->SOF != NULL)
{
pdev->pClass->SOF(pdev);
}
}

return USBD_OK;
}
So InComplete functions in "usbd_core.c" file should be connected to USB class implementation like SOF function in "USBD_LL_SOF".
USBD_StatusTypeDef USBD_LL_IsoINIncomplete(USBD_HandleTypeDef *pdev,
uint8_t epnum)
{
/* Prevent unused arguments compilation warning */
//UNUSED(pdev);
//UNUSED(epnum);

if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
if (pdev->pClass->IsoINIncomplete != NULL)
{
pdev->pClass->IsoINIncomplete(pdev, epnum);
}
}

return USBD_OK;
}
//------------------------------------------------------------------------------
USBD_StatusTypeDef USBD_LL_IsoOUTIncomplete(USBD_HandleTypeDef *pdev,
uint8_t epnum)
{
/* Prevent unused arguments compilation warning */
//UNUSED(pdev);
//UNUSED(epnum);

if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
if (pdev->pClass->IsoOUTIncomplete != NULL)
{
pdev->pClass->IsoOUTIncomplete(pdev, epnum);
}
}

return USBD_OK;
}
After these corrections made functions "USBD_AUDIO_IsoINIncomplete" and "USBD_AUDIO_IsoOutIncomplete" in "usbd_audio.c" are called.

I believe that's why your UAC2 log reports messages like "feedback packet 1 has invalid packet length 0, ignoring packet" you mentioned in that thread https://www.diyaudio.com/community/threads/uac2-0-on-stm32.393081/post-7208377, because InComplete inerrupts are not serviced. I faced with the very same situation with "usbd_core.c". With corrected file InComplete interrupts are called and UAC2 log reports no messages mentioned above.
 
I believe that's why your UAC2 log reports messages like "feedback packet 1 has invalid packet length 0, ignoring packet" you mentioned in that thread https://www.diyaudio.com/community/threads/uac2-0-on-stm32.393081/post-7208377, because InComplete inerrupts are not serviced. I faced with the very same situation with "usbd_core.c". With corrected file InComplete interrupts are called and UAC2 log reports no messages mentioned above.
The reason for "feedback packet 1 has invalid packet length 0, ignoring packet" is simply the processing of incomplete interrupts as I mentioned before. I'm running the FW with feedback at 1 microframe intervals.
 
@bohrok2610 , while working with stm32 MCUs, in ISO Incomplete ISR like
C:
uint8_t  USBD_AUDIO_IsoINIncomplete (USBD_HandleTypeDef *pdev, uint8_t epnum)
{
    // Check all IN-endpoints
    for (int i = 1; i <= USBD_AUDIO_MAX_IN_EP; i++) {
        if (USB_DIEPCTL(i) & USB_OTG_DIEPCTL_EPENA) {
            if (epnum == (AUDIO_IN_EP & EP_ADDR_MSK)) {
                // Flip parity if not flipped already
                   USBD_AUDIO_HandleTypeDef *haudio;
                haudio = &((USBD_AUDIO_DeviceTypeDef*)pdev->pClassData)->in;
                if (haudio->parity != PARITY_FLIP) {
                    if (_debug_output) {
                        printf("diepctl2: %d %08lx %ld\r\n", i, USB_DIEPCTL(epnum), HAL_GetTick());
                    }
                    FLIP_PARITY(epnum);
                    USB_CLEAR_INCOMPLETE_IN_EP(epnum);
                    USBD_LL_FlushEP(pdev, AUDIO_IN_EP);
                    USBD_LL_Transmit(pdev, AUDIO_IN_EP, inEmptyBuffer, AUDIO_IN_PACKET_SIZE(haudio->fs, haudio->bytes_per_sample));
                    haudio->parity = PARITY_FLIP;
                }
            }
        }
    }
    return (uint8_t)USBD_OK;
}
checking endpoint number that caused interrupt like "if (epnum == (AUDIO_IN_EP & EP_ADDR_MSK))" is useless and even harmfull, because stm32 hardware and stm32 USB library itself do not allow to determine endpoint number which is the interrupt source. stm32 USB library always transfers epnum = 0. The code under epnum check is unreachable.
 
Good catch! Apparently another porting error.

The above code should be:
Code:
...
            if (i == (AUDIO_IN_EP & EP_ADDR_MSK)) {
                // Flip parity if not flipped already
                   USBD_AUDIO_HandleTypeDef *haudio;
                haudio = &((USBD_AUDIO_DeviceTypeDef*)pdev->pClassData)->in;
                if (haudio->parity != PARITY_FLIP) {
                    if (_debug_output) {
                        printf("diepctl2: %d %08lx %ld\r\n", i, USB_DIEPCTL(i), HAL_GetTick());
                    }
                    FLIP_PARITY(i);
                    USB_CLEAR_INCOMPLETE_IN_EP(i);
                    USBD_LL_FlushEP(pdev, AUDIO_IN_EP);
                    USBD_LL_Transmit(pdev, AUDIO_IN_EP, inEmptyBuffer, AUDIO_IN_PACKET_SIZE(haudio->fs, haudio->bytes_per_sample));
                    haudio->parity = PARITY_FLIP;
                }
            }
...
STM32 HAL driver sets epnum to 0 on purpose to force interrupt handler to go through all endpoints.
 
This type of SW is not generic as it works with a particular board so the code in this thread works with STM32F723E-DISCO board. I have more comprehensive SW for UAC2 on STM32 MCUs but those versions are only for my own STM32F7/H7 boards.

There is an alternative on this site for I2S output on STM32F446 with board gerbers included (see here). It has a slightly different approach to mine so I'm not sure how well it works.