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?

Parents
  • Hi 

    I think I found the problem. The problem comes from a wrong pointer type. I declared

    static volatile nrf_saadc_value_t*	ADC_BlockToFill		= NULL;

    as 

    static volatile uint32_t*	ADC_BlockToFill		= NULL;

    This error was the reason why the copy loop copies two 16-byte values from the ADC buffer to one word in the I2S buffer. The complete code looks as shown below

    #include "nrf_gpio.h"
    #include "nrf_drv_ppi.h"
    #include "nrf_drv_i2s.h"
    #include "nrf_drv_saadc.h"
    #include "nrf_drv_timer.h"
    
    #include "app_error.h"
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #define ADC_TRIGGER		15
    #define LED_0			17
    #define BUTTON_0		13
    #define BUTTON_1		14
    #define TRIGGER			31
    #define SAMPLES			128
    
    static bool				IsStarted		= false;
    static volatile nrf_saadc_value_t*	ADC_BlockToFill		= NULL;
    static volatile uint32_t*		I2S_BlockToFill		= NULL;
    
    static uint32_t			I2S_Buffer[SAMPLES];
    static nrf_saadc_value_t	ADC_Buffer[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)
    {
        nrf_gpio_pin_toggle(ADC_TRIGGER);
    }
    
    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 = (nrf_saadc_value_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;
        }
    
        if(!p_Released->p_tx_buffer)
        {
            nrf_drv_i2s_buffers_t const next_buffers = {
                .p_tx_buffer = I2S_Buffer,
            };
            APP_ERROR_CHECK(nrf_drv_i2s_next_buffers_set(&next_buffers));
        }
        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));
    
            I2S_BlockToFill = (uint32_t*)p_Released->p_tx_buffer;
            nrf_drv_i2s_stop();
        }
    
        return;
    }
    
    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, 50), NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);
        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, SAMPLES));
        APP_ERROR_CHECK(nrf_drv_ppi_channel_enable(PPI_Channel));
    }
    
    void I2S_Init(void)
    {
        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));
    }
    
    int main(void)
    {
        APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    
        SAADC_Init();
        I2S_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_cfg_output(ADC_TRIGGER);
        nrf_gpio_pin_clear(TRIGGER);
        nrf_gpio_pin_set(LED_0);
    
        while(1)
        {
    	if(nrf_gpio_pin_read(BUTTON_0) == false)
    	{
    	    if(IsStarted == false)
    	    {
    		NRF_LOG_INFO("I2S sender started...");
    		IsStarted = true;
    		nrf_gpio_pin_clear(LED_0);
    		nrf_gpio_pin_set(TRIGGER);
    	    }
    	}
    	else if(nrf_gpio_pin_read(BUTTON_1) == false)
    	{
    	    if(IsStarted)
    	    {
    		NRF_LOG_INFO("I2S sender stopped...");
    		IsStarted = false;
    		nrf_gpio_pin_set(LED_0);
    		nrf_gpio_pin_clear(TRIGGER);
    		nrf_drv_i2s_stop();
    	    }
    	}
    
    	// ADC conversion, I2S transmitter ready and started by user?
    	if(ADC_BlockToFill && IsStarted)
            {
    	    // Copy the buffer
    	    for(uint32_t i = 0; i < SAMPLES; i++)
    	    {
    		//I2S_Buffer[i] = (uint32_t)(0x0000 | (i & 0xFFFF));
    		I2S_Buffer[i] = (uint32_t)(0x0000 | (ADC_BlockToFill[i] & 0xFFFF));
    	    }
    
    	    nrf_drv_i2s_buffers_t const InitialBuffer = {
    		.p_tx_buffer = I2S_Buffer,
    		.p_rx_buffer = NULL,
    	    };
    
    	    APP_ERROR_CHECK(nrf_drv_i2s_start(&InitialBuffer, SAMPLES, 0));
    
    	    ADC_BlockToFill = NULL;
            }
    
    	if(NRF_LOG_PROCESS())
    	{
    	    NRF_LOG_FLUSH();
    	}
        }
    }

    I have tested the code with my 1 kHz square wave and got the following plot:

    The signal has a length of 10 samples.

    And a 1 kHz tone from my speaker:


    It´s not perfect, but I think it´s working now. Slight smile

  • Hello again, Kampino

    Kampino said:
    This error was the reason why the copy loop copies two 16-byte values from the ADC buffer to one word in the I2S buffer. The complete code looks as shown below

    Ah, yes, this seems like a likely explanation - keenly spotted!

    Kampino said:
    It´s not perfect, but I think it´s working now.

    Yes, this is much better at least. I see that you get some missed samples when your buffer is filled (presumably). I recommend that you make use of the double buffering feature of the SAADC to avoid these. All you need to do in order for the SAADC to seamlessly continue into the secondary buffer when the first one is filled is to provide 2 buffers during the initialization of the SAADC. You can see this demonstrated in the SAADC peripheral example as well, in the nRF5 SDK.
    You should then get a totally 'smooth' (no drops) output.

    Looking back at your original post I see that you had double buffering there, but not in your latest post - what is the reason for this?

    Best regards,
    Karl

Reply
  • Hello again, Kampino

    Kampino said:
    This error was the reason why the copy loop copies two 16-byte values from the ADC buffer to one word in the I2S buffer. The complete code looks as shown below

    Ah, yes, this seems like a likely explanation - keenly spotted!

    Kampino said:
    It´s not perfect, but I think it´s working now.

    Yes, this is much better at least. I see that you get some missed samples when your buffer is filled (presumably). I recommend that you make use of the double buffering feature of the SAADC to avoid these. All you need to do in order for the SAADC to seamlessly continue into the secondary buffer when the first one is filled is to provide 2 buffers during the initialization of the SAADC. You can see this demonstrated in the SAADC peripheral example as well, in the nRF5 SDK.
    You should then get a totally 'smooth' (no drops) output.

    Looking back at your original post I see that you had double buffering there, but not in your latest post - what is the reason for this?

    Best regards,
    Karl

Children
No Data
Related