I2S for Seeed Studio Xiao nRF52840 using SPH0645

Hello, I am trying to configure our board seeed studio xiao nRF52840 with a MEMS microphone Knowles SPH0645. Since the microphone needs 32 bit SWIDTH, i tried creating 2 PWM signals to emulate the SCK and LRCLK for 32 bits because nrf52840 cannot do 32 bits in master mode. So far I have used examples from blinky pwm and i2s echo. The current code looks like this: 

/*
 * Copyright (c) 2016 Intel Corporation
 * Copyright (c) 2020 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file Sample app to demonstrate PWM.
 */

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/device.h>


#include <zephyr/drivers/pwm.h>
#include <zephyr/drivers/i2s.h>
#include <zephyr/drivers/gpio.h>

#include <string.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_ctrl.h>

#define MODULE main

LOG_MODULE_REGISTER(MODULE, 3);

#define MIC_CTRL  2
#define I2S_RX_NODE         DT_NODELABEL(i2s_rx)
#define SAMPLE_FREQUENCY 44100
#define SAMPLE_BIT_WIDTH 24
#define BYTES_PER_SAMPLE sizeof(int32_t)
#define NUMBER_OF_CHANNELS 2
#define SAMPLES_PER_BLOCK ((SAMPLE_FREQUENCY / 10) * NUMBER_OF_CHANNELS)
#define INITIAL_BLOCKS 2
#define TIMEOUT 1000

/* Size of a block for 100 ms of audio data. */
#define BLOCK_SIZE(_sample_rate, _number_of_channels) \
    (BYTES_PER_SAMPLE * (_sample_rate / 10) * _number_of_channels)

/* Driver will allocate blocks from this slab to receive audio data into them.
 * Application, after getting a given block from the driver and processing its
 * data, needs to free that block.
 */
#define MAX_BLOCK_SIZE   BLOCK_SIZE(SAMPLE_FREQUENCY, 2)
#define BLOCK_COUNT      5
K_MEM_SLAB_DEFINE_STATIC(mem_slab, MAX_BLOCK_SIZE, BLOCK_COUNT, 4);

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_led0));

//#define MAX_PERIOD1 PWM_HZ(4096000U)
#define MAX_PERIOD1 PWM_SEC(1U)
#define MIN_PERIOD1 PWM_SEC(1U) / 128U

// #define MAX_PERIOD2 PWM_HZ(64000U)
#define MAX_PERIOD2 PWM_HZ(10U)
#define MIN_PERIOD2 PWM_SEC(1U) / 128U

// LOG_MODULE_REGISTER(Recording, LOG_LEVEL_DBG);

K_THREAD_STACK_DEFINE(recording_thread_stack, 20000);

static const struct device *mic = DEVICE_DT_GET(I2S_RX_NODE);

static K_SEM_DEFINE(enable_recording, 1, 1);
static bool recording_active=false;

static struct k_thread recording_thread;
static k_tid_t worker_thread_id ;

void mic_worker_thread(void *p1, void *p2, void *p3) ;
int ret;

void recording_init() {
    if (!device_is_ready(mic)) {
        printk("Microphone device is not supported : %s", mic->name);
        return;
    }

    struct i2s_config config = {
        .word_size= SAMPLE_BIT_WIDTH,
        .channels = NUMBER_OF_CHANNELS,
        .format = I2S_FMT_DATA_FORMAT_I2S,
        .options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER,
        .frame_clk_freq = SAMPLE_FREQUENCY, /* Sampling rate */
        .mem_slab = &mem_slab,/* Memory slab to store rx/tx data */
        .block_size = MAX_BLOCK_SIZE,/* size of memory buffer in bytes */
        .timeout = TIMEOUT, /* Number of milliseconds to wait in case Tx queue is full or RX queue is empty, or 0, or SYS_FOREVER_MS */
    };

int err = i2s_configure(mic, I2S_DIR_RX, &config);
    i2s_trigger(mic, I2S_DIR_RX, I2S_TRIGGER_PREPARE);
    k_sleep(K_MSEC(100));
    if (err < 0) {
        LOG_ERR("Failed to initialize Microphone (%d)", err);
        return;
    }
    k_sem_take(&enable_recording, K_FOREVER);

    printk("Recording module initialized");
    worker_thread_id = k_thread_create(&recording_thread, recording_thread_stack,
                    K_THREAD_STACK_SIZEOF(recording_thread_stack),
                    mic_worker_thread,
                    NULL, NULL, NULL,
                    4, 0, K_NO_WAIT);
}

