How to capture NRF_I2S_EVENT_TXPTRUPD against timer using DPPI?

Hi everyone, I am attempting to measure I2S rate against internal clock frequency since I am using APLL. I am following examples in NRF5340_Audio application.

What i have in my code is:

  • Configure TIMER2 as TIMER, with CC1 with Clear mask set at INT32_MAX to avoid wraparound errors.
  • Configure I2S
  • Allocate DPPI channel. Subscribe to it with CC0 channel. Publish to it from TXPTRUPD.

What I expect is a value in range from 0 to INT32_MAX returned from nrfx_timer_capture_get for CC0. What I get is constant 0, so i am convinced, that DPPI doesn't trigger. I am also positive, that I2S runs, as interrupt is called for NRF_I2S_EVENT_TXPTRUPD.

I am also attaching my modified code.

/*
 *  Copyright (c) 2021, PACKETCRAFT, INC.
 *
 *  SPDX-License-Identifier: LicenseRef-PCFT
 */

#include "audio_i2s.h"

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/pinctrl.h>
#include <nrfx_i2s.h>
#include <nrfx_timer.h>
#include <nrfx_clock.h>
#include <nrfx_dppi.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_core.h>

#include "audio_sync_timer.h"

#define I2S_NL DT_NODELABEL(i2s0)

enum audio_i2s_state {
	AUDIO_I2S_STATE_UNINIT,
	AUDIO_I2S_STATE_IDLE,
	AUDIO_I2S_STATE_STARTED,
};

static enum audio_i2s_state state = AUDIO_I2S_STATE_UNINIT;

PINCTRL_DT_DEFINE(I2S_NL);

LOG_MODULE_REGISTER(audio_i2s, LOG_LEVEL_INF);

static const nrfx_timer_t i2s_meas_timer = NRFX_TIMER_INSTANCE(2);

#if CONFIG_AUDIO_SAMPLE_RATE_8000_HZ
#define CONFIG_AUDIO_RATIO NRF_I2S_RATIO_384X
#define MCK_SETUP I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV4
#elif CONFIG_AUDIO_SAMPLE_RATE_16000_HZ
#define CONFIG_AUDIO_RATIO NRF_I2S_RATIO_384X
#define MCK_SETUP I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV2
#elif CONFIG_AUDIO_SAMPLE_RATE_24000_HZ
#define CONFIG_AUDIO_RATIO NRF_I2S_RATIO_256X
#define MCK_SETUP I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV2
#elif CONFIG_AUDIO_SAMPLE_RATE_48000_HZ
#define CONFIG_AUDIO_RATIO NRF_I2S_RATIO_128X
#define MCK_SETUP I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV2
#else
#error "Current AUDIO_SAMPLE_RATE_HZ setting not supported"
#endif

#if CONFIG_NRF5340_AUDIO_CS47L63_DRIVER
#define FORMAT_PCM NRF_I2S_FORMAT_I2S
#else
#define FORMAT_PCM NRF_I2S_FORMAT_ALIGNED
#endif

static nrfx_i2s_t i2s_inst = NRFX_I2S_INSTANCE(0);

static nrfx_i2s_config_t cfg = {
	/* Pins are configured by pinctrl. */
	.skip_gpio_cfg = true,
	.skip_psel_cfg = true,
	.irq_priority = DT_IRQ(I2S_NL, priority),
	.mode = NRF_I2S_MODE_MASTER,
	.format = FORMAT_PCM,
	.alignment = NRF_I2S_ALIGN_LEFT,
	.ratio = CONFIG_AUDIO_RATIO,
	.mck_setup = MCK_SETUP,
#if (CONFIG_AUDIO_BIT_DEPTH_16)
	.sample_width = NRF_I2S_SWIDTH_16BIT,
#elif (CONFIG_AUDIO_BIT_DEPTH_32)
	.sample_width = NRF_I2S_SWIDTH_32BIT,
#else
#error Invalid bit depth selected
#endif /* (CONFIG_AUDIO_BIT_DEPTH_16) */
	.channels = NRF_I2S_CHANNELS_STEREO,
	.clksrc = NRF_I2S_CLKSRC_ACLK,
	.enable_bypass = false,
};

static i2s_blk_comp_callback_t i2s_blk_comp_callback;
static i2s_blk_comp_single_cb_t i2s_blk_cb_rx;
static i2s_blk_comp_single_cb_t i2s_blk_cb_tx;

static uint32_t* next_tx;
static uint32_t* curr_tx;
static uint32_t* next_rx;
static uint32_t* curr_rx;

static uint32_t backup_buf_rx[I2S_SAMPLES_NUM];
static uint32_t backup_buf_tx[I2S_SAMPLES_NUM];

