SAADC Oversample acquisition time

I have been doing some measurements on the execution time of my code and noticed something strange.

Currently, I am using an application timer to trigger the ADC to sample at 256Hz. 

The code to trigger the ADC in the application timer interrupt handler is shown below, 

Timer Interrupt Handler and Snippet of the function to trigger ADC sampling

( ignore the third ADC channel which is sampled at a different freq and not enabled currently )

static void timer_timeout_handler(void * p_context)
{
	adc_sample();
  LED_drv_timer_update();
}

static void adc_sample()
{
	int timer1 = app_timer_cnt_get();
	temp_cnt_tck++;
	int16_t temp[5] = {10};
	if( !m_saadc_initialized )
	{
		*((volatile uint32_t *)0x40007FFC) = 1;
		saadc_init2();
		if( ( (temp_cnt_tck % (tempSR/TEMP_OVER) ) == 0) && (temp_on) && (temp_cnt_tck>0))
		{
			NRF_SAADC->CH[2].PSELP = ANALOG_TEMPERATURE_SENSOR_PIN << SAADC_CH_PSELP_PSELP_Pos;
			NRF_SAADC->CH[2].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos;
			NRF_SAADC->RESULT.MAXCNT = 3;
		}
		m_saadc_initialized = true;
	}
	saadc_sample();
	
	int timer2 = app_timer_cnt_get();
	int timer_d;
	app_timer_cnt_diff_compute(timer2,timer1,&timer_d);
	NRF_LOG_INFO("Ticks %d %d %d\n",timer1, timer2,timer_d);
	NRF_LOG_FLUSH();
	.
    .
    .
    .
}

SAADC Task Trigger ( taken from the forum )

ADC is reset to address the issue with high DMA current event after un-initialization when

bust + oversample + scan mode is enabled

static void saadc_sample()
{

  NRF_SAADC->TASKS_START = 1;
  while (NRF_SAADC->EVENTS_STARTED == 0);
  NRF_SAADC->EVENTS_STARTED = 0;
	
  // Do a SAADC sample, will put the result in the configured RAM buffer.
  NRF_SAADC->TASKS_SAMPLE = 1;
  while (NRF_SAADC->EVENTS_END == 0);
  NRF_SAADC->EVENTS_END = 0;
  NRF_SAADC->TASKS_STOP = 1;
	saadc_uninit2();
	*((volatile uint32_t *)0x40007FFC) = 0;
	m_saadc_initialized = false;
}

SAADC Configuration ( taken from the forum )

