NRFX RTC -> PPI -> ADC

I've been piecing examples together and I think I'm getting close.  I'm using the NRF52 DK PCB.  

I can get the RTC to trigger callbacks but it seems to be running way too  fast.  Prescaler is set to 32767 but I'm getting about 10 ticks per second.  

I'm not able to get the PPI to trigger any activity on the ADC however.  This is how I have the code setup:

void main(void) { 
  
  rtc_init();
  ppi_init();
  adc_init();

  while(1) ;
}

void rtc_init() {
  uint32_t err_code;

  // Initialize RTC instance
  nrfx_rtc_config_t config = NRFX_RTC_DEFAULT_CONFIG;
  config.prescaler = RTC_FREQ_TO_PRESCALER(1);
  printk("Prescaler = %d\n", config.prescaler);

  IRQ_CONNECT(DT_IRQN(DT_NODELABEL(rtc2)), DT_IRQ(DT_NODELABEL(rtc2), priority), nrfx_rtc_2_irq_handler, NULL, 0);

  err_code = nrfx_rtc_init(&rtc, &config, rtc_handler);
  ERR_CHECK(err_code, "rtc initialization error");;

  // Enable tick event & interrupt
  nrfx_rtc_tick_enable(&rtc, true);

  nrfx_rtc_enable(&rtc);
}

void ppi_init(void) {
  /**** RTC -> ADC *****/
  // Trigger task sample from timer
  nrfx_err_t err_code = nrfx_ppi_channel_alloc(&m_timer_saadc_ppi_channel);
  ERR_CHECK(err_code, "PPI channel allocation error");
  

  err_code = nrfx_ppi_channel_assign(m_timer_saadc_ppi_channel, 
        nrfx_rtc_event_address_get(&rtc, NRF_RTC_EVENT_TICK), 
        nrf_saadc_task_address_get(DT_REG_ADDR(DT_NODELABEL(adc)), NRF_SAADC_TASK_SAMPLE));
  ERR_CHECK(err_code, " PPI channel assignment error");

  err_code = nrfx_ppi_channel_enable(m_timer_saadc_ppi_channel);
  ERR_CHECK(err_code, " PPI channel enable error");

}

void adc_init(void) {
  nrfx_err_t err;
  nrfx_saadc_adv_config_t adc_adv_config = NRFX_SAADC_DEFAULT_ADV_CONFIG;

  err = nrfx_saadc_init(DT_IRQ(DT_NODELABEL(adc), priority));
  ERR_CHECK(err, "nrfx_saadc init error");
  
  err = nrfx_saadc_channels_config(adc_channels, NUM_ADC_CHANS);
  ERR_CHECK(err,"nrfx_saadc channel config error");
  
  err = nrfx_saadc_advanced_mode_set(0b00000011, SAADC_RESOLUTION_VAL_14bit, &adc_adv_config, adc_handler);
  ERR_CHECK(err, "saadc setting advanced mode");

  err = nrfx_saadc_buffer_set(&(adc_samples[CURRENT_BUFFER].samples[0]), NUM_ADC_CHANS);
  ERR_CHECK(err, "saadc buffer set");

}

My RTC tick handler is working (I have it print out a message on the UART) but the ADC handler doesn't seem to be called.   

I'm not sure if I need to add an IRQ_CONNECT() for the adc. If I add 

IRQ_CONNECT(DT_IRQN(DT_NODELABEL(adc)), DT_IRQ(DT_NODELABEL(adc), priority), nrfx_saadc_irq_handler, NULL, 0);
to the code, then I get this compile time error:
gen_isr_tables.py: error: multiple registrations at table_index 7 for irq 7 (0x7)
Existing handler 0x6389, new handler 0x4059
Has IRQ_CONNECT or IRQ_DIRECT_CONNECT accidentally been invoked on the same irq multiple times?

I have the following in my prj.conf:


