This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Streaming audio data from ADC with I2S

Hello,

I´m working on my audio project and I try to stream some audio data (currently a 1 kHz square wave signal with 3.3 V peak) over I2S. The audio signal is sampled by an ADC in the free-running mode.

#include "app_error.h"
#include "nrf_drv_ppi.h"
#include "nrf_drv_i2s.h"
#include "nrf_drv_saadc.h"
#include "nrf_drv_timer.h"
#include "nrf_gpio.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#define LED_0			17
#define BUTTON_0		13
#define BUTTON_1		14
#define TRIGGER			31
#define SAMPLES			128

static bool			IsStarted		= false;
static volatile uint32_t*	I2S_BlockToFill		= NULL;
static volatile uint32_t*	ADC_BlockToFill		= NULL;

static uint32_t			I2S_Buffer[2][SAMPLES];
static nrf_saadc_value_t	ADC_Buffer[2][SAMPLES];
static nrf_ppi_channel_t	PPI_Channel;

static const nrf_drv_timer_t	Timer = NRF_DRV_TIMER_INSTANCE(0);

void on_Timer_Handler(nrf_timer_event_t EventType, void* p_Context)
{
}

void on_SAADC_Handler(nrf_drv_saadc_evt_t const* p_Event)
{
    if(p_Event->type == NRF_DRV_SAADC_EVT_DONE)
    {
        APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(p_Event->data.done.p_buffer, SAMPLES));

	ADC_BlockToFill = (uint32_t*)p_Event->data.done.p_buffer;
    }
}

static void DataHandler(nrf_drv_i2s_buffers_t const* p_Released, uint32_t Status)
{
    // 'nrf_drv_i2s_next_buffers_set' is called directly from the handler
    // each time next buffers are requested, so data corruption is not
    // expected.
    ASSERT(p_Released);

    // When the handler is called after the transfer has been stopped
    // (no next buffers are needed, only the used buffers are to be
    // released), there is nothing to do.
    if(!(Status & NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED))
    {
        return;
    }

    // First call of this handler occurs right after the transfer is started.
    // No data has been transferred yet at this point, so there is nothing to
    // check. Only the buffers for the next part of the transfer should be
    // provided.
    if(!p_Released->p_tx_buffer)
    {
        nrf_drv_i2s_buffers_t const NextBuffer = {
            .p_tx_buffer = I2S_Buffer[1],
	    .p_rx_buffer = NULL,
        };

        APP_ERROR_CHECK(nrf_drv_i2s_next_buffers_set(&NextBuffer));

	nrf_gpio_pin_clear(TRIGGER);

        I2S_BlockToFill = I2S_Buffer[1];
    }
    else
    {
        // The driver has just finished accessing the buffers pointed by
        // 'p_Released'. They can be used for the next part of the transfer
        // that will be scheduled now.
        APP_ERROR_CHECK(nrf_drv_i2s_next_buffers_set(p_Released));

        // The pointer needs to be typecasted here, so that it is possible to
        // modify the content it is pointing to (it is marked in the structure
        // as pointing to constant data because the driver is not supposed to
        // modify the provided data).
        I2S_BlockToFill = (uint32_t*)p_Released->p_tx_buffer;
    }
}

void SAADC_Init(void)
{
    APP_ERROR_CHECK(nrf_drv_saadc_init(NULL, on_SAADC_Handler));
    APP_ERROR_CHECK(nrf_drv_ppi_init());

    nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
    timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;

    APP_ERROR_CHECK(nrf_drv_timer_init(&Timer, &timer_cfg, on_Timer_Handler));
    nrf_drv_timer_extended_compare(&Timer, NRF_TIMER_CC_CHANNEL0, nrf_drv_timer_us_to_ticks(&Timer, 23), NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
    nrf_drv_timer_enable(&Timer);

    APP_ERROR_CHECK(nrf_drv_ppi_channel_alloc(&PPI_Channel));
    APP_ERROR_CHECK(nrf_drv_ppi_channel_assign(PPI_Channel, nrf_drv_timer_compare_event_address_get(&Timer, NRF_TIMER_CC_CHANNEL0), nrf_drv_saadc_sample_task_get()));

    nrf_saadc_channel_config_t Channel0 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN1);
    Channel0.acq_time = NRF_SAADC_ACQTIME_10US;

    APP_ERROR_CHECK(nrf_drv_saadc_channel_init(0, &Channel0));
    APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(ADC_Buffer[0], SAMPLES));
    APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(ADC_Buffer[1], SAMPLES));
    APP_ERROR_CHECK(nrf_drv_ppi_channel_enable(PPI_Channel));
}

