UAC2.0 on STM32

The best result I have is with the following feedback calculation. But background clicks still present, the sound is not pure. By means of debugger I can see Read pointer reaches Write pointer after 10200 - 10700 SAI transfers.
The feedback calculation seem reasonable now. I don't think the issue is with feedback but somehow your implementation is running a bit slow.

You should check that incoming / outgoing data rates are matching. E.g. cumulate incoming and outgoing sample count and output those every 1-2 seconds.
 
You should check that incoming / outgoing data rates are matching. E.g. cumulate incoming and outgoing sample count and output those every 1-2 seconds.
Yes, I observe incoming / outgoing data. At the moment when Read / Write pointers are equal the system state is the following one.
UAC2_DataPointers.jpg
Received packets count (AudioPack_cnt) = 10296
Outputted packets count (SAIPack_cnt) = 10210
Received data bytes (RecData) = 1991232
Read pointer = Write pointer = 25152
So as for Received/Outputted packets count, Received packets count is bigger than Outputted packets count, the difference is 10296 - 10210 = 86. Outputting starts when Received packets count is 80, so there is nothing strange with 86 Received packets difference.
SAI always outputs fixed number of bytes - 192 - per transfer. For 10210 transfers it outputs 192 * 10210 = 1960320 bytes. Received bytes - outputted bytes = 1991232 - 1960320 = 30912 bytes. Then minus 80 received packets before outputting starts: 30912 - 80 * 192 = 15552 bytes. So Write pointer is ahead of Read pointer on 15552 bytes or 15552 / 192 = 81 packets. Write pointer changes faster than Read pointer. That is why I think I have wrong feedback value estimation.

UPDATE: the avarage Received packet size is 1991232 / 10296 = 193,3986 bytes, that is PC reacts for feedback and changes packet size.
 
Last edited:
With Windows 10 host you should be able to run 192k several minutes (maybe hours) without any buffer overruns even without feedback (or with just using nominal rate as feedback). That is why I believe something else than feedback is causing the problem. You mentioned that you have external MCK. How are you setting the SAI clock?
 
How are you setting the SAI clock?
SAI clock settings for external sync
C:
EXT_SYNC_GPIO->AFR[1] |= (EXT_SYNC_AF << (4 * (EXT_SYNC_PIN - 8)));
EXT_SYNC_GPIO->MODER |= (2 << (2 * EXT_SYNC_PIN));
RCC->DCKCFGR1 |= RCC_DCKCFGR1_SAI1SEL_1;
Tried to clock SAI from onboard quartz, the result is almost the same.
C:
#define   SAI_MCLK_196_608MHz              ((2 << RCC_PLLSAICFGR_PLLSAIQ_Pos) | \
                                           (262 << RCC_PLLSAICFGR_PLLSAIN_Pos))

#define   SAI_MCLK_180_633MHz              ((2 << RCC_PLLSAICFGR_PLLSAIQ_Pos) | \
                                           (241 << RCC_PLLSAICFGR_PLLSAIN_Pos))

enum SAIQDividers
{
  SAIQDIV_1xFs  = 15,
  SAIQDIV_2xFs  = 7,
  SAIQDIV_4xFs  = 3,
  SAIQDIV_8xFs  = 1,
  SAIQDIV_16xFs = 0,
};

void SAI_PLLSAIConfig(uint32_t AudioFrequency)
{
  RCC->CR &= ~RCC_CR_PLLSAION;
  while((RCC->CR & RCC_CR_PLLSAIRDY) == RCC_CR_PLLSAIRDY);

  RCC->DCKCFGR1 &= ~RCC_DCKCFGR1_PLLSAIDIVQ;
 
  switch(AudioFrequency)
  {
    //48 x n
    default:
      RCC->PLLSAICFGR = SAI_MCLK_196_608MHz;
    break;
    
    //44,1 x n
    case USB_AUDIO_CONFIG_FREQ_44_1_K:
    case USB_AUDIO_CONFIG_FREQ_88_2_K:
    case USB_AUDIO_CONFIG_FREQ_176_4_K:
    case USB_AUDIO_CONFIG_FREQ_352_8_K:
    case USB_AUDIO_CONFIG_FREQ_705_6_K:
      RCC->PLLSAICFGR = SAI_MCLK_180_633MHz;
    break;
  }
 
  uint32_t SAIQDivValue = 0;
 
  switch(AudioFrequency)
  {
    //48, 44,1
    default:
      SAIQDivValue = SAIQDIV_1xFs;
    break;
    
    case USB_AUDIO_CONFIG_FREQ_88_2_K:
    case USB_AUDIO_CONFIG_FREQ_96_K:
      SAIQDivValue = SAIQDIV_2xFs;
    break;
    
    case USB_AUDIO_CONFIG_FREQ_176_4_K:
    case USB_AUDIO_CONFIG_FREQ_192_K:
      SAIQDivValue = SAIQDIV_4xFs;
    break;
    
    case USB_AUDIO_CONFIG_FREQ_352_8_K:
    case USB_AUDIO_CONFIG_FREQ_384_K:
      SAIQDivValue = SAIQDIV_8xFs;
    break;
    
    case USB_AUDIO_CONFIG_FREQ_705_6_K:
    case USB_AUDIO_CONFIG_FREQ_768_K:
      SAIQDivValue = SAIQDIV_16xFs;
    break;
  }
 
  RCC->DCKCFGR1 |= SAIQDivValue << RCC_DCKCFGR1_PLLSAIDIVQ_Pos;
      
  RCC->CR |= RCC_CR_PLLSAION;
  while((RCC->CR & RCC_CR_PLLSAIRDY) != RCC_CR_PLLSAIRDY);
}
In both cases output SAI signals - MCLK, BCLK, LR - are tested by means of an oscilloscope, their frequencies correspond to nominal ones.
In both cases system clock is 216 MHz with the following PLL settings
C:
//Onboard quartz is 24 MHz
RCC->PLLCFGR = (2 << 28) | RCC_PLLCFGR_PLLSRC | \
                 (9 << RCC_PLLCFGR_PLLQ_Pos) | \
                 (288 << RCC_PLLCFGR_PLLN_Pos) | \
                 (16 << RCC_PLLCFGR_PLLM_Pos);
 