static void i2s_comp_handler(nrfx_i2s_buffers_t const *released_bufs, uint32_t status)
{
	uint32_t frame_start_ts = audio_sync_timer_capture_get();
	// uint32_t frame_start_ts = 0;

	if(status == NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED){
		if(!released_bufs) {
			LOG_WRN("I2S TX cant keep up!");
			return;
		}

		if (!(released_bufs->p_rx_buffer || released_bufs->p_tx_buffer)) {
			LOG_WRN("I2S callback, got empty bufs!");
			return;
		}

		if(i2s_blk_comp_callback){
			i2s_blk_comp_callback(frame_start_ts, released_bufs->p_rx_buffer, released_bufs->p_tx_buffer);
		}
	}

}

void audio_i2s_set_next_buf(const uint8_t *tx_buf, uint32_t *rx_buf, size_t samples)
{
	__ASSERT_NO_MSG(state == AUDIO_I2S_STATE_STARTED);
	if (IS_ENABLED(CONFIG_STREAM_BIDIRECTIONAL) || (CONFIG_AUDIO_DEV == GATEWAY)) {
		__ASSERT_NO_MSG(rx_buf != NULL);
	}

	if (IS_ENABLED(CONFIG_STREAM_BIDIRECTIONAL) || (CONFIG_AUDIO_DEV == HEADSET)) {
		__ASSERT_NO_MSG(tx_buf != NULL);
	}

	nrfy_i2s_xfer_desc_t desc  = {
		.buffer_size = I2S_SAMPLES_NUM,
		.p_rx_buffer = rx_buf,
		.p_tx_buffer = (uint32_t *)tx_buf,
	};

    nrfy_i2s_buffers_set(i2s_inst.p_reg, &desc);
}

void audio_i2s_start(const uint8_t *tx_buf, uint32_t *rx_buf) {
	nrfy_i2s_enable(i2s_inst.p_reg);
    /* Clear spurious RXPTRUPD and TXPTRUPD events (see nRF52 anomaly 55). */
    nrfy_i2s_event_clear(i2s_inst.p_reg, NRF_I2S_EVENT_TXPTRUPD | NRF_I2S_INT_STOPPED_MASK);
    nrfy_i2s_int_enable(i2s_inst.p_reg,  NRF_I2S_INT_TXPTRUPD_MASK);

	nrfy_i2s_xfer_desc_t desc  = {
		.buffer_size = I2S_SAMPLES_NUM,
		.p_rx_buffer = rx_buf,
		.p_tx_buffer = (uint32_t *)tx_buf,
	};

    nrfy_i2s_buffers_set(i2s_inst.p_reg, &desc);
    nrfy_i2s_xfer_start(i2s_inst.p_reg, NULL);

	curr_tx = tx_buf;
	curr_rx = rx_buf;
	next_tx = NULL;
	next_rx = NULL;

	state = AUDIO_I2S_STATE_STARTED;
}

void audio_i2s_stop(void)
{
	__ASSERT_NO_MSG(state == AUDIO_I2S_STATE_STARTED);

	nrfx_i2s_stop(&i2s_inst);

	state = AUDIO_I2S_STATE_IDLE;
}

void audio_i2s_blk_comp_cb_register(i2s_blk_comp_callback_t blk_comp_callback, i2s_blk_comp_single_cb_t rx_callback, i2s_blk_comp_single_cb_t tx_callback)
{
	i2s_blk_comp_callback = blk_comp_callback;
	i2s_blk_cb_rx = rx_callback;
	i2s_blk_cb_tx = tx_callback;
}

static struct host_freq_data_bis{
	int16_t diff[1000];

	int meas_idx;
} freq_measurement_i2s;

// static volatile uint32_t tx_repeat, rx_repeat = 0;
#define NEXT_FREQ_BIS_IDX(cntr) (cntr < 999 ? cntr + 1 : 0)
#define PREV_FREQ_BIS_IDX(cntr) (cntr > 0 ? cntr - 1 : 999)

