Output of my Audio DK is noisy/clipping when coming via i2s

I am quite new to the nRF5340 Audio DK and used  's project on https://github.com/ace-johnny/nrfadk-hello_codec to get me started.

The overall project goal is to process audio data coming via line-in on the nRF5340 to add filters, effects, etc. and output again via the headphone.

In order to achieve this I transmit the data from the codec via i2s to the MCU, process it there and send it back. However, something in this chain is introducting a lot of noise/clipping and I can't find the reason for this. Right now my processing of the received data is just copying it 1:1 to the transmit buffer - no change whatsoever.

When I directly route the line-in input to the output and not going via i2s to the MCU and back everything is crystal clear.

This is the code I use:

/**
 * @file        main.c
 * 
 * @brief       Audio DK HW_CODEC test using I2S loop and tone/noise generators.
 */

#include <zephyr/kernel.h>
#include <nrf.h>
#include <nrfx_clock.h>

#include "cs47l63_comm.h"

////////////////////////////////////////////////////////////////////////////////
// NRFX_CLOCKS

#define HFCLKAUDIO_12_288_MHZ 0x9BA6
#define ENABLE_LINEIN
#undef ENABLE_MIC


/**
 * @brief       Initialize the high-frequency clocks and wait for each to start.
 * 
 * @details     HFCLK =         128,000,000 Hz
 *              HFCLKAUDIO =     12,288,000 Hz
 */
static int nrfadk_hfclocks_init(void)
{
	nrfx_err_t err;


	// HFCLK
	err = nrfx_clock_divider_set(NRF_CLOCK_DOMAIN_HFCLK, NRF_CLOCK_HFCLK_DIV_1);
	if (err != NRFX_SUCCESS) return (err - NRFX_ERROR_BASE_NUM);

	nrfx_clock_start(NRF_CLOCK_DOMAIN_HFCLK);
	while (!nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLK, NULL)) k_msleep(1);


	// HFCLKAUDIO
	nrfx_clock_hfclkaudio_config_set(HFCLKAUDIO_12_288_MHZ);

	nrfx_clock_start(NRF_CLOCK_DOMAIN_HFCLKAUDIO);
	while (!nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLKAUDIO, NULL)) k_msleep(1);


	return 0;
}



////////////////////////////////////////////////////////////////////////////////
// NRF_I2S

#define MCKFREQ_6_144_MHZ 0x66666000


#define I2S_BUFF_SIZE 256  // Define an appropriate buffer size

static int16_t rx_buffer[I2S_BUFF_SIZE];  // Buffer for received data
static int16_t tx_buffer[I2S_BUFF_SIZE]; 

void process_audio(int16_t *rx, int16_t *tx, size_t size)
{
    for (size_t i = 0; i < size; i++) {
		tx[i] = rx[i];  // Simple passthrough (modify as needed)
        	//tx[i] = (int16_t)(rx[i]*0.1);  // Volume control
		//tx[size-1-i] = rx[i];  // Reverse - doesn't work... -> only outputs silence...?
    }
}

/**
 * @brief       Initialize and start the I2S peripheral using NRF registers.
 * 
 * @details     I2S master, 48kHz 16bit, Left mono, TX only.
 */
static int nrfadk_i2s_reg_init(void)
{
	// Configure and enable
	NRF_I2S0->CONFIG.CLKCONFIG =    I2S_CONFIG_CLKCONFIG_CLKSRC_ACLK;
	NRF_I2S0->CONFIG.MCKFREQ =      MCKFREQ_6_144_MHZ;
	NRF_I2S0->CONFIG.RATIO =        I2S_CONFIG_RATIO_RATIO_128X;
	NRF_I2S0->CONFIG.CHANNELS =     I2S_CONFIG_CHANNELS_CHANNELS_Left;	
	NRF_I2S0->CONFIG.TXEN =         I2S_CONFIG_TXEN_TXEN_Enabled;  // Send audio samples to the nRF5340
	NRF_I2S0->CONFIG.RXEN = 	I2S_CONFIG_RXEN_RXEN_Enabled;  // Enable RX to receive the data from the nRF5340

	NRF_I2S0->ENABLE =              I2S_ENABLE_ENABLE_Enabled;

	// Start TX buffer
	NRF_I2S0->RXD.PTR = 		(uint32_t)rx_buffer;
	NRF_I2S0->TXD.PTR = 		(uint32_t)tx_buffer;
	NRF_I2S0->RXTXD.MAXCNT =        I2S_BUFF_SIZE / sizeof(uint32_t);	

        // Clear pending events
    	NRF_I2S0->EVENTS_RXPTRUPD = 0;
	    NRF_I2S0->EVENTS_TXPTRUPD = 0;

	NRF_I2S0->TASKS_START =         I2S_TASKS_START_TASKS_START_Trigger;
	return 0;
}

void i2s_polling_loop(void)
{
    while (1)
    {
        // Wait for new RX data
        while (NRF_I2S0->EVENTS_RXPTRUPD == 0);
		
        // Clear RX event
        NRF_I2S0->EVENTS_RXPTRUPD = 0;

        // Process audio data
        process_audio(rx_buffer, tx_buffer, I2S_BUFF_SIZE); // For now, just set tx_buffer == rx_buffer

        // Ensure we restart I2S for continuous operation
        NRF_I2S0->TASKS_START = I2S_TASKS_START_TASKS_START_Trigger;
	while (NRF_I2S0->EVENTS_TXPTRUPD == 0);
    }
}

////////////////////////////////////////////////////////////////////////////////
// HW_CODEC

/** CS47L63 driver state handle. */
static cs47l63_t cs47l63_driver;


