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
  • Hello,

    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.
    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?

    Is there a particular reason why you are not using the nRF52832's I2S peripheral directly for this?
    If there is, please let me know about your application requirements and constraints, so I may advice you on how to increase the performance according to this.
    If there is an option to instead use the I2S peripheral directly then this is what I would advice, as this should give you a great performance increase compared to what you are seeing now, without involving the SAADC peripheral.

    If you are not sure how to use the I2S peripheral it may be helpful to take a look at the I2S loopback example from the nRF5 SDK which demonstrates both output and input using the peripheral.

    Best regards,
    Karl

Reply
  • Hello,

    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.
    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?

    Is there a particular reason why you are not using the nRF52832's I2S peripheral directly for this?
    If there is, please let me know about your application requirements and constraints, so I may advice you on how to increase the performance according to this.
    If there is an option to instead use the I2S peripheral directly then this is what I would advice, as this should give you a great performance increase compared to what you are seeing now, without involving the SAADC peripheral.

    If you are not sure how to use the I2S peripheral it may be helpful to take a look at the I2S loopback example from the nRF5 SDK which demonstrates both output and input using the peripheral.

    Best regards,
    Karl

Children
  • Hi Karl,

    Is there a particular reason why you are not using the nRF52832's I2S peripheral directly for this?

    what do you mean by this question? I already use the I2S peripheral to stream out the data from the nRF52832, but the data is generated by an ADC that samples some sort of device to get the audio data. In my setup, the "device" is a 3.3 V square wave generator, but it should get replaced with an analog microphone. I only use the generator to get a known signal.

    Feel free to ask if you have additional questions :)

  • Kampino said:
    what do you mean by this question? I already use the I2C peripheral to stream out the data from the nRF52832

    I meant the I2S, not I2C / TWI - I was of the understanding from your initial ticket that you were using the SAADC to read (through sampling) a I2S signal, is this not the case?
    If so, I would advice that you make use of the nRF52832's I2S peripheral directly, instead of reading it with the SAADC.

    I posed the question in case there was a constraint or requirement by your application that forced you to use the SAADC instead of the I2S peripheral to read the incoming I2S data stream, that I might not be aware of. If there is not, then I think we should look at switching to using the I2S peripheral instead :) 
    How do you think this would fit with your application?

    Best regards,
    Karl

  • Hi Karl,

    sorry I meant I2S (not I2C as you mentioned - I have fixed it). Please take a look at the overview drawing.


    The "Microphone" is replaced by my squarewave generator, but the idea is that the nRF52 will sample the analog input signal with a SAADC and transmit the sampled signal over I2S to my master.

    But it turns out that the signal quality on my master is very bad (see the screenshot) when I use the SAADC to sample the analog audio signal and use the result as the I2S data. The quality is good when I don´t use the SAADC and generate the I2S data "on the fly" by the CPU (in this case a ramp).

    So I think that the conversion with the ADC and the transmission over I2S runs into some sort of error (i. e. overwriting data while they got transmitted, etc.), but I can not figure out the problem.

    Hope it is clear now :)

  • Hello again,

    Thank you for your patience with this.

    Kampino said:
    sorry I meant I2S (not I2C as you mentioned - I have fixed it). Please take a look at the overview drawing.

    Thank you for clarifying this with the drawing - this does indeed make the overview a lot more clear! :) 

    Kampino said:
    The "Microphone" is replaced by my squarewave generator, but the idea is that the nRF52 will sample the analog input signal with a SAADC and transmit the sampled signal over I2S to my master.

    Sure, this is a good way to simulate it for testing.
    Could you perhaps set the generator to a sine-wave, and share the SAADC's raw measurements of this with me? This should help us pinpoint the issue.

    Kampino said:
    So I think that the conversion with the ADC and the transmission over I2S runs into some sort of error (i. e. overwriting data while they got transmitted, etc.), but I can not figure out the problem.

    Yes, from your testing it sounds like it might be an issue with the SAADC usage. However, I do not immediately see what that might be what might be causing this. Is the code you shared earlier the complete code that is running, or only a section of a larger program?

    Best regards,
    Karl

  • 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.

Related