4242
4343/*
4444 * AUDIO PLL setting: Frequency = Fref * (DIV_SELECT + NUM / DENOM)
45- * = 24 * (32 + 77/100)
46- * = 786.48 MHz
45+ * = 24 * (32 + 96 / 125) = 24 * (32.768)
46+ * = 786.432 MHz = 48kHz * 16384
47+ *
48+ * This default clocking is used during initial configuration; it also works well for
49+ * frequencies that evenly divide 192kHz, such as 8/12/24/48kHz. However, it doesn't work
50+ * well for 44.1/22/11kHz, so there's the possibility of using a different
51+ * setting when playing a particular sample.
4752 */
4853const clock_audio_pll_config_t audioPllConfig = {
4954 .loopDivider = 32 , /* PLL loop divider. Valid range for DIV_SELECT divider value: 27~54. */
5055 .postDivider = 1 , /* Divider after the PLL, should only be 1, 2, 4, 8, 16. */
51- .numerator = 77 , /* 30 bit numerator of fractional loop divider. */
52- .denominator = 100 , /* 30 bit denominator of fractional loop divider */
56+ .numerator = 96 , /* 30 bit numerator of fractional loop divider. */
57+ .denominator = 125 , /* 30 bit denominator of fractional loop divider */
5358};
5459
5560static I2S_Type * const i2s_instances [] = I2S_BASE_PTRS ;
56- static uint8_t i2s_in_use ;
61+ static uint8_t i2s_in_use , i2s_playing ;
5762
5863static I2S_Type * SAI_GetPeripheral (int idx ) {
5964 if (idx < 0 || idx >= (int )MP_ARRAY_SIZE (i2s_instances )) {
@@ -344,7 +349,11 @@ void port_i2s_deinit(i2s_t *self) {
344349 }
345350 SAI_TransferAbortSend (self -> peripheral , & self -> handle );
346351 i2s_clock_off (self -> peripheral );
347- i2s_in_use &= ~(1 << SAI_GetInstance (self -> peripheral ));
352+
353+ uint32_t instance_mask = 1 << SAI_GetInstance (self -> peripheral );
354+ i2s_in_use &= ~instance_mask ;
355+ i2s_playing &= ~instance_mask ;
356+
348357 if (!i2s_in_use ) {
349358 CCM_ANALOG -> PLL_AUDIO = CCM_ANALOG_PLL_AUDIO_BYPASS_MASK | CCM_ANALOG_PLL_AUDIO_POWERDOWN_MASK | CCM_ANALOG_PLL_AUDIO_BYPASS_CLK_SRC (kCLOCK_PllClkSrc24M );
350359 }
@@ -354,13 +363,51 @@ void port_i2s_deinit(i2s_t *self) {
354363 }
355364}
356365
366+ static uint32_t gcd (uint32_t a , uint32_t b ) {
367+ while (b ) {
368+ uint32_t tmp = a % b ;
369+ a = b ;
370+ b = tmp ;
371+ }
372+ return a ;
373+ }
374+
375+ static void set_sai_clocking_for_sample_rate (uint32_t sample_rate ) {
376+ mp_arg_validate_int_range ((mp_uint_t )sample_rate , 4000 , 192000 , MP_QSTR_sample_rate );
377+
378+ uint32_t target_rate = sample_rate ;
379+ // ensure the PWM rate of MQS will be adequately high
380+ while (target_rate < 175000 ) {
381+ target_rate <<= 1 ;
382+ }
383+ target_rate *= 4096 ; // various prescalers divide by this much
384+ uint32_t div = gcd (target_rate % 24000000 , 24000000 );
385+ clock_audio_pll_config_t config = {
386+ .loopDivider = target_rate / 24000000 ,
387+ .postDivider = 1 ,
388+ .numerator = (target_rate % 24000000 ) / div ,
389+ .denominator = 24000000 / div ,
390+ };
391+ CLOCK_InitAudioPll (& config );
392+ }
393+
357394void port_i2s_play (i2s_t * self , mp_obj_t sample , bool loop ) {
358395 self -> sample = sample ;
359396 self -> loop = loop ;
360397 self -> bytes_per_sample = audiosample_bits_per_sample (sample ) / 8 ;
361398 self -> channel_count = audiosample_channel_count (sample );
399+ int instance = SAI_GetInstance (self -> peripheral );
400+ i2s_playing |= (1 << instance );
362401 uint32_t sample_rate = audiosample_sample_rate (sample );
363402 if (sample_rate != self -> sample_rate ) {
403+ if (__builtin_popcount (i2s_playing ) <= 1 ) {
404+ // as this is the first/only i2s instance playing audio, we can
405+ // safely change the overall clock used by the SAI peripheral, to
406+ // get more accurate frequency reproduction. If another i2s
407+ // instance is playing, then we can't touch the audio PLL and have
408+ // to live with what we can get, which may be inaccurate
409+ set_sai_clocking_for_sample_rate (sample_rate );
410+ }
364411 SAI_TxSetBitClockRate (self -> peripheral , SAI_CLOCK_FREQ , sample_rate , 16 , 2 );
365412 self -> sample_rate = sample_rate ;
366413 }
0 commit comments