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

Glitches in adc sampling

I am sampling 3 sensors using the ADC peripheral, clocking it once every 15us to sample the 3 sensors (3us acquisition time + 2us processing = 5us per channel).

I use the easy DMA in double buffer to sample 402 samples and interrupt me so i can process them and prepare the DMA next buffer (interrupt every 402*15/3=2100us).

In the interrupt context, I copy the buffer into a continuous big buffer and to excel and this is what i see:

when i stop the adc sampling and start it again (vertical spike above), i sometimes have a glitch that looks as if one sample is missed and i thus get channel 2 instead of 1 as shown in the above image. The vertical spike seen in the image is just a marker i add to the debug software buffer to mark where i re-enabled adc sampling.

The image above contains ~6 concatenated buffers (each buffer holds 402 samples = 134 samples from each of the 3).

Following is the code i use:

// this is double buffer hooked to the adc pointer. it is used to sample 3 channels 
static nrf_saadc_value_t adc_3_channels_double_buffer[2][402];
static uint32_t adc_3_channels_double_buffer_index = 0;

//this is a debug buffer holding 50 adc buffers 
static nrf_saadc_value_t adc_3_channels_debug_buffer[402*50]; 
static int adc_3_channels_debug_buffer_index = 0;

// tis function is called to start adc sampling 
void start_adc_sampling(void)
{
  // NRF_TIMER1 is a timer used to clock adc sampling of the 3 sensors we have - clock every 15us 
  NRF_TIMER1->MODE = (TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos); 
  NRF_TIMER1->PRESCALER = 4; // 1mhz --> 1us 
  NRF_TIMER1->CC[0] = 15; 
  NRF_TIMER1->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Msk;
  
  // ppi #6: connect adc timer to adc sampling 
  NRF_PPI->CH[5].EEP = (uint32_t) &NRF_TIMER1->EVENTS_COMPARE[0];
  NRF_PPI->CH[5].TEP = (uint32_t) &NRF_SAADC->TASKS_SAMPLE;
  
  // ppi #9: connect adc buffer end to buffer start to get continuous double buffer sampling with no sampl
  NRF_PPI->CH[8].EEP = (uint32_t) &NRF_SAADC->EVENTS_END;
  NRF_PPI->CH[8].TEP = (uint32_t) &NRF_SAADC->TASKS_START;
  
  // Enable all ppi channels 
  NRF_PPI->CHENSET = (PPI_CHENSET_CH5_Enabled << PPI_CHENSET_CH5_Pos) | 
                     (PPI_CHENSET_CH8_Enabled << PPI_CHENSET_CH8_Pos);

  // general adc config (nrf_drv_saadc_init)
  NRF_SAADC->ENABLE = (SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos);
  NRF_SAADC->RESOLUTION = NRF_SAADC_RESOLUTION_12BIT;
  NRF_SAADC->OVERSAMPLE = NRF_SAADC_OVERSAMPLE_DISABLED;
  NRF_SAADC->INTENCLR = NRF_SAADC_INT_ALL;
  NRF_SAADC->EVENTS_END = 0x0UL;
  volatile uint32_t dummy = NRF_SAADC->EVENTS_END;
  NRF_SAADC->EVENTS_STARTED = 0x0UL;
  dummy = NRF_SAADC->EVENTS_STARTED;
  (void)dummy;
  NVIC_SetPriority(SAADC_IRQn, SAADC_CONFIG_IRQ_PRIORITY);
  NVIC_ClearPendingIRQ(SAADC_IRQn);
  NVIC_EnableIRQ(SAADC_IRQn);
  NRF_SAADC->INTENSET = NRF_SAADC_INT_END;
  
  // configure the adc to sample the 3 sensors 
  for ( int i=0; i<3; i++ )
  {
    NRF_SAADC->CH[i].PSELN = NRF_SAADC_INPUT_DISABLED;
    NRF_SAADC->CH[i].PSELP = NRF_SAADC_INPUT_DISABLED;
    NRF_SAADC->CH[i].CONFIG =
            ((NRF_SAADC_RESISTOR_DISABLED   << SAADC_CH_CONFIG_RESP_Pos)   & SAADC_CH_CONFIG_RESP_Msk)
            | ((NRF_SAADC_RESISTOR_DISABLED << SAADC_CH_CONFIG_RESN_Pos)   & SAADC_CH_CONFIG_RESN_Msk)
            | ((NRF_SAADC_GAIN1_4           << SAADC_CH_CONFIG_GAIN_Pos)   & SAADC_CH_CONFIG_GAIN_Msk)
            | ((NRF_SAADC_REFERENCE_VDD4    << SAADC_CH_CONFIG_REFSEL_Pos) & SAADC_CH_CONFIG_REFSEL_Msk)
            | ((NRF_SAADC_ACQTIME_3US       << SAADC_CH_CONFIG_TACQ_Pos)   & SAADC_CH_CONFIG_TACQ_Msk)
            | ((NRF_SAADC_MODE_SINGLE_ENDED << SAADC_CH_CONFIG_MODE_Pos)   & SAADC_CH_CONFIG_MODE_Msk)
            | ((NRF_SAADC_BURST_DISABLED    << SAADC_CH_CONFIG_BURST_Pos)  & SAADC_CH_CONFIG_BURST_Msk);
  }
  NRF_SAADC->CH[0].PSELP = NRF_SAADC_INPUT_AIN1;
  NRF_SAADC->CH[1].PSELP = NRF_SAADC_INPUT_AIN2;
  NRF_SAADC->CH[2].PSELP = NRF_SAADC_INPUT_AIN3;
  
  // assigne the 1st adc buffer without starting it yet (nrf_drv_saadc_buffer_convert)
  NRF_SAADC->RESULT.PTR = (uint32_t)adc_3_channels_double_buffer[0];
  NRF_SAADC->RESULT.MAXCNT = 402;
  NRF_SAADC->EVENTS_STARTED = 0x0UL;
  dummy = NRF_SAADC->EVENTS_STARTED;
  
  // trigger samplig 
  uint8_t val;
  sd_nvic_critical_region_enter(&val); 
  NRF_TIMER1->TASKS_START = 1; 
  NRF_SAADC->TASKS_START = 1;
  sd_nvic_critical_region_exit(val);

  // configure the 2nd adc buffer once the 1st is started 
  while (NRF_SAADC->EVENTS_STARTED==0);
  NRF_SAADC->EVENTS_STARTED = 0x0UL;
  dummy = NRF_SAADC->EVENTS_STARTED;
  NRF_SAADC->RESULT.PTR = (uint32_t)adc_3_channels_double_buffer[1];
  adc_3_channels_double_buffer_index = 0;
}

