nRF52840 - SDK16 - RTC + SAADC + PPI does not work

Ηι

Hi everyone,

I have written a code to sample from an analog input using RTC and PPI.

The SAADC does not start on RTC compare event.

The RTC compare channel works fine. When I enable the interrupts, I capture the NRFX_RTC_INT_COMPARE0 in the rtc_handler function, however, the I cannot trigger the ADC sampling through PPI

What am I missing? 

This my implementation:

/*To do

 - Use the new nrfx implementation and not the legacy
 - Disable the legacy definitions from sdk_config


*/

#include "app_error.h"
#include "app_timer.h"
#include "app_util_platform.h"
#include "boards.h"
#include "nrf.h"
#include "nrf_delay.h"
#include "nrf_drv_clock.h"
#include "nrf_drv_timer.h"
#include "nrf_pwr_mgmt.h"
#include "nrfx_ppi.h"
#include "nrfx_rtc.h"
#include "nrfx_saadc.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#define SAMPLES_IN_BUFFER 5 // 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;

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_DONE), 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);
}

// We wll not use the rtc handler in that case since we want to trigger ADC sampling through PPI
static void rtc_handler(nrfx_rtc_int_type_t int_type) {
  uint32_t err_code;

  if (int_type == NRFX_RTC_INT_COMPARE0) {
    NRF_LOG_INFO("Print here");

    nrfx_rtc_counter_clear(&rtc);
    err_code = nrfx_rtc_cc_set(&rtc, COMPARE_CHANNEL_0, COUNTER_VALUE, true);
    APP_ERROR_CHECK(err_code);
  }
}

// configure all RTC settings
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
}

void saadc_callback(nrfx_saadc_evt_t const *p_event) {

  ret_code_t err_code;
  NRF_LOG_INFO("Event type: %d", p_event->type);

  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
    NRF_LOG_INFO("Event type: NRFX_SAADC_EVT_DONE");
    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_event_clear(NRF_SAADC_EVENT_END);
  // 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);
}

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);
}

// Initialize the low frequency clock. Do not need to initialize if using the soft device
static void lfclk_config(void) {
  ret_code_t err_code = nrf_drv_clock_init();
  APP_ERROR_CHECK(err_code);

  nrf_drv_clock_lfclk_request(NULL);
}

static void log_init() {

  uint32_t err_code = NRF_LOG_INIT(NULL);
  APP_ERROR_CHECK(err_code);

  NRF_LOG_DEFAULT_BACKENDS_INIT();
}

static void pwr_mgmt_init() {
  ret_code_t ret_code = nrf_pwr_mgmt_init();
  APP_ERROR_CHECK(ret_code);
}

/**
 * @brief Function for main application entry.
 */
int main(void) {

  log_init();
  pwr_mgmt_init();
  lfclk_config();

  NRF_POWER->DCDCEN = 1; // Enabling the DCDC converter for lower current consumption

  saadc_init();
  rtc_config();
  ppi_init();

  NRF_LOG_INFO("SAADC HAL simple example started.");

  while (1) {
    nrf_pwr_mgmt_run();
    NRF_LOG_FLUSH();
  }
}

/** @} */

