i2s on NRF5340 - Feeding from FIFO

Hi there.

I am trying to implement a 16-bit, 32kHz, 2 channel i2s interface which is being fed by a FIFO (the final source of this FIFO will be a DAB/FM radio module but could be any audio source). I am running on an nrf5340dk. I am witnessing the i2s say that it is not being supplied data fast enough (based on log messages) but I can't see why this is as the i2s interface is being fed as fast as the FIFO will allow it. What can I do to get around this?

The current output looks like this:

```

[00:00:00.407,989] <inf> i2s_transmission: Start of main
[00:00:00.408,020] <dbg> i2s_transmission: audio_receive: inside
[00:00:00.411,041] <dbg> i2s_transmission: write_to_i2s_buffer: Inside

[00:00:00.411,071] <inf> i2s_nrfx: I2S MCK frequency: 1026327, actual PCM rate: 32072
[00:00:00.411,132] <dbg> i2s_transmission: i2s_init: i2s transmission started

[00:00:00.411,193] <dbg> i2s_transmission: i2s_init: Finished

[00:00:00.411,224] <dbg> i2s_transmission: write_to_i2s_buffer: Size of FIFO after initialising i2s = 3

[00:00:01.062,713] <err> i2s_nrfx: Next buffers not supplied on time
[00:00:01.063,690] <err> i2s_nrfx: Cannot write in state: 4
--- 9 messages dropped ---
[00:00:01.072,753] <err> i2s_nrfx: Cannot write in state: 4

```

based on the following main:

```

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

LOG_MODULE_REGISTER(i2s_transmission, LOG_LEVEL_DBG);

/* Prepare for 32 sample values */
#define NUM_SAMPLES 32

/* Sine Wave Sample */
static int16_t data_frame[NUM_SAMPLES] = {
      6392,  12539,  18204,  23169,  27244,  30272,  32137,  32767,  32137,
     30272,  27244,  23169,  18204,  12539,   6392,      0,  -6393, -12540,
    -18205, -23170, -27245, -30273, -32138, -32767, -32138, -30273, -27245,
    -23170, -18205, -12540,  -6393,     -1,
};

/* The size of the memory block should be a multiple of data_frame size */
#define BLOCK_SIZE (2 * sizeof(data_frame))

/* The number of memory blocks in a slab has to be at least 2 per queue */
#define NUM_BLOCKS 6

/* Define a new Memory Slab which consists of NUM_BLOCKS blocks
   __________________________________________________________________________________________
  |    Block 0   |    Block 1   |    Block 2   |    Block 3   |    Block 4   |    Block 5   |
  |    0...31    |    0...31    |    0...31    |    0...31    |    0...31    |    0...31    |
  |______________|______________|______________|______________|______________|______________|
*/
static K_MEM_SLAB_DEFINE(mem_slab, BLOCK_SIZE, NUM_BLOCKS, NUM_SAMPLES);
void* mem_blocks;

/* Get I2S device from the devicetree */
const struct device *i2s_dev = DEVICE_DT_GET(DT_NODELABEL(i2s_tx));
struct i2s_config i2s_cfg;

/* FIFO for holding audio data to be sent to i2s tx */
static K_FIFO_DEFINE(rx_samples_fifo);
int fifo_count = 0;
#define NUM_AUDIO_BLOCKS_FIFO 2
typedef struct audio_data {
    void *fifo_reserved; // Required since FIFO's internal structure is that of a linked list
    int16_t data_buffer[NUM_SAMPLES * NUM_AUDIO_BLOCKS_FIFO];
} audio_data_t;

#define FIFO_MEM_SIZE 10
audio_data_t fifo_memory[FIFO_MEM_SIZE];
int idx_fifo_memory_write = 0;
int idx_fifo_memory_read = 0;

/* Semaphore tracking how many audio buffers we have to load into FIFO */
K_SEM_DEFINE(new_rx_audio_samps_sem, 0, 10);
int sem_value = 0;
int sem_total = 0;

// TODO remove
int big_count = 0;

bool i2s_init()
{
    if (!device_is_ready(i2s_dev)) {
        printk("%s is not ready\n", i2s_dev->name);
        return false;
    }

    /* Configure the I2S device */
    i2s_cfg.word_size = 16;
    i2s_cfg.channels = 2; // L + R channel
    i2s_cfg.format = I2S_FMT_DATA_FORMAT_I2S;
    i2s_cfg.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER;
    i2s_cfg.frame_clk_freq = 32000;
    i2s_cfg.mem_slab = &mem_slab;
    i2s_cfg.block_size = BLOCK_SIZE;
    i2s_cfg.timeout = 10000;
    int ret = i2s_configure(i2s_dev, I2S_DIR_TX, &i2s_cfg);
    if (ret < 0) {
        printk("Failed to configure the I2S stream: (%d)\n", ret);
        return false;
    }

    /* Start the transmission of data */
    ret = i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START);
    if (ret < 0) {
        printk("Failed to start the transmission: %d\n", ret);
        return false;
    }

    LOG_DBG("i2s transmission started\n");

    /* Write Data */
    // Attempt to pack buffer with dummy data to keep i2s interface fed
    ret = i2s_buf_write(i2s_dev, data_frame, sizeof(data_frame));
    if (ret < 0) {
        printk("Error: first i2s_write failed with %d\n", ret);
    }

    ret = i2s_buf_write(i2s_dev, data_frame, sizeof(data_frame));
    if (ret < 0) {
        printk("Error: first i2s_write failed with %d\n", ret);
    }

    ret = i2s_buf_write(i2s_dev, data_frame, sizeof(data_frame));
    if (ret < 0) {
        printk("Error: first i2s_write failed with %d\n", ret);
    }

    ret = i2s_buf_write(i2s_dev, data_frame, sizeof(data_frame));
    if (ret < 0) {
        printk("Error: first i2s_write failed with %d\n", ret);
    }
    LOG_DBG("Finished\n");
    return true;
}

void write_to_i2s_buffer()
{
    LOG_DBG("Inside\n");

    // Try configuring i2s in first time write_to_i2s_buffer is called
    /* Initialise i2s device */
    bool i2s_ret = i2s_init();

    LOG_DBG("Size of FIFO after initialising i2s = %d\n", fifo_count);
    if (!i2s_ret)
    {
        LOG_ERR("Failed to initialise i2s peripheral");
    }

    while (1)
    {
        // Get audio samples out of FIFO
        audio_data_t *rx_samp = k_malloc(sizeof(audio_data_t)); // Should be using k_aligned_alloc?
        rx_samp = k_fifo_get(&rx_samples_fifo, K_FOREVER);
        fifo_count--;

        int ret = i2s_buf_write(i2s_dev, &rx_samp->data_buffer, BLOCK_SIZE);

        if (ret < 0) {
            printk("Error: i2s_write failed with %d\n", ret);
            //! Handle this better - should be able to restart the i2s transmission
        }

        // k_free(rx_samp);
    }
}
   
void audio_receive()
{
    LOG_DBG("inside");
   
    while(1)
    {
        // Place into FIFO if an input audio buffer is available
        k_sem_take(&new_rx_audio_samps_sem, K_FOREVER);
        sem_value -= 1;

        audio_data_t *rx_data = &fifo_memory[idx_fifo_memory_write];
        if (rx_data != NULL)
        {
            for(int i = 0; i < NUM_AUDIO_BLOCKS_FIFO * NUM_SAMPLES; i++)
            {
                rx_data->data_buffer[i] = big_count;
                big_count = (big_count + 1) % 10000;
            }

            k_fifo_put(&rx_samples_fifo, rx_data);
            idx_fifo_memory_write = (idx_fifo_memory_write + 1) % FIFO_MEM_SIZE;
            fifo_count++;
        }
    }
    LOG_DBG("exiting");
}
   
static void pack_fifo_isr(struct k_timer *dummy)
{
    k_sem_give(&new_rx_audio_samps_sem);
    sem_value += 1;
    sem_total += 1;
}

/* Timer for filling of FIFO buffer */
K_TIMER_DEFINE(fifo_fill_tmr, pack_fifo_isr, NULL);

K_THREAD_DEFINE(audio_receive_id, 1024, audio_receive, NULL, NULL, NULL, 4, 0, 0);
K_THREAD_DEFINE(write_i2s_buff_id, 1024, write_to_i2s_buffer, NULL, NULL, NULL, 3, 0, 3);
   
int main(void) {
    LOG_INF("Start of main");
    k_timer_start(&fifo_fill_tmr, K_USEC(0), K_USEC(1000));
}

```

