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?

  • Hi Karl,

    no this is the complete program and the program shouldn´t do more tasks. I only want to use the nRF52 to test the I2S receiver for my FPGA.

  • Kampino said:
    no this is the complete program and the program shouldn´t do more tasks. I only want to use the nRF52 to test the I2S receiver for my FPGA.

    Thank you for clarifying - I assumed so from your original post, but I thought it best to be completely sure.

    Could you also share with me your sdk_config.h file?

    Best regards,
    Karl

  • Hi Karl,

    please take a look at the attached file.

    60011.sdk_config.h

  • Hello,

    Thank you for your patience.

    Could you set your SAADC to be in non-low power mode? This is configured by the NRFX_SAADC_CONFIG_LP_MODE. Low power mode has a slightly higher latency on buffer shifts, which may degrade performance in your case.

    I also notice in the sdk_config that you have the legacy TIMER_ENABLED defined, while also having the NRFX_TIMER_ENABLED defined. This will cause the apply_old_config file to overwrite your NRFX_TIMER configurations. It is recommended that you remove all the legacy define (remove completely, do not leave it defined to 0) when using the NRFX drivers. I notice that this is also the case for your I2S configuration.
    Please change this as well, so that only the NRFX_ configurations remains.

    Let me know if you see any difference when you are no longer using the SAADC in low-power mode.

    Best regards,
    Karl

  • Hi Karl,

    thank you for your reply. I will check it as soon as I have time for it. Slight smile (hopefully today or tomorrow)

Related