I2S configuration error when running code.

Good day! My code is adapted from the i2s echo example. I removed all the code regarding the button click and codec because it is not needed in my application. Below is the code. When i run the code, i get errors on configuring the Rx stream. 

/*
 * Copyright (c) 2021 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/drivers/i2s.h>
#include <zephyr/drivers/gpio.h>
#include <string.h>
#include <zephyr/drivers/pwm.h>


#define I2S_RX_NODE  DT_NODELABEL(i2s_rx)

#define SAMPLE_FREQUENCY    62500
#define SAMPLE_BIT_WIDTH    32
#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

// PWM DEFINES
static const struct pwm_dt_spec pwm_led0 = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0));
static const struct pwm_dt_spec pwm_led1 = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led1));

#define BITCLK PWM_KHZ(4000U)
#define WORD_S PWM_HZ(62500U)

#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 int16_t echo_block[SAMPLES_PER_BLOCK];
static volatile bool echo_enabled = true;
static K_SEM_DEFINE(toggle_transfer, 1, 1);


static void process_block_data(void *mem_block, uint32_t number_of_samples)
{
	static bool clear_echo_block;

	if (echo_enabled) {
		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;
		}

		clear_echo_block = true;
	} else if (clear_echo_block) {
		clear_echo_block = false;
		memset(echo_block, 0, sizeof(echo_block));
	}
}

static bool configure_streams(const struct device *i2s_dev_rx,
			      const struct i2s_config *config)
{
	int ret;

	ret = i2s_configure(i2s_dev_rx, I2S_DIR_RX, config);
	if (ret < 0) {
		printk("Failed to configure RX stream: %d\n", ret);
		return false;
	}

	return true;
}

static bool prepare_transfer(const struct device *i2s_dev_rx)
{
	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);
	}

	return true;
}

static bool trigger_command(const struct device *i2s_dev_rx,
			    enum i2s_trigger_cmd cmd)
{
	int ret;

	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;
	}

	return true;
}

int main(void)
{
	uint32_t bclk_period;
	uint32_t ws_period;
	int ret1;
	int ret2;

	printk("Starting main thread\n\r");
	printk("PWM-based blinky\n");

	if (!pwm_is_ready_dt(&pwm_led0)) {
		printk("Error: PWM device %s is not ready\n",
		       pwm_led0.dev->name);
		return 0;
	}

	if (!pwm_is_ready_dt(&pwm_led1)) {
		printk("Error: PWM device %s is not ready\n",
		       pwm_led0.dev->name);
		return 0;
	}

	/*
	 * In case the default MAX_PERIOD value cannot be set for
	 * some PWM hardware, decrease its value until it can.
	 *
	 * Keep its value at least MIN_PERIOD * 4 to make sure
	 * the sample changes frequency at least once.
	 */

	bclk_period = BITCLK;
	while (pwm_set_dt(&pwm_led0, bclk_period, bclk_period / 2U)) {
		bclk_period /= 2U;
		printk("half bclk");
	}

	ws_period = WORD_S;
	while (pwm_set_dt(&pwm_led1, ws_period, ws_period / 2U)) {
		ws_period /= 2U;
		printk("half ws");
	}

	ws_period = WORD_S;
	bclk_period = BITCLK;

	const struct device *const i2s_dev_rx = DEVICE_DT_GET(I2S_RX_NODE);
	struct i2s_config config;

	printk("I2S echo sample\n");


	if (!device_is_ready(i2s_dev_rx)) {
		printk("%s is not ready\n", i2s_dev_rx->name);
		return 0;
	}


	config.word_size = SAMPLE_BIT_WIDTH;
	config.channels = NUMBER_OF_CHANNELS;
	config.format = I2S_FMT_DATA_FORMAT_I2S;
	config.options = I2S_OPT_BIT_CLK_SLAVE | I2S_OPT_FRAME_CLK_SLAVE;
	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, &config)) {
		return 0;
	}

	for (;;) {
		k_sem_take(&toggle_transfer, K_FOREVER);

		if (!prepare_transfer(i2s_dev_rx)) {
			return 0;
		}

		if (!trigger_command(i2s_dev_rx,
				     I2S_TRIGGER_START)) {
			return 0;
		}

		printk("Streams started\n");

		while (k_sem_take(&toggle_transfer, K_NO_WAIT) != 0) {
			void *mem_block;
			uint32_t block_size;
			int ret;

			ret = i2s_read(i2s_dev_rx, &mem_block, &block_size);
			if (ret < 0) {
				printk("Failed to read data: %d\n", ret);
				break;
			}

			process_block_data(mem_block, SAMPLES_PER_BLOCK);
		}

		if (!trigger_command(i2s_dev_rx,
				     I2S_TRIGGER_DROP)) {
			return 0;
		}

		printk("Streams stopped\n");
	}

	while (1) {
		ret1 = pwm_set_dt(&pwm_led0, bclk_period, bclk_period / 2U);
		if (ret1) {
			printk("Error %d: failed to set pulse width\n", ret1);
			return 0;
		}

		ret2 = pwm_set_dt(&pwm_led1, ws_period, ws_period / 2U);
		if (ret2) {
			printk("Error %d: failed to set pulse width\n", ret2);
			return 0;
		}
	}
}

