nRF52840 - SDK16 - SAADC Driver - Non-blocking sampling does not work inside a loop

Hi everyone,

I want to sample eight analog sensors connected on P0.30 through a multiplexer. I created an app_timer that expires every 10ms. When the timer expires I start sampling in non-blocking mode calling the nrf_drv_saadc_sample() in a for loop. The problem is that the event handler (saadc_callback() in my case) I assigned during the SAADC initialization is called just once, during the first iteration of the loop, meaning that just one of the eight sensors is sampled.

  // Create SAADC timer.
  err_code = app_timer_create(&m_SAADC_timer_id, APP_TIMER_MODE_REPEATED, pressure_sensors_read);
  APP_ERROR_CHECK(err_code);
 

void pressure_sensors_read(struct s_sampling *sampling) {
  ret_code_t err_code;

  for (uint8_t i = 0; i < NUM_FLEXIFORCE_SENSORS; i++) {
    err_code = nrf_drv_saadc_sample();
    APP_ERROR_CHECK(err_code);
  }
  sampling->index = 0;
}

SAADC initialization

- I configured the ADC converter to default settings by just reconfiguring the resolution

- I used the default channel configuration settings by reconfiguring the gain and the reference voltage

void saadc_init(void) {
  ret_code_t err_code;
  uint8_t saadc_input;

  nrfx_saadc_config_t adc_config = NRFX_SAADC_DEFAULT_CONFIG; // set default settings
  adc_config.resolution = NRF_SAADC_RESOLUTION_12BIT;         // configure the resolution

  // GPIO assignments - https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpin.html
  // By default the aquitition time is 10us. You can change the aquitition time from NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE at nrfx_saadc.h
  // Configure the SAADC input based on the EVB version
  if (evb == EVB_V2) {
    saadc_input = NRF_SAADC_INPUT_AIN1; // This is the analog input (P0.03) used for EVB.v2
  } else if (evb == EVB_V3) {
    saadc_input = NRF_SAADC_INPUT_AIN6; // This is the analog input (P0.30) used for EVB.v3
  }

  // Configure the channel
  nrf_saadc_channel_config_t channel_config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(saadc_input); // use default configuration settings
  channel_config.gain = NRF_SAADC_GAIN1_4;                                                          // configure the gain
  channel_config.reference = NRF_SAADC_REFERENCE_VDD4;                                              // configure the reference voltage

  // adc initialize with custom configuratiom
  err_code = nrf_drv_saadc_init(&adc_config, saadc_callback);
  APP_ERROR_CHECK(err_code);

  err_code = nrf_drv_saadc_channel_init(0, &channel_config);
  APP_ERROR_CHECK(err_code);

  err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAMPLES_IN_BUFFER); // na - designating the buffer in which to fill the samples produces by nrf_drv_saadc_sample or NRF_SAADC_TASK_SAMPLE.
  APP_ERROR_CHECK(err_code);

  err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1], SAMPLES_IN_BUFFER);
  APP_ERROR_CHECK(err_code);
}

Event handler

void saadc_callback(nrf_drv_saadc_evt_t const *p_event) {

  ret_code_t err_code;

  if (p_event->type == NRF_DRV_SAADC_EVT_DONE) {

    err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);

    sampling.flexi_force[sampling.index++] = p_event->data.done.p_buffer[0]; // na - Buffer the ADC values from flexi force (8 x sensors)

    selectMuxPin(sampling.index); // na - change MUX channel to read the next sensor
  }
}

sdk_config.h configuration

// <e> SAADC_ENABLED - nrf_drv_saadc - SAADC peripheral driver - legacy layer
//==========================================================
#ifndef SAADC_ENABLED
#define SAADC_ENABLED 1
#endif
// <o> SAADC_CONFIG_RESOLUTION  - Resolution
 
// <0=> 8 bit 
// <1=> 10 bit 
// <2=> 12 bit 
// <3=> 14 bit 

#ifndef SAADC_CONFIG_RESOLUTION
#define SAADC_CONFIG_RESOLUTION 1
#endif

// <o> SAADC_CONFIG_OVERSAMPLE  - Sample period
 
