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

Dual channel ADC capture missing samples from one channel

Dear All,

I am trying to implement a simple data recorder on nRF52840 DK that digitizes signals over SAADC and writes them onto the SD card. The program is working fine for just one input channel, but when I try to extend it to use two SAADC channels I either miss all samples from Channel 0 or all from Channel 1. Which channel is missing in a run, seems to be random. Here's the jist of the code:

#define sampling_rate (2*20000)


// -------------------------------------------------------------------------------------------------------
void adc_handler(nrfx_saadc_evt_t const * p_event)
{
  if (NRFX_SAADC_EVT_DONE == p_event->type)
  {
    // copy 'p_event->data.done.size' from 'p_event->data.done.p_buffer' to FIFO
	// ...

    // set next buffer for continuity
    nrfx_saadc_buffer_convert(...);
  }
  
  // ...
}


// =======================================================================================================
int main(void)
{
  // initialize ADC
  nrfx_saadc_config_t const adc_config=
  {
    .resolution= NRF_SAADC_RESOLUTION_12BIT,    
    .oversample= NRF_SAADC_OVERSAMPLE_DISABLED, 
    .interrupt_priority= 2,
    .low_power_mode= false
  };

  nrfx_saadc_init(&adc_config, adc_handler);

  // initalize channels
  nrf_saadc_channel_config_t const adc_channel_config0=
  {
    .resistor_p= NRF_SAADC_RESISTOR_DISABLED,
    .resistor_n= NRF_SAADC_RESISTOR_DISABLED,
    .gain= NRF_SAADC_GAIN1,
    .reference= NRF_SAADC_REFERENCE_INTERNAL,
    .acq_time= NRF_SAADC_ACQTIME_20US,
    .mode= NRF_SAADC_MODE_SINGLE_ENDED,
    .burst= NRF_SAADC_BURST_DISABLED,
    .pin_p= NRF_SAADC_INPUT_AIN0,     // AIN0: P0.02, see https://infocenter.nordicsemi.com/topic/ps_nrf52840/pin.html?cp=4_0_0_6_0_0#aqfn73
    .pin_n= NRF_SAADC_INPUT_DISABLED
  };

  nrf_saadc_channel_config_t const adc_channel_config1=
  {
    .resistor_p= NRF_SAADC_RESISTOR_DISABLED,
    .resistor_n= NRF_SAADC_RESISTOR_DISABLED,
    .gain= NRF_SAADC_GAIN1,
    .reference= NRF_SAADC_REFERENCE_INTERNAL,
    .acq_time= NRF_SAADC_ACQTIME_20US,
    .mode= NRF_SAADC_MODE_SINGLE_ENDED,
    .burst= NRF_SAADC_BURST_DISABLED,
    .pin_p= NRF_SAADC_INPUT_AIN7,     // AIN7: P0.31, see https://infocenter.nordicsemi.com/topic/ps_nrf52840/pin.html?cp=4_0_0_6_0_0#aqfn73
    .pin_n= NRF_SAADC_INPUT_DISABLED
  };

  nrfx_saadc_channel_init(0, &adc_channel_config0);
  nrfx_saadc_channel_init(1, &adc_channel_config1);

  // calibrate ADC
  nrfx_saadc_calibrate_offset();
  // wait until ready ...
  
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // set up 20 ksps (per channel) sampling rate
  uint32_t adc_cc= (16000000ul / ((uint32_t) sampling_rate));
  NRF_SAADC->SAMPLERATE = (SAADC_SAMPLERATE_MODE_Timers << SAADC_SAMPLERATE_MODE_Pos) | (adc_cc << SAADC_SAMPLERATE_CC_Pos);


  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
start_sampling:

  // set first ADC buffer
  nrfx_saadc_buffer_convert(...);
    
  // start ADC sampling
  nrfx_saadc_sample();

  // when ADC has started, set second buffer
  while (!nrfx_saadc_is_busy());
  nrfx_saadc_buffer_convert(...);

  while (!button_pressed)
  {
    // write data from FIFO to SD card
  }

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
stop_sampling:

  // stop sampling
  nrfx_saadc_abort();
}

Ideally, I would like to have the data of the two channels in an interleaved format (1x 12-bit sample from Ch0 stored as uint16, then 1x 12-bit sample from Ch1 stored as uint16, and so on). Can you spot any errors there? (In the real code, I have error checks after every function call. They don't indicate any errors.)

Thanks!
Tamas

Parents
  • Hi Tamas,

    The internal timer can only be used with a single channel, see Continuous sampling:

    "Note: Note that the internal timer can only be used when a single input channel is enabled."

    You will have to use a TIMER/RTC to trigger the sample task at the desired interval in order to sample two channels. You can reference the SAADC example in the SDK, or one of the examples in this GitHub repository.

    It is also not generally recommended to to mix usage of the driver APIs and writing directly to the peripheral registers.

    Best regards,
    Jørgen

  • NewtoM said:
    As for the SAADC; it's a pity the internal timer is not an option for multi-channel acquisition! I very much liked the idea of not bothering the CPU with single "sample task" triggers, which becomes a real overhead when you are about to use high rates with many channels. It would be cool to have the internal timer for more channels in future revisions...

    There is no need to bother the CPU in order to trigger the SAMPLE task. If you setup a TIMER or RTC peripheral to generate a COMPARE event on the desired sample rate, you can use PPI to trigger the SAMPLE task in the SAADC peripheral directly using hardware signals.

    NewtoM said:
    As for mixing the use of driver API and peripheral registers: as far as I can recall, SAADC driver did not have a function to configure internal timer. Has it changed recently?

    In fact, it has changed. We introduced a new SAADC driver API in nrfx in SDK 17.0.0, which divides the driver into a simple and an advanced mode configuration. In the advanced mode configuration, it is possible to set a internal_timer_cc parameter to enable the internal timer of the SAADC and use continuous mode. Unfortunately, there is not much documentation about this driver yet, but you can find a few examples in above link.

Reply
  • NewtoM said:
    As for the SAADC; it's a pity the internal timer is not an option for multi-channel acquisition! I very much liked the idea of not bothering the CPU with single "sample task" triggers, which becomes a real overhead when you are about to use high rates with many channels. It would be cool to have the internal timer for more channels in future revisions...

    There is no need to bother the CPU in order to trigger the SAMPLE task. If you setup a TIMER or RTC peripheral to generate a COMPARE event on the desired sample rate, you can use PPI to trigger the SAMPLE task in the SAADC peripheral directly using hardware signals.

    NewtoM said:
    As for mixing the use of driver API and peripheral registers: as far as I can recall, SAADC driver did not have a function to configure internal timer. Has it changed recently?

    In fact, it has changed. We introduced a new SAADC driver API in nrfx in SDK 17.0.0, which divides the driver into a simple and an advanced mode configuration. In the advanced mode configuration, it is possible to set a internal_timer_cc parameter to enable the internal timer of the SAADC and use continuous mode. Unfortunately, there is not much documentation about this driver yet, but you can find a few examples in above link.

Children
Related