/** CS47L63 subsystems configuration. */
static const uint32_t cs47l63_cfg[][2] =
{

	// Audio Serial Port 1 (I2S slave, 48kHz 16bit, Left mono, RX and TX)
	{ CS47L63_ASP1_CONTROL2,
		(0x10  << CS47L63_ASP1_RX_WIDTH_SHIFT) |        // 16bit
		(0x10  << CS47L63_ASP1_TX_WIDTH_SHIFT) |        // 16bit
		(0b010 << CS47L63_ASP1_FMT_SHIFT)               // I2S
	},
	{ CS47L63_ASP1_CONTROL3,
		(0b00 << CS47L63_ASP1_DOUT_HIZ_CTRL_SHIFT)      // Always 0
	},
	// Enable the various channels for RX and TX
	{ CS47L63_ASP1_ENABLES1,
		(0 << CS47L63_ASP1_RX2_EN_SHIFT) |              // Disabled
		(1 << CS47L63_ASP1_RX1_EN_SHIFT) |              // Enabled
		(0 << CS47L63_ASP1_TX2_EN_SHIFT) |              // Disabled
		//(0 << CS47L63_ASP1_TX1_EN_SHIFT)                // Disabled
		(1 << CS47L63_ASP1_TX1_EN_SHIFT)                // Enabled - we want to send something to the nRF5340
	},

#ifdef ENABLE_LINEIN
	// Enable line-in
	{ CS47L63_INPUT2_CONTROL1, 0x00050020 },/* MODE=analog */ 
	{ CS47L63_IN2L_CONTROL1, 0x10000000 },  /* SRC=IN2LP */
	{ CS47L63_IN2R_CONTROL1, 0x10000000 },  /* SRC=IN2RP */
	{ CS47L63_INPUT_CONTROL, 0x0000000C },  /* IN2_EN=1 */
	// Set volume for line-in
	{ CS47L63_IN2L_CONTROL2, 0x00800080 },  /* VOL=0dB, MUTE=0 */
	{ CS47L63_IN2R_CONTROL2, 0x00800080 },  /* VOL=0dB, MUTE=0 */
	{ CS47L63_INPUT_CONTROL3, 0x20000000 }, /* VU=1 */
	// Important
	/* Route IN2L and IN2R to I2S */
	{ CS47L63_ASP1TX1_INPUT1, 0x800012 },
	{ CS47L63_ASP1TX2_INPUT1, 0x800013 },
#endif

	// Output 1 Left (reduced MIX_VOLs to prevent clipping summed signals)
	// this here is only there so we hear also that i2s data is sent 
	
	{ CS47L63_OUT1L_INPUT1,
		(0x2B  << CS47L63_OUT1LMIX_VOL1_SHIFT) |        // quite weak
		(0x020 << CS47L63_OUT1L_SRC1_SHIFT)             // ASP1_RX1 // from MCU (currently a sine wave only)
	},

	{ CS47L63_OUT1L_INPUT2,
		(0x2B  << CS47L63_OUT1LMIX_VOL2_SHIFT) |        // 
		(0x021 << CS47L63_OUT1L_SRC2_SHIFT)             // ASP1_RX2
	},
	
#undef ENABLE_LINEIN
#ifdef ENABLE_LINEIN
	// We need both channels here, even if we only have one output channel
	// If we uncomment the next two {} we won´t get any line in pass-through, only i2s
	{
		CS47L63_OUT1L_INPUT3,
		(0x2B  << CS47L63_OUT1LMIX_VOL3_SHIFT) |
		(0x012 << CS47L63_OUT1L_SRC3_SHIFT)    // 0x12=IN2L
	},
	{
		CS47L63_OUT1L_INPUT4,
		(0x2B  << CS47L63_OUT1LMIX_VOL4_SHIFT) |
		(0x013 << CS47L63_OUT1L_SRC4_SHIFT)    // 0x13=IN2R
	},
#endif

	{ CS47L63_OUTPUT_ENABLE_1,
		(1 << CS47L63_OUT1L_EN_SHIFT)                   // Enabled
	},
};


/**
 * @brief       Write a configuration array to multiple CS47L63 registers.
 * 
 * @param[in]   config: Array of address/data pairs.
 * @param[in]   length: Number of registers to write.
 * 
 * @retval      `CS47L63_STATUS_OK`     The operation was successful.
 * @retval      `CS47L63_STATUS_FAIL`   Writing to the control port failed.
 */
static int nrfadk_hwcodec_config(const uint32_t config[][2], uint32_t length)
{
	int ret;
	uint32_t addr;
	uint32_t data;

	for (int i = 0; i < length; i++)
	{
		addr = config[i][0];
		data = config[i][1];

		ret = cs47l63_write_reg(&cs47l63_driver, addr, data);
		if (ret) return ret;
	}

	return CS47L63_STATUS_OK;
}


/**
 * @brief       Initialize the CS47L63, start clocks, and configure subsystems.
 * 
 * @details     MCLK1 =   6,144,000 Hz  (I2S MCK = CONFIG.MCKFREQ)
 *              FSYNC =      48,000 Hz  (I2S LRCK = MCK / CONFIG.RATIO)
 *              BCLK =    1,536,000 Hz  (I2S SCK = LRCK * CONFIG.SWIDTH * 2)
 *              FLL1 =   49,152,000 Hz  (MCLK1 * 8)
 *              SYSCLK = 98,304,000 Hz  (FLL1 * 2)
 * 
 * @retval      `CS47L63_STATUS_OK`     The operation was successful.
 * @retval      `CS47L63_STATUS_FAIL`   Initializing the CS47L63 failed.
 * 
 * @note        I2S MCK must already be running before calling this function.
 */
