nRF5340 Audio DK - I2S - send audio in Zephyr

Hello, I am using the nRF5340 Audio DK with Zephyr and want to use I2S to send audio to an I2S device. So far I have had little success with the microcontroller halting in the arch_system_halt() function.

So far I have managed to get a similar code working on the nrf52840 in Arduino/platformio. As IDE I use clion. (the debugger for the nRF5340 Audio DK is working)

Here is the concrete situation of what I want to achieve:

I have the nRF5340 Audio DK and want to send an I2S signal from the pins P0.14 (HW_CODEC_BCLK), P0.15 (HW_CODEC_DOUT), P0.16 (HW_CODEC_FSYNC). (As labled on the board and in https://infocenter.nordicsemi.com/pdf/nRF5340_Audio_UG_v1.0.0.pdf)

This I2S signal has the specifications:

  • Sample Rate 44100Hz
  • Left alignedLeft
  • 16 Bit sample width
  • Left channel
  • MCK setup 32MDIV23
  • Ratio 32

I have used 2 approaches so far but both end up halting in arch_system_halt() at one point or another.
I assume something with the interrupts is not working correctly.

I will detail both approaches and what errors I encountered in hopes it can help with finding a solution.
The difference between the approaches was that in the first I ported the code from the nrf52840 working project (it is based on the old API in nrf_i2s.h)
The second approach uses the new API from nrfx_i2s.h.

As for the setup done similarly in both:

1) In the prj.conf the following flags have been set so far

CONFIG_GPIO=y
CONFIG_CPLUSPLUS=y

(I need the C++ support for later on)

2) In the nrfx_config_nrf5340_application.h I have set the following flags:

  • NRFX_I2S_ENABLED 1
  • NRFX_I2S0_ENABLED 1

Approach 1

As said, I have used a ported version of the code that I have used with the nrf52840.

Here are the relevant snippets:

#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include "nrfx.h"
#include "nrfx_i2s.h"
#include "nrf5340_application.h"
#include <stddef.h>


uint32_t _sckPin = 14;  // Pin P0.14
uint32_t _lrckPin = 16;  // Pin P0.16
uint32_t _sdoutPin = 15;  // Pin P0.15

#define WORD_SIZE 4     // 1 word = 4 bytes

uint8_t * buffer;
uint32_t buffer_size = 1024;  // 1 KB


void i2s_irq_handler();
void setup();
void loop();


int main(void) {
    setup();

    while (1) {
        // Do something
        // refill buffer etc.
    }
}

void setup() {
    // Setup buffer
    
    
    // Pin setup
    nrf_i2s_pins_t pins = {
            .sck_pin = _sckPin,
            .lrck_pin = _lrckPin,
            .mck_pin = NRF_I2S_PIN_NOT_CONNECTED,
            .sdout_pin = _sdoutPin,
            .sdin_pin = NRF_I2S_PIN_NOT_CONNECTED
    };
    nrf_i2s_pins_set(NRF_I2S0_S, &pins);
    
    // Configure I2S
    nrf_i2s_config_t config = {
            .mode = NRF_I2S_MODE_MASTER,
            .format = NRF_I2S_FORMAT_I2S,
            .alignment = NRF_I2S_ALIGN_LEFT,
            .sample_width = NRF_I2S_SWIDTH_16BIT,
            .channels = NRF_I2S_CHANNELS_LEFT,
            .mck_setup = NRF_I2S_MCK_32MDIV23,
            .ratio = NRF_I2S_RATIO_32X
    };
    nrf_i2s_configure(NRF_I2S0_S, &config);

    
    //setting up the I2S transfer
    nrf_i2s_transfer_set(NRF_I2S0_S, buffer_size/WORD_SIZE, NULL, (uint32_t const *)buffer);

    //Enable i2s peripheral
    nrf_i2s_enable(NRF_I2S0_S);

    //Enable i2s tx event interrupt
    nrf_i2s_int_enable(NRF_I2S0_S, NRF_I2S_INT_TXPTRUPD_MASK);
    

    //Assigning i2s_irq_handler as i2s interrupt handler
    __NVIC_SetVector(I2S0_IRQn, (uint32_t)i2s_irq_handler);
    
    //Setting I2S interrupt priority
    //Could not find NRFX_I2S_CONFIG_IRQ_PRIORITY thus setting it to 7
    NRFX_IRQ_PRIORITY_SET(I2S_IRQn, 7);

    //Enabling I2S interrupt in NVIC
    NRFX_IRQ_ENABLE(I2S0_IRQn);

    //Start I2S  
    nrf_i2s_task_trigger(NRF_I2S0_S, NRF_I2S_TASK_START);
}