Parents
  • Hi,

    It would be useful to see your sdk_config.h file as well, but if your comments are correct, you have set "low power mode = true". This mode will keep the SAADC peripheral in a low power state between sampling by delaying the triggering of the START task after an END event until the next sampling is triggered. By default, the driver will trigger the START task when the END event is received, to be ready for the SAMPLE task, but this will keep the SAADC peripheral in a state where EasyDMA is enabled, causing high current consumption. When low power mode is enabled, the function nrfx_saadc_sample_task_get() will return the address of the START task, while triggering of the SAMPLE task is handled in the SAADC IRQ handler, when the STARTED event is generated. Triggering the START task multiple times before the END event is generated can cause undesired behavior.

    In you code you have setup a second PPI channel, which connects the NRF_SAADC_EVENT_DONE event to the returned task address from nrfx_saadc_sample_task_get(). The DONE event is generated when one sample have been captured, but possibly before the result is ready and transferred to RAM. The number of DONE events before the result is done depends on the OVERSAMPLE configuration. Anyway, if you have set the low power mode, this PPI channel will cause the START task to be triggered multiple times. I'm not sure what the intention of this PPI channel was, but my guess would be that you want to fill the 5-sample buffer as quickly as possible when the RTC event triggers? In that case, you can try to pass the address of the SAADC SAMPLE task directly, and replace the DONE event with RESULTDONE event. This PPI channel will also trigger the SAMPLE task on the 5th DONE/RESULTDONE event, which may cause issues as described in this thread.

    Best regards,
    Jørgen

  • Thank you for your time Jorgen!!

    I removed the second PPI channel and cleaned up the saadc callback function

    Now for low power mode and buffer size equals to 1 it works fine.

    This is the new 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 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
      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_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_enable(m_ppi_channel); // Enable the PPI channel
      APP_ERROR_CHECK(err_code);
    
    }

    and this is the saadc callback function

    void saadc_callback(nrfx_saadc_evt_t const *p_event) {
    
      ret_code_t err_code;
      NRF_LOG_INFO("Event type: %d", p_event->type);
    
      if (p_event->type == NRFX_SAADC_EVT_DONE) {
    
        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);
      err_code = nrfx_rtc_cc_set(&rtc, COMPARE_CHANNEL_0, COUNTER_VALUE, false);
      APP_ERROR_CHECK(err_code);
    }

    However, when the buffer size is greater than one the NRFX_SAADC_EVT_DONE is not produced. The problem is because I do not re-set the compare channel (I clear the counter and re-set the compare channel inside the saadc callback).

    I'm not sure what the intention of this PPI channel was, but my guess would be that you want to fill the 5-sample buffer as quickly as possible when the RTC event triggers? In that case, you can try to pass the address of the SAADC SAMPLE task directly, and replace the DONE event with RESULTDONE event.

    Yes this is my intention. Do you mean to retain the second PPI channel and assign like that?

    err_code = nrfx_ppi_channel_assign(m_start_SAADC_ppi_channel, nrf_saadc_event_address_get(NRF_SAADC_EVENT_RESULTDONE), NRF_SAADC_TASK_SAMPLE);
      APP_ERROR_CHECK(err_code);

    I tried but it didn't work

    How could I work around this issue?

Reply
  • Thank you for your time Jorgen!!

    I removed the second PPI channel and cleaned up the saadc callback function

    Now for low power mode and buffer size equals to 1 it works fine.

    This is the new 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 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
      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_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_enable(m_ppi_channel); // Enable the PPI channel
      APP_ERROR_CHECK(err_code);
    
    }

    and this is the saadc callback function

    void saadc_callback(nrfx_saadc_evt_t const *p_event) {
    
      ret_code_t err_code;
      NRF_LOG_INFO("Event type: %d", p_event->type);
    
      if (p_event->type == NRFX_SAADC_EVT_DONE) {
    
        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);
      err_code = nrfx_rtc_cc_set(&rtc, COMPARE_CHANNEL_0, COUNTER_VALUE, false);
      APP_ERROR_CHECK(err_code);
    }

    However, when the buffer size is greater than one the NRFX_SAADC_EVT_DONE is not produced. The problem is because I do not re-set the compare channel (I clear the counter and re-set the compare channel inside the saadc callback).

    I'm not sure what the intention of this PPI channel was, but my guess would be that you want to fill the 5-sample buffer as quickly as possible when the RTC event triggers? In that case, you can try to pass the address of the SAADC SAMPLE task directly, and replace the DONE event with RESULTDONE event.

    Yes this is my intention. Do you mean to retain the second PPI channel and assign like that?

    err_code = nrfx_ppi_channel_assign(m_start_SAADC_ppi_channel, nrf_saadc_event_address_get(NRF_SAADC_EVENT_RESULTDONE), NRF_SAADC_TASK_SAMPLE);
      APP_ERROR_CHECK(err_code);

    I tried but it didn't work

    How could I work around this issue?

Children
  • Yes, I meant similar to that, but NRF_SAADC_TASK_SAMPLE does not hold the register address, this is just an enum variable. Can you try with nrf_saadc_task_address_get(NRF_SAADC_TASK_SAMPLE) to the last argument?

  • Thank you Jorgen,

    I assigned the second PPI channel as you suggested but it didn't work

    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 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
      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_RESULTDONE), nrf_saadc_task_address_get(NRF_SAADC_TASK_SAMPLE)); // Make sure that the SAADC is always started when it ends a conversion
      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);
    }

    Well, as soon as I enable the second PPI channel, it stop working as expected even for a buffer size = 1

    It generates the DONE event one, three, five or six times before stopping working

    I would like to share with you my project and the sdk_config file


    5758.pca10056.zip

Related