I2S: configuring clock details with Zephyr

Hello,

I have developed an NCS/Zephyr-based application that is able to stream out .WAV files over the I2S peripheral.

I have a dev board using a MAX98357A audio amplifier. This amplifier is able to interpret the I2S output from my nRF52840 and it plays my .WAV file normally out of a speaker.

Here is the I2S configuration I'm using to configure the Zephyr I2S driver:

    my_i2s_config.word_size = SAMPLE_BIT_WIDTH;			// 16
	my_i2s_config.channels = NUMBER_OF_CHANNELS;		// 1
	my_i2s_config.format = I2S_FMT_DATA_FORMAT_I2S;
	my_i2s_config.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER;
	my_i2s_config.frame_clk_freq = SAMPLE_FREQUENCY;	// 16000
	my_i2s_config.mem_slab = &mem_slab;
	my_i2s_config.block_size = BLOCK_SIZE;
	my_i2s_config.timeout = TIMEOUT;

With these settings, I am observing these I2S output clocks on my oscilloscope:
BCLK = 510kHz
LRCLK = 15.9kHz
BCLK to LRCLK Ratio: 32 

Now, I want to integrate a more advanced audio amplifier: the TI TAS2563. However, this audio amplifier does not support BCLK to LRCLK ratio of 32. It requires a minimum ratio of 64.

I still want my audio sampling frequency to be 16kHz, so I need to increase the BCLK speed up to ~1.024MHz.

I know that the previous NRFX drivers had registers for configuring the MCK_SETUP and MCK/LRCK ratio. I had a previous project (before NCS/Zephyr) where we used a MCK_SETUP value of NRF_I2S_MCK_32MDIV21 and a ratio value of NRF_I2S_RATIO_96X to get 16kHz streaming working. If I'm remembering the math correctly, this would give:

32MHz / 21 = 1.524MHz I2S peripheral clock
1.524MHz / 96 BCLKs/LRCLK = 15.9kHz LRCLK (close enough for 16kHz sampling)

Unfortunately, I do not see the options in the Zephyr driver to achieve this level of configuration over the I2S peripheral. 

Is there a way to configure the I2S peripheral from Zephyr such that I have a 16kHz LRCLK and a BCLK to LRCLK ratio of 64 or higher?