void audio_i2s_irq(void) {
	uint32_t event_mask;
	uint32_t *prev_tx, *prev_rx = NULL;
	nrfy_i2s_xfer_desc_t * p_xfer;
	uint32_t* ptr_tx = NULL;
	uint32_t* ptr_rx = NULL;
	static int32_t last_frame_us = 0;

	// uint32_t frame_start_ts = audio_sync_timer_capture_get();
	int32_t frame_start_us = (int32_t)nrfx_timer_capture_get(&i2s_meas_timer, NRF_TIMER_CC_CHANNEL0);

	int log_idx = NEXT_FREQ_BIS_IDX(freq_measurement_i2s.meas_idx);

	int32_t frame_diff = frame_start_us - last_frame_us;
	frame_diff = frame_diff < 0 ? frame_diff + INT32_MAX : frame_diff;

	freq_measurement_i2s.diff[log_idx] = frame_start_us - last_frame_us;
	last_frame_us = frame_start_us;
	freq_measurement_i2s.meas_idx = log_idx;

	event_mask = nrfy_i2s_events_process(i2s_inst.p_reg,
		NRFY_EVENT_TO_INT_BITMASK(NRF_I2S_EVENT_TXPTRUPD) |
		NRFY_EVENT_TO_INT_BITMASK(NRF_I2S_EVENT_RXPTRUPD) |
		NRFY_EVENT_TO_INT_BITMASK(NRF_I2S_EVENT_STOPPED),
		NULL);

	if(event_mask & NRF_I2S_EVENT_STOPPED) {
		return; // TODO: Figure out what now!
	}

	prev_tx = curr_tx;
	curr_tx = next_tx;
	prev_rx = curr_rx;
	curr_rx = next_rx;

	// if(i2s_blk_cb_tx) {
	// 	i2s_blk_cb_tx(frame_start_ts, prev_tx, &ptr_tx);
	// }
	
	// if(i2s_blk_cb_rx) {		
	// 	i2s_blk_cb_rx(frame_start_ts, prev_rx, &ptr_rx);
	// }

	next_tx = ptr_tx;
	next_rx = ptr_rx;

	ptr_tx = (ptr_tx != NULL) ? ptr_tx : backup_buf_tx;
	ptr_rx = (ptr_rx != NULL) ? ptr_rx : backup_buf_rx;
	
	audio_i2s_set_next_buf(ptr_tx, ptr_rx, I2S_SAMPLES_NUM);

	nrf_barrier_w();
}

int32_t audio_i2s_measure_timebase(int samples) {
	int32_t acc_diff = 0;

	int idx = PREV_FREQ_BIS_IDX(freq_measurement_i2s.meas_idx);
	for (int i = 0; i < samples; i++) {
		acc_diff += (int32_t)freq_measurement_i2s.diff[idx];

		idx = PREV_FREQ_BIS_IDX(idx);
	}

	return acc_diff;
}

static uint8_t dppi_channel_i2s_tick_capture;

void audio_i2s_init(void)
{
	__ASSERT_NO_MSG(state == AUDIO_I2S_STATE_UNINIT);

	nrfx_err_t ret;
	nrfx_dppi_t dppi = NRFX_DPPI_INSTANCE(0);

	curr_tx = NULL;
	curr_rx = NULL;
	next_tx = NULL;
	next_rx = NULL;

	nrfx_timer_config_t tmr_config = NRFX_TIMER_DEFAULT_CONFIG(1000000);
    tmr_config.mode = NRF_TIMER_MODE_TIMER;
    tmr_config.bit_width = NRF_TIMER_BIT_WIDTH_32;
    tmr_config.interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY;

    ret = nrfx_timer_init(&i2s_meas_timer, &tmr_config, NULL);

	nrfx_timer_extended_compare(&i2s_meas_timer, NRF_TIMER_CC_CHANNEL1, INT32_MAX, NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK, false); // Convert timer return to int32_t :)
    nrfx_timer_enable(&i2s_meas_timer);

	nrfx_clock_hfclkaudio_config_set(HFCLKAUDIO_12_288_MHZ);

	NRF_CLOCK->TASKS_HFCLKAUDIOSTART = 1;

	/* Wait for ACLK to start */
	while (!NRF_CLOCK_EVENT_HFCLKAUDIOSTARTED) {
		k_sleep(K_MSEC(1));
	}

	ret = pinctrl_apply_state(PINCTRL_DT_DEV_CONFIG_GET(I2S_NL), PINCTRL_STATE_DEFAULT);
	__ASSERT_NO_MSG(ret == 0);

	IRQ_CONNECT(DT_IRQN(I2S_NL), DT_IRQ(I2S_NL, priority), nrfx_isr, audio_i2s_irq, 0);
	irq_enable(DT_IRQN(I2S_NL));

	ret = nrfx_i2s_init(&i2s_inst, &cfg, i2s_comp_handler);
	__ASSERT_NO_MSG(ret == NRFX_SUCCESS);

	state = AUDIO_I2S_STATE_IDLE;

	/* Initialize capturing of current timestamps */
	ret = nrfx_dppi_channel_alloc(&dppi, &dppi_channel_i2s_tick_capture);
	if (ret - NRFX_ERROR_BASE_NUM) {
		LOG_ERR("nrfx DPPI channel alloc error (I2S RXPupd) - Return value: %d", ret);
		return -ENOMEM;
	}

	nrf_timer_subscribe_set(i2s_meas_timer.p_reg,
				NRF_TIMER_CC_CHANNEL0,
				dppi_channel_i2s_tick_capture);

	nrf_i2s_publish_set(i2s_inst.p_reg, NRF_I2S_EVENT_TXPTRUPD, dppi_channel_i2s_tick_capture);
	ret = nrfx_dppi_channel_enable(&dppi, dppi_channel_i2s_tick_capture);
	if (ret - NRFX_ERROR_BASE_NUM) {
		LOG_ERR("nrfx DPPI channel enable error (I2S RXPupd) - Return value: %d", ret);
		return -EIO;
	}
}

Related