Notice a commented out `k_free` in there. It seems that if I include this the program fails due to a memory access error. My guess is that I'm freeing the memory before i2s_buf_write gets a chance to write to the i2s buffer. Would a K_WAIT or equivalent help here?

If you would like to see all of the code it lives here.

Parents
  • that it is not being supplied data fast enough (based on log messages)

    Can you quote the logs exactly?

    What version of the SDK do you use?

    Regards,
    Sigurd Hellesvik

  • The log shown above is the exact output and the version of the SDK used is 2.9.1.

  • Ah did not see the logs in my first read of this ticket.

    What happens if you use K_NO_WAIT instead of K_FOREVER in k_fifo_get()?

    From Zephyr FIFO docs, it looks like the FIFO will only wait if it is empty. Do you agree?
    "A data item may be removed from a FIFO by a thread. If the FIFO’s queue is empty a thread may choose to wait for a data item to be given."

Reply
  • Ah did not see the logs in my first read of this ticket.

    What happens if you use K_NO_WAIT instead of K_FOREVER in k_fifo_get()?

    From Zephyr FIFO docs, it looks like the FIFO will only wait if it is empty. Do you agree?
    "A data item may be removed from a FIFO by a thread. If the FIFO’s queue is empty a thread may choose to wait for a data item to be given."

Children
  • This is a good point. I am away from the hardware at the moment but I'll try that at the earliest possible opportunity and get back to you.

    On a more general note. Is the FIFO the best choice for what I'm trying to do here or is there a slightly more clever way to asynchronously pass audio samples from an arbitrary source to the i2s buffer?