nRF9161 DK and an I2S microphone

I'm trying to convert some code from ESP32 to nRF, and I'm having problems with the microphone input. Mind you, I'm a beginner with nRF, so I'm sure I'm missing something!

I have this in the prj.conf file:

CONFIG_I2S=y
CONFIG_I2S_NRFX=y


and this nrf9161dk_nrf9161_ns.overlay:
&pinctrl {
    i2s0_default: i2s0_default {
        group1 {
            psels = 
                <NRF_PSEL(I2S_SCK_M, 0, 21)>,  /* BCLK */
                <NRF_PSEL(I2S_SDIN, 0, 22)>,   /* DOUT*/
                <NRF_PSEL(I2S_LRCK_M, 0, 23)>; /* LRCL */
        };
    };
};

&i2s0 {
    compatible = "nordic,nrf-i2s";
    status = "okay";
    pinctrl-0 = <&i2s0_default>;
    pinctrl-names = "default";
};

I can see this in the GPIO overview in VS Code, and I made sure those pins weren't in use. My SPH0645s BCLK is connected to P0.21, DOUT is connected to P0.22, LRCL is connected to P0.23. 

Here's the code, mic_input.c:

#define SAMPLE_RATE 48000
#define BLOCK_TIME_MS 125
#define SAMPLES_PER_BLOCK (SAMPLE_RATE * BLOCK_TIME_MS / 1000)
#define BLOCK_SIZE (SAMPLES_PER_BLOCK * sizeof(int16_t))

K_MEM_SLAB_DEFINE(i2s_rx_slabs, BLOCK_SIZE, 2, 4);
__aligned(4) static int16_t raw_buf[SAMPLE_RATE / 8];

static void mic_thread(void *, void *, void *)
{
    printf("Mic thread started!\n");
    printf("Block size: %d\n", BLOCK_SIZE);
    printf("Buffer size: %d\n", sizeof(raw_buf));

    // Finn I2S-enhet
    i2s_dev = DEVICE_DT_GET(I2S_DEV_NODE);
    __ASSERT(device_is_ready(i2s_dev), "I2S device not ready");
    __ASSERT(i2s_dev, "I2S device not found");

    // Konfigurer I2S
    struct i2s_config cfg = {
        .word_size      = 16,
        .channels       = 1,
        .format         = I2S_FMT_DATA_FORMAT_I2S,
        .options        = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER,
        .frame_clk_freq = 48000,
        .block_size     = 12000, // matcher slab blokk
        .mem_slab       = &i2s_rx_slabs,
        .timeout        = 2000,
    };

    //Configure I2S RX
    int ret = i2s_configure(i2s_dev, I2S_DIR_RX, &cfg);
    if(ret < 0) {
        LOG_ERR("I2S configure failed: %d", ret);
        return;
    }
    printf("i2S configured!\n");

    // Reset og gjør klar buffere
    ret = i2s_trigger(i2s_dev, I2S_DIR_RX, I2S_TRIGGER_PREPARE);
    if (ret < 0) {
        LOG_ERR("I2S trigger prepare failed: %d", ret);
        return;
    }
    printf("i2S prepared!\n");
    
    //Trigger I2S RX
    ret = i2s_trigger(i2s_dev, I2S_DIR_RX, I2S_TRIGGER_START);
    if (ret < 0) {
        printk("I2S trigger start failed: %d\n", ret);
        return;
    }
    printf("i2S triggered!\n");

    k_sleep(K_MSEC(1));   // Vent litt så clocks starter

    printf("Staring I2S RX...\n");
    
    void *mem_block;
    size_t size;
    
    while (true) {
        ret = i2s_read(i2s_dev, &mem_block, &size);
    
        if (ret == 0) {
            // Vi fikk en gyldig buffer!
            memcpy(raw_buf, mem_block, size);
    
            // Nå kan du bruke raw_buf fritt
            printk("First sample: %d\n", ((int32_t *)raw_buf)[0]);
    
            // Release buffer etter bruk!
            //i2s_release(i2s_dev, I2S_DIR_RX);
        }
        else if (ret == -EAGAIN) {
            printk("No audio available, waiting...\n");
            k_sleep(K_MSEC(10));
        }
        else {
            printk("i2s_read() error: %d\n", ret);
            k_sleep(K_MSEC(100));
        }
    }

}