void start_recording() {
    recording_active = true;
    // Initialize file for recording.
    int ret = i2s_trigger(mic, I2S_DIR_RX, I2S_TRIGGER_START);
    if (ret) {
        LOG_ERR("Unable to configure trigger start for I2S bus (%d)", ret);
        return;
    }
    k_sem_give(&enable_recording);
}

void stop_recording() {
    recording_active = false;
    printk("Stopping recording");
    int ret = i2s_trigger(mic, I2S_DIR_RX, I2S_TRIGGER_STOP);
    if (ret) {
        LOG_ERR("Unable to stop trigger for I2S bus (%d)", ret);
    }
    k_sem_take(&enable_recording, K_FOREVER);
}

void mic_worker_thread(void *p1, void *p2, void *p3) {
    printk("Worker thread started");
    void* rx_buffer;
    size_t bytes_read;
    while (k_sem_take(&enable_recording, K_FOREVER) == 0) {
            bytes_read=0;
            int ret=0;
            if  (k_mem_slab_alloc(&mem_slab, &rx_buffer, K_MSEC(200)) == 0) {
                ret = i2s_read(mic, &rx_buffer, &bytes_read);
                if (ret < 0) {
                    if ( ret != -5) {
                        printk("Worker thread error (%d)\r\n", ret);
                    }
                } else {
                    printk(" raw rx: %d - Received %d bytes => %d samples ", ((uint32_t *)rx_buffer)[0], bytes_read, bytes_read / sizeof(uint32_t));
                }
                k_mem_slab_free(&mem_slab, &rx_buffer);
            }
        k_sem_give(&enable_recording);

    }
    printk("Worker thread ended");
}

int main(void)
{
	uint32_t period1;
	uint32_t max_period1;  
	int ret1;

	uint32_t period2;
	uint32_t max_period2;
	int ret2;

	printk("Starting main thread\n\r");
  	recording_init();
  	const struct device *mic_ctrl_dev = device_get_binding("GPIO_0");
    gpio_pin_configure(mic_ctrl_dev,MIC_CTRL,GPIO_OUTPUT);
   
   	int pin = gpio_pin_set(mic_ctrl_dev,MIC_CTRL,1);
   	printk("%d", pin);

	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",
            	pwm_led1.dev->name);
    	return 0;
	}

	max_period1 = MAX_PERIOD1;
	max_period2 = MAX_PERIOD2;


	/*
	 * 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.
	 */
	printk("Calibrating for channel %d...\n", pwm_led0.channel);
	period1 = MAX_PERIOD1;
	period2 = MAX_PERIOD2;

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

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

		printk("Print test smooth\n");

		printk("Start recording again\r\n");
		start_recording();
		k_sleep(K_MSEC(2000));
		printk("Stop recording again\r\n");
		stop_recording();
		k_sleep(K_MSEC(2000));
	}

	return 0;
}

and the overlay file looks like this:

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

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

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

&sw_pwm {
	status = "fail";
	channel-gpios = <&gpio0 13 PWM_POLARITY_INVERTED>;
};

&pinctrl {
	pwm0_default: pwm0_default {
		group1 {
			psels = <NRF_PSEL(PWM_OUT0, 0, 3)>;
			nordic,invert;
		};
	};
    
	pwm0_sleep: pwm0_sleep {
		group1 {
			psels = <NRF_PSEL(PWM_OUT0, 0, 3)>;
			low-power-enable;
		};
	};

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

	pwm1_sleep: pwm1_sleep {
		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";
};

This code builds but when I transfer the uf2 in the seeed studio xiao bootloader mode, the board disappears from the COM ports which makes it hard for me to debug the code. I also don't fully understand the i2s code yet, if someone can help it would be much appreciated. Thank you

Related