Skip to content

Commit cb5e1a1

Browse files
committed
mimxrt: Fix output frequency for samples that don't divide 192kHz
This makes all the samples from Dan's collection register as 440Hz when playing on pwmio or i2sout, using https://webaudiodemos.appspot.com/pitchdetect/index.html to detect the frequency played (all should show as A 440Hz; an error of up to 20 "cents" should be treated as OK) There's an audible carrier with PWM output and the 8kHz samples. This is probably a limitation of the peripheral which is documented as being for input signals of 44 kHz or 48 kHz; the carrier frequency is a fixed multiple of the sample frequency. Closes #7800
1 parent 3e657af commit cb5e1a1

File tree

2 files changed

+57
-6
lines changed

2 files changed

+57
-6
lines changed

ports/mimxrt10xx/common-hal/audiobusio/__init__.c

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,23 @@
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
*/
4853
const 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

5560
static 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

5863
static 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+
357394
void 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
}

shared-bindings/audiopwmio/PWMAudioOut.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@
5656
//| :param int quiescent_value: The output value when no signal is present. Samples should start
5757
//| and end with this value to prevent audible popping.
5858
//|
59+
//| **Limitations:** On mimxrt10xx, low sample rates may have an audible
60+
//| "carrier" frequency. The manufacturer datasheet states that the "MQS" peripheral
61+
//| is intended for 44 kHz or 48kHz input signals.
62+
//|
5963
//| Simple 8ksps 440 Hz sin wave::
6064
//|
6165
//| import audiocore

0 commit comments

Comments
 (0)