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!
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
@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?
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()".
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.
One can compare that functions with for example "USBD_LL_SOF", which is correct.
So InComplete functions in "usbd_core.c" file should be connected to USB class implementation like SOF function in "USBD_LL_SOF".
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.
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;
}
{
// 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;
}
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);
}
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);
}
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;
}
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;
}
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;
}
{
if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
if (pdev->pClass->SOF != NULL)
{
pdev->pClass->SOF(pdev);
}
}
return USBD_OK;
}
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;
}
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;
}
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.
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.
There was an issue with the version if you attempt to migrate the cube file file too.
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.
Here is the correct usbd_core.c implementation.
Attachments
Last edited:
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 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 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).
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
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
@bohrok2610 , while working with stm32 MCUs, in ISO Incomplete ISR like
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.
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;
}
Good catch! Apparently another porting error.
The above code should be:
STM32 HAL driver sets epnum to 0 on purpose to force interrupt handler to go through all endpoints.
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;
}
}
...
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.
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 (& 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.
@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.
- Home
- Design & Build
- Equipment & Tools
- UAC2 I2S input on STM32F723E-DISCO