This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

SAADC Sampling Issue

Hello,

I am trying to sample and reconstruct an audio signal using the SAADC and I2S respectively. I have a double buffer setup for the SAADC, where we process the data in one buffer while sampling in the other, but I am getting discontinuities in the output signal every time saadc_handler() is called. The attached scope shot captures the analog input waveform (yellow) and reconstructed output from I2S codec (green). It has been verified that the discontinuity happens exactly when SAADC changes output buffer from one to the other. My understanding is that the I2S output stream is set up correctly (we have tested with this i2s_example, and it worked perfectly), so I am assuming there is something I need to change when sampling.

The sampling and reconstruction frequency are both at 31.25 kHz, and each input buffer size is 1024 (with an output buffer size of 2048).

#include <nrf.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "nrf.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"
#include "nrf_twi_mngr.h"
#include "nrf_drv_spi.h"
#include "app_util_platform.h"
#include "nordic_common.h"
#include "app_timer.h"
#include "mem_manager.h"
#include "nrf_drv_clock.h"
#include "nrf_drv_gpiote.h"
#include "nrf_drv_saadc.h"
#include "nrf_drv_ppi.h"
#include "nrf_drv_timer.h"
#include "nrf_power.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include "nrf_dfu_utils.h"
#include "nrf_dfu_settings.h"

#define SAADC_SAMPLE_PERIOD 32 // period for 31.25 kHz
#define SAADC_SAMPLES_IN_BUFFER 1024

#define SAADC_OVERSAMPLE NRF_SAADC_OVERSAMPLE_DISABLED
#define SAADC_BURST_MODE 0

#define DC_OFFSET 1.62
#define DC_SUPPLY 3.6
#define MAX_I2S_VAL 32767

#define PIN_MCK    (13)
#define PIN_SCK    (22)
#define PIN_LRCK   (24)
#define PIN_SDOUT  (23)

static const nrf_drv_timer_t   m_timer = NRF_DRV_TIMER_INSTANCE(3);
static const nrf_drv_timer_t   m_timer_fast = NRF_DRV_TIMER_INSTANCE(0);
static nrf_saadc_value_t       m_buffer_pool[2][SAADC_SAMPLES_IN_BUFFER];
static nrf_ppi_channel_t       m_ppi_channel;

static int16_t                 big_output_table [SAADC_SAMPLES_IN_BUFFER*2];

static int input_flag = 0;
static int output_flag = 0;
static int data_ready = 0;
static uint32_t table_size;

static void lfclk_config(void)
{
    ret_code_t err_code = nrf_drv_clock_init();
    APP_ERROR_CHECK(err_code);
    nrf_drv_clock_lfclk_request(NULL);
}

int flag_toggle(int flag)
{
  if (flag == 0){
    return 1;
  }
  else{
    return 0;
  }
}

void saadc_handler(nrf_drv_saadc_evt_t const * p_event)
{
    if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
    {

      ret_code_t err_code;
      err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_SAMPLES_IN_BUFFER);
      APP_ERROR_CHECK(err_code);
      int16_t* buffer = p_event->data.done.p_buffer;
      
      for (int i = 0; i < SAADC_SAMPLES_IN_BUFFER; i++){
        float voltage_intermediate = (DC_SUPPLY*buffer[i]/4096 - DC_OFFSET);
        float i2s_value =  (voltage_intermediate / (DC_SUPPLY/2));
        big_output_table[input_flag*SAADC_SAMPLES_IN_BUFFER+i] = (int16_t) (MAX_I2S_VAL * i2s_value);
      }
      input_flag = flag_toggle(input_flag);
      
    }
}

static void timer_handler(nrf_timer_event_t event_type, void* p_context)
{

}

static void timer_handler_fast(nrf_timer_event_t event_type, void* p_context)
{

}

