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>

  • Hello,

    Nikosant03 said:
    Thank you for your answer Karl,

    No problem at all, Nick - I am happy to help!

    Nikosant03 said:
    So, what is the suggested solution for sampling the sensors?

    This depends on the timing requirements for the sampling. Do you want them to happen as quickly as possible, or at a specific interval?
    Either way the recommended approach for keeping a precise sampling interval is to trigger the sampling using the PPI peripheral, in which you may connect an event (such as a TIMER CC event) to a task (such as the SAMPLE task) to perform the samplings. This way each sampling does not require CPU intervention, and so they will happen whenever they need to, rather than whenever the CPU has time to trigger them. You could see this demonstrated in the SAADC example from the nRF5 SDK.

    Nikosant03 said:
    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?

    This depends on which events you have enabled interrupts for, and other factors such as the SAADC configuration - the DONE event for example is only generated when a buffer is filled up, which in turn depends on the size of the provided buffer and the frequency of sampling.
    Could it for example be an option to increase the buffer size to 8, and then trigger the samplings through PPI using a TIMER, and handle the filled buffer in the following DONE event? We might also have to look into using the PPI for the switching the GPIO's if possible, since the mux switch will not happen on time if the CPU is busy elsewhere, unless the switching is run from the highest priority (which it cant be if the SoftDevice is present).

    Nikosant03 said:

    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

    Thank you for clarifying. I think the issue here might be with the way the samples are initiated, so switching to the PPI approach should solve this.

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

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

    Best regards,
    Karl

  • 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

Reply
  • 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

