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.