Cannot build I2S echo application for NRF5340

Hi, I am a beginner into the embedded audio space, and happened upon this board to use for our university project. Our team's current plan would be to produce audio directly on the board and play it through the headphone jack, but have not found much success with the NRF5340 Audio application, as it has a lot of integration with Bluetooth (which we don't need for now). Right now we just need a simple application that plays a tune on the board.

In my search I have found the I2S echo application, which seems like a simple start for our use case. However, when trying to build the application we encountered the following error:

-- Found BOARD.dts: C:/ncs/v2.6.2/zephyr/boards/arm/nrf5340_audio_dk_nrf5340/nrf5340_audio_dk_nrf5340_cpuapp.dts
-- Found devicetree overlay: boards/nrf5340dk_nrf5340_cpuapp.overlay
devicetree error: pinctrl-names property in /soc/peripheral@50000000/i2s@28000 in C:/ncs/v2.6.2/zephyr/misc/empty_file.c has 1 strings, expected 2 strings

This is our build configuration (all other options left as default):

SDK version:

Any help would be appreciated! Thanks!

  • Hi  ,  ,  Thanks for all the help so far! I have been poking around with using audio_i2s_start()audio_i2s_blk_comp_cb_register(), and audio_i2s_stop() for continuous I2S audio streaming, and I've arrived at the following sample:

    #include <zephyr/kernel.h>
    #include <nrfx_clock.h>
    #include <audio_i2s.h>
    #include <macros_common.h>
    #include <hw_codec.h>
    #include <tone.h>
    
    #include <zephyr/logging/log.h>
    LOG_MODULE_REGISTER(main, CONFIG_MAIN_LOG_LEVEL);
    
    #define CONFIG_LOGGING
    
    #define BLK_PERIOD_US 1000
    
    /* Number of audio blocks given a duration */
    #define NUM_BLKS(d) ((d) / BLK_PERIOD_US)
    /* Single audio block size in number of samples (stereo) */
    /* clang-format off */
    #define BLK_SIZE_SAMPLES(r) (((r)*BLK_PERIOD_US) / 1000000)
    
    #define NUM_BLKS_IN_FRAME      NUM_BLKS(CONFIG_AUDIO_FRAME_DURATION_US)
    #define BLK_MONO_NUM_SAMPS     BLK_SIZE_SAMPLES(CONFIG_AUDIO_SAMPLE_RATE_HZ)
    
    #define BLK_MONO_SIZE_OCTETS   (BLK_MONO_NUM_SAMPS * CONFIG_AUDIO_BIT_DEPTH_OCTETS)
    
    static uint8_t *tx_buf;
    static uint32_t *rx_buf;
    
    /* Alternate-buffers used when there is no active audio stream.
     * Used interchangeably by I2S.
     */
    static struct {
    	uint8_t __aligned(WB_UP(1)) buf_0[BLK_MONO_SIZE_OCTETS];
    	uint8_t __aligned(WB_UP(1)) buf_1[BLK_MONO_SIZE_OCTETS];
    	bool buf_0_in_use;
    	bool buf_1_in_use;
    } alt;
    
    /**
     * @brief	Get first available alternative-buffer.
     *
     * @param	p_buffer	Double pointer to populate with buffer.
     *
     * @retval	0 if success.
     * @retval	-ENOMEM No available buffers.
     */
    static int alt_buffer_get(void **p_buffer)
    {
    	if (!alt.buf_0_in_use) {
    		alt.buf_0_in_use = true;
    		*p_buffer = alt.buf_0;
    	} else if (!alt.buf_1_in_use) {
    		alt.buf_1_in_use = true;
    		*p_buffer = alt.buf_1;
    	} else {
    		return -ENOMEM;
    	}
    
    	return 0;
    }
    
    /**
     * @brief	Frees both alternative buffers.
     */
    static void alt_buffer_free_both(void)
    {
    	alt.buf_0_in_use = false;
    	alt.buf_1_in_use = false;
    }
    
    static void i2s_block_complete_callback()
    {
    	/*** Data exchange ***/
    	audio_i2s_set_next_buf(tx_buf, rx_buf);
    }
    
    int main(void) 
    {
        int ret;
    
    #ifdef CONFIG_LOGGING
        printk("\nLogging I2S Info ...\n");
        printk("\nCONFIG_I2S_LRCK_FREQ_HZ:     %d\n", CONFIG_I2S_LRCK_FREQ_HZ);
        printk("\nCONFIG_I2S_CH_NUM:           %d\n", CONFIG_I2S_CH_NUM);
        printk("\nCONFIG_AUDIO_BIT_DEPTH_BITS: %d\n", CONFIG_AUDIO_BIT_DEPTH_BITS);
    #endif
    
    	/* Use this to turn on 128 MHz clock for cpu_app */
    	ret = nrfx_clock_divider_set(NRF_CLOCK_DOMAIN_HFCLK, NRF_CLOCK_HFCLK_DIV_1);
    	ret -= NRFX_ERROR_BASE_NUM;
    	if (ret) {
    		return ret;
    	}
    
    	audio_i2s_blk_comp_cb_register(i2s_block_complete_callback);
    	audio_i2s_init();
    
    	ret = hw_codec_init();
    	if (ret) {
    		LOG_ERR("Failed to initialize HW codec: %d", ret);
    		return ret;
    	}
    	hw_codec_volume_set(100);
    
    	printk("\nStarting I2S!\n");
    
    	/* Starting I2S */
    
    	/********** I2S TX **********/
    	ret = alt_buffer_get((void **)&tx_buf);
    	ERR_CHK(ret);
    
    	/********** I2S RX **********/
    	ret = alt_buffer_get((void **)&rx_buf);
    	ERR_CHK(ret);
    
    	size_t tone_size;
    	ret = tone_gen((uint16_t *)tx_buf, &tone_size, 1000, CONFIG_I2S_LRCK_FREQ_HZ, 1.0);
    	if (ret || tone_size != BLK_MONO_SIZE_OCTETS) {
    		LOG_ERR("Failed to generate test tone");
    		return ret;
    	}
    
    	ret = hw_codec_default_conf_enable();
    	ERR_CHK(ret);
    	audio_i2s_start(tx_buf, rx_buf);
    	
    	k_msleep(5000);
    
    	ret = hw_codec_soft_reset();
    	ERR_CHK(ret);
    	audio_i2s_stop();
    	
    	alt_buffer_free_both();
    
    	printk("\nI2S Stopped!\n");
    
        return 0;
    }

    It basically re-uses the alt_buffer_get() function to first fetch empty buffers each of size BLK_MONO_SIZE_OCTETS since I do not want two channels. It then fills the tx_buf with a single period of sine wave via tone_gen

    I know that: 

      BLK_MONO_SIZE_OCTETS = 32

      nCONFIG_I2S_LRCK_FREQ_HZ = 16000
     
      nCONFIG_I2S_CH_NUM = 1

      nCONFIG_AUDIO_BIT_DEPTH_BITS = 16
    This means that a single tx_buf (which is of size 32bytes -> 16 samples), when filled with a single sine period, should play to 1000Hz. However, I am hearing 2000Hz when measuring with my device. I'm not sure if I've missed anything or did some steps wrong. I have attached the full project as a zip file if interested (there is a .git file inside so you can track all the changes I've made starting from the nrf5340_audio application). I would really appreciate some pointers and thanks again for your help!

  • For I2S there is one buffer that will supply both left and right interleaved. So it sounds like you are supplying it with only the left one and then the underlying I2S-driver will divide that into two channels, meaning you get half the audio to each channel, hence double the frequency. You can double check this by looking at the I2S with a logic analyzer. An easy way around this is to use the pcm_zero_pad function we made for this. 

  • Here I am sorry that I have to disappoint you  , we do not have any design that uses the DSP features of the Cirrus chip as this is more Cirrus' domain. The DSP features like noise cancellation, beam forming and all the bells and whistles are highly dependent on the layout of the final design and is usually something the end-costumer deals with directly with Cirrus. You could try to reach out to Cirrus directly, but unfortunately I know that their support might be harder to get through than ours. 

  • After some work I've finally been able to put together something. In the code attached I have used audio_i2s_set_next_buf to continuously update the I2S buffer to play a 1000Hz sine wave. I hope this example can be of some help for folks looking for a "hello_world" example coming into NRF5340. Thanks again to  ,  , and  ! I've also attached Johnny's repo here: https://github.com/ace-johnny/nrfadk-hello_codec for anyone that is interested!

    i2s_1000hz.7z

  • Hi Johnny, here is the github repo I've just created: https://github.com/Tom2096/nrfadk-audio_i2s_sine1k

    Some plans I have for this moving forward:

    - Use zephyr ring buffer to supply data into audio_i2s_set_next_buf

    - Play around with setting channels=2 for cleaner code

    - Eventually write a true I2S echo application to echo microphone input  

    - Get rid of all the unnecessary modules/drivers that are not used from the nrf5340_audio application

    Hope you find this repo useful!

Related