K_THREAD_DEFINE(mic_thread_instance, 2048, mic_thread, NULL, NULL, NULL, 7, 0, 0);

... but I only get:

Mic thread started!
Block size: 12000
Buffer size: 12000
i2S configured!
*** Booting nRF Connect SDK v3.0.0-3bfc46578e42 ***
*** Using Zephyr OS v4.0.99-3e0ce7636fa6 ***
[00:00:00.318,511] <inf> i2s_nrfx: I2S MCK frequency: 1523809, actual PCM rate: 47619
[00:00:00.319,976] <err> mic: I2S trigger prepare failed: -5

Where do I start?

Parents
  • Hi,

    I do not immediately see the problem, but you get -5 (-EIO) returned from i2s_trigger(). Can you debug and step down until you see more precisely where the error comes from?

  • Hei Einar, thanks for getting back to me.

    I get this error:
    Failed to read memory at 0x40028000

    ... but I'm not sure that's what I'm looking for. Stepping in gets me here in the end:

    Do I understand this correctly?
    When I run...

        ret = i2s_trigger(i2s_dev, I2S_DIR_RX, I2S_TRIGGER_PREPARE);

    ... cmd_allowed will be set to FALSE because the state error is not equal to I2S_STATE_ERROR...?
    Should it be?

  • I've made some progress, but nothing that really gives data. I've removed I2S_TRIGGER_PREPARE and of course the code now does... something. But still no valid data.

    #include <zephyr/kernel.h>
    #include <zephyr/device.h>
    #include <zephyr/drivers/i2s.h>
    #include <string.h>
    #include <stdio.h>
    #include <math.h>
    
    #include "mic_input.h"
    #include "filters.h"
    
    #define I2S_DEV_NODE DT_NODELABEL(i2s0)
    
    #define SAMPLE_RATE 48000
    //#define SAMPLE_BITS 24 //in .h file   
    #define BLOCK_TIME_MS 125
    #define CHANNELS 1
    #define SAMPLE_BYTES (SAMPLE_BITS / 8)
    #define SAMPLES_PER_BLOCK ((SAMPLE_RATE * BLOCK_TIME_MS) / 1000)
    #define BLOCK_SIZE (SAMPLES_PER_BLOCK * sizeof(int32_t))
    
    static const struct device *i2s_dev;
    
    __aligned(4) static float work_buf[SAMPLES_PER_BLOCK];
    K_MEM_SLAB_DEFINE(i2s_rx_slabs, BLOCK_SIZE, 4, 4);
    K_MSGQ_DEFINE(samples_msgq, sizeof(sum_queue_t), 8, 4);
    
    bool foundData = false;
    static void mic_thread(void *, void *, void *)
    {
        printf("Mic thread started\n");
    
        i2s_dev = DEVICE_DT_GET(I2S_DEV_NODE);
        __ASSERT(device_is_ready(i2s_dev), "I2S device not ready");
    
        struct i2s_config cfg = {
            .word_size = SAMPLE_BITS,
            .channels = CHANNELS,
            .format = I2S_FMT_DATA_FORMAT_I2S,
            .options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER,
            .frame_clk_freq = SAMPLE_RATE,
            .block_size = BLOCK_SIZE,
            .mem_slab = &i2s_rx_slabs,
            .timeout = 200,
        };
    
        printf("Buffer size: %d\n", BLOCK_SIZE);
    
        int ret = i2s_configure(i2s_dev, I2S_DIR_RX, &cfg);
        if (ret < 0) {
            printf("I2S configure failed: %d\n", ret);
            return;
        }
    
        printf("I2S configured!\n");
    
        ret = i2s_trigger(i2s_dev, I2S_DIR_RX, I2S_TRIGGER_START);
        if (ret < 0) {
            printf("I2S trigger start failed: %d\n", ret);
            return;
        }
    
        printf("I2S triggered!\n");
    
        void *mem_block;
        size_t size;
    
        while (true)
        {
            foundData = false;
            ret = i2s_read(i2s_dev, &mem_block, &size);
    
            if (ret == 0)
            {
                const int32_t *samples = (int32_t *)mem_block;
                int printed = 0;
    
    	    //test if data is found
                for(int i = 0; i < SAMPLES_PER_BLOCK; i++) {
                    if (samples[i] != 0) {
                        foundData = true;
                        printf("Found data in sample[%d] = 0x%08X\n", i, samples[i]);
                        break;
                    }
                }
    
                // print 10 samples
                for (int i = 0; i < SAMPLES_PER_BLOCK && printed < 10; i++) {
                    int32_t s = samples[i];
                    if (s != 0) {
                        printf("sample[%d] = 0x%08X (%d)\n", i, s, s);
                        printed++;
                    }
                }
    
                for (int i = 0; i < SAMPLES_PER_BLOCK; i++) {
                    work_buf[i] = unpack_24bit(samples[i]) / 8388608.0f;  // 2^23
                }
    	    //do something smart with the work_buf[] here
    
                k_mem_slab_free(&i2s_rx_slabs, mem_block);
    
            }
            else if (ret == -EAGAIN)
            {
                printf("No data yet (possible timeout)...\n");
                k_sleep(K_MSEC(10));
            }
            else
            {
                printf("i2s_read() failed: %d\n", ret);
                k_sleep(K_MSEC(100));
            }
        }
    }
    
    K_THREAD_DEFINE(mic_thread_instance, 1024 * 4, mic_thread, NULL, NULL, NULL, 7, 0, 0);

  • Hi,

    This code looks sensible. I assume you have had a look at the I2S echo sample?

    You write that you get no valid data. Does that mean no data at all, or not valid data? How do you determine it? Do you see anything from the logs? Also, what do you see if you check the I2S lines with a logic analyzer (most analyzers should be able to decode I2S)

  • The buffer is just zeros. But other than that, the code runs "fine" now.

    I have seen that sample, but I'll go through it again to see if there's anything I've missed. Tomorrow we'll use an oscilloscope to check the pins to see if clocks and such are working as they should.

  • It seems that the buffer is zeros because the microphone needs an oversampling of 64: WS must be BCLK/64. It seems like the I2S implementation on the nRF9161 isn't capable of that.

    I've tried setting the i2s to be slave instead of master:

        struct i2s_config cfg = {
            .word_size     = SAMPLE_BITS,
            .channels      = CHANNELS,
            .format        = I2S_FMT_DATA_FORMAT_I2S,
            .options       = I2S_OPT_BIT_CLK_SLAVE | I2S_OPT_FRAME_CLK_SLAVE,
            .frame_clk_freq= SAMPLE_RATE,
            .block_size    = BLOCK_SIZE,
            .mem_slab      = &i2s_dma_slab,
            .timeout       = 200,
        };


    with nrf9161dk_nrf9161_ns.overlay:

    &pinctrl {
        /* PWM0.ch0 → BCLK on P0.21 */
        pwm0_ch0: pwm0_bclk {
            group1 { psels = <NRF_PSEL(PWM_OUT0, 0, 21)>; };
        };
    
        /* PWM0.ch0 → LRCLK on P0.23 */
        pwm0_ch1: pwm0_lr {
            group1 {
                psels = <NRF_PSEL(PWM_OUT1, 0, 23)>;
            };
        };
    
        /* I2S-slave: SDIN=22, BCLK=21, LRCLK=23 */
        i2s0_default: i2s0_default {
            group1 {
                psels = <NRF_PSEL(I2S_SDIN,   0, 22)>,
                        <NRF_PSEL(I2S_SCK_S,  0, 21)>,
                        <NRF_PSEL(I2S_LRCK_S, 0, 23)>;
            };
        };
    };
    
    /* Enable both PWM0 and PWM1 (each gets its own prescaler + countertop) */
    &pwm0 {
        status = "okay";
        /* To states: default + sleep */
        pinctrl-names = "default", "sleep";
        pinctrl-0 = <&pwm0_ch0 &pwm0_ch1>;
        pinctrl-1 = <&pwm0_ch0 &pwm0_ch1>;
    };
    
    /* I2S0 in slave mode using the remapped pins */
    &i2s0 {
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&i2s0_default>;
    };
    


    .. and pwm_clocks.c
    #include "pwm_clocks.h"
    #include <zephyr/device.h>
    #include <zephyr/drivers/pwm.h>
    #include <zephyr/sys/util.h>
    #include "mic_input.h" // for SAMPLE_RATE
    
    #define PWM0_NODE DT_NODELABEL(pwm0)
    // #define PWM1_NODE    DT_NODELABEL(pwm1)
    
    #define BCLK_CH 0
    #define LRCLK_CH 1 // 0 for pwm1
    
    static const struct device *pwm_bclk;
    // static const struct device *pwm_lrclk;
    
    void pwm_clocks_init(void)
    {
        /* Acquire PWM devices */
        pwm_bclk = DEVICE_DT_GET(PWM0_NODE);
        // pwm_lrclk = DEVICE_DT_GET(PWM1_NODE);
        __ASSERT(device_is_ready(pwm_bclk), "PWM0 device not ready");
        //__ASSERT(device_is_ready(pwm_lrclk), "PWM1 device not ready");
    
        /* Query source clock rate (cycles per second) */
        uint64_t clk_b;
        uint64_t clk_l;
        int err;
        err = pwm_get_cycles_per_sec(pwm_bclk, BCLK_CH, &clk_b);
        __ASSERT(err == 0, "Failed to get PWM0 clock rate (%d)", err);
        err = pwm_get_cycles_per_sec(pwm_bclk, LRCLK_CH, &clk_l);
        // err = pwm_get_cycles_per_sec(pwm_lrclk, LRCLK_CH, &clk_l);
        __ASSERT(err == 0, "Failed to get PWM1 clock rate (%d)", err);
    
        /* Desired frequencies */
        const uint32_t bclk_hz = SAMPLE_RATE * 64U;
        const uint32_t lrclk_hz = SAMPLE_RATE;
    
        /* Calculate period (ticks) for each PWM */
        uint32_t b_period = (uint32_t)(clk_b / bclk_hz);
        uint32_t lr_period = (uint32_t)(clk_l / lrclk_hz);
    
        /* 50% duty cycle */
        uint32_t b_pulse = b_period / 2U;
        uint32_t lr_pulse = lr_period / 2U;
    
        /* Configure PWM0 for BCLK */
        err = pwm_set_cycles(pwm_bclk, BCLK_CH, b_period, b_pulse, PWM_POLARITY_NORMAL);
        __ASSERT(err == 0, "Failed to set BCLK PWM (%d)", err);
    
        err = pwm_set_cycles(pwm_bclk, LRCLK_CH, lr_period, lr_pulse, PWM_POLARITY_NORMAL);
        __ASSERT(err == 0, "Failed to set LRCLK PWM (%d)", err);
    
        /* Configure PWM1 for LRCLK */
        // err = pwm_set_cycles(pwm_lrclk, LRCLK_CH, lr_period, lr_pulse, PWM_POLARITY_NORMAL);
        // __ASSERT(err == 0, "Failed to set LRCLK PWM (%d)", err);
    
        printk("PWM clocks running: BCLK @ %u Hz, LRCLK @ %u Hz\n",
               bclk_hz, lrclk_hz);
    }
    

    Which gives me:

    <err> pwm_nrfx: Incompatible period.

    Well, thinking perhaps using just one pwm-channel might be too much, I tried using two:

    with nrf9161dk_nrf9161_ns.overlay:

    &pinctrl {
        /* PWM0.ch0 → BCLK on P0.21 */
        pwm0_ch0: pwm0_bclk {
            group1 { psels = <NRF_PSEL(PWM_OUT0, 0, 21)>; };
        };
    
        /* PWM1.ch0 → LRCLK on P0.23 */
        pwm1_lr: pwm1_lr {
                group1 { psels = <NRF_PSEL(PWM_OUT0, 1, 23)>; };
        };
    
        /* I2S-slave: SDIN=22, BCLK=21, LRCLK=23 */
        i2s0_default: i2s0_default {
            group1 {
                psels = <NRF_PSEL(I2S_SDIN,   0, 22)>,
                        <NRF_PSEL(I2S_SCK_S,  0, 21)>,
                        <NRF_PSEL(I2S_LRCK_S, 0, 23)>;
            };
        };
    };
    
    &pwm0 {
            status = "okay";
            pinctrl-names = "default","sleep";
            pinctrl-0 = <&pwm0_ch0>;
            pinctrl-1 = <&pwm0_ch0>;
    };
    &pwm1 {
            status = "okay";
            pinctrl-names = "default","sleep";
            pinctrl-0 = <&pwm1_lr>;
            pinctrl-1 = <&pwm1_lr>;
    };
    
    /* I2S0 in slave mode using the remapped pins */
    &i2s0 {
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&i2s0_default>;
    };
    


    but that won't even start, but returns:
    Mic init complete
    [00:00:00.390,930] <err> os: ***** SECURE FAULT *****
    [00:00:00.390,960] <err> os: Address: 0xc
    [00:00:00.390,960] <err> os: Attribution unit violation
    [00:00:00.390,991] <err> os: r0/a1: 0x00000001 r1/a2: 0x00000017 r2/a3: 0x00000000
    [00:00:00.390,991] <err> os: r3/a4: 0x00800000 r12/ip: 0x00000000 r14/lr: 0x000149d7
    [00:00:00.390,991] <err> os: xpsr: 0x01000000
    [00:00:00.391,021] <err> os: Faulting instruction address (r15/pc): 0x0001474c
    [00:00:00.391,052] <err> os: >>> ZEPHYR FATAL ERROR 41: Unknown error on CPU 0
    [00:00:00.391,082] <err> os: Current thread: 0x2000ce80 (main)
    [00:00:00.453,857] <err> os: Halting system

  • A small update: Using an external clock (like the working ESP32) gives data as we expect! Blush

    We desperately need to find a way to generate the clocks using the nRF9161 alone...

Reply Children
  • Hi,

    Moskus said:
    A small update: Using an external clock (like the working ESP32) gives data as we expect!

    I see, do you mean just clocking the microphone with that, or did you test with the nRF in slave mode using this clock?

    Moskus said:
    It seems that the buffer is zeros because the microphone needs an oversampling of 64: WS must be BCLK/64. It seems like the I2S implementation on the nRF9161 isn't capable of that.

    Can you elaborate on that? From Master clock (MCK) and CONFIG.RATIO that should be OK? Is there any need to use the PWM?

    Regarding the PWM, the "Incompatible period" comes from the PWM frequency having to be the same on all channels on the same PWM instance.

    For the secure fault, I wonder if you are using overlapping peripheral instances, and it is allready used by secure mode firmware (TF-M). But there is no overlap with PWM instances and other peripherals (see Instatiation), so that may not be the case. I see the error happened at 0x2000ce80 (check again if you modifi the firmware). What do you see there with addr2line?

Related