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

Parents
  • Hello,

    I am not familiar with the board in question, but unless there are a third party interface MCU that handle the programming through the SWD interface, then it's likely a serial bootloader involved (e.g. UART, USB etc), and when updating from bootloader it typically will involve reset the board between firmware updates, that commonly will also need the PC to re-enumrate the USB descriptors for possible changes, so this may cause what you see. For development getting development board with an interface MCU (e.g. nRF52840-DK) can be a good idea. In any case it may sound like you should reach out to the module manufacturer for help in this case.

    Kenneth

Reply
  • Hello,

    I am not familiar with the board in question, but unless there are a third party interface MCU that handle the programming through the SWD interface, then it's likely a serial bootloader involved (e.g. UART, USB etc), and when updating from bootloader it typically will involve reset the board between firmware updates, that commonly will also need the PC to re-enumrate the USB descriptors for possible changes, so this may cause what you see. For development getting development board with an interface MCU (e.g. nRF52840-DK) can be a good idea. In any case it may sound like you should reach out to the module manufacturer for help in this case.

    Kenneth

Children
Related