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