UAC2 I2S input on STM32F723E-DISCO

Member
Joined 2005
Paid Member
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: 348
  • STM32F723E-DISCO-UAC2.zip
    STM32F723E-DISCO-UAC2.zip
    1.2 MB · Views: 412
@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.
 
I dont think this is related to @bohrok2610’s code but has anyone got it working via virtualbox? It seems it works on mac, but if I try to move the usb device to the virtualbox VM it fails to connect it (it doesn’t get to ubuntu).
 
@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.
 
Hello. I've lurked here for a while. Have you considered posting your work with i2s input and output on GitHub? It'll be great if everyone can use that instead of relying on "Tinyusb" for audio, a git some have had trouble with in the past.
 
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.
 
I'm sorry but I can't find information about the github repo you referenced. I am not very familiar with your forums, please excuse me. I am developing a uac2 implementation for the STM32 and would like to collaborate with others that are working in that area. Can you refer me to someone who is working on that repo? Thanks!
 
@bohrok2610

Hello (& happy new year everyone),

Thank you very much for your work.

I'm running your raw code.

I get an FFT of the same type as NickKUK above, with lots of THD (my source is a DSP with an output THD of 5 digits behind the decimal point).

I tried some changes suggested by EvSap above but no better result.

I may have missed something.

Can you provide a final code to obtain a nice FFT like the one in your initial post?

Thanks in advance.
 
Hello,

@bohrok2610

I finaly make your code working fine. The bad FFT was due to hardware issue.

On my STF32F723ZET6 custom board, I had to add resistors in series with the I2S signals :

1KΩ on BCK and FS
10KΩ on SDATA

With theses resistors the FFT is perfect from 44,1 to 96K (the range I want to use). On 176,4 and 192K it seem not working fine (perhaps I need to find good resistors value for 192K and try after lower frequencies)

I already had in past this kind of issue using I2S_CKIN input with a clock generator. With some STM32 processors it's possible to connect directly the clock to the pin, with others i need to add a 1KΩ serie resistor else USB crash.