CONFIG_LOG=y
CONFIG_LOG_PROCESS_THREAD_SLEEP_MS=100
CONFIG_ADC=y
CONFIG_NRFX_RTC2=y
CONFIG_NRFX_SAADC=y
CONFIG_ADC_NRFX_SAADC=y
CONFIG_ADC_ASYNC=y
Any thoughts?
  • I can get the RTC to trigger callbacks but it seems to be running way too  fast.  Prescaler is set to 32767 but I'm getting about 10 ticks per second.  

    Does it scale when changing the prescaler? I notice the CONFIG_LOG_PROCESS_THREAD_SLEEP_MS is 100ms, just wondering if that accidently is the same as the 10Hz you see or not.

    It's not possible to update the PRESCALER if the rtc is already running, can you just check that the rtc is indeed stopped before you configure it (maybe even try a different rtc instance if it's already in use by "something").

    I'm not sure if I need to add an IRQ_CONNECT() for the adc. If I add 

    I suggest you remove CONFIG_ADC=y in this case, since I suspect that may indirectly enable the ADC handler already. 

    If you still have problems maybe you can share your entire project folder so I can build and run on an nRF52-DK.

    Edit: I do believe you also need to call the saadc start task before the sample task will have any action, see Figure 4 here and nrfx_saadc_mode_trigger() api.

    Kenneth

  • If I decrease the prescaler, the It will go faster for sure.  I haven't bothered hooking it up to a logic analyzer to measure the exact timing but can.  In some other posts, I read that only RTC2 can be used for applications to avoid issues if you're running the BT stack.  But I can try a different one to see if that does anything.

    I removed the CONIFG_ADC=y but was presented with these errors


    warning: ADC_ASYNC (defined at drivers/adc/Kconfig:31) was assigned the value 'y' but got the value
    'n'. Check these unsatisfied dependencies: ADC (=n). See
    docs.zephyrproject.org/.../kconfig.html and/or look up ADC_ASYNC in the
    menuconfig/guiconfig interface. The Application Development Primer, Setting Configuration Values,
    and Kconfig - Tips and Best Practices sections of the manual might be helpful too.


    warning: ADC_NRFX_SAADC (defined at drivers/adc/Kconfig.nrfx:25) was assigned the value 'y' but got
    the value 'n'. Check these unsatisfied dependencies: ADC (=n). See
    docs.zephyrproject.org/.../kconfig.html and/or look up
    ADC_NRFX_SAADC in the menuconfig/guiconfig interface. The Application Development Primer, Setting
    Configuration Values, and Kconfig - Tips and Best Practices sections of the manual might be helpful
    too.

    That being said, it seems to be working a bit better now in that my ADC handler is being called.  So ADC_NRFX_SAADC and ADC_ASYNC are not needed?  What do they do?

    I added a call to nrfx_saadc_mode_trigger() which initiates a the START task, and that gets one conversion complete, but it doesn't look like any of the RTC based triggers are doing anything via the PPI.

    Here's what's I'm seeing:


    *** Booting Zephyr OS build v3.1.99-ncs1-rc2 ***
    Prescaler = 32767
    NRFX_SAADC_EVT_READY
    NRFX_SAADC_EVT_BUF_REQ
    tick interrupt received
    NRFX_SAADC_EVT_DONE
    tick interrupt received
    tick interrupt received
    ....

    I've created a zip file with the project files and uploaded it here: https://www.dropbox.com/s/gnjsjse63eurjnv/nrfx.zip?dl=0

    Let me know what you find.  

  • I also tried using a timer instead of the RTC but still no go.  

    /** TIMER **/
    static nrfx_timer_t timer1 = NRFX_TIMER_INSTANCE(1);
    
    void timer_init(void) {
      nrfx_timer_config_t timer_cfg = {
        .bit_width = NRF_TIMER_BIT_WIDTH_32, 
        .frequency = NRF_TIMER_FREQ_1MHz, 
        .interrupt_priority = 6,
        .mode = NRF_TIMER_MODE_TIMER,
        .p_context = NULL
      };
      ERR_CHECK(nrfx_timer_init(&timer1, &timer_cfg, NULL), "timer init");
      nrfx_timer_extended_compare(&timer1, NRF_TIMER_CC_CHANNEL0, nrfx_timer_ms_to_ticks(&timer1, 1000), NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
    
      // PPI 
      nrfx_err_t err_code = nrfx_ppi_channel_alloc(&ppi_channel);
      ERR_CHECK(err_code, "PPI channel allocation error");
    
      err_code = nrfx_ppi_channel_assign(ppi_channel, 
            nrfx_timer_event_address_get(&timer1, NRF_TIMER_EVENT_COMPARE0),
            nrf_saadc_task_address_get((void*) DT_REG_ADDR(DT_NODELABEL(adc)), NRF_SAADC_TASK_START));
      ERR_CHECK(err_code, " PPI channel assignment error");
    
      err_code = nrfx_ppi_channel_enable(ppi_channel);
      ERR_CHECK(err_code, " PPI channel enable error");
    
      nrfx_timer_enable(&timer1);
    }

    And I've tried to change the init order and put the RTC start at the end (I read that the timer should be started after the PPI is initalized)

      adc_init();
      rtc_init();
      ppi_init();
      nrfx_rtc_enable(&rtc); //i read this should be done after the ppi is initalized

    but I'm still not getting the PPI to start the conversion.

    And I tried switching the TASK to the START task instead of the sample task but no change.


      err_code = nrfx_ppi_channel_assign(ppi_channel,
            nrfx_rtc_event_address_get(&rtc, NRF_RTC_EVENT_TICK),
            nrf_saadc_task_address_get((void*) DT_REG_ADDR(DT_NODELABEL(adc)), NRF_SAADC_TASK_START));
  • I've made a bit more progress.  I've identified that the SAMPLE task is being sent by the PPI, but it's not sufficient to enable continuous conversions.  This is a bit longer of a post than it needs to be, but I thought it might be helpful to others given how little I'm finding documented on using the nrfx sdaac drivers.

    If I call the nrfx_saadc_mode_trigger() with the PPI disabled, I get these callbacks:

    NRFX_SAADC_EVT_READY
    NRFX_SAADC_EVT_BUF_REQ

    with the PPI enabled, issuing 'SAMPLE' tasks, I get

    NRFX_SAADC_EVT_READY
    NRFX_SAADC_EVT_BUF_REQ
    NRFX_SAADC_EVT_DONE

    So there needs to be another SAMPLE issued after the BUF_REQ (and nrfx_saadc_buffer_set() ).  If I issue this manually right after the call to nrfx_saadc_buffer_set(), the DONE callback is received.   However, subsequent SAMPLE requests do nothing.  

    If I call nrfx_saadc_mode_trigger() right after nrfx_saadc_buffer_set(), I get NRFX_INVALID_STATE.  

    Seems my problem is around how to get scan mode sampling at a fixed sampling frequency.  

    According to the nrf2832 datasheet (section 37.5.2), 

    Continuous sampling can be achieved by using the internal timer in the ADC, or triggering the SAMPLE task from one of the general purpose timers through the PPI

    But this is obviously not working for me... 

    I added some debugging logic to see what tasks were being called and how they worked.. 

    *** Booting Zephyr OS build v3.1.99-ncs1-rc2 ***
    TASK: 00000000 [START]
    NRFX_SAADC_EVT_READY
    NRFX_SAADC_EVT_BUF_REQ
    TASK: 00000004 [SAMPLE]
    NRFX_SAADC_EVT_DONE
    TASK: 00000004 [SAMPLE]

    I'm not getting any subsequent samples taken after the last sample.  This is me just trying to trigger events using the callback functions and not use the PPI

    void adc_handler(const nrfx_saadc_evt_t *p_event) { 
      switch (p_event->type) {
        case NRFX_SAADC_EVT_DONE:      ///< Event generated when the buffer is filled with samples.
          printk("NRFX_SAADC_EVT_DONE\n");
          adc_enqueue();
          nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_SAMPLE);
          break;
        case NRFX_SAADC_EVT_LIMIT:         ///< Event generated when one of the limits is reached.
          printk("NRFX_SAADC_EVT_LIMIT\n");
          break;
        case NRFX_SAADC_EVT_CALIBRATEDONE: ///< Event generated when the calibration is complete.
          printk("NRFX_SAADC_EVT_CALIBRATEDONE\n");
          break;
        case NRFX_SAADC_EVT_BUF_REQ:       ///< Event generated when the next buffer for continuous conversion is requested.
          printk("NRFX_SAADC_EVT_BUF_REQ\n");
          nrfx_saadc_buffer_set(adc_samples[DOUBLE_BUFFER].samples, NUM_ADC_CHANS);
          current_adc_buffer = DOUBLE_BUFFER;
          nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_SAMPLE);
          break;
        case NRFX_SAADC_EVT_READY:         ///< Event generated when the first buffer is acquired by the peripheral and sampling can be started.
          printk("NRFX_SAADC_EVT_READY\n");
          break;
        case NRFX_SAADC_EVT_FINISHED:
          printk("NRFX_SAADC_EVT_FINISHED\n");
          break;
        default:
          printk("GOT UNKOWN SAAdC EVENT %d", p_event->type);
          break;
      }
    }
    

    However, if I add a START before the SAMPLE in the case when I get a DONE, then I get continuous conversion

    *** Booting Zephyr OS build v3.1.99-ncs1-rc2 ***
    TASK: 00000000
    NRFX_SAADC_EVT_READY
    NRFX_SAADC_EVT_BUF_REQ
    TASK: 00000004
    NRFX_SAADC_EVT_DONE
    TASK: 00000000
    TASK: 00000004
    NRFX_SAADC_EVT_BUF_REQ
    TASK: 00000004
    NRFX_SAADC_EVT_DONE
    TASK: 00000000
    TASK: 00000004
    NRFX_SAADC_EVT_BUF_REQ
    TASK: 00000004
    NRFX_SAADC_EVT_DONE
    TASK: 00000000

    So I guess where I'm stuck is that although the documentation says all I need to do is issue a SAMPLE for continuous conversion, it seems I also need a START per conversion.  I'm not sure how to send two tasks via PPI for each event.  

    p.s. this cracked me up in nrfx_errors.h

    /** @brief Base number of error codes. */
    #define NRFX_ERROR_BASE_NUM         0x0BAD0000
  • Hi,

    It's not clear if you have one analog input or several analog inputs configured here, can you clarify? Reason for asking is that if you have more than one analog input configured you must refer to scan mode description. 

    If you want to share your project please drag&drop the zip file into the text box you reply in.

    Kenneth

Related