// <0=> Disabled 
// <1=> 2x 
// <2=> 4x 
// <3=> 8x 
// <4=> 16x 
// <5=> 32x 
// <6=> 64x 
// <7=> 128x 
// <8=> 256x 

#ifndef SAADC_CONFIG_OVERSAMPLE
#define SAADC_CONFIG_OVERSAMPLE 0
#endif

// <q> SAADC_CONFIG_LP_MODE  - Enabling low power mode
 

#ifndef SAADC_CONFIG_LP_MODE
#define SAADC_CONFIG_LP_MODE 0
#endif

// <o> SAADC_CONFIG_IRQ_PRIORITY  - Interrupt priority
 

// <i> Priorities 0,2 (nRF51) and 0,1,4,5 (nRF52) are reserved for SoftDevice
// <0=> 0 (highest) 
// <1=> 1 
// <2=> 2 
// <3=> 3 
// <4=> 4 
// <5=> 5 
// <6=> 6 
// <7=> 7 

#ifndef SAADC_CONFIG_IRQ_PRIORITY
#define SAADC_CONFIG_IRQ_PRIORITY 6
#endif

What I am I doing wrong? Should I avoid placing the nrf_drv_saadc_sample() inside the for loop? Or should I tweak the configurations?

Thanks, Nick

