I2S_TDM: Allow for bclk_div = 1 if I2S_CLK_SRC_EXTERNAL (IDFGH-14352)
Is your feature request related to a problem?
My project involves reading 8 channel audio at 24 bit / 44.1 kHz using I2S TDM256 mode from an AKM5538 ADC (at the moment I'm developing using 4 channels with a ESP32-C6 board but my C5 sample devkits should be coming in about 2 weeks). This results in a BCLK of 11.2896 MHz. In order to minimize MCLK (and hence, BCLK) jitter resulting from using the fractional divider, I want to use an external oscillator and run the I2S driver in I2S_CLK_SRC_EXTERNAL mode. Alas, when I feed MCLK = 11.2896 MHz and set I2S_MCLK_MULTIPLE_256 (which fits perfectly!), the driver complains
W (308) i2s_tdm: the current mclk multiple is too small, adjust the mclk multiple to 768
which means that the driver wants to have I2S_MCLK_MULTIPLE_384 for 4 channels and _768 for 8 channels. I assume this is to minimize the effects of the fractional divider when using the internal clock, which makes sense. But if I use an external 11.2896 MHz clock, the driver also wants _384 for 4 channels and _768 for 8, although in this case the divider will always be integer for a 44.1 kHz sample rate. In this case, MCLK could be equal to BCLK, which is fine for the AK5538. I would like to avoid having to use a higher frequency xtal oscillator for 33.8688 MHz (rare and expensive) or 45.1584 MHz, which would impose stricter constraints as far as PCB layout. The same is true for all other sample rates, e.g. 48 kHz requiring a 12.288 MHz BCLK, etc.
And why is that in the first place? I'm not even using I2S_DATA_BIT_WIDTH_24BIT. Looking at the driver code, as soon as the bclk_div looks too small it automatically requests a x3 factor even if this is technically not required when using an external clock.
Describe the solution you'd like.
The warning above is generated in i2s_tdm.c: i2s_tdm_calculate_clock(), which apparently does not check if .clk_src = I2S_CLK_SRC_EXTERNAL is set, and then simply use the .ext_clk_freq_hz setting. After all, when clocking in the data bits, as long as you sample the bits with the rising edge of BCLK, everything should be fine, and MCLK plays no role at all.
The following additional code does the trick:
/* While RECEIVING multiple slots, the data will go wrong if the bclk_div is equal or smaller than 2 */
if (clk_cfg->clk_src == I2S_CLK_SRC_EXTERNAL) {
clk_info->mclk = clk_cfg->ext_clk_freq_hz;
clk_info->bclk_div = clk_info->mclk / clk_info->bclk;
ESP_LOGI(TAG, "mclk = %lu, bclk_div = %d", clk_info->mclk, clk_info->bclk_div );
} else if (clk_info->bclk_div <= 2) {
clk_info->bclk_div = 3;
clk_info->mclk = clk_info->bclk * clk_info->bclk_div;
ESP_LOGW(TAG, "the current mclk multiple is too small, adjust the mclk multiple to %"PRIu32, clk_info->mclk / rate);
}
Granted, this solution implies a bit of responsibility on the user, but then if you work with cases like these you ought to be knowing what you do anyway.
The code example is only for master mode, and it Works For Me [TM], but should be similarly implemented for slave mode as well as for PDM and STD. In fact, the code in i2s_std.c:i2s_std_calculate_clock() is identical. In i2s_pdm.c:i2s_pdm_tx_calculate_clock() and i2s_pdm.c:i2s_pdm_rx_calculate_clock(), the code is a bit different but the same logic should apply here as well.
I also think that the logic of setting blck_div to 3 if it's <= 2 does not actually make sense anyway. This should only happen for I2S_DATA_BIT_WIDTH_24BIT because then, you really want to avoid a half-integer divider because that's the worst case for a fractional divider.
N.B.: Interestingly, Roland use in their VG-99 and GR-55 guitar synthesizers (which I'm trying to interface to) also an external clock reference of 16.9344 MHz for their ADCs and DACs, which is 384 * 44.1 kHz. So they avoid any non-integer dividers as well.
Describe alternatives you've considered.
None I can think of, except using a higher frequency xtal oscillator, which implies stricter constraints on RF PCB layout.
Additional context.
In order to minimize jitter, you want the fractional part to be as close to 0 or 1 as possible. In the case of 44.1 kHz, when choosing the 160 MHz clock, 160000000/11289600 ≈ 14.172, which means that the divider will be 15 for ≈ 17.2% of clock cycles and 14 for the rest. (Don't know how the ESP32 clock generator handles this.) The induced phase noise will reduce the effective ADC resolution by 0.5 .. 2 bit depending on the quality of the fractional divider and the PLL overall, which, in audiophile solutions, might not be accepted. In case of a 240 MHz clock, the case is even worse because the divider then needs to be 21.2585. Which means, in audiophile setups, you want an external (precision) clock, and as a PCB layouter, you want the lowest frequency possible.
Hi @h-milz ,
Thanks for your feedback. We will look into this request.
Hi @h-milz, thanks for reporting!
Fix Might Help
There was indeed an issue about the mclk division while using EXTERNAL source clock, it has been fixed recently in the commit 6cc2c717, I guess you actually want to allow mclk_div to be 1, please have a check if this commit can help.
Why Use I2S_MCLK_MULTIPLE_384
As for the I2S_MCLK_MULTIPLE_256, it is not suitable for 24-bit data width, because the MCLK to BCLK division can only be integral, otherwise the final sample rate will be inaccurate. Let's take the calculation:
- If we input the MCLK in frequency:
MCLK = 44100 * 256 = 11.2896 MHz - And the BCLK is supposed to be
BCLK = 44100 * 24 * 4 = 4.2336 MHz - Then
bclk_div = MCLK / BCLK = 2.6667, only the integer2takes effect in the hardware, so the actual BCLK will be11.2896 / 2 = 5.9448 MHz(because BCLK is divided from MCLK in hardware) and the sample rate will actually be5.9448 MHz / (24*4) = 58800 Hz.
This is why the driver will force to use I2S_MCLK_MULTIPLE_384, it is aimed to guarantee a correct sample rate.
Real Requirement
Back to the real requirement.
First of all, I'd like to clarify the clocks in the I2S, the I2S clock generation path is SCLK -> MCLK -> BCLK -> WS(sample rate). So I guess your real requirement is to make mclk_div = SCLK / MCLK is integral and better to minimize it to 1, i.e., source clock = EXTERNAL clock = MCLK, luckily, it has been fixed in the commit above (available on master 3 weeks ago).
Secondly, you have to choose a mclk_multiple that can be divided by 3 integrally because you're using 24bit data width;
Thirdly, you might also want to make bclk_div = MCLK / BCLK = 1, but bclk_div should at least be 2 in the I2S hardware, and even 2 is not stable enough. That's why bclk_div is forced to carry up tp 3 when it is equal or smaller than 2.
Hi @L-KAYA Thank you for pointing this out. I'm perfectly aware why one needs an integer multiple of 3 when using 24 bits slot width, no worries ;-) But then I'm using a slot width of 32 with the ADC resolution set to 24 (or rather, I use only the 24 MSB). I understand that this slot width is fixed for TDM in practice (at least the AKM and I think TI ADCs and DACs do not support anything else) but not in general.
Can you elaborate on why the hardware requests a bclk_div of 2? Is it in order to get a 50% duty cycle for BCLK, which means the hardware wants to send MCLK at least via a flip-flop? This would indeed make sense. That would imply a 22.5792 MHz oscillator which is something I can live with (and this would mean I2S_MCLK_MULTIPLE_512).
I'll give the recent commit a try asap.
I'm using a slot width of 32 with the ADC resolution set to 24
Yeah, you are totally right! You need I2S_MCLK_MULTIPLE_512 in your case.
Can you elaborate on why the hardware requests a bclk_div of 2? Is it in order to get a 50% duty cycle for BCLK, which means the hardware wants to send MCLK at least via a flip-flop?
I don't have the detailed info about how this implemented in the hardware, I guess MCLK would be the actual sample clock, for example, while BCLK at a rising (sometimes falling) edge, the signal on the data line will be latched at the next MCLK rising edge.
I referred from the slave role, the I2S slave (TX mode) will always send the signal on the data line with a latency compared to the BCLK, and if we only increase the MCLK, the latency will be shorter, so probably the data is actually be sent on the MCLK edge, so as the RX dir, the data can only be sampled on the MCLK edge.
I think this delay is aimed to guarantee the data signal on line to be stable when it is sampled (i.e., if we sample at the BCLK edge strictly, the data signal might still at a rising/falling status). Therefore, MCLK must be faster than the BCLK, so that it can sample after the BCLK edge.
@h-milz Thanks for reporting, would you please help share if any updates for the issue? Thanks.
My project is on hold for various reasons but I will pick it up within the next few weeks, hopefully able to verify if the current implementation works for me. Please keep this open for now.
Hi @h-milz , Do you have any updates?
I must admit that the project is currently on hold so that there is no update. I'll close the ticket for now and if there is any update I can reopen and comment.