void saadc_sampling_event_init(void) {
    ret_code_t err_code;
    err_code = nrf_drv_ppi_init();
    APP_ERROR_CHECK(err_code);

    nrf_drv_timer_config_t timer_config = NRF_DRV_TIMER_DEFAULT_CONFIG;
    timer_config.frequency = NRF_TIMER_FREQ_16MHz;
    err_code = nrf_drv_timer_init(&m_timer, &timer_config, timer_handler);
    APP_ERROR_CHECK(err_code);

    /* setup m_timer for compare event */
    uint32_t ticks = nrf_drv_timer_us_to_ticks(&m_timer, SAADC_SAMPLE_PERIOD);
    nrf_drv_timer_extended_compare(&m_timer, NRF_TIMER_CC_CHANNEL0, ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);
    nrf_drv_timer_enable(&m_timer);

    // second timer setup for m_timer_fast (runs twice as fast)
    err_code = nrf_drv_timer_init(&m_timer_fast, &timer_config, timer_handler_fast);
    APP_ERROR_CHECK(err_code);
    uint32_t ticks_fast = nrf_drv_timer_us_to_ticks(&m_timer_fast, SAADC_SAMPLE_PERIOD/2);
    nrf_drv_timer_extended_compare(&m_timer_fast, NRF_TIMER_CC_CHANNEL1, ticks_fast, NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK, true);
    nrf_drv_timer_enable(&m_timer_fast);

    uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer, NRF_TIMER_CC_CHANNEL0);
    uint32_t saadc_sample_event_addr = nrf_drv_saadc_sample_task_get();

    /* setup ppi channel so that timer compare event is triggering sample task in SAADC */
    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_ppi_channel_assign(m_ppi_channel, timer_compare_event_addr, saadc_sample_event_addr);
    APP_ERROR_CHECK(err_code);
}

void saadc_sampling_event_enable(void)
{
    ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
    APP_ERROR_CHECK(err_code);
}

void saadc_init(void) {

    ret_code_t err_code;

    nrf_drv_saadc_config_t saadc_config = {0};
    saadc_config.low_power_mode = 1;
    saadc_config.resolution = NRF_SAADC_RESOLUTION_12BIT;

    // set up voltage ADC
    nrf_saadc_channel_config_t channel_config0 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN1);
    channel_config0.gain = NRF_SAADC_GAIN1_6;
    channel_config0.reference = NRF_SAADC_REFERENCE_INTERNAL;

    err_code = nrf_drv_saadc_init(&saadc_config, saadc_handler);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_saadc_channel_init(0, &channel_config0);
    APP_ERROR_CHECK(err_code);

    // set up double buffers
    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAADC_SAMPLES_IN_BUFFER);
    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1], SAADC_SAMPLES_IN_BUFFER);
}

void log_init(void)
{
    ret_code_t err_code = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(err_code);

    NRF_LOG_DEFAULT_BACKENDS_INIT();
}