static int nrfadk_hwcodec_init(void)
{
	int ret = CS47L63_STATUS_OK;


	// Initialize driver
	ret += cs47l63_comm_init(&cs47l63_driver);


	// Start FLL1 and SYSCLK
	ret += cs47l63_fll_config(&cs47l63_driver, CS47L63_FLL1,
	                          CS47L63_FLL_SRC_MCLK1, 6144000, 49152000);

	ret += cs47l63_fll_enable(&cs47l63_driver, CS47L63_FLL1);

	ret += cs47l63_fll_wait_for_lock(&cs47l63_driver, CS47L63_FLL1);

	ret += cs47l63_update_reg(&cs47l63_driver, CS47L63_SYSTEM_CLOCK1,
	                          CS47L63_SYSCLK_EN_MASK, CS47L63_SYSCLK_EN);


	// Configure subsystems
	ret += nrfadk_hwcodec_config(cs47l63_cfg, ARRAY_SIZE(cs47l63_cfg));


	return ret;
}



////////////////////////////////////////////////////////////////////////////////
// MAIN

int main(void)
{
	// Initialize Audio DK

	if (nrfadk_hfclocks_init() ||
	    nrfadk_i2s_reg_init()  ||
	    nrfadk_hwcodec_init())
	{
		printk("\nError initializing Audio DK\n");
		return -1;
	}

	printk("\nAudio DK initialized\n");
	k_msleep(1250);



	// Unmute OUT1L I2S playback and enable NOISE/TONE1 generators

	cs47l63_update_reg(&cs47l63_driver, CS47L63_OUT1L_VOLUME_1,
	                   CS47L63_OUT_VU_MASK | CS47L63_OUT1L_MUTE_MASK,
	                   CS47L63_OUT_VU | 0);

	printk("\nOUT1L unmuted for 5000ms\n");

	k_msleep(5000);
	i2s_polling_loop(); // We actually never return from here (testing)

	while(1);
	cs47l63_update_reg(&cs47l63_driver, CS47L63_OUT1L_VOLUME_1,
	                   CS47L63_OUT_VU_MASK | CS47L63_OUT1L_MUTE_MASK,
	                   CS47L63_OUT_VU | CS47L63_OUT1L_MUTE);

	printk("OUT1L muted\n");
	k_msleep(1250);



	// Shutdown Audio DK (reverse order initialized)

	cs47l63_update_reg(&cs47l63_driver, CS47L63_OUTPUT_ENABLE_1,
	                   CS47L63_OUT1L_EN_MASK, 0);
	printk("\nOUT1L disabled\n");
	k_msleep(250);

	cs47l63_update_reg(&cs47l63_driver, CS47L63_SYSTEM_CLOCK1,
	                   CS47L63_SYSCLK_EN_MASK, 0);
	printk("SYSCLK disabled\n");
	k_msleep(250);

	cs47l63_fll_disable(&cs47l63_driver, CS47L63_FLL1);
	printk("FLL1 disabled\n");
	k_msleep(250);

	NRF_I2S0->TASKS_STOP =  I2S_TASKS_STOP_TASKS_STOP_Trigger;
	NRF_I2S0->ENABLE =      I2S_ENABLE_ENABLE_Disabled;
	printk("I2S disabled\n");
	k_msleep(250);

	nrfx_clock_stop(NRF_CLOCK_DOMAIN_HFCLKAUDIO);
	while (nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLKAUDIO, NULL)) k_msleep(1);
	printk("HFCLKAUDIO stopped\n");
	k_msleep(250);

	nrfx_clock_stop(NRF_CLOCK_DOMAIN_HFCLK);
	while (nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLK, NULL)) k_msleep(1);
	printk("HFCLK stopped\n");
	k_msleep(250);


	printk("\nAudio DK shutdown\n\n");
	k_msleep(100);


	return 0;
}

process_audio() is the simple function which just sets the TX buffer to the RX buffer's values. 

(Here I want eventually to add my processing)

