nRF5340 I2S microphone in Zephyr

Hello, I'm trying to get an I2S microphone working and have not had any success. I've done my best to review the other I2S related issues for the nrf5340 in Zephyr, but I'm obviously missing something.
Situation: I have an I2S microphone (44.1KHz, 24bit) configured and I'm able to read the data into the first memory block. However, after the first memory block is filled (35280 bytes) the I2S clocks stop and I don't get any more data. I do get IO errors, or failed to allocate next RX Buffer: -ENOMEM errors. I've tried to bump the thread memory, main processor memory, stack size for the thread but I'm missing something. I've duplicated the sample and swapped the I2S microphone with a PDM microphone and it works perfectly fine using the dmic interface, ie clock persists when it's meant to. (Tested to ensure the mem_slab is correctly defined and usable.)

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2s.h>


#define SAMPLE_FREQUENCY 44100
#define SAMPLE_BIT_WIDTH 32
#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);

LOG_MODULE_REGISTER(Recording, LOG_LEVEL_DBG);

K_THREAD_STACK_DEFINE(recording_thread_stack, 20000);

static const struct device *mic = DEVICE_DT_GET(DT_ALIAS(i2s0));


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

void recording_init() {
    if (!device_is_ready(mic)) {
        LOG_INF("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);

    LOG_INF("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;
    LOG_INF("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) {
    LOG_INF("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) {
                        LOG_INF("Worker thread error (%d)\r\n", ret);
                    }
                } else {
                    LOG_INF(" 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);

    }
    LOG_INF("Worker thread ended");
}

void main(){
  LOG_INF("Starting main thread\n\r");
  recording_init();
  
  while(1) {
    LOG_INF("Start recording again\r\n");
    start_recording();
    k_sleep(K_MSEC(2000));
    LOG_INF("Stop recording again\r\n");
    stop_recording();
    k_sleep(K_MSEC(2000));
  }
}

I would like any insight into what I'm missing and how I can get this sample to work. Thank you for your patience and support.

Parents
  • Hi Trevor,

    However, after the first memory block is filled (35280 bytes) the I2S clocks stop and I don't get any more data. I do get IO errors, or failed to allocate next RX Buffer: -ENOMEM errors. I've tried to bump the thread memory, main processor memory, stack size for the thread but I'm missing something.

    The unavailability of TX buffers have nothing to do with the Thread stack size. You need to increase the BLOCK_COUNT and probably BLOCK_SIZE to fit more_in_count/bigger incoming RX data while defining your mem_slab.

  • Thanks for the suggestions Susheel,
    I've already got a block size of 35280 bytes, across 5 blocks. At this point it's impractical for my application to have anything larger when I can use the PDM with significantly smaller blocks. I do want to understand why this isn't working though.
    Do you have any additional suggestions into why I'm getting this error? I understand the stack size is not directly related, I was doing that just in-case there was a temporary buffer for handling the allocations that was being thrown on the stack during the process.

  • Hello.

    Thank you for your patience.

    leadrover said:
    I've already got a block size of 35280 bytes, across 5 blocks.

    According to the documentation for K_MEM_SLAB_DEFINE_STATIC, the block number (slab_num_blocks) needs to be a multiple of the memory slab's buffer (slab_align) to. In your code I can see that slab_num_blocks = 5 and slab_align = 4.
    If you need the boundary alignment, you need to make sure that the block count is a multiple of slab_align.

    Best regards,

    Maria

  • Thanks Maria! Sorry for the delayed response I've been out of office. I'll update my code and see if that resolves the issues.

Reply Children
Related