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?
Parents
  • 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

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

  • Kenneth,

    I am using multiple channels.  I want to scan up to 8 channels at (ideally) 1ksps.  In my code, I am testing with 2 channels.  I provided the zip file via a dropbox link in my last message, but I'll share it here using the method requested as well.  

    7824.nrfx.zip

    Thanks,
    Reza

  • Hi,

    Just to clarify a bit.

    To sample one analog input channel you need to trigger one SAMPLE task. If you have enabled 2 channels, then you need to trigger SAMPLE task two times to sample both inputs.

    When you call nrfx_saadc_buffer_set() you provide a size that will decide when NRFX_SAADC_EVT_DONE will be executed. Shortly after the NRFX_SAADC_EVT_DONE you will get an NRFX_SAADC_EVT_BUF_REQ event where you need to prepare a new buffer by calling nrfx_saadc_buffer_set().

    When the NRFX_SAADC_EVT_DONE occurs, you will need to trigger a START task again. This can be done by either calling nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_START), but a better idea might be to make a ppi channel between the SAADC DONE event and the SAADC START task.

    I believe the above explain much of your application flow here.

    All that said, I do see a timing problem when using very high values (>4096) with the PRESCALER. I am not entirely sure why that is, it could be an hardware issue of some sort that occur if LFCLK is running at the time the PRESCALER is enabled. I suggest you reduce the PRESCALER (<4096), and instead use COMPARE event to trigger the SAMPLE task.

    Edit: If you want to sample 8 channels at 1kHz. In that case I think it is a good idea to also increase the buffer size when calling nrfx_saadc_buffer_set() by quite a lot (e.g. 100 will cause the DONE&REQ events to trigger every 100 samples =~100ms).

    Best regards,
    Kenneth

  • Actually, looking at the PRESCALER register, it's only 12 bits wide, so a max value of 4096 makes sense.  If I use the compare event, is there a way to auto-reset the counter to zero?  

    That being said, I wanted to set it to a high value for testing -- in my application, I want to sample 8 channels at 1khz each, so this shouldn't be a problem.  Though, with a target rate of 8khz, I'm thinking a timer might be a better trigger than the rtc; with the RTC PRESCALER set to 3, I get a sampler rate of 8192hz.  Not sure what the math would be to get RTC to trigger at 8khz using the counter compare.

    I'll go through your logic/feedback and see if that'll sort out my issues.  Thanks.

  • I've implemented the logic and it looks like it's working fine now.  This explanation you provided is very valuable - is there someplace outside this forum that you could share/post it so it's easier to find for others?  Or perhaps add it to some documentation.

    Internally, it looks like the code tries to have a double-buffer on hand.  Can I invoke START after DONE assuming that the SDK has a double-buffer ready and then copy the contents out of the previous buffer and provide a new buffer when I get the BUF_REQ event?  This is what I'm doing now:

    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(); // <--- COPIES THE CONTENTS OF THE ADC BUFFER OUT
          nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_START);      
          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); // <-- provides the pointer to the next buffer
          current_adc_buffer = DOUBLE_BUFFER; // <-- SWAPS OUT MY INTERNAL BUFFERS
          break;
      }
    }
    

    but would this work (and would it be better?)

    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");
          nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_START);      
          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); // <-- provides the pointer to the next buffer
          adc_enqueue(); // <--- COPIES THE CONTENTS OF THE ADC BUFFER OUT
          current_adc_buffer = DOUBLE_BUFFER; // <-- SWAPS OUT MY INTERNAL BUFFERS
          break;
      }
    }

    I now need to add the BT stack to see if all of this plays nicely together.   Thanks for all the help so far!

Reply
  • I've implemented the logic and it looks like it's working fine now.  This explanation you provided is very valuable - is there someplace outside this forum that you could share/post it so it's easier to find for others?  Or perhaps add it to some documentation.

    Internally, it looks like the code tries to have a double-buffer on hand.  Can I invoke START after DONE assuming that the SDK has a double-buffer ready and then copy the contents out of the previous buffer and provide a new buffer when I get the BUF_REQ event?  This is what I'm doing now:

    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(); // <--- COPIES THE CONTENTS OF THE ADC BUFFER OUT
          nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_START);      
          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); // <-- provides the pointer to the next buffer
          current_adc_buffer = DOUBLE_BUFFER; // <-- SWAPS OUT MY INTERNAL BUFFERS
          break;
      }
    }
    

    but would this work (and would it be better?)

    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");
          nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_START);      
          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); // <-- provides the pointer to the next buffer
          adc_enqueue(); // <--- COPIES THE CONTENTS OF THE ADC BUFFER OUT
          current_adc_buffer = DOUBLE_BUFFER; // <-- SWAPS OUT MY INTERNAL BUFFERS
          break;
      }
    }

    I now need to add the BT stack to see if all of this plays nicely together.   Thanks for all the help so far!

Children
No Data
Related