Children
  • 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);

  • Hi Karl,

    I tried to follow your suggestion. I used PPI, RTC and SAADC in low power mode. For this example, I have set the counter to 1s and the saadc buffer to 8. The main idea is to trigger sampling every 1s and keep sampling until to fill the buffer. So, I used two PPI channels:

    - One channel to trigger NRF_SAADC_TASK_START on NRF_RTC_EVENT_COMPARE_0 event (initialize the sampling)

    - One channel to trigger NRF_SAADC_TASK_START on NRF_SAADC_EVENT_END (keep sampling until to fill the buffer)

    When the NRFX_SAADC_EVT_DONE is generated:

    1. I disable the second PPI channel 

    nrfx_ppi_channel_disable(m_start_SAADC_ppi_channel);

    2. Read the data from the buffer

    3. Clear the counter

    4. Clear the NRF_SAADC_EVENT_STARTED

    5. Enable the second channel

    nrfx_ppi_channel_enable(m_start_SAADC_ppi_channel)

    Finally, wait for the compare event to start all over again

    I do not know if this is the approach you were referring to but when I measure the average current with the PPK II is quite high (around 600-700uA). Could you please review and advise?

    Definitions and instances

    #define SAMPLES_IN_BUFFER 3 // define the number of samples to get berore generate the NRFX_SAADC_EVT_DONE event. A sample if captured for every clock tick
    #define RTC_FREQUENCY 8     // tick period (frequency in Hz) - e.g RTC_FREQUENCY 8 -> period = 1/8 = 125ms
    #define COMPARE_CHANNEL_0 0 // define the compare channel to set - RTC1 has 3 x compare channels while RTC1 & RTC2 have 4 x compare channels
    #define COUNTER_VALUE 8    // define counter's value. Instacts the counter to produce an event after COUNTER_VALUE x period - e.g if period is 125ms and COUNTER_VALUE 8 the event will be produced after 8 x 125ms = 1s
    
    APP_TIMER_DEF(m_notifications_timer); /**< Handler for repeated timer used send notifications. */
    
    const nrfx_rtc_t rtc = NRFX_RTC_INSTANCE(2);        // create an instance for RTC2 (RTC0 is used by the Sofd Device and RTC1 by the app timer)
    static nrf_ppi_channel_t m_ppi_channel;             // create an instance for PPI channels
    static nrf_ppi_channel_t m_start_SAADC_ppi_channel; // create an instance for PPI channels
    static nrf_saadc_value_t m_buffer_pool[2][SAMPLES_IN_BUFFER];
    static uint32_t m_adc_evt_counter;

    This is my SAADC init function

    void saadc_init(void) {
      ret_code_t err_code;
      static const nrfx_saadc_config_t default_config = NRFX_SAADC_DEFAULT_CONFIG; // from sdk_config file: resolution = 12bit , low power mode = true
    
      nrf_saadc_channel_config_t channel_config = NRFX_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(&default_config, 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);
    }

    This is my RTC init function

    static void rtc_config() {
    
      uint32_t err_code; // variable to hold the error values
    
      nrfx_rtc_config_t rtc_config = NRFX_RTC_DEFAULT_CONFIG;      // set the default settings for RTC
      rtc_config.prescaler = RTC_FREQ_TO_PRESCALER(RTC_FREQUENCY); // configure the prescaler: e.g 32768 / (4095 + 1) = 8Hz = 125ms (tick event every 125ms)
    
      err_code = nrfx_rtc_init(&rtc, &rtc_config, rtc_handler); // initialize the RTC. After initialization, the instance is in power off state and it must be enabled
      APP_ERROR_CHECK(err_code);
    
      /* Setting a CC (Compare Channel). Set COMPARE_CHANNEL as compare channel. When the counter value is equal to COUNTER_VALUE the NRF_RTC_EVENT_COMPARE_0 will be generated.
      Let interrupts disabled (false) since we do not use interrupt to start saadc, we use the PPI based on the NRF_RTC_EVENT_COMPARE_0 event */
      err_code = nrfx_rtc_cc_set(&rtc, COMPARE_CHANNEL_0, COUNTER_VALUE, false);
      APP_ERROR_CHECK(err_code);
    
      nrfx_rtc_enable(&rtc); // enable the RTC
    }

    This is the PPI init function

    static void ppi_init(void) {
    
      uint32_t err_code;
      uint32_t rtc_compare_event_addr; // variable to hold the address of rtc event
      uint32_t rtc_clear_task_addr;    // variable to hold the address of rtc task
      uint32_t saadc_sample_task_addr; // variable to hold the address of saadc task
    
      rtc_compare_event_addr = nrfx_rtc_event_address_get(&rtc, NRF_RTC_EVENT_COMPARE_0); // get the address of COMPARE 0 event
      rtc_clear_task_addr = nrfx_rtc_task_address_get(&rtc, NRF_RTC_TASK_CLEAR);          // get the address of RTC clear task
      saadc_sample_task_addr = nrfx_saadc_sample_task_get();                              // getting the address of NRF_SAADC_TASK_SAMPLE. If low power is enabled it will get the address of NRF_SAADC_TASK_START
    
      err_code = nrfx_ppi_channel_alloc(&m_ppi_channel);  // allocate the channel to start SAADC task on RTC comprare event
      APP_ERROR_CHECK(err_code);
    
      err_code = nrfx_ppi_channel_alloc(&m_start_SAADC_ppi_channel); // allocate a channel to start SAADC when conversion ends
      APP_ERROR_CHECK(err_code);
    
      err_code = nrfx_ppi_channel_assign(m_ppi_channel, rtc_compare_event_addr, saadc_sample_task_addr); // Start the SAADC on RTC COMPARE event
      APP_ERROR_CHECK(err_code);
    
      err_code = nrfx_ppi_channel_assign(m_start_SAADC_ppi_channel, nrf_saadc_event_address_get(NRF_SAADC_EVENT_END), nrfx_saadc_sample_task_get()); // Make sure that the SAADC is always started when it ends a conversion
      APP_ERROR_CHECK(err_code);
    
       //err_code = nrfx_ppi_channel_fork_assign(m_ppi_channel, rtc_clear_task_addr); // fork the RTC clear task to RTC comprare[0] event
       //APP_ERROR_CHECK(err_code);
    
      err_code = nrfx_ppi_channel_enable(m_ppi_channel); // Enable the PPI channel
      APP_ERROR_CHECK(err_code);
    
      err_code = nrfx_ppi_channel_enable(m_start_SAADC_ppi_channel); // Enable the PPI channel
      APP_ERROR_CHECK(err_code);
    }

    This is the saadc event handling function

    void saadc_callback(nrfx_saadc_evt_t const *p_event) {
    
      ret_code_t err_code;
    
      if (p_event->type == NRFX_SAADC_EVT_DONE) { // the NRFX_SAADC_EVT_DONE event is produced when the buffer is full. An saadc value is captured for every clock tick
    
        err_code = nrfx_ppi_channel_disable(m_start_SAADC_ppi_channel);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);
    
        int i;
        NRF_LOG_INFO("ADC event number: %d", (int)m_adc_evt_counter);
    
        for (i = 0; i < SAMPLES_IN_BUFFER; i++) {
    
          NRF_LOG_INFO("%d", p_event->data.done.p_buffer[i]);
        }
        m_adc_evt_counter++;
      }
    
      nrfx_rtc_counter_clear(&rtc);
      nrf_saadc_event_clear(NRF_SAADC_EVENT_STARTED);
      // nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
    
      err_code = nrfx_ppi_channel_enable(m_start_SAADC_ppi_channel); // Enable the start_SAADC PPI channel
      APP_ERROR_CHECK(err_code);
    }

  • Hello again,

    Thank you for your patience with this.
    I have been unavailable for some days now, and I will unfortunately be out of office until over new years as well. I will try to answer as much as possible from your previous comment in this comment, but if you require further technical support with this, or another issue or question, in my absence I recommend that you create a new ticket for this. Please also note that the forum is operating with reduced staff during the upcoming holiday season here in Norway - apologies for any inconvenience this might cause.

    Nikosant03 said:

    I tried to follow your suggestion. I used PPI, RTC and SAADC in low power mode. For this example, I have set the counter to 1s and the saadc buffer to 8. The main idea is to trigger sampling every 1s and keep sampling until to fill the buffer. So, I used two PPI channels:

    - One channel to trigger NRF_SAADC_TASK_START on NRF_RTC_EVENT_COMPARE_0 event (initialize the sampling)

    - One channel to trigger NRF_SAADC_TASK_START on NRF_SAADC_EVENT_END (keep sampling until to fill the buffer)

    This is close to what I had suggested, but if I understand your intentions correctly you do not need to use two PPI channels for this - instead, you could have the RTC count for 1 second continuously, and have a single PPI channel connecting the RTC CC event to the SAADC_START task, and then make sure to provide the next buffer to the SAADC when the DONE event is generated. As long as there is enough time between samplings for the SAADC to be provided the new buffer when the previous one is filled, this should achieve the same functionality.

    Nikosant03 said:
    I do not know if this is the approach you were referring to but when I measure the average current with the PPK II is quite high (around 600-700uA). Could you please review and advise?

    That is indeed quite high - were the logger enabled / in use when you did this testing? This could increase your power consumption by a couple of hundred µA, especially if you are using the UART backend. If so, please try to disable the logger module and run the current consumption test again. If you need logging you could use the RTT backend for a lower consumption, or instead use a LED to indicate that the program is executing as intended during testing, or similar.

    The main power draw here is likely coming from the enabled PPI channels, since the PPI channels require that the HFCLK is always kept running. This could come out as a couple of hundred µA (but still 6-700 µA is too high.
    In cases where the periodic sampling happens very seldomly it could be more power efficient to wake the CPU to have it trigger the sampling and go back to sleep, rather than to always keep the HFCLK running.

    Since you are only sampling on a single channel, and using an external mux for the input, you might be able to shave off a couple of µA from the power consumption by using the SAADC internal timer to trigger the samplings as well.

    Looking through the provided code I notice that you have this line:

    static const nrfx_saadc_config_t default_config = NRFX_SAADC_DEFAULT_CONFIG; // from sdk_config file: resolution = 12bit , low power mode = true
    
    Where the comment seems to indicate that the LP mode is set in the default configuration with 12 bit resolution, and I would to point out that this is not the default configuration, so you must make changes to your NRFX_SAADC_CONFIG_RESOLUTION and NRFX_SAADC_CONFIG_LP_MODE in sdk_config to have the SAADC use 12 bit resolution and LP mode. I assume you have already done this, but I thought I should mention it just in case.

    I hope you have a great holiday season, Nick!

    Best regards,
    Karl

Related