// adc interrupt - called once every 402 samples to process the sampled buffer and prepare the nexe buffer 
void SAADC_IRQHandler(void)
{
  // only event end interruopt is enabled anyway  
 if ( nrf_saadc_event_check(NRF_SAADC_EVENT_END) )
 {
   // clear event
    NRF_SAADC->EVENTS_END = 0x0UL;
    volatile uint32_t dummy = NRF_SAADC->EVENTS_END;
   
   // get filled buffer and swap logically 
   nrf_saadc_value_t *src = adc_3_channels_double_buffer[adc_3_channels_double_buffer_index];
   adc_3_channels_double_buffer_index = 1 - adc_3_channels_double_buffer_index;
   
    if ( !flag_stop_sampling )
    {
      // ensure start before init 
      while (NRF_SAADC->EVENTS_STARTED==0);
      NRF_SAADC->EVENTS_STARTED = 0x0UL;
      dummy = NRF_SAADC->EVENTS_STARTED;
      (void)dummy;
      
      // init next buffer (current buffer being sampled is the other buffer so we have time to do this) 
      NRF_SAADC->RESULT.PTR = (uint32_t)src;
      NRF_SAADC->RESULT.MAXCNT = 402;
    }

    // copy the buffer into a big continuous buffer with room for 50 buffers 
    for ( int i=0; i<402; i+=3, src += 3 )
    {
      adc_3_channels_debug_buffer[adc_3_channels_debug_buffer_index++] = src[0];
      adc_3_channels_debug_buffer[adc_3_channels_debug_buffer_index++] = src[1];
      adc_3_channels_debug_buffer[adc_3_channels_debug_buffer_index++] = src[2];
    }
    
    // if someone sets this flag - i want to stop the adc sampling 
    if ( flag_stop_sampling )
    {
      // disconenct ppi
      NRF_PPI->CHENCLR = ((PPI_CHENCLR_CH5_Clear << PPI_CHENCLR_CH5_Pos) | 
                          (PPI_CHENCLR_CH8_Clear << PPI_CHENCLR_CH8_Pos));
      
      // adc timer shutdown (nrf_drv_timer_uninit) 
      NRF_TIMER1->SHORTS = 0;
      NRF_TIMER1->INTENCLR = 0xFFFFFFFF;
      NRF_TIMER1->TASKS_SHUTDOWN = 1;
      
#if 0 // problem ! - this block must be commented out in order to avoid channels switching in between trains   

      // adc shutdown (nrf_drv_saadc_uninit)
      NRF_SAADC->INTENCLR = NRF_SAADC_INT_ALL; 
      NVIC_DisableIRQ(SAADC_IRQn);
      NRF_SAADC->TASKS_STOP = 0x1UL;
      uint32_t timeout = 10000;
      while (NRF_SAADC->EVENTS_STOPPED==0 && timeout > 0)
      {
        --timeout;
      }
      ASSERT(timeout > 0);
      NRF_SAADC->ENABLE = (SAADC_ENABLE_ENABLE_Disabled << SAADC_ENABLE_ENABLE_Pos);
      NRF_SAADC->CH[0].PSELP = NRF_SAADC_INPUT_DISABLED;
      NRF_SAADC->CH[1].PSELP = NRF_SAADC_INPUT_DISABLED;
      NRF_SAADC->CH[2].PSELP = NRF_SAADC_INPUT_DISABLED;

#endif
    }
  }
  else
  {
    ASSERT(0);
  }
}

