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.

  • Hello,

    Where do you re-enable the SAADC? That is, if flag_stop_sampling was set to 1, and then you start the sampling again?

     

    Best regards,

    Edvin

  • I start the sampling again at least few seconds later from stopping it. It is done from main() by calling start_adc_sampling()

    What i think i am looking is to understand why the initial call to start_adc_sampling works, while a call after stopping the sampling does not work (it only work if i do NOT do the code the is commented out by '#if 0 ... #endif').

    By the way - the attached code is just part of a bigger chunk of code involving 9 PPI channels, all 4 timers and 4 GPIOTE. It deals with rotating a stepper motor and sampling input from 2 encoder channels + another sensor (these are shown in the picture i placed)

  • Hello,

    I understand. If you replace "#if 0" with "#if 1", the problem occurs.
    I assume that you have set flag_stop_sampling to false before start_adc_sampling, right?


    Can you try to break in the handler when the issue occurs, and try to read the registers:
    NRF_SAADC->CH[0].PSELP,
    NRF_SAADC->CH[1].PSELP,
    NRF_SAADC->CH[2].PSELP,

    And see whether they are still configured to the right pins, or if any of them are disabled?

    Best regards,
    Edvin

  • 1. Yes - shutting down the SAADC (the "#if 0" code) causes the problem to occur in most of the times.

    2. I did check the 3 PSELP registers and they all contain correct values when the problem occur, so this is not the root cause. You can also see that my code has very simple in that respect:

             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;

    3. My guess is that when re enabling the SAADC the ADC samples one channel, but not to the EasyDMA and when it starts filling up the DMA buffer the entire sampling shifts one sample causing me to see the 2nd channel where the 1st channel is expected. Instead of {ch0,ch1,ch2,ch0,ch1,ch2...} i see  {ch1,ch2,ch0,ch1,ch2...}. 

  • Does this mean that the readings, other from that the channels are swapped, are ok? They are not suppose to have the same pattern as before the brake in the readings?

     

    I.e. in the graph from your original post, only the colors are wrong after X = 4561?

     

    BR,

    Edvin

Related