nrfadk_i2s_reg_init() initializes the i2s on the nRF5340 side. I've added here also the reception of the data from the codec
i2s_polling_loop() constantly checks if i2s data is present, then copies it over and starts the back transmission
cs47l63_cfg() configures the line-in input, the i2s in the ASP1 and the output channel coming from i2s 
My assumption is that the error is in one of those regions:
i2s_polling_loop() - maybe done completely wrong at all?
cs47l63_cfg() - I am confused - do I have a stereo input with line-in or only mono? Will I need ASP1RX1,2 and ASP1TX1,2 then or just ASP1RX1 and ASP1TX1?
Is it OK to use int16_t as the values in the buffer but uint32_t to address the elements? Does the i2s config in the codec match?
Is the noise maybe just a result of the MCU not being able to keep up? 
The audio is properly played back in genereal but there is noise/clipping present all the time - not only during loud parts of the music.
  • Hi  ,

    Can you provide status update on the noise/clipping issue? 

    Best regards,
    Dejan

  • Hi dejans,

    thanks for asking.

    My "solution" was buried somewhere in this thread between multiple posts. I noticed that once I increase the buffer size from 256 bytes like in ace.johnny's barebone example to e.g. 8192 the clipping/noise basically is non-existent anymore. As for the reason, I can only suspect it has to do, that the read/write cycle via i2s happens much less frequently than before. I still haven´t looked fully into optimizing this part, as I am still struggeling with some simple audio processing like the echo/delay mentioned above. Also I haven´t yet found a suitable example in the nRF Connect SDK to show how the whole receive/send chain is realized via interrupt driven/DMA. If you could point me to the right direction, this would be much appreciated. Thank you!

  • Having another weird issue, that I cannot really explain.

    Basically, the Zephyr example https://github.com/zephyrproject-rtos/zephyr/tree/main/samples/drivers/i2s/echo should be more or less what I want to achieve. Unfortunately it's build for the WM8731.

    But basically the code is pretty much independant from the actual codec. So I thought about merging the relevant parts of that example into my existing bare bone example.

    This required me to change things like:

    #if DT_NODE_EXISTS(DT_NODELABEL(i2s_rxtx))

    to 

    #if DT_NODE_EXISTS(DT_NODELABEL(i2s0))

    as the i2s_rxtx was not present for the nrf5340_adk - or at least that's what I figured out.

    The strange thing is now that as soon as I remove the initialization of the i2s component via  's func nrfadk_i2s_reg_init() with the one from the example the subsequenct call to nrfadk_hwcodec_init() will fail and thus the whole code. Making my own init_i2s() function from what's in the main of https://github.com/zephyrproject-rtos/zephyr/blob/main/samples/drivers/i2s/echo/src/main.c will let the call to nrfadk_hwcodec_init() fail. More precisely: As long as in nrfadk_i2s_reg_init() does not call 

    NRF_I2S0->TASKS_START = I2S_TASKS_START_TASKS_START_Trigger;
    it will fail (I commented that particular line to verify the effect).
    I'm not yet talking about making use of the new i2s initialization like it's done in the Zephyr example. It's just that if I don't to the i2s initialization like @ace.johnny did, the HW Codec init fails.

    I am not sure but is  's variant maybe not using Zephyr functions at all by using those "NRF_I2S0->XYZ " style of configuring? 
    I can only assume that maybe this initialization does something to the clocking configuration (I can configure the CLK in the NRF_I2S0 approach but not via the example from the Zephyr project), which then is a problem for the subsequent call to nrfadk_hwcodec_init()?

    Any idea?

    I'll attach the current version of the code below.


    /**
     * @file        main.c
     *
     * @brief       Audio DK HW_CODEC test using I2S loop and tone/noise generators.
     *
     * Original source by ace.johnny (https://github.com/ace-johnny/nrfadk-hello_codec)
     */
    
    #include <zephyr/kernel.h>
    #include <nrf.h>
    #include <nrfx_clock.h>
    
    #include "cs47l63_comm.h"
    #include <zephyr/drivers/i2s.h>
    
    ////////////////////////////////////////////////////////////////////////////////
    // NRFX_CLOCKS
    
    #define HFCLKAUDIO_12_288_MHZ 0x9BA6
    
    #undef ENABLE_LINEIN
    #undef ENABLE_LINEIN_PASSTHROUGH
    #define ENABLE_MIC
    #undef ENABLE_MIC_PASSTHROUGH
    
    #if DT_NODE_EXISTS(DT_NODELABEL(i2s0))
    #define I2S_RX_NODE DT_NODELABEL(i2s0)
    #define I2S_TX_NODE I2S_RX_NODE
    #else
    #define I2S_RX_NODE DT_NODELABEL(i2s0_rx)
    #define I2S_TX_NODE DT_NODELABEL(i2s0_tx)
    #endif
    
    static K_SEM_DEFINE(toggle_transfer, 1, 1);
    
    // i2s related defines
    #define SAMPLE_FREQUENCY 48000
    #define SAMPLE_BIT_WIDTH 16
    #define BYTES_PER_SAMPLE sizeof(int16_t)
    #define NUMBER_OF_CHANNELS 2
    /* Such block length provides an echo with the delay of 100 ms. */
    #define SAMPLES_PER_BLOCK ((SAMPLE_FREQUENCY / 10) * NUMBER_OF_CHANNELS)
    #define INITIAL_BLOCKS 2
    #define TIMEOUT 1000
    
    #define BLOCK_SIZE (BYTES_PER_SAMPLE * SAMPLES_PER_BLOCK)
    #define BLOCK_COUNT (INITIAL_BLOCKS + 2)
    K_MEM_SLAB_DEFINE_STATIC(mem_slab, BLOCK_SIZE, BLOCK_COUNT, 4);
    
    static bool configure_streams(const struct device *i2s_dev_rx, const struct device *i2s_dev_tx, const struct i2s_config *config);
    
    static int16_t echo_block[SAMPLES_PER_BLOCK];
    
    /**
     * @brief       Initialize the high-frequency clocks and wait for each to start.
     *
     * @details     HFCLK =         128,000,000 Hz
     *              HFCLKAUDIO =     12,288,000 Hz
     */
    static int nrfadk_hfclocks_init(void)
    {
    	nrfx_err_t err;
    
    	// HFCLK
    	err = nrfx_clock_divider_set(NRF_CLOCK_DOMAIN_HFCLK, NRF_CLOCK_HFCLK_DIV_1);
    	if (err != NRFX_SUCCESS)
    		return (err - NRFX_ERROR_BASE_NUM);
    
    	nrfx_clock_start(NRF_CLOCK_DOMAIN_HFCLK);
    	while (!nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLK, NULL))
    		k_msleep(1);
    
    	// HFCLKAUDIO
    	nrfx_clock_hfclkaudio_config_set(HFCLKAUDIO_12_288_MHZ);
    
    	nrfx_clock_start(NRF_CLOCK_DOMAIN_HFCLKAUDIO);
    	while (!nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLKAUDIO, NULL))
    		k_msleep(1);
    
    	return 0;
    }
    
    ////////////////////////////////////////////////////////////////////////////////
    // NRF_I2S
    
    static void process_block_data(void *mem_block, uint32_t number_of_samples)
    {
    	for (int i = 0; i < number_of_samples; ++i)
    	{
    		int16_t *sample = &((int16_t *)mem_block)[i];
    		*sample += echo_block[i];
    		echo_block[i] = (*sample) / 2;
    	}
    }
    
    #define MCKFREQ_6_144_MHZ 0x66666000
    #define I2S_BUFF_SIZE 8192 // Define an appropriate buffer size
    
    static int16_t rx_buffer[I2S_BUFF_SIZE]; // Buffer for received data
    static int16_t tx_buffer[I2S_BUFF_SIZE];
    
    #define DELAY 2048
    void process_audio(int16_t *rx, int16_t *tx, size_t size)
    {
    	static int16_t delay_buffer[DELAY];
    	static int delay_index = 0;
    
    	// Simple echo
    	for (int i = 0; i < size; ++i)
    	{
    		int16_t delayed_sample = delay_buffer[delay_index];
    		tx[i] = rx[i] + delayed_sample * 0.5;
    		// printk("%d %d\n", tx[i], rx[i]);
    		delay_buffer[delay_index] = rx[i];
    		delay_index = (delay_index + 1) % DELAY;
    	}
    
    	/*
    	for (size_t i = 0; i < size; i++) {
    		tx[i] = rx[i];  // Simple passthrough (modify as needed)
    				// tx[i] = (int16_t)(rx[i]*0.1);  // Volume control
    		// tx[size-1-i] = rx[i];  // Reverse - doesn't work... -> only outputs silence...?
    		}
    	*/
    }
    
    /**
     * @brief       Initialize and start the I2S peripheral using NRF registers.
     *
     * @details     I2S master, 48kHz 16bit, Left mono, TX only.
     */
    
    static int nrfadk_i2s_reg_init(void)
    {
    	// Configure and enable
    	NRF_I2S0->CONFIG.CLKCONFIG = I2S_CONFIG_CLKCONFIG_CLKSRC_ACLK;
    	NRF_I2S0->CONFIG.MCKFREQ = MCKFREQ_6_144_MHZ;
    	NRF_I2S0->CONFIG.RATIO = I2S_CONFIG_RATIO_RATIO_128X;
    	NRF_I2S0->CONFIG.CHANNELS = I2S_CONFIG_CHANNELS_CHANNELS_Stereo; // if I2S_CONFIG_CHANNELS_CHANNELS_Left the left and right channel will be the same
    	NRF_I2S0->CONFIG.TXEN = I2S_CONFIG_TXEN_TXEN_Enabled;
    	NRF_I2S0->CONFIG.RXEN = I2S_CONFIG_RXEN_RXEN_Enabled; // Enable RX (new)
    
    	NRF_I2S0->ENABLE = I2S_ENABLE_ENABLE_Enabled;
    
    #define ECHO
    #ifdef ECHO
    	// Start TX buffer
    	NRF_I2S0->RXD.PTR = (uint32_t)rx_buffer;
    	NRF_I2S0->TXD.PTR = (uint32_t)tx_buffer;
    	NRF_I2S0->RXTXD.MAXCNT = I2S_BUFF_SIZE / sizeof(uint32_t);
    #endif
    	
    	// Clear pending events
    	NRF_I2S0->EVENTS_RXPTRUPD = 0;
    	NRF_I2S0->EVENTS_TXPTRUPD = 0;
    
    	NRF_I2S0->TASKS_START = I2S_TASKS_START_TASKS_START_Trigger;
    	
    	return 0;
    }
    
    static int nrfadk_i2s_reg_init_new(const struct device *const i2s_dev_rx, const struct device *const i2s_dev_tx, struct i2s_config config)
    {
    	if (!device_is_ready(i2s_dev_rx))
    	{
    		printk("%s is not ready\n", i2s_dev_rx->name);
    		return 1;
    	}
    
    	if (i2s_dev_rx != i2s_dev_tx && !device_is_ready(i2s_dev_tx))
    	{
    		printk("%s is not ready\n", i2s_dev_tx->name);
    		return 1;
    	}
    	
    	config.word_size = SAMPLE_BIT_WIDTH;
    	config.channels = NUMBER_OF_CHANNELS;
    	config.format = I2S_FMT_DATA_FORMAT_I2S;
    	config.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER;
    	config.frame_clk_freq = SAMPLE_FREQUENCY;
    	config.mem_slab = &mem_slab;
    	config.block_size = BLOCK_SIZE;
    	config.timeout = TIMEOUT;
    	if (!configure_streams(i2s_dev_rx, i2s_dev_tx, &config))
    	{
    		return 1;
    	}
    	printk("i2s initialization successfull.");
    	return 0;
    }
    static bool configure_streams(const struct device *i2s_dev_rx,
    							  const struct device *i2s_dev_tx,
    							  const struct i2s_config *config)
    {
    	int ret;
    
    	if (i2s_dev_rx == i2s_dev_tx)
    	{
    		ret = i2s_configure(i2s_dev_rx, I2S_DIR_BOTH, config);
    		if (ret == 0)
    		{
    			return true;
    		}
    		/* -ENOSYS means that the RX and TX streams need to be
    		 * configured separately.
    		 */
    		if (ret != -ENOSYS)
    		{
    			printk("Failed to configure streams: %d\n", ret);
    			return false;
    		}
    	}
    
    	ret = i2s_configure(i2s_dev_rx, I2S_DIR_RX, config);
    	if (ret < 0)
    	{
    		printk("Failed to configure RX stream: %d\n", ret);
    		return false;
    	}
    
    	ret = i2s_configure(i2s_dev_tx, I2S_DIR_TX, config);
    	if (ret < 0)
    	{
    		printk("Failed to configure TX stream: %d\n", ret);
    		return false;
    	}
    
    	return true;
    }
    static bool prepare_transfer(const struct device *i2s_dev_rx,
    							 const struct device *i2s_dev_tx)
    {
    	int ret;
    
    	for (int i = 0; i < INITIAL_BLOCKS; ++i)
    	{
    		void *mem_block;
    
    		ret = k_mem_slab_alloc(&mem_slab, &mem_block, K_NO_WAIT);
    		if (ret < 0)
    		{
    			printk("Failed to allocate TX block %d: %d\n", i, ret);
    			return false;
    		}
    
    		memset(mem_block, 0, BLOCK_SIZE);
    
    		ret = i2s_write(i2s_dev_tx, mem_block, BLOCK_SIZE);
    		if (ret < 0)
    		{
    			printk("Failed to write block %d: %d\n", i, ret);
    			return false;
    		}
    	}
    
    	return true;
    }
    
    static bool trigger_command(const struct device *i2s_dev_rx,
    							const struct device *i2s_dev_tx,
    							enum i2s_trigger_cmd cmd)
    {
    	int ret;
    
    	if (i2s_dev_rx == i2s_dev_tx)
    	{
    		ret = i2s_trigger(i2s_dev_rx, I2S_DIR_BOTH, cmd);
    		if (ret == 0)
    		{
    			return true;
    		}
    		/* -ENOSYS means that commands for the RX and TX streams need
    		 * to be triggered separately.
    		 */
    		if (ret != -ENOSYS)
    		{
    			printk("Failed to trigger command %d: %d\n", cmd, ret);
    			return false;
    		}
    	}
    
    	ret = i2s_trigger(i2s_dev_rx, I2S_DIR_RX, cmd);
    	if (ret < 0)
    	{
    		printk("Failed to trigger command %d on RX: %d\n", cmd, ret);
    		return false;
    	}
    
    	ret = i2s_trigger(i2s_dev_tx, I2S_DIR_TX, cmd);
    	if (ret < 0)
    	{
    		printk("Failed to trigger command %d on TX: %d\n", cmd, ret);
    		return false;
    	}
    
    	return true;
    }
    
    void i2s_polling_loop(void)
    {
    	while (1)
    	{
    		// Wait for new RX data
    		while (NRF_I2S0->EVENTS_RXPTRUPD == 0)
    			;
    
    		// Clear RX event
    		NRF_I2S0->EVENTS_RXPTRUPD = 0;
    
    		// Process audio data
    		process_audio(rx_buffer, tx_buffer, I2S_BUFF_SIZE);
    
    		while (NRF_I2S0->EVENTS_TXPTRUPD == 0)
    			;
    	}
    }
    
    ////////////////////////////////////////////////////////////////////////////////
    // HW_CODEC
    
    /** CS47L63 driver state handle. */
    static cs47l63_t cs47l63_driver;
    
    /** CS47L63 subsystems configuration. */
    static const uint32_t cs47l63_cfg[][2] =
    	{
    		// Audio Serial Port 1 (I2S slave, 48kHz 16bit, stereo, RX/TX)
    		{
    			CS47L63_ASP1_CONTROL2,						// 0x101000200 (matches above)
    			(0x10 << CS47L63_ASP1_RX_WIDTH_SHIFT) |		// 16bit (default is 0x18 = 24 bit)
    				(0x10 << CS47L63_ASP1_TX_WIDTH_SHIFT) | // 16bit (default is 0x18 = 24 bit)
    				(0b010 << CS47L63_ASP1_FMT_SHIFT)		// I2S (is default)
    		},
    		{
    			CS47L63_ASP1_CONTROL3,
    			(0b00 << CS47L63_ASP1_DOUT_HIZ_CTRL_SHIFT) // Always 0 (matches above)
    		},
    		// Enable the various channels for RX and TX
    		{
    			CS47L63_ASP1_ENABLES1,				   // 0x10001 (no match)
    			(1 << CS47L63_ASP1_RX2_EN_SHIFT) |	   // Disabled
    				(1 << CS47L63_ASP1_RX1_EN_SHIFT) | // Enabled
    				(1 << CS47L63_ASP1_TX2_EN_SHIFT) | // Disabled
    				(1 << CS47L63_ASP1_TX1_EN_SHIFT)   // Enabled
    		},
    
    #ifdef ENABLE_MIC
    		// Enable digital MIC
    		/* Set MICBIASes */
    		{CS47L63_LDO2_CTRL1, 0x0005},	 // p165, 0b00000101 -> Bit 0, LDO2_EN=1, Bit 2, LDO2_DISCH=1, Voltage = 0x0 (default) 2.4V
    		{CS47L63_MICBIAS_CTRL1, 0x00EC}, // p166, 0b11101100 -> Bit 2, MICB1_DISCH=1, discharge when disable, (default = 1)
    										 //                     Bit 3, MICB1_RATE=1, 1 = Pop-free start-up/shutdown (default = 0)
    										 //                     Bit 5-7 = 0x7 = 2.2V (default)
    										 // We only need MICB1B related settings.
    										 //{ CS47L63_MICBIAS_CTRL5, 0x0272 },  // p166, 0b0010 0111 0010 -> Bit 1 MICB1A_DISCH=1, discharge when disable (default)
    										 //                        -> Bit 4 MICB1B_EN=1
    										 //                        -> Bit 5 MICB1B_DISCH=1, discharge when disable (default)
    										 //                        -> Bit 6 MICB1B_SRC=1 = VDD_A (default 0, MICBIAS regulator)
    										 //                        -> Bit 9 MICB1C_DISCH=1, discharge when disable (default)
    
    		{CS47L63_MICBIAS_CTRL5, 0x0070}, // Only set Bit 4,5,6 fo MICB1B = PDM Mic
    		/* Enable IN1L */
    		//{ CS47L63_INPUT_CONTROL, 0x000F },  // p29, 0b00001111 -> Bit 0, IN1R_EN,
    		//                    Bit 1, IN1L_EN,
    		//                    Bit 2, IN2R_EN,
    		//                    Bit 3, IN2L_EN
    
    		{CS47L63_INPUT_CONTROL, 0x0002}, // The PDM only needs IN1L_EN -> Bit 1 set
    
    		/* Enable PDM mic as digital input */
    		{CS47L63_INPUT1_CONTROL1, 0x50021}, // p31, 0b0101 00000000 0010 0001 -> Bit 0, Input Path 1 Mode=Digital input
    											//                                -> Bit 5, fixed at according to p181? (see also p30 "Note")
    											//                                -> Bit 16-18 Input Path 1 Oversample control, p27
    											//                                   = 0b101 = 3.072 MHz, controls IN1_PDMCLK freq
    
    		/* Un-mute and set gain to 0dB */
    		{CS47L63_IN1L_CONTROL2, 0x800080}, // p31 & p34, p181 0b1000 0000 0000 0000 1000 0000
    										   // Bit 1-7 = 0x40 = 0dB Input Path 1L PGA Volume (analog only)
    										   // Bit 16-23 = 0x80 = default 0dB Input Path 1L Digial volume
    										   // Bit 28 = 0 (default 1), unmute
    
    		// We don´t actually need the settings for IN1R for PDM MIC
    		//{ CS47L63_IN1R_CONTROL2, 0x800080 },  // p31 & p34, p181 0b1000 0000 0000 0000 1000 0000
    		// Bit 1-7 = 0x40 = 0dB Input Path 1R PGA Volume (analog only)
    		// Bit 16-23 = 0x80 = default 0dB Input Path 1R Digial volume
    		// Bit 28 = 0 (default 1), unmute
    
    		/* Volume Update */
    		{CS47L63_INPUT_CONTROL3, 0x20000000}, // p181, set IN_VU to 1
    
    		/* Send PDM MIC to I2S Tx */
    		{CS47L63_ASP1TX1_INPUT1, 0x800010},
    		// We don´t actually need IN1R but nvm
    		{CS47L63_ASP1TX2_INPUT1, 0x800011},
    #endif
    
    		// Output 1 left/right (reduced MIX_VOLs to prevent clipping summed signals)
    		{
    			CS47L63_OUT1L_INPUT1,
    			(0x40 << CS47L63_OUT1LMIX_VOL1_SHIFT) | // 0x2b = -21dB, 0x40 = 0dB, 0x2e=-18dB, 0x28=-24dB
    				(0x020 << CS47L63_OUT1L_SRC1_SHIFT) // ASP1_RX1 // from MCU (currently a sine wave only)
    		},
    
    		{
    			CS47L63_OUT1L_INPUT2,
    			(0x40 << CS47L63_OUT1LMIX_VOL2_SHIFT) | // 0x2b = -21dB, 0x40 = 0dB, 0x2e=-18dB, 0x28=-24dB
    				(0x021 << CS47L63_OUT1L_SRC2_SHIFT) // ASP1_RX2
    		},
    
    		{
    			CS47L63_OUTPUT_ENABLE_1,
    			(1 << CS47L63_OUT1L_EN_SHIFT) // Enabled
    		},
    };
    
    /**
     * @brief       Write a configuration array to multiple CS47L63 registers.
     *
     * @param[in]   config: Array of address/data pairs.
     * @param[in]   length: Number of registers to write.
     *
     * @retval      `CS47L63_STATUS_OK`     The operation was successful.
     * @retval      `CS47L63_STATUS_FAIL`   Writing to the control port failed.
     */
    static int nrfadk_hwcodec_config(const uint32_t config[][2], uint32_t length)
    {
    	int ret;
    	uint32_t addr;
    	uint32_t data;
    
    	for (int i = 0; i < length; i++)
    	{
    		addr = config[i][0];
    		data = config[i][1];
    
    		ret = cs47l63_write_reg(&cs47l63_driver, addr, data);
    		if (ret)
    			return ret;
    	}
    
    	return CS47L63_STATUS_OK;
    }
    
    // Buggy configuration - leaves ASP2 unconfigured in default GPIO mode
    const uint32_t GPIO_configuration[][2] = {
    	{CS47L63_GPIO6_CTRL1, 0x61000001}, // ASP2
    	{CS47L63_GPIO7_CTRL1, 0x61000001}, // ASP2
    	{CS47L63_GPIO8_CTRL1, 0x61000001}, // ASP2
    
    	/* Enable CODEC LED */
    	{CS47L63_GPIO10_CTRL1, 0x41008001},
    };
    
    /**
     * @brief       Initialize the CS47L63, start clocks, and configure subsystems.
     *
     * @details     MCLK1 =   6,144,000 Hz  (I2S MCK = CONFIG.MCKFREQ)
     *              FSYNC =      48,000 Hz  (I2S LRCK = MCK / CONFIG.RATIO)
     *              BCLK =    1,536,000 Hz  (I2S SCK = LRCK * CONFIG.SWIDTH * 2)
     *              FLL1 =   49,152,000 Hz  (MCLK1 * 8)
     *              SYSCLK = 98,304,000 Hz  (FLL1 * 2)
     *
     * @retval      `CS47L63_STATUS_OK`     The operation was successful.
     * @retval      `CS47L63_STATUS_FAIL`   Initializing the CS47L63 failed.
     *
     * @note        I2S MCK must already be running before calling this function.
     */
    static int nrfadk_hwcodec_init(void)
    {
    	int ret = CS47L63_STATUS_OK;
    
    	// Initialize driver
    	ret += cs47l63_comm_init(&cs47l63_driver);
    
    	// Start FLL1 and SYSCLK
    	ret += cs47l63_fll_config(&cs47l63_driver, CS47L63_FLL1,
    							  CS47L63_FLL_SRC_MCLK1, 6144000, 49152000);
    
    	ret += cs47l63_fll_enable(&cs47l63_driver, CS47L63_FLL1);
    
    	ret += cs47l63_fll_wait_for_lock(&cs47l63_driver, CS47L63_FLL1);
    
    	ret += cs47l63_update_reg(&cs47l63_driver, CS47L63_SYSTEM_CLOCK1,
    							  CS47L63_SYSCLK_EN_MASK, CS47L63_SYSCLK_EN);
    
    	// nrfadk_hwcodec_config(clock_configuration, ARRAY_SIZE(clock_configuration));
    	nrfadk_hwcodec_config(GPIO_configuration, ARRAY_SIZE(GPIO_configuration));
    
    	// Configure subsystems
    	ret += nrfadk_hwcodec_config(cs47l63_cfg, ARRAY_SIZE(cs47l63_cfg));
    
    	return ret;
    }
    
    ////////////////////////////////////////////////////////////////////////////////
    // MAIN
    
    int main(void)
    {
    	// Initialize Audio DK
    
    	// Initialize Audio DK
    	const struct device *const i2s_dev_rx = DEVICE_DT_GET(I2S_RX_NODE);
    	const struct device *const i2s_dev_tx = DEVICE_DT_GET(I2S_TX_NODE);
    	struct i2s_config config;
    
    	if (nrfadk_hfclocks_init() ||
    		nrfadk_i2s_reg_init() ||  // Original initializaiton. As soon as I remove this line the initialization in nrfadk_hwcodec_init() will fail!
    		nrfadk_i2s_reg_init_new(i2s_dev_rx, i2s_dev_tx, config) || // 
    		nrfadk_hwcodec_init())
    	{
    		printk("\nError initializing Audio DK\n");
    		return -1;
    	}
    
    	printk("\nAudio DK initialized\n");
    	k_msleep(1250);
    
    	// ***********************************
    	//
    	//
    	// Code below here is not important yet
    	//
    	// *************************************
    
    	// Unmute OUT1L I2S playback and enable NOISE/TONE1 generators
    
    	cs47l63_update_reg(&cs47l63_driver, CS47L63_OUT1L_VOLUME_1,
    					   CS47L63_OUT_VU_MASK | CS47L63_OUT1L_MUTE_MASK,
    					   CS47L63_OUT_VU | 0);
    
    	i2s_polling_loop();
    	// We never return!
    
    	cs47l63_update_reg(&cs47l63_driver, CS47L63_OUT1L_VOLUME_1,
    					   CS47L63_OUT_VU_MASK | CS47L63_OUT1L_MUTE_MASK,
    					   CS47L63_OUT_VU | CS47L63_OUT1L_MUTE);
    
    	printk("OUT1L muted\n");
    	k_msleep(1250);
    
    	// Shutdown Audio DK (reverse order initialized)
    
    	cs47l63_update_reg(&cs47l63_driver, CS47L63_OUTPUT_ENABLE_1,
    					   CS47L63_OUT1L_EN_MASK, 0);
    	printk("\nOUT1L disabled\n");
    	k_msleep(250);
    
    	cs47l63_update_reg(&cs47l63_driver, CS47L63_SYSTEM_CLOCK1,
    					   CS47L63_SYSCLK_EN_MASK, 0);
    	printk("SYSCLK disabled\n");
    	k_msleep(250);
    
    	cs47l63_fll_disable(&cs47l63_driver, CS47L63_FLL1);
    	printk("FLL1 disabled\n");
    	k_msleep(250);
    
    	NRF_I2S0->TASKS_STOP = I2S_TASKS_STOP_TASKS_STOP_Trigger;
    	NRF_I2S0->ENABLE = I2S_ENABLE_ENABLE_Disabled;
    	printk("I2S disabled\n");
    	k_msleep(250);
    
    	nrfx_clock_stop(NRF_CLOCK_DOMAIN_HFCLKAUDIO);
    	while (nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLKAUDIO, NULL))
    		k_msleep(1);
    	printk("HFCLKAUDIO stopped\n");
    	k_msleep(250);
    
    	nrfx_clock_stop(NRF_CLOCK_DOMAIN_HFCLK);
    	while (nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLK, NULL))
    		k_msleep(1);
    	printk("HFCLK stopped\n");
    	k_msleep(250);
    
    	printk("\nAudio DK shutdown\n\n");
    	k_msleep(100);
    
    	return 0;
    }
    

     

  • While looking for a potential solution I stumbled over  I2S: configuring clock details with Zephyr and am confused. Does this mean I cannot use a Zephyr only approach to talk to the codec on the Audio DK but need to use the nrfx_i2s driver variant? 

  • Hi,

    I have discussed your approach with developers. There are several things you should consider:
    1. no polling is needed when you work with I2S because there is a callback which is automatically triggered when there is more I2S data for sending
    2. there is stereo input from the line-in jack
    3. the use of higher-level API is recommended
    4. consider using LOG_ instead of printk
    5. when you work with tx and rx buffers, there needs to be intermediate buffer as I2S works on tx and rx buffers at the same time

    Best regards,
    Dejan

Related