void i2s_irq_handler(void) {
    //Checking TXPTRUPD event
    if (nrf_i2s_event_check(NRF_I2S0_S, NRF_I2S_EVENT_TXPTRUPD)) {
        //clear TXPTRUPD event
        nrf_i2s_event_clear(NRF_I2S0_S, NRF_I2S_EVENT_TXPTRUPD);
        
        //Give I2S the next buffer; assume that buffer is already filled correctly
        nrf_i2s_tx_buffer_set(NRF_I2S0_S, (uint32_t const *)buffer);
    }
}

(It can be assumed that the buffer is setup already with the right values)


One aspect that I was unsure of was whether to use NRF_I2S0_S or NRF_I2S0_NS. What is the difference between them?

This code as it is halts as soon as it calls the __NVIC_SetVector.

It seems to break as soon as the address of the handler function is written into the vector table.

Approach 2

For this I have used the newer API from nrfx_i2s.h.


Here are the relevant snippets:

#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>

#include "nrfx.h"
#include "nrfx_i2s.h"
#include "nrf5340_application.h"

uint32_t _sckPin = 14;  // Pin P0.14
uint32_t _lrckPin = 16;  // Pin P0.16
uint32_t _sdoutPin = 15;  // Pin P0.15

#define WORD_SIZE 4     // 1 word = 4 bytes

uint8_t * buffer;
uint32_t buffer_size = 1024;  // 1 KB

// I2S Instance
nrfx_i2s_t i2s_instance = NRFX_I2S_INSTANCE(0);

void setup();
void i2s_irq_handler(nrfx_i2s_buffers_t const *p_released, uint32_t status);



int main(void) {
    setup();

    while (1) {
        // Do something
        // refill buffer etc.
    }
}

void setup() {
    // Make default config with pins
    nrfx_i2s_config_t config = NRFX_I2S_DEFAULT_CONFIG(
            _sckPin,
            _lrckPin,
            NRF_I2S_PIN_NOT_CONNECTED,
            _sdoutPin,
            NRF_I2S_PIN_NOT_CONNECTED);
    
    // Change mck_setup and ratio
    config.mck_setup = NRF_I2S_MCK_32MDIV23;
    config.ratio = NRF_I2S_RATIO_32X;
    
    // Initialize I2S
    nrfx_err_t ret = nrfx_i2s_init(&i2s_instance, &config, i2s_irq_handler);  // This returns NRFX_SUCCESS

    // Setup buffers
    nrfx_i2s_buffers_t buffers = {
            .p_rx_buffer = nullptr,
            .p_tx_buffer = (uint32_t*)buffer,
            .buffer_size = static_cast<uint16_t>(buffer_size / WORD_SIZE)
    };
    
    // Start I2S with buffers and 0 flags
    nrfx_i2s_start(&i2s_instance, &buffers, 0);
}

void i2s_irq_handler(nrfx_i2s_buffers_t const *p_released, uint32_t status) {
    if (status == NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED) {
        nrfx_i2s_buffers_t buffers = {
                .p_rx_buffer = nullptr,
                .p_tx_buffer = (uint32_t*)buffer,
                .buffer_size = static_cast<uint16_t>(buffer_size / WORD_SIZE)
        };
        
        // assume buffer has been refilled
        nrfx_i2s_next_buffers_set(&i2s_instance, &buffers);
    }
}

(It can be assumed that the buffer is setup already with the right values)

As soon as nrfx_i2s_start is called the microcontroller halts.

Additionally, there was an issue when compiling. Initially the nrfx_i2s_init was not found. This could be solved by adding the nrfx_i2s.c to the target sources specifically.

Conclusion:
I am stuck on how to continue and how to debug this.
Does anyone have a clue what I have been missing?

Foot note:
It has generally been quite difficult to search for good guides regarding I2S and the nrf series. Especially examples/guides regarding development with Arduino/platformio that actually work.

Parents
  • Hello,

    So when you specify that you want to do this with Zephyr, do you mean NCS, or do you want to just use Zephyr? Have you considered using the NCS Audio App? Are you necessarily doing LE Audio?

    It has generally been quite difficult to search for good guides regarding I2S and the nrf series. Especially examples/guides regarding development with Arduino/platformio that actually work.

    Yeah, I guess everything related to Audio is a bit more on the complex side, which means that it is best supported on NCS&Zephyr. It would be difficult to handle this complexity without and RTOS. I do like this documentation we have on our LE Audio Application, though having support for Arduino is not something we've prioritized.

    Regards,

    Elfving

Reply
  • Hello,

    So when you specify that you want to do this with Zephyr, do you mean NCS, or do you want to just use Zephyr? Have you considered using the NCS Audio App? Are you necessarily doing LE Audio?

    It has generally been quite difficult to search for good guides regarding I2S and the nrf series. Especially examples/guides regarding development with Arduino/platformio that actually work.

    Yeah, I guess everything related to Audio is a bit more on the complex side, which means that it is best supported on NCS&Zephyr. It would be difficult to handle this complexity without and RTOS. I do like this documentation we have on our LE Audio Application, though having support for Arduino is not something we've prioritized.

    Regards,

    Elfving

Children
No Data
Related