int main(void)
{
    APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
    NRF_LOG_DEFAULT_BACKENDS_INIT();

    SAADC_Init();

    nrf_gpio_cfg_input(BUTTON_0, NRF_GPIO_PIN_PULLUP);
    nrf_gpio_cfg_input(BUTTON_1, NRF_GPIO_PIN_PULLUP);

    nrf_gpio_cfg_output(TRIGGER);
    nrf_gpio_cfg_output(LED_0);
    nrf_gpio_pin_clear(TRIGGER);
    nrf_gpio_pin_set(LED_0);

    nrf_drv_i2s_config_t I2S_Config = NRF_DRV_I2S_DEFAULT_CONFIG;
    I2S_Config.sdin_pin	    = NRFX_I2S_PIN_NOT_USED;
    I2S_Config.sdout_pin    = 27;
    I2S_Config.lrck_pin	    = 26;
    I2S_Config.sck_pin	    = 25;
    I2S_Config.mck_pin	    = 2;
    I2S_Config.mck_setup    = NRF_I2S_MCK_32MDIV8;
    I2S_Config.ratio	    = NRF_I2S_RATIO_96X;
    I2S_Config.channels	    = NRF_I2S_CHANNELS_STEREO;
    APP_ERROR_CHECK(nrf_drv_i2s_init(&I2S_Config, DataHandler));

    nrf_drv_i2s_buffers_t const InitialBuffer = {
	.p_tx_buffer = I2S_Buffer[0],
        .p_rx_buffer = NULL,
    };

    while(1)
    {
	if(nrf_gpio_pin_read(BUTTON_0) == false)
	{
	    if(IsStarted == false)
	    {
		APP_ERROR_CHECK(nrf_drv_i2s_start(&InitialBuffer, SAMPLES, 0));
		NRF_LOG_INFO("I2S sender started...");
		IsStarted = true;
		nrf_gpio_pin_set(TRIGGER);
		nrf_gpio_pin_clear(LED_0);
	    }
	}
	else if(nrf_gpio_pin_read(BUTTON_1) == false)
	{
	    if(IsStarted)
	    {
		nrf_drv_i2s_stop();
		NRF_LOG_INFO("I2S sender stopped...");
		IsStarted = false;
		nrf_gpio_pin_clear(TRIGGER);
		nrf_gpio_pin_set(LED_0);
	    }
	}

	if(I2S_BlockToFill && ADC_BlockToFill)
        {
	    for(uint32_t i = 0x00; i < SAMPLES; i++)
	    {
		//int16_t Sample = (int16_t)(32768 * sin(2.0 * 3.14 * (double)i / ((double)SAMPLES)));
		I2S_BlockToFill[i] = (uint32_t)(0x0000 | (ADC_BlockToFill[i] & 0xFFFF));
	    }

            I2S_BlockToFill = NULL;
	    ADC_BlockToFill = NULL;
        }

	if(NRF_LOG_PROCESS())
	{
	    NRF_LOG_FLUSH();
	}
    }
}

The code from above will give a pretty poor signal when I use the ADC.

I have tried to figure out the reason for this and replaced the line

I2S_BlockToFill[i] = (uint32_t)(0x0000 | (ADC_BlockToFill[i] & 0xFFFF));

with

I2S_BlockToFill[i] = (uint32_t)(0x0000 | (i & 0xFFFF));

to produce data from a ramp signal and the signal looks much better.

So it looks like the problem is based on the ADC. I have tried to use two buffers to use the same swapping buffer system as in the I2S example.

How can I improve this result?

  • Thank you for testing this, that seems peculiar to me.
    I will attempt to replicate this on my end tomorrow.

    Best regards,
    Karl

  • Hello again, Daniel

    Thank you for your continued patience with this.

    I have taken a look at the behavior you described, and concluded that it is likely caused by the 'm_zero_samples_to_ignore' parameter of the unmodified I2S example, which comes into play at the beginning of every transfer.

    The comment in the check_samples function details it further:

            // Normally a couple of initial samples sent by the I2S peripheral
            // will have zero values, because it starts to output the clock
            // before the actual data is fetched by EasyDMA. As we are dealing
            // with streaming the initial zero samples can be simply ignored.

    You could also read more about this in the comments at the beginning of the prepare_tx_data function.

    I will be going out of office starting today, and I will not be back until after new years. If you require further technical support in my absence, or have any other issues or questions, please create a new ticket for this. Please also note that the forum will be operating with reduced staffing during the upcoming holiday season here in Norway - apologies for any inconveniences this might cause.

    I hope you have a great holiday season, Daniel! :) 

    Best regards,
    Karl

  • Hi Karl,

    thank you for the answer Slight smile

    Everything is clear now and my project works as expected. I will close the ticket and wish you a great holiday and some relaxing Christmas days.

    Best wishes

Related