In the code i have '#if 0' in line 131 commenting out the bit of code that causes the glitch problem.

When i remove it i see no glitches.

In the blocked code shuts down the ADc in order to save power.

It blocked code is art of code within the ADC interrupt that is trigered when someone set 'flag_stop_sampling' to 1 in order to stop ADC sampling.

My point is - if i disable the ADC peripheral (as i do) in between samples and later on wake it up again and re configure the entire logic - how come i see glitches of channels in my buffer.

My target was to make my software implementation to do the sampling regardless of softdevice and other tasks interrupts.

  • yes. readings are ok, they just appear in the wrong location in the RAM buffer. As if the EasyDMA starts from the 2nd sample and thus a constant offset occurs - {ch1,ch2,ch0,ch1,ch2...} instead of {ch0,ch1,ch2,ch0,ch1,ch2...}

    I did registers dump before i start the SAADC each time, and when i disable the SAADC before i start it again (the "if 0" code), i see some leftover value in the RESULT.AMOUNT register that its value is somewhere in between 0x00000000 and 0x00000192 (0x192 is the buffer size - 402). I also see EVENTS_STOPPED=1.

    how can i clear down RESULT.AMOUNT before i start SAADC samling?

    I modified the code used to shut down the ADC and added delay and it seems to work,

    This is the code:

      uint8_t val;
      sd_nvic_critical_region_enter(&val); // new - prevent interruption 
      NRF_SAADC->INTENCLR = NRF_SAADC_INT_ALL; 
      NVIC_DisableIRQ(SAADC_IRQn);
      while ( NRF_SAADC->EVENTS_END==0x0UL ); // new - ensure end alignmnet 
      NRF_SAADC->EVENTS_STOPPED = 0x0UL;
      volatile uint32_t dummy = NRF_SAADC->EVENTS_STOPPED;
      (void)dummy;
      NRF_SAADC->TASKS_STOP = 0x1UL;
      uint32_t timeout = 10000;
      while (NRF_SAADC->EVENTS_STOPPED==0 && timeout > 0)
      {
        --timeout;
      }
      ASSERT(timeout > 0);
      nrf_delay_us(10); // new - delay equal to two samples (3us sampling rate + 2us convert)
      NRF_SAADC->ENABLE = (SAADC_ENABLE_ENABLE_Disabled << SAADC_ENABLE_ENABLE_Pos);
      sd_nvic_critical_region_exit(val); // new 
    

    Look at "new" comments to see what i have added.

    Seems like the proper sampling of SAADC is directly affected by the timing i use to shut down the SAADC.

    I must understand why is that and what am i doing wrong?

    I also saw this issue that seems relevant: 

    https://devzone.nordicsemi.com/f/nordic-q-a/16885/saadc-scan-mode-sample-order-is-not-always-consistent/134614#134614

  • Hello,

    I have spoken with some of out ADC engineers. They say that this is most likely related to a bug.

     

    Can you please check Jørgen's answer marked in green on this page:

    https://devzone.nordicsemi.com/f/nordic-q-a/20291/offset-in-saadc-samples-with-easy-dma-and-ble

     

    Best regards,

    Edvin

     

  • Thanks, Edvin. I have already seen the link you have provided and also posted my question over there as well few days ago.

    I believe my situation is different since i have used PPI to chain END event to START task of the SAADC sampling (one of the solution suggested in the link you have provided), and this works fine. i do not have any channels swaps during an on-going continuous sampling.

    My problem is that the channels swap occurs when restarting SAADC sampling some time after i aborted a previous sampling. When doing that i have a constant channels swap.

    To overcome this i need to add 10us delay in the SAADC abort routine (as i already explained above in the last provided code example).

    All i need is for you to confirm that that my SAADC abort patch with the 10us delay makes sense.

    I have added 10us delay here:

    NRF_SAADC->TASKS_STOP = 0x1UL;
    uint32_t timeout = 10000;
    while (NRF_SAADC->EVENTS_STOPPED==0 && timeout > 0)
    {
    --timeout;
    }
    ASSERT(timeout > 0);
    nrf_delay_us(10); // new - delay equal to two samples (3us sampling rate + 2us convert)
    NRF_SAADC->ENABLE = (SAADC_ENABLE_ENABLE_Disabled << SAADC_ENABLE_ENABLE_Pos);

  • Hello,

    I haven't worked with the workaround in that post, but I see that you trigger TASKS_SAMPLE from the &NRF_TIMER1->EVENTS_COMPARE[0]; in PPI, so it might very well be the issue.

    What happens if you try to change TASKS_SAMPLE with TASKS_START in NRF_PPI->CH[5].TEP ?

     

    Best regards,

    Edvin

  • I still haven't solved the excessive power consumption issue.

    In order to isolate the problem i have this code here that i use to test:

    void a2d_test(void)
    {
      // timer that clocks the adc once every 18us 
      NRF_TIMER1->MODE = (TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos); 
      NRF_TIMER1->PRESCALER = 4; // 1mhz --> 1us 
      NRF_TIMER1->CC[0] = 18; 
      NRF_TIMER1->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Msk;
    
      // connect the timer to adc sampling 
      NRF_PPI->CH[0].EEP = (uint32_t) &NRF_TIMER1->EVENTS_COMPARE[0];
      NRF_PPI->CH[0].TEP = (uint32_t) &NRF_SAADC->TASKS_SAMPLE;
    
      // enable ppi 
      NRF_PPI->CHENSET = PPI_CHENSET_CH0_Enabled << PPI_CHENSET_CH0_Pos;
    
      // configure the adc to sample 3 channels 111 samples per channel (total 333 samples)
      NRF_SAADC->ENABLE = (SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos);
      NRF_SAADC->RESOLUTION = NRF_SAADC_RESOLUTION_12BIT;
      NRF_SAADC->OVERSAMPLE = NRF_SAADC_OVERSAMPLE_DISABLED;
      NRF_SAADC_EVENTS_CLEAR(EVENTS_END)
      NRF_SAADC_EVENTS_CLEAR(EVENTS_STARTED)
      for ( int i=0; i<3; i++ )
      {
        NRF_SAADC->CH[i].PSELN = NRF_SAADC_INPUT_DISABLED;
        NRF_SAADC->CH[i].CONFIG =
                ((NRF_SAADC_RESISTOR_DISABLED   << SAADC_CH_CONFIG_RESP_Pos)   & SAADC_CH_CONFIG_RESP_Msk)
                | ((NRF_SAADC_RESISTOR_DISABLED << SAADC_CH_CONFIG_RESN_Pos)   & SAADC_CH_CONFIG_RESN_Msk)
                | ((NRF_SAADC_GAIN1_4           << SAADC_CH_CONFIG_GAIN_Pos)   & SAADC_CH_CONFIG_GAIN_Msk)
                | ((NRF_SAADC_REFERENCE_VDD4    << SAADC_CH_CONFIG_REFSEL_Pos) & SAADC_CH_CONFIG_REFSEL_Msk)
                | ((NRF_SAADC_ACQTIME_3US       << SAADC_CH_CONFIG_TACQ_Pos)   & SAADC_CH_CONFIG_TACQ_Msk)
                | ((NRF_SAADC_MODE_SINGLE_ENDED << SAADC_CH_CONFIG_MODE_Pos)   & SAADC_CH_CONFIG_MODE_Msk)
                | ((NRF_SAADC_BURST_DISABLED    << SAADC_CH_CONFIG_BURST_Pos)  & SAADC_CH_CONFIG_BURST_Msk);
      }
      NRF_SAADC->CH[0].PSELP = NRF_SAADC_INPUT_AIN1;
      NRF_SAADC->CH[1].PSELP = NRF_SAADC_INPUT_AIN2;
      NRF_SAADC->CH[2].PSELP = NRF_SAADC_INPUT_AIN3;
      NRF_SAADC->RESULT.PTR = (uint32_t)train_adc_buffer[0];
      NRF_SAADC->RESULT.MAXCNT = 333;
      NRF_SAADC_EVENTS_CLEAR(EVENTS_STARTED)
      
      // trigger the timer that clocks the adc 
      NRF_TIMER1->TASKS_START = 1; // 
    
      // repeat 10 times 
      for ( int i=0; i<10; i++ )
      {
        // trigger the adc 
        NRF_SAADC->TASKS_START = 1;
        
        // wait until adc ends its sampling (not stopped)  
        NRF_SAADC_EVENTS_CLEAR(EVENTS_END)
        while (NRF_SAADC->EVENTS_END==0);
    
        // set its pointer to the desired location 
        NRF_SAADC->RESULT.PTR = (uint32_t)train_adc_buffer[0];
      }
    
      // stop the ADC 
      // problem - any delay i add here or any interruption between EVENTS_END and STOP casues permaenent 1.2ma consumtion nrf_delay_us(3);
      NRF_SAADC_EVENTS_CLEAR(EVENTS_STOPPED)
      NRF_SAADC->TASKS_STOP = 1;
      while (NRF_SAADC->EVENTS_STOPPED==0);
      
      // disable the adc 
      NRF_SAADC->ENABLE = (SAADC_ENABLE_ENABLE_Disabled << SAADC_ENABLE_ENABLE_Pos);
      NRF_SAADC->CH[0].PSELP = NRF_SAADC_INPUT_DISABLED;
      NRF_SAADC->CH[1].PSELP = NRF_SAADC_INPUT_DISABLED;
      NRF_SAADC->CH[2].PSELP = NRF_SAADC_INPUT_DISABLED;
    
      // disaple the ppi   
      NRF_PPI->CHENCLR = (PPI_CHENCLR_CH0_Clear << PPI_CHENCLR_CH0_Pos); 
    
      // disable the timer   
      NRF_TIMER1->TASKS_STOP = 1;
      NRF_TIMER1->SHORTS = 0;
      NRF_TIMER1->TASKS_SHUTDOWN = 1;
    }
    

    Please look at the comment where i say "problem" right when stopping the ADC.

    Whenever i have a delay between EVENTS_END and  NRF_SAADC->TASKS_STOP=1; i get excessive permanent power consumption of ~1.2mA.

    From this point on there is no way to reduce the power unless i RESET the chip.

    If i use PPI to STOP the ADC, the problem is gone since EVENTS_END and TASK_STOP occur at the same time. But in my system i cannot do this.

    Do you now about this issue and why it occurs?

Related