With Windows 10 host you should be able to run 192k several minutes (maybe hours) without any buffer overruns even without feedback (or with just using nominal rate as feedback).
IMHO the time depends on USB-host controller clock vs. DAC clock vs. buffer size in the device. IMO a properly implemented UAC2 driver will always end up passing corresponding number of samples each microframe, on any OS.

When troubleshooting the ignored implicit feedback above, the device was RTX6001 analyzer with XMOS. At duplex analog loopback I was getting clicks every 10 seconds. The samplerate difference calculated from the captured packets statistics was 6.5Hz at 48kHz which is a lot. It suggests a 128-frames buffer in the XMOS, half of which would correspond to 6.5 sample every second per 10 seconds.

Another diyaudio user was kind to make the same test on his RTX + linux PC and got clicks every 50 seconds - his clocks deviated 5 times less. Still unusable for longer measurements.

After enabling the implicit feedback the clicks were gone completely.
 
@EvSap
Your clock configurations seem to be ok. Is the onboard oscillator 25MHz?

For debugging the data rates I output to SWV at every 8000th OUT event (i.e. every second) cumulative number of samples coming from USB and going out through SAI. This makes it very easy to see that the data rates are equal.
...
OUT: 192000 DMA: 192000
OUT: 192000 DMA: 192000
OUT: 192000 DMA: 192000
OUT: 192000 DMA: 192000
OUT: 192000 DMA: 192000
...
 
Is the onboard oscillator 25MHz?
24 MHz. PLL settings can be seen in my previous post under the last spoiler.

For debugging the data rates I output to SWV at every 8000th OUT event (i.e. every second) cumulative number of samples coming from USB and going out through SAI. This makes it very easy to see that the data rates are equal.
Good suggestion, thank you.

With slight modification of feedback implementation I managed to get rid of audio buffer overruns. Now gap between write/read pointers is always very close to nominal value, +/- 2 packets. But sound still contains clicks and some other distortions. I can observe OVRUDR flag rising in SAI status register after transfer completes. Think this may be the reason of sound distortions. Now SAI DMA is used to transmit only one packet and after transfer completes the next one is initiated in SAI DMA interrupt routine. Maybe that is the reason and SAI DMA should be used in circular mode with double buffer option enabled.
 
Couldn't you get exactly 192kHz out of PLL with these:
PLLSAIN=384
PLLSAIQ=2
PLLM=24
PLLN=288
PLLQ=6
Not sure I understand what you mean. For 192 kHz audio frequency SAI input clock should be 192000 * 256 = 49,152 MHz. To achieve this value I use the following settings and the frequency is 49,125 MHz.
SAI_49_125MHz.jpg
With settings you suggested the frequency is 48 MHz
SAI_48MHz.jpg

After several tests it is found out that SAI behaves strangely. Data is outputted (SD pin activity) only at 192 kHz. For other frequencies SD always has 0 value. When SAI DMA is in circular mode data is not outputted but other 3 signals are active.
 
Are you using FIFO with DMA?
Yes, I am. Have tried 2 variants. 1) DMA in "one shot" mode with new transfer initiated in DMA transfer complete interrupt. In this case OVRUDR flag rises in SAI status register and data is ouputted only for 192 kHz, I can here sound with distortions. For 48 / 96 kHz only MCLK, BCLK and LR signals are valid.
SAI_DMA_one_shot_mode.jpg
2) DMA in circular mode. In this case FREQ flag is cleared in SAI status register and no data is ouputted at all.
SAI_DMA_circular_mode.jpg
Now trying to get SAI working correctly by means of simple test application, have not succeeded yet.
 
Have you implemented the DMA half transfer complete and full transfer complete callbacks?
Only full transfer complete interrrupt is used in "one shot" mode to reinitialize transfer again.

Finally managed to get SAI outputting data for 48 / 96 kHz audio frequencies too. But there is a very strange issue. Half of data is nulled periodically. This happens regardless of audio frequency and resolution. Have tried 48 / 96 / 192 / 384 / 768 kHz @ 16 / 32 bits and DMA "one shot" and circular modes. The result is always the same: half of data is nulled periodically.
Test application is very simple containing just SAI and DMA initialization. For DMA circular mode then SAI starts tranfer, no DMA interrupts activated. For DMA "one shot" mode full transfer complete interrupt is used to reinitialize transfer again. Data are formed only once at the very beginning and stay unchangeable. Data buffer is filled with 0x0F.
Please watch short videos explaining what I am talking about. The first video is for data signal which is nulled periodically. And the second one is for LR signal which is clean meander. Audio stream is 48 kHz @ 16 bits

 
I have not used DMA normal mode ("one shot").

The normal approach with circular buffers is to treat the DMA buffer as 2 half buffers and use half transfer complete and full transfer complete callbacks to fill appropriate half buffer with new data. In double buffer mode DMA uses 2 buffers (memory pointers). If there are no transfer errors the DMA in circular (or double buffer) mode should run continuously after enabling. So if you have pre-filled the buffer with some data that data should be outputted even without half/full transfer complete callbacks.