ADC Continuous Sampling Issue - NRF52840 on Nano 33

Hello,

I developed a script which has a 40Hz timer which repeatedly starts the NRF52840's SAADC in continuous mode (with a 40kHz rate) which is supposed to fill a revolving buffer in RAM while my CPU does other activities.

An interrupt is then triggered at the end of the ADC collection (SAADC "END" EVENT), which notifies the CPU to send out those ADC values either Serially or through BLE (working through that later).

My snippet implementation is below. The issue I am having right now is that no samples are being read into the buffer, and I confirmed this further by regularly checking the SAADC's RESULT.AMOUNT register which stayed at zero indefinitely. I also verified that the STARTED EVENT was triggered, just no samples are being read despite the SAADC being set to continuous mode.

Any ideas?

// Contains the (16-bit) results for the SAADC - volatile buffer
constexpr uint16_t NUM_SAADC_RESULT_BUFFERS = 40;
constexpr uint16_t SAADC_RESULT_BUFFER_SIZE = 1024; 
volatile nrf_saadc_value_t SAADC_RESULT_BUFFER[NUM_SAADC_RESULT_BUFFERS * SAADC_RESULT_BUFFER_SIZE];

// Start the transmission
bool START_TRANSMISSION = false;

// Buffer index (For the SAADC Buffers)
uint32_t BUFFER_INDEX = 0;


/**
 * SAADC IRQ Handler: Weak Link to be taken here and triggered during END event in SAADC (Enabled elsewhere)
 */
extern "C" void SAADC_IRQHandler_v( void ) {
  // Check to see if the ADC has filled up the result buffer
  if (nrf_saadc_event_check(NRF_SAADC_EVENT_END))
  {   
    nrf_saadc_event_clear(NRF_SAADC_EVENT_END); // Clear the "END" event

    START_TRANSMISSION = true; // Start transmission
   
    BUFFER_INDEX = BUFFER_INDEX >= NUM_SAADC_RESULT_BUFFERS - 1 ? 0 : BUFFER_INDEX + 1;
  }
}

static void configure_saadc_channel_2()
{
  // Configure A1 & A2 channel as differential
  nrf_saadc_channel_config_t channel_config =
  {
    .resistor_p = NRF_SAADC_RESISTOR_DISABLED,
    .resistor_n = NRF_SAADC_RESISTOR_DISABLED,
    .gain       = NRF_SAADC_GAIN1_6,
    .reference  = NRF_SAADC_REFERENCE_INTERNAL,
    .acq_time   = NRF_SAADC_ACQTIME_3US,
    .mode       = NRF_SAADC_MODE_DIFFERENTIAL,
    .burst      = NRF_SAADC_BURST_DISABLED,
    .pin_p      = NRF_SAADC_INPUT_AIN2,
    .pin_n      = NRF_SAADC_INPUT_AIN3
  };
  
  // Configure the NRF CH[1] to values above
  // CH[1] is used instead of CH[0] because CH[0] is used during the analogRead Arduino API Function
  NRF_SAADC->CH[1].CONFIG =
            ((channel_config.resistor_p << SAADC_CH_CONFIG_RESP_Pos)   & SAADC_CH_CONFIG_RESP_Msk)
          | ((channel_config.resistor_n << SAADC_CH_CONFIG_RESN_Pos)   & SAADC_CH_CONFIG_RESN_Msk)
          | ((channel_config.gain       << SAADC_CH_CONFIG_GAIN_Pos)   & SAADC_CH_CONFIG_GAIN_Msk)
          | ((channel_config.reference  << SAADC_CH_CONFIG_REFSEL_Pos) & SAADC_CH_CONFIG_REFSEL_Msk)
          | ((channel_config.acq_time   << SAADC_CH_CONFIG_TACQ_Pos)   & SAADC_CH_CONFIG_TACQ_Msk)
          | ((channel_config.mode       << SAADC_CH_CONFIG_MODE_Pos)   & SAADC_CH_CONFIG_MODE_Msk)
          | ((channel_config.burst      << SAADC_CH_CONFIG_BURST_Pos)  & SAADC_CH_CONFIG_BURST_Msk);

  // Configure the Negative & Positive Ends of CH[1]
  NRF_SAADC->CH[1].PSELN = channel_config.pin_n;
  NRF_SAADC->CH[1].PSELP = channel_config.pin_p;
}

inline void sample_voltage()
{
  nrf_saadc_task_trigger(NRF_SAADC_TASK_START); // Start the ADC and prepare the result buffer in RAM.
}

static void configure_saadc_channels()
{
  nrf_saadc_disable();

  // Configure each SAADC Channel
  // Note: Channel 1 (CH[0]) is not used as it is reserved for analogRead functionality
  //       through Arduino's platform. 
  configure_saadc_channel_2();

  // Configure the resolution
  NRF_SAADC->RESOLUTION = NRF_SAADC_RESOLUTION_12BIT;   // 12-bit - 14-bit attainable only through oversampling

  // Disable oversampling
  //NRF_SAADC->OVERSAMPLE = NRF_SAADC_OVERSAMPLE_DISABLED;

  // Enable Continuous Mode & set to 16MHz / 400 (CC)
  NRF_SAADC->SAMPLERATE = (SAADC_SAMPLERATE_MODE_Timers << SAADC_SAMPLERATE_MODE_Pos)
                          | ((uint32_t)400 << SAADC_SAMPLERATE_CC_Pos);
  
  // Configure RESULT Buffer and MAXCNT
  NRF_SAADC->RESULT.PTR = (uint32_t)SAADC_RESULT_BUFFER;
  NRF_SAADC->RESULT.MAXCNT = SAADC_RESULT_BUFFER_SIZE;

  // Enable the SAADC IRQ
  NRF_SAADC->EVENTS_END = 0;
  nrf_saadc_int_enable( NRF_SAADC_INT_END ); // Set the END mask to an interrupt
  NVIC_SetPriority( SAADC_IRQn, 3UL );
  NVIC_EnableIRQ( SAADC_IRQn );

  nrf_saadc_enable(); // Enable the SAADC
  
  // Calibrate the SAADC  by finding its offset
  NRF_SAADC->TASKS_CALIBRATEOFFSET = 1;
  while (NRF_SAADC->EVENTS_CALIBRATEDONE == 0);
  NRF_SAADC->EVENTS_CALIBRATEDONE = 0;
  while (NRF_SAADC->STATUS == (SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos));
}

static uint32_t inline collect_adc_data()
{
  nrf_saadc_disable();
  NRF_SAADC->RESULT.PTR = (uint32_t)(SAADC_RESULT_BUFFER + (BUFFER_INDEX * SAADC_RESULT_BUFFER_SIZE));
  nrf_saadc_enable(); // Enable the SAADC
  
  sample_voltage();
}

void setup()
{
  collect_adc_data();
}

For someone who also may be experiencing this issue or other ghost faults when working with the NRF52840 SAADC, I was able to isolate the issue was that the incoming ADC SAMPLE task was being triggered before the first SAMPLE task had completed. This caused a hard-fault and the board to stall.