overlay

/*
 * Copyright (c) 2021 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

&pinctrl {

	pwm0_custom: pwm0_custom {
        group1 {
            psels = <NRF_PSEL(PWM_OUT0, 0, 3)>;
            nordic,invert;
        };
    };

    pwm0_csleep: pwm0_csleep {
        group1 {
            psels = <NRF_PSEL(PWM_OUT0, 0, 3)>;
            low-power-enable;
        };
    };
	pwm1_custom: pwm1_custom {
        group1 {
            psels = <NRF_PSEL(PWM_OUT0, 0, 28)>;
            nordic,invert;
        };
    };

    pwm1_csleep: pwm1_csleep {
        group1 {
            psels = <NRF_PSEL(PWM_OUT0, 0, 28)>;
            low-power-enable;
        };
    };
	i2s0_default_alt: i2s0_default_alt {
		group1 {
			psels = <NRF_PSEL(I2S_SDIN, 0, 29)>;
		};
	};
	i2s0_sleep: i2s0_sleep {
		group1 {
			psels = <NRF_PSEL(I2S_SDIN, 0, 29)>;             
			low-power-enable;
		};
	};
};


i2s_rx: &i2s0 {
	status = "okay";
	pinctrl-0 = <&i2s0_default_alt>;
	pinctrl-names = "default";
};

&pwm0 {
    status = "okay";
    pinctrl-0 = <&pwm0_custom>;
    pinctrl-1 = <&pwm0_csleep>;
    pinctrl-names = "default", "sleep";
};

&pwm1 {
    status = "okay";
    pinctrl-0 = <&pwm1_custom>;
    pinctrl-1 = <&pwm1_csleep>;
    pinctrl-names = "default", "sleep";
};

/{
    pwmleds {
        compatible = "pwm-leds";
        pwm_led0: pwm_led_0 {
            pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
        };
		pwm_led1: pwm_led_1 {
            pwms = <&pwm1 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
        };
    };
	aliases {
		pwm-led0 = &pwm_led0;
		pwm-led1 = &pwm_led1;
	
	};
};

  • Does the SPH0645 need to be master? I read somewhere that nRF I2S slave just means it needs an outside source for the SCK and LRCLK for it to work, hence why I am using PWM outputs on GPIOs to act as clock. I am using this and this as my references. 

    To be more precise I am using I2S slave on the nRF with PWMs as external clock while also having the microphone (SPH0645) in slave mode. 

  • Hello, can you please confirm if what I said is right thank you

  • Hi Daniel

    Sorry for the slow response, many of us where out in Easter holiday last week. 

    Thanks for providing links to the blog you followed, now I understand better what you are trying to do. 

    I agree that this method should be viable, but this is not something we have tested internally. 

    Do you make all the external connections as described in the readme of the github repo, to wire the two PWM signals to the I2S SCK and LRCLK pins? 

    It seems you don't configure the SCK and LRCLK I2S pins in your overlay. Are you sure that these pins are set correctly? 

    Again a scope trace would be useful, to verify that the signals going into the nRF I2S lines is correct. 

    Best regards
    Torbjørn

  • Hello, I adjusted my pinctrl in the overlay to configure the SCK and LRCLK for slave mode. I also connected wire lines to follow this 

     

    &pinctrl {
    
    	pwm0_custom: pwm0_custom {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 0, 3)>;
                nordic,invert;
            };
        };
    
        pwm0_csleep: pwm0_csleep {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 0, 3)>;
                low-power-enable;
            };
        };
    	pwm1_custom: pwm1_custom {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 0, 28)>;
                nordic,invert;
            };
        };
    
        pwm1_csleep: pwm1_csleep {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 0, 28)>;
                low-power-enable;
            };
        };
    	i2s0_default_alt: i2s0_default_alt {
    		group1 {
    			psels = <NRF_PSEL(I2S_SDIN, 0, 29)>, <NRF_PSEL(I2S_SCK_S, 1, 15)>, <NRF_PSEL(I2S_LRCK_M, 1, 14)>;
    		};
    	};
    	i2s0_sleep: i2s0_sleep {
    		group1 {
    			psels = <NRF_PSEL(I2S_SDIN, 0, 29)>, <NRF_PSEL(I2S_SCK_S, 1, 15)>, <NRF_PSEL(I2S_LRCK_M, 1, 14)>;
    			low-power-enable;
    		};
    	};
    };

    I'll try to use an oscilloscope ASAP to check for signals. BTW if it matters, the most common errors I get is on the i2s_read() part where it outputs -5 and sometimes -11.

    Last question, how can I extract the audio data from the i2s memory to send it to another device using BLE? Thank you for your response!

  • Btw, if you are asking me to probe the PWM signals using oscilloscope I already have results on that. It showed correct frequency and good 50 duty cycle. The Frequency of CH1 signal is 4MHz if zoomed in. It just shows 5MHz because of zoom out.

Related