static void saadc_init2(void)
{
  // Configure SAADC singled-ended channel, Internal reference (0.6V) and 1/6 gain.
  NRF_SAADC->CH[0].CONFIG = (SAADC_CH_CONFIG_GAIN_Gain4    << SAADC_CH_CONFIG_GAIN_Pos) |
                            (SAADC_CH_CONFIG_MODE_Diff       << SAADC_CH_CONFIG_MODE_Pos) |
                            (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) |
                            (SAADC_CH_CONFIG_RESN_Bypass    << SAADC_CH_CONFIG_RESN_Pos) |
                            (SAADC_CH_CONFIG_RESP_Bypass     << SAADC_CH_CONFIG_RESP_Pos) |
                            (SAADC_CH_CONFIG_TACQ_40us        << SAADC_CH_CONFIG_TACQ_Pos)	|
														(SAADC_OVERSAMPLE_OVERSAMPLE_Over4x << SAADC_OVERSAMPLE_OVERSAMPLE_Pos)|
														(SAADC_CH_CONFIG_BURST_Enabled   << SAADC_CH_CONFIG_BURST_Pos);
  // Configure the SAADC channel with VDD as positive input, no negative input(single ended).
  NRF_SAADC->CH[0].PSELP = ANALOG_CH0_PIN << SAADC_CH_PSELP_PSELP_Pos;
  NRF_SAADC->CH[0].PSELN = ANALOG_VREF_PIN << SAADC_CH_PSELN_PSELN_Pos;
	
	NRF_SAADC->CH[1].CONFIG = (SAADC_CH_CONFIG_GAIN_Gain4    << SAADC_CH_CONFIG_GAIN_Pos) |
                            (SAADC_CH_CONFIG_MODE_Diff       << SAADC_CH_CONFIG_MODE_Pos) |
                            (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) |
                            (SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESN_Pos) |
                            (SAADC_CH_CONFIG_RESP_Bypass     << SAADC_CH_CONFIG_RESP_Pos) |
                            (SAADC_CH_CONFIG_TACQ_40us        << SAADC_CH_CONFIG_TACQ_Pos)|
														(SAADC_OVERSAMPLE_OVERSAMPLE_Over4x << SAADC_OVERSAMPLE_OVERSAMPLE_Pos)|
														(SAADC_CH_CONFIG_BURST_Enabled   << SAADC_CH_CONFIG_BURST_Pos);

  // Configure the SAADC channel with VDD as positive input, no negative input(single ended).
  NRF_SAADC->CH[1].PSELP = ANALOG_CH1_PIN << SAADC_CH_PSELP_PSELP_Pos;
  NRF_SAADC->CH[1].PSELN = ANALOG_VREF_PIN2 << SAADC_CH_PSELN_PSELN_Pos;
		
	NRF_SAADC->CH[2].CONFIG = (SAADC_CH_CONFIG_GAIN_Gain1_6    << SAADC_CH_CONFIG_GAIN_Pos) |
                            (SAADC_CH_CONFIG_MODE_SE        << SAADC_CH_CONFIG_MODE_Pos) |
                            (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) |
                            (SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESN_Pos) |
                            (SAADC_CH_CONFIG_RESP_Bypass     << SAADC_CH_CONFIG_RESP_Pos) |
                            (SAADC_CH_CONFIG_TACQ_40us        << SAADC_CH_CONFIG_TACQ_Pos)|
														(SAADC_OVERSAMPLE_OVERSAMPLE_Over4x << SAADC_OVERSAMPLE_OVERSAMPLE_Pos)|
														(SAADC_CH_CONFIG_BURST_Enabled  << SAADC_CH_CONFIG_BURST_Pos);

//  // Configure the SAADC channel with VDD as positive input, no negative input(single ended).
  NRF_SAADC->CH[2].PSELP = SAADC_CH_PSELP_PSELP_NC << SAADC_CH_PSELP_PSELP_Pos;
  NRF_SAADC->CH[2].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos;

  // Configure the SAADC resolution.
  NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_12bit << SAADC_RESOLUTION_VAL_Pos;

  // Configure result to be put in RAM at the location of "result" variable.
  NRF_SAADC->RESULT.MAXCNT = 2;
  NRF_SAADC->RESULT.PTR = (uint32_t)&result[0];

  // No automatic sampling, will trigger with TASKS_SAMPLE.
  NRF_SAADC->SAMPLERATE = SAADC_SAMPLERATE_MODE_Task << SAADC_SAMPLERATE_MODE_Pos;

  // Enable SAADC (would capture analog pins if they were used in CH[0].PSELP)
	NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos;
  

here I am trying to measure the time taken for the saadc_sample function to execute, which is

is a blocking function that waits for the 2 channels to finish sampling ( oversampling 4x, 40us acquisition time, burst enabled on all channels ).

I noticed that the time taken is the same ( 3 ticks = approx 90us ) regardless of the oversampling configuration, whereas intuitively the time taken should

be 2 ( channel count ) * 40us ( acquisition time ) * 4 ( oversample ) = 320us. 

Is this normal or is there something that I am missing?

Output from RTT 

Thanks!

  • Hello,

    You are correct that the oversampling will cause the SAADC will take longer.

    Depending on what version of the SDK you are using, the answer may differ, but I am using the nRF5 SDK v17.1.0 for now. Please specify what SDK you are using for follow up questions.

    The saadc_sample() function will only trigger the sampling task by setting the NRF_SAADC_TASK_SAMPLE register. This does not mean that the sampling is done when saadc_sample() has returned. When the sample is ready, it will trigger the callback that you have set up.

    If you want to measure the time the actual transfer takes, you can use a free timer to test this. Something like:

    test_timer_init()
    {
        NRF_TIMER3->BITMODE                 = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
        NRF_TIMER3->PRESCALER               = 0;
        NRF_TIMER3->MODE                    = TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos;
        NRF_TIMER3->TASKS_START = 1;
    }
    
    static void timer_timeout_handler(void * p_context)
    {
        NRF_TIMER3->TASKS_CAPTURE[0] = 1; // Trigger first timestamp
    	adc_sample();
      LED_drv_timer_update();
    }
    
    void saadc_callback() // The function where you get the SAADC result
    {
        uint32_t timer_sample_a;
        uint32_t timer_sample_b;
        uint32_t timer_diff_ticks;
        NRF_TIMER3->TASKS_CAPTURE[1] = 1; // Trigger second timestamp
        
        timer_sample_a = NRF_TIMER3->CC[0];
        timer_sample_b = NRF_TIMER3->CC[1];
        timer_diff_ticks = timer_sample_b - timer_sample_a; // clock freq 16MHz
        NRF_LOG_INFO("diff ticks %d, %d us", timer_diff, (timer_diff>>4)); // >>4 = /16
        ...
        // whatever you already had here.
        
        
    }

    Best regards,

    Edvin

  • Hi Edvin thanks for replying.

    I am using SDK v13, but in this case I am writing directly to the adc registers so the SDK version should not have an effect?

    As you can see from the code provided saadc_sample is a custom function. And when that function is called it not only sets the sample task but also waits for the conversion to finish ie when the result buffer is full (events_end). So therefore shouldn't the time taken measured be accurate?

  • bryanhsieh said:
    I am using SDK v13, but in this case I am writing directly to the adc registers so the SDK version should not have an effect?

    no, it shouldn't. I just didn't check all versions. 

    however, since I found a function called nrfx_saadc_sample() in SDK17.1.0, I assumed it was a newer version of the saadc_sample. So I didn't notice you had implemented that yourself. 

    Did you test using the TIMER peripheral directly like I suggested in the previous reply? I am not sure how you set up the app_timer. Perhaps the app_timer uses a different prescaler, causing it to have a different tick rate than what you think.

    BR,

    Edvin

  • I used app_timer as it was readily available and it uses RTC with the prescaler set to 0 so the resolution should be 30.5us. 

    And a tick count of 3 seems to match with 2 channels at 40us acquistion time (2*40 + conversion).

    However, the tick count doesnt change when I change the oversample rate, it does change when I change the acquistion time. 

    I will try with a timer later, but it will just have a better resolution so I dont think it will change the result as the RTC should not be limited by its resolution with 40us acquistion time and 4x oversample.  

    Thanks.

  • Ok, in that case, please make sure that your APP_TIMER_CONFIG_RTC_FREQUENCY is set to 0 in sdk_config.h. 

    Can you please try to print the CH[0].CONFIG and CH[1].CONFIG registers before you sample, and can you also please check your NRF_SAADC.OVERSAMPLE register? 

    Something like this:

    NRF_LOG_INFO("settings:\nCH[0].CONFIG: 0x%08x\nCH[1].CONFIG: 0x%08x\nSAADC.OVERSAMPLE: 0x%08x", NRF_SAADC->CH[0].CONFIG,
                                                                                                    NRF_SAADC->CH[1].CONFIG,
                                                                                                    NRF_SAADC->OVERSAMPLE);

Related