int main(void)
{
  // I2S Config
  
  // MCKFREQ = 1 MHz
  NRF_I2S->CONFIG.MCKFREQ = I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV31 << I2S_CONFIG_MCKFREQ_MCKFREQ_Pos;
  
  // Ratio = 32 -> output at 31.25 kHz
  NRF_I2S->CONFIG.RATIO = I2S_CONFIG_RATIO_RATIO_32X << I2S_CONFIG_RATIO_RATIO_Pos;
    
  // Master mode, 16Bit, left aligned
  NRF_I2S->CONFIG.MODE = I2S_CONFIG_MODE_MODE_MASTER << I2S_CONFIG_MODE_MODE_Pos;
  NRF_I2S->CONFIG.SWIDTH = I2S_CONFIG_SWIDTH_SWIDTH_16BIT << I2S_CONFIG_SWIDTH_SWIDTH_Pos;
  NRF_I2S->CONFIG.ALIGN = I2S_CONFIG_ALIGN_ALIGN_LEFT << I2S_CONFIG_ALIGN_ALIGN_Pos;
  
  // Format = I2S
  NRF_I2S->CONFIG.FORMAT = I2S_CONFIG_FORMAT_FORMAT_I2S << I2S_CONFIG_FORMAT_FORMAT_Pos;
  
  // Use LEFT only 
  NRF_I2S->CONFIG.CHANNELS = I2S_CONFIG_CHANNELS_CHANNELS_LEFT << I2S_CONFIG_CHANNELS_CHANNELS_Pos;
  
  // Configure pins
  NRF_I2S->PSEL.MCK = (PIN_MCK << I2S_PSEL_MCK_PIN_Pos);
  NRF_I2S->PSEL.SCK = (PIN_SCK << I2S_PSEL_SCK_PIN_Pos); 
  NRF_I2S->PSEL.LRCK = (PIN_LRCK << I2S_PSEL_LRCK_PIN_Pos); 
  NRF_I2S->PSEL.SDOUT = (PIN_SDOUT << I2S_PSEL_SDOUT_PIN_Pos);
  NRF_I2S->ENABLE = 1;

  // Enable transmission
  NRF_I2S->CONFIG.TXEN = (I2S_CONFIG_TXEN_TXEN_ENABLE << I2S_CONFIG_TXEN_TXEN_Pos);
  
  // Enable MCK generator
  NRF_I2S->CONFIG.MCKEN = (I2S_CONFIG_MCKEN_MCKEN_ENABLE << I2S_CONFIG_MCKEN_MCKEN_Pos);

  // Two Smaller Output Tables
  // NRF_I2S->TXD.PTR = (uint32_t) &output_table[output_flag][0];
  // table_size = sizeof(output_table[0]) / sizeof(uint32_t);

  // Bigger Output Table (no switching)
  NRF_I2S->TXD.PTR = (uint32_t) &big_output_table[0];
  table_size = sizeof(big_output_table) / sizeof(uint32_t);
  
  NRF_I2S->RXTXD.MAXCNT = table_size;

  nrf_gpio_cfg_output(27);
  
  // Start transmitting I2S data
  NRF_I2S->TASKS_START = 1;

  // PPI Config 
  nrf_power_dcdcen_set(1);
  lfclk_config();

  log_init();
  saadc_init();
  saadc_sampling_event_init();
  saadc_sampling_event_enable();

  while (1)
  {
    //__WFI();
  }
}
 

Here is a screenshot of the scope image of the input and output signals:

 

Parents
  • Hi,

    How does the buffer look when it is filled with the failing samples? Have you tried moving the processing of the buffer outside of the interrupt handler/callback handler (possibly copy the content to a third buffer or using a third buffer to allow double buffering and processing of one buffer)? I would also recommend that you move the nrf_drv_saadc_buffer_convert() call after you have processed/copied the buffer. Calling this function will trigger the START task, allowing the SAADC peripheral to overwrite the buffer while you process it.

    There is a new SAADC driver coming in NRFX that will handle double buffering pointer updates better, and provide more advanced features, but unfortunately, we do not provide any examples of this yet as it is not integrated into the SDK.

    Best regards,
    Jørgen

  • Hi Jørgen,

    I tried both of those suggestions, and I couldn't get a working solution. Moving nrf_drv_saadc_buffer_convert() call didn't seem to have an effect, likely since the buffer processing time is still shorter than the time it takes for the secondary buffer to fill up.

    Moving the buffer processing outside of the interrupt handler to the timer handlers didn't seem to resolve the issue as well; there is still a small glitch in the output buffer that lines up with everytime the saadc_handler() gets past the if statement (I tested this by toggling a GPIO everytime the if statement in the interrupt handler was true). I am thinking 

    Let me know if you have any other suggestions about this issue, and thank you for your advice!

Reply
  • Hi Jørgen,

    I tried both of those suggestions, and I couldn't get a working solution. Moving nrf_drv_saadc_buffer_convert() call didn't seem to have an effect, likely since the buffer processing time is still shorter than the time it takes for the secondary buffer to fill up.

    Moving the buffer processing outside of the interrupt handler to the timer handlers didn't seem to resolve the issue as well; there is still a small glitch in the output buffer that lines up with everytime the saadc_handler() gets past the if statement (I tested this by toggling a GPIO everytime the if statement in the interrupt handler was true). I am thinking 

    Let me know if you have any other suggestions about this issue, and thank you for your advice!

Children
No Data
Related