UAC2 I2S input on STM32F723E-DISCO

Member
Joined 2005
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: 208
  • STM32F723E-DISCO-UAC2.zip
    1.2 MB · Views: 253
  • Like
Reactions: 2 users
Member
Joined 2005
The FW uses STM32CubeProgrammer's SWV (Serial Wire Viewer) for debug output. So printf goes to SWV. System clock in SWV should be set to 216 MHz. Existing FW debug output can be switched on in main.c:
bool _debug_output = true;
 
  • Like
Reactions: 1 user
Member
Joined 2022
@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.
 
Member
Joined 2019
That’s a IDE issue, but i had that issue and the got it compiling and working.
There was an issue with the version if you attempt to migrate the cube file file too.
 
Member
Joined 2005
Yes, it appears I made a mistake when porting my FW to STM32F723E-DISCO. My original FW has the correct usbd_core.c implementation.

Here is the correct usbd_core.c implementation.
 

Attachments

  • usbd_core.zip
    3 KB · Views: 164
Last edited:
Member
Joined 2005
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.
 
Member
Joined 2019
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).
 
Member
Joined 2022
Thank you so much for sharing your code!

I've been pulling my hair out trying to get UAC2 working on F723E-DISCO.

It'll be of great help for my DIY DAC design
 
Member
Joined 2022
@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.
 
Member
Joined 2005
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.
 
Member
Joined 2018
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.
 
Member
Joined 2005
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!
 
Top