Parents
  • Hi Chris

    The Zephyr I2S driver is quite generic and will not be able to cover all the configuration options of the I2S peripheral in the nRF devices. 

    The developers behind the LE Audio project ended up using the nrfx_i2s driver rather than the Zephyr driver for just this reason, and you might want to do the same in order to get full control over the I2S peripheral. 

    I can take a closer look at the driver implementation tomorrow and see if I can spot a way to get the configuration you need, but I wouldn't be surprised if this can only be handled by the nrfx driver. 

    Best regards
    Torbjørn

  • Apologies for the lateness of this reply.

    I have spent some time rewriting my audio thread to use the nrfx I2S driver, instead of the Zephyr I2S driver. I now have a version of my application that is successfully clocking out data over I2S again, and I can adjust the clock speeds by changing inputs to the nrfx_i2s_config_t struct.

    However, I am still having trouble controlling the BCLK to LRCLK ratio, specifically.
    I have tried a variety of inputs, adjusting the mck_setup, ratio, and sample_width parameters. However I am still unable to achieve a BCLK to LRCLK ratio of 64. It seems that no matter what ratio I apply (48X/64X/96X/192X) the actual ratio I observe is 32 BCLKs per LRCLK.

    I also dug through the sample_width parameter definitions and tried out I2S_CONFIG_SWIDTH_SWIDTH_16BitIn32 (#defined as 0x6), which did allow me to hit a ratio of 48 BCLKs per LRCLK. I tried I2S_CONFIG_SWIDTH_SWIDTH_8BitIn32 (#define as 0x5), but that again gives me an observed ratio of 32.


    Here is a table I created of the I2S config parameters I have tested, and the output clock speeds I have observed on my oscilloscope:

    My target I2S parameters:
    -16kHz sampling
    -Mono audio
    -16 bits/sample
    -BCLK to LRCLK ratio of 64, 96, or 128 (so, BCLK freq of 1.024MHz/1.536MHz/2.048MHz)

    Is there a process behind the scenes that is calculating clock speeds automatically?
    Why isn't the nrfx_i2s_config_t.ratio parameter being respected?
    What other parameters can I change to hit 64 BCLKs per LRCLK?

  • Hi  

    I tried this out today. I swapped the pins so that my SCK hardware is connected to the MCK output instead. This gives me a SCK rate of 1.53MHz with an LRCK of 16kHz, which matches the ratio I desire. The SCK output (now routed to my unused MCK pin) continues to run at 512kHz (ratio of 32x from the LRCK of 16kHz).

    However, you are also correct that the I2S peripheral is changing the SDOUT data according to the SCK rate, not the MCK rate. So this 'hack' technically gives me the SCK/LRCK ratio I desired, but now the data output does not align with the SCK, so it is still unusable.

    Are there any other techniques to directly set the SCK and LRCK speeds, rather than having the driver calculate them for me automatically?

    It would be great if both the LRCK and SCK could be directly set as a multiple of the I2S peripheral MCK value. Something like:

    MCK: 1.524MHz (set by mck_setup = NRF_I2S_MCK_32MDIV21, 32MHz oscillator / 21 = 1.524 MHz)
    BCK: 1.524MHz (1:1 BCK to LRCK ratio)
    LRCK: 15.875kHz (96 MCK cycles = 1 LRCK cycle, 1.524MHz/96 = 15.875kHz)

  • According to the formulas in the Infocenter documentation you linked to above, the two relevant formulas are:

    LRCK = MCK / CONFIG.RATIO

    SCK = 2 * LRCK * CONFIG.SWIDTH

    MCK is set by config.mck_setup = NRF_I2S_MCK_32MDIV21, giving MCK = 1.536MHz.

    config.ratio = NRF_I2S_RATIO_96X should give 16kHz (1.536MHz/16kHz = 96)

    SCK = 2 * LRCK * CONFIG.SWIDTH

    SCK = 2 * 16kHz * config.swidth

    Desired SCK: 1.024MHz, or 1.536MHz, or 2.048MHz

    1.024MHz = 2 * 16kHz * config.swidth
    config.swidth = 32

    Shouldn't this give me a BCK of 1.024MHz?

  • I can see that the nrf_i2s_swidth_t type, which is used for the config.swidth field, is defined at lines 176-197 in sdk/modules/hal/nordic/nrfx/hal/nrf_i2s.h:

    /** @brief I2S sample widths. */
    typedef enum
    {
        NRF_I2S_SWIDTH_8BIT          = I2S_CONFIG_SWIDTH_SWIDTH_8Bit,      ///< 8 bit.
        NRF_I2S_SWIDTH_16BIT         = I2S_CONFIG_SWIDTH_SWIDTH_16Bit,     ///< 16 bit.
        NRF_I2S_SWIDTH_24BIT         = I2S_CONFIG_SWIDTH_SWIDTH_24Bit,     ///< 24 bit.
    #if NRF_I2S_HAS_SWIDTH_32BIT
        NRF_I2S_SWIDTH_32BIT         = I2S_CONFIG_SWIDTH_SWIDTH_32Bit,     ///< 32 bit.
    #endif
    #if defined(I2S_CONFIG_SWIDTH_SWIDTH_8BitIn16) || defined(__NRFX_DOXYGEN__)
        NRF_I2S_SWIDTH_8BIT_IN16BIT  = I2S_CONFIG_SWIDTH_SWIDTH_8BitIn16,  ///< 8 bit sample in a 16-bit half-frame.
    #endif
    #if defined(I2S_CONFIG_SWIDTH_SWIDTH_8BitIn32) || defined(__NRFX_DOXYGEN__)
        NRF_I2S_SWIDTH_8BIT_IN32BIT  = I2S_CONFIG_SWIDTH_SWIDTH_8BitIn32,  ///< 8 bit sample in a 32-bit half-frame.
    #endif
    #if defined(I2S_CONFIG_SWIDTH_SWIDTH_16BitIn32) || defined(__NRFX_DOXYGEN__)
        NRF_I2S_SWIDTH_16BIT_IN32BIT = I2S_CONFIG_SWIDTH_SWIDTH_16BitIn32, ///< 16 bit sample in a 32-bit half-frame.
    #endif
    #if defined(I2S_CONFIG_SWIDTH_SWIDTH_24BitIn32) || defined(__NRFX_DOXYGEN__)
        NRF_I2S_SWIDTH_24BIT_IN32BIT = I2S_CONFIG_SWIDTH_SWIDTH_24BitIn32, ///< 24 bit sample in a 32-bit half-frame.
    #endif
    } nrf_i2s_swidth_t;

    However, when I try to use NRF_I2S_SWIDTH_32BIT or I2S_CONFIG_SWIDTH_SWIDTH_16BitIn32, I get errors that these values are not defined. Which makes sense, they are behind an #if defined block. These values are also ghosted out in the vs code editor, reinforcing that they are not defined in the current build:

    I tried defining NRF_I2S_HAS_SWIDTH_32BIT and I2S_CONFIG_SWIDTH_SWIDTH_16BitIn32 in my application, hoping to enable these 32 bit sample width options:

    #define NRF_I2S_HAS_SWIDTH_32BIT 1
    #define I2S_CONFIG_SWIDTH_SWIDTH_32Bit 1

    However, this did not appear to work. The 32 bit sample width options remain disabled.

    What do I need to do to enable the 32 bit sample width options?

  • I am noticing that despite my best efforts, I cannot seem to get an SCK/LRCK ratio of 64. I can get a ratio of 32, and I can get 48, but not 64.

    I can't help but wonder if this is related to the sample widths only showing support for 8, 16, and 24 bits.

    Given the formula SCK = 2 * LRCK * CONFIG.SWIDTH, it makes some sense to me that the max SCK/LRCK ratio I can get is 48, when the max SWIDTH I can support is 24 bits (2*24 = 48, and the LRCK cancels out).

    This makes me increasingly suspicious that the key to hitting my desired clock settings is enabling support for 32 bit sample widths for the I2S peripheral.

    What do I need to do to enable the 32 bit sample width options in the I2S driver?

  • Hi Chris

    The reason these options are conditional in the build is that they are only available on some of our devices, not all. As you can see from the nRF5340 CONFIG.SWIDTH register definition they are available in this device, but not in the CONFIG.SWIDTH register for the nRF52840 unfortunately. 

    In other words you would have to switch to an nRF5340 to get access to these modes. 

    Best regards
    Torbjørn

Reply Children
No Data
Related