Parents
  • Hello Nick,

    What I am I doing wrong? Should I avoid placing the nrf_drv_saadc_sample() inside the for loop? Or should I tweak the configurations?

    You mentioned that the sensors are multiplexed on the same pin - but I do not see how this multiplexing is implemented, how does the target of the measurements change inbetween each for-loop iteration?
    Furthermore, calling the nrf_drv_saadc_sample in a loop is not best practice, since this might not leave enough time for the conversions to actually finish before the next one is called - which might be causing the trouble you are seeing. With the default acquisition time of 10 µs each conversion will take at least 12 µs.

    How long does your multiplexing implementation require for each switching? How important is it for your application that the samplings happen at an accurate period of 10 ms? If an accurate samplerate is important I would recommend triggering sampling using the PPI peripheral rather than using the CPU, since the CPU might be busy in a higher-priority interrupt at any time.

    The problem is that the event handler (saadc_callback() in my case) I assigned during the SAADC initialization is called just once, during the first iteration of the loop, meaning that just one of the eight sensors is sampled.

    You mention that the event handler is only called once - do you by this mean that the DONE event is generated only once?
    The DONE event will be generated when the provided buffer is filled - what is the value of SAMPLES_IN_BUFFER?

    sdk_config.h configuration

    Lastly, I see that you are using the legacy SAADC definitions. You should rather use the NRFX_SAADC definitions directly - since the nrfx_saadc driver is what is being used behind the scenes anyways - and remove all the legacy defines from your sdk_config to avoid that the NRFX_SAADC configuration is overwritten by the legacy configuration by the apply_old_config file. This will happen if the legacy define's are defined in your sdk_config, so they should be removed or commented out entirely to avoid this.

    Best regards,
    Karl

  • Thank you for your answer Karl,

    You mentioned that the sensors are multiplexed on the same pin - but I do not see how this multiplexing is implemented, how does the target of the measurements change inbetween each for-loop iteration?

    After each sampling, I call this function to switch the channel of the multiplexer

    void selectMuxPin(uint8_t index) {
      NRF_LOG_INFO("Index value: %d", index);
      for (int i = 0; i < MUX_CTRL_CHA; i++) {
    
        if (index & (1 << i)) {
          nrf_gpio_pin_set(ctrlPins[i]);
        } else {
          nrf_gpio_pin_clear(ctrlPins[i]);
        }
      }
    }

    How long does your multiplexing implementation require for each switching?

    I didn't measure the switching transition of the MUX but according to the datasheet it should be around 50ns

    Furthermore, calling the nrf_drv_saadc_sample in a loop is not best practice, since this might not leave enough time for the conversions to actually finish before the next one is called - which might be causing the trouble you are seeing

    So, what is the suggested solution for sampling the sensors?

    You mention that the event handler is only called once - do you by this mean that the DONE event is generated only once?

    The event handler is called every time an event is generated by the SAADC module, right? In my case, I call the nrf_drv_saadc_sample in a loop of eight iterations (because I have eight sensors) but only after the first nrf_drv_saadc_sample call the event handler is called, so probably the DONE event is generated just once?

    How important is it for your application that the samplings happen at an accurate period of 10 ms? If an accurate samplerate is important I would recommend triggering sampling using the PPI peripheral rather than using the CPU, since the CPU might be busy in a higher-priority interrupt at any time.

     

    Our application requires a 100Hz sampling frequency, that's why I set the sampling period to 10ms. The application does not require a super-duper accurate sampling rate but I will consider using the PPI peripheral after solving the current issue Slight smile

    You should rather use the NRFX_SAADC definitions directly - since the nrfx_saadc driver is what is being used behind the scenes anyways

    Ok I will replace the legacy with nrfx

    and remove all the legacy defines from your sdk_config to avoid that the NRFX_SAADC configuration is overwritten by the legacy configuration by the apply_old_config file.

    Do you mean, to comment out these definitions from the sdk_config?

    // <e> SAADC_ENABLED - nrf_drv_saadc - SAADC peripheral driver - legacy layer
    //==========================================================
    
    // <o> SAADC_CONFIG_RESOLUTION  - Resolution
     
    // <0=> 8 bit 
    // <1=> 10 bit 
    // <2=> 12 bit 
    // <3=> 14 bit 
    
    #ifndef SAADC_CONFIG_RESOLUTION
    #define SAADC_CONFIG_RESOLUTION 1
    #endif
    
    // <o> SAADC_CONFIG_OVERSAMPLE  - Sample period
     
    // <0=> Disabled 
    // <1=> 2x 
    // <2=> 4x 
    // <3=> 8x 
    // <4=> 16x 
    // <5=> 32x 
    // <6=> 64x 
    // <7=> 128x 
    // <8=> 256x 
    
    #ifndef SAADC_CONFIG_OVERSAMPLE
    #define SAADC_CONFIG_OVERSAMPLE 0
    #endif
    
    // <q> SAADC_CONFIG_LP_MODE  - Enabling low power mode
     
    
    #ifndef SAADC_CONFIG_LP_MODE
    #define SAADC_CONFIG_LP_MODE 0
    #endif
    
    // <o> SAADC_CONFIG_IRQ_PRIORITY  - Interrupt priority
     
    
    // <i> Priorities 0,2 (nRF51) and 0,1,4,5 (nRF52) are reserved for SoftDevice
    // <0=> 0 (highest) 
    // <1=> 1 
    // <2=> 2 
    // <3=> 3 
    // <4=> 4 
    // <5=> 5 
    // <6=> 6 
    // <7=> 7 
    
    #ifndef SAADC_CONFIG_IRQ_PRIORITY
    #define SAADC_CONFIG_IRQ_PRIORITY 6
    #endif
    
    // </e>

  • Hi Karl,

    Do you want them to happen as quickly as possible, or at a specific interval?

    To happen a quickly as possible

    This depends on which events you have enabled interrupts for

    How do I enable interrupts based on specific events? I do not see any function in SAADC driver. The available events are these? Also looking at the saadc example of SDK16, I cannot find any code line that enables any event interrupt (let's say the NRFX_SAADC_EVT_DONE event), but the example works fine.

    Yes, along with SAADC_ENABLE especially, if it is present.

    I modified the code using the SAADC Driver (nrfx) and commented out all the legacy definitions.

    void saadc_init(void) {
      ret_code_t err_code;
    
      nrf_saadc_channel_config_t channel_config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN6); // AIN6 is the pin P0.30 - https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpin.html
    
      err_code = nrfx_saadc_init(NULL, saadc_callback);
      APP_ERROR_CHECK(err_code);
    
      err_code = nrfx_saadc_channel_init(0, &channel_config);
      APP_ERROR_CHECK(err_code);
    
      err_code = nrfx_saadc_buffer_convert(m_buffer_pool[0], SAMPLES_IN_BUFFER);
      APP_ERROR_CHECK(err_code);
    
      err_code = nrfx_saadc_buffer_convert(m_buffer_pool[1], SAMPLES_IN_BUFFER);
      APP_ERROR_CHECK(err_code);
    }
    

    However, if I do now set the SAADC_ENABLED to 1 the code doesn't compile and returns "undefined reference" error. 

    According to the Migration guide for nrfx driversSAADC_ENABLED must be set to 1? This is the same issue with this ticket. Sorry for asking the same thing but I have to ensure that I'm doing it the right way.

    Thanks

    Nick

  • Hello Nick,

    Nikosant03 said:
    How do I enable interrupts based on specific events? I do not see any function in SAADC driver. The available events are these?

    The HAL layer events are handled in the driver in the irq_handler, and then the appropriate NRFX event is passed to the application layer event handler through the call to m_cb.event_handler(&evt_data);
    The available NRFX events can be seen in the nrfx_saadc.h file - keep in mind that not everyone of these events are forwarded to the application layer, so you might have to modify the driver to make this happen if your require some other events.

    For SDK v16 you will have to check the nrfx v1.8 tagged release of the nrfx repo, since this is what is included with the nRF5 SDK v16.0.0.

    Nikosant03 said:
    However, if I do now set the SAADC_ENABLED to 1 the code doesn't compile and returns "undefined reference" error. 


    Yes, SAADC_ENABLED should not be left defined - i.e it needs to be removed or commented out of sdk_config.h all together. Only NRFX_SAADC_ENABLED should be defined in sdk_config, along with the other NRFX_SAADC_* configurations.

    Nikosant03 said:
    According to the Migration guide for nrfx driversSAADC_ENABLED must be set to 1? This is the same issue with this ticket. Sorry for asking the same thing but I have to ensure that I'm doing it the right way.

    No need to apologize - I would rather that you ask whatever question you have, in case anything is unclear. Please know that you are not the first, and likely not the last, to ask about the sdk_config.h and the confusion around the legacy definitions :) 

    The note you quote from is actually trying to say that you need to remove SAADC_ENABLED all together - it is not enough to leave it defined to 0, because then it is still defined, which will cause the apply_old_config to kick in and overwrite the particular peripheral's sdk_config.h NRFX configurations.

    Best regards,
    Karl

  • Thank you for your time Karl,

    The note you quote from is actually trying to say that you need to remove SAADC_ENABLED all together - it is not enough to leave it defined to 0, because then it is still defined, which will cause the apply_old_config to kick in and overwrite the particular peripheral's sdk_config.h NRFX configurations.

    Crystal clear!!

    This depends on which events you have enabled interrupts for

    I see the SAADC example (SDK16):

    The nrfx_saadc_init() call enables the NRF_SAADC_INT_END interrupt. This interrupt occurs when the ADC has filled up the buffer. When the buffer get full the NRF_SAADC_INT_END interrupt occurs and handled by the nrfx_saadc_irq_handler(). The handler checks the state of HALL layer events. If the NRF_SAADC_EVENT_END received, the NRFX_SAADC_EVT_DONE event is passed to the application layer through the m_cb.event_handler(&evt); call. So up to this stage the buffer is full

    Then the event handler calls the nrfx_saadc_buffer_convert. This function converts the ADC into an appropriate value and prepare the buffer for the next sampling event? I've read the function description but I am not sure if I understood correctly.

    So everything seems to work without enabling the NRF_SAADC_INT_DONE interrupt. The DONE interrupt occurs after calling the nrfx_saadc_buffer_convert?  So, do I need the NRF_SAADC_INT_DONE in my case or is it safe to adapt with the saadc example?

    keep in mind that not everyone of these events are forwarded to the application layer, so you might have to modify the driver to make this happen if your require some other events.

    I use SDK16 and the available SAADC events are:

    /** @brief SAADC driver event types. */
    typedef enum {
      NRFX_SAADC_EVT_DONE,         ///< Event generated when the buffer is filled with samples.
      NRFX_SAADC_EVT_LIMIT,        ///< Event generated after one of the limits is reached.
      NRFX_SAADC_EVT_CALIBRATEDONE ///< Event generated when the calibration is complete.
    } nrfx_saadc_evt_type_t;

    This is because SDK16 used the nrfx v1.8 right? I see that all of the three events are handled and passed to the application layer by the nrfx_saadc_irq_handler(), so this is ok to me. Moreover, I do not need the additional events provided in nrfx 2.5.0 release.

  • Hello again,

    Thank you for your patience.

    Nikosant03 said:
    Thank you for your time Karl,
    Nikosant03 said:
    Crystal clear!!

    No problem at all, Nick - I am glad to hear that you found my comment useful! :)

    Nikosant03 said:
    Then the event handler calls the nrfx_saadc_buffer_convert. This function converts the ADC into an appropriate value and prepare the buffer for the next sampling event? I've read the function description but I am not sure if I understood correctly.

    Almost - the nrfx_saadc_buffer_convert provides a new buffer to the SAADC so that it is ready to continue with the sampling.
    The data from the previous buffer is available in the DONE event structure.

    Nikosant03 said:
    So everything seems to work without enabling the NRF_SAADC_INT_DONE interrupt.

    Yes, the NRFX SAADC driver does not make use of the HAL NRF_SAADC_INT_DONE event, correct. The new NRFX SAADC v2 available in the SDK v17 does however make use of it, so you may take a look there if you would like to see its usage.

    Nikosant03 said:
    So, do I need the NRF_SAADC_INT_DONE in my case or is it safe to adapt with the saadc example?

    You could modify the driver to also enable the HAL interrupt for DONE by calling the nrf_saadc_int_enable function with the INT_DONE mask, and then add a case for the DONE event in the saadc irq handler. Keep in mind that the NRF_SAADC_EVENT_DONE is not the same as the NRFX_SAADC_EVENT_DONE - the former is a HAL layer event which is generated for each sample, while the latter is a nrfx driver layer event which is generated each time the provided buffer is filled.
    This also means that you will get the NRF_SAADC_EVENT_DONE a lot more often than the NRFX_SAADC_EVENT_DONE, and so you will need to make sure that this matches the requirements and constraints of your application.

    It is safe to enable the done interrupt, but you will need to make sure that the rest of your application is fine with the increased SAADC interrupts. If your application also uses the SoftDevice you might also want to trigger the next sampling through PPI, to ensure that they happen as soon as possible. If you use PPI you should should do your event handling in main.c /outside of the SAADC driver, to avoid having to tangle PPI usage into the SAADC driver.
    With the PPI approach could then just disable the PPI channel as part of your NRFX_SAADC_EVENT_DONE handling, to stop the back-to-back samplings from happening. If you do not use PPI you will need to add some other kind of conditional that is set/cleared whenever you have sampled all the muxed channels once.

    Try this, and let me know if you achieve the desired functionality!  

    Best regards,
    Karl

  • Hi Karl

    The new NRFX SAADC v2 available in the SDK v17

    I downloaded the SDK v17, but I cannot find where the NRF_SAADC_INT_DONE is used in the saadc peripheral example. I didn't even find where the DONE interrupt is enabled

    Keep in mind that the NRF_SAADC_EVENT_DONE is not the same as the NRFX_SAADC_EVENT_DONE

    During the last sampling, I will receive both events? One event for the sample and one event for filled buffer?

    I enabled the DONE interrupt inside the nrfx_saadc_init function but after that, it stop sampling at all. I have to see the NRFX SAADC v2 

    nrf_saadc_int_enable(NRF_SAADC_INT_DONE);

Reply
  • Hi Karl

    The new NRFX SAADC v2 available in the SDK v17

    I downloaded the SDK v17, but I cannot find where the NRF_SAADC_INT_DONE is used in the saadc peripheral example. I didn't even find where the DONE interrupt is enabled

    Keep in mind that the NRF_SAADC_EVENT_DONE is not the same as the NRFX_SAADC_EVENT_DONE

    During the last sampling, I will receive both events? One event for the sample and one event for filled buffer?

    I enabled the DONE interrupt inside the nrfx_saadc_init function but after that, it stop sampling at all. I have to see the NRFX SAADC v2 

    nrf_saadc_int_enable(NRF_SAADC_INT_DONE);

Children
No Data
Related