This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Understanding SAADC output buffer nondeterministic behavior.

Hi there, 

I am implementing the SAADC example into my application whereby I should be able to sample all 8 ADC channels "simultaneously" at a relatively high frequency.

I am using the same setup given in the example ->    \examples\peripheral\saadc. 

 1. Set up a ppi channel so that timer compare event is triggering sample task in SAADC. This roughly 160 uS. 

 2.  Initialize ADC channels with default configuration. 

	    nrf_saadc_channel_config_t channel_config0 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);
		nrf_saadc_channel_config_t channel_config1 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN1);
		nrf_saadc_channel_config_t channel_config2 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN2);
	    nrf_saadc_channel_config_t channel_config3 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN3);
		nrf_saadc_channel_config_t channel_config4 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN4);
		nrf_saadc_channel_config_t channel_config5 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN5);
		nrf_saadc_channel_config_t channel_config6 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN6);
		nrf_saadc_channel_config_t channel_config7 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN7);


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

		 err_code = nrf_drv_saadc_channel_init(1, &channel_config1);	// 	POGO 7, pos Buff out 1
		 APP_ERROR_CHECK(err_code);
		 
		 err_code = nrf_drv_saadc_channel_init(2, &channel_config2); 
		 APP_ERROR_CHECK(err_code);
		 
		 err_code = nrf_drv_saadc_channel_init(3, &channel_config3);
		 APP_ERROR_CHECK(err_code);
		 
		 err_code = nrf_drv_saadc_channel_init(4, &channel_config4); 
		 APP_ERROR_CHECK(err_code);
		 
		 err_code = nrf_drv_saadc_channel_init(5, &channel_config5); 
		 APP_ERROR_CHECK(err_code);
		 
		 err_code = nrf_drv_saadc_channel_init(6, &channel_config6); 
		 APP_ERROR_CHECK(err_code);
		 
		 err_code = nrf_drv_saadc_channel_init(7, &channel_config7); 
		 APP_ERROR_CHECK(err_code);

3. Every time my input signal completes a full period, I start a new ADC conversion, like this. 

	err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAMPLES_IN_BUFFER);	//Non blocking call
	APP_ERROR_CHECK(err_code);

That all works fine, since my input signal is square wave at 250 Hz, I have verified that with my logic analyzer. 

However the Issue is when a read the output buffer which is 200 long. 25 samples per input signal period 0.4 ms. 

Having initialized my ADC channels as mentioned above: 

I would expect the results to be stored, like this: (ADCx = AINx). 

[ ADC0 , ADC1 , ADC2 , ADC3 , ADC4 , ADC5 , ADC6 , ADC7] and repeat this behavior for n Samples. 

However, I actually get this order: triggering samples every 160 uS. 

[ ADC6 , ADC7 , ADC0 , ADC1 , ADC2 ,  ADC3 , ADC4 , ADC5]

If I change the sample rate to somehting diffferent like 1 mS. 

I get : 

[ ADC5 , ADC3 , ADC0 , ADC4 , ADC2 ,  ADC7 , ADC1 , ADC6]

So, Am I wrong at assuming that the value corresponding to each AIN should correspond to the order in which I initialized the channels ?  

Why does the order change depending on the sampling frequency ? 

What would be the best approach to still sample all channels and read the data for each channel accordingly ? 

Thanks ! 

Parents Reply
  • Hi Jared, 

    I got the same weird order in the ADC buffer output in the example SDK, I am using SDK15.1. 

    Are there any considerations that should be taken into account when using the SAADC, in terms of setup or configuration for those 8 GPIO channels ? 

    Are they all fully available ?

    How likely is the softdevice to cause interference between the SAADC sampling process ?  

    Looking forward to hearing from you. 

    Thanks For looking into this ! 

    8686.saadc.zip

Children
  • Hello again.

    Your case strongly resemblance the one described in this thread. In short, we have seen some issues where the layout of samples in memory are re-ordered when using the SAADC. This is usually a result of the START task being preempted/delayed after the SAMPLE task has been triggered by the PPI. A symptom of this issue is usually a reordering of samples in memory and loss of data from some channels ( it doesn't seem like you're observing the latter). The problem and purposed solutions are explained in the thread.

    However,  I would like you to try a couple of things before you try the solutions in that thread:

    1. I see that you only use one buffer, and call 
      nrf_drv_saadc_buffer_convert() in the callback handler. This might be problematic if the SAMPLE task is triggered during the updating of the pointer to the result buffer. Try removing the function call from the callback handler. It shouldn't be necessary to call the function if you only use one buffer.
    2. You can try to increase the SAMPLES_IN_BUFFER

    Note: that you need to remove the trigger of TASK_START in nrfx_saadc_irq_handler() if you implement solution 1 in the linked thread.

    best regards
    Jared 
  • Hi Jared,

    I know it has been a long time, since I open this thread however I am still having this issue, I actually got it working by  adjusting my data calculation to the buffer shifting, so basically living with wrong behavior, and it seems to work fine, I woudl really like to ge to the bottom of this issue, since as soon as I change any configuration parameters like acquisition time or increase the sampling rate for the timer the samples get out of order again. 

    I have seen the thread that you suggested, and tried but It did not quite work, will you by any chance have a code example of the fix suggested ? 

     Thanks 

  • Hi,

    First, did you try the two options I suggested?:

    Jared said:

    However,  I would like you to try a couple of things before you try the solutions in that thread:

    1. I see that you only use one buffer, and call 
      nrf_drv_saadc_buffer_convert() in the callback handler. This might be problematic if the SAMPLE task is triggered during the updating of the pointer to the result buffer. Try removing the function call from the callback handler. It shouldn't be necessary to call the function if you only use one buffer.
    2. You can try to increase the SAMPLES_IN_BUFFER

     

    Secondly,

    which of the two options in the linked thread did you try to implement? Could you share what you tried?

    regards

    Jared 

  • Hi Jared, 

    Yes, I tried to implement both options mentioned above. 

    How would increasing the buffer size help mitigate this issue ? 

    This is how I have been currently doing it up until now: 

    /*
    
        This is how I implement the sampling event for the SAADC. 
    
    
    */
    
    void saadc_sampling_event_init(void)
    {
        ret_code_t err_code;
        
        /* Initialize ppi driver */
        err_code = nrf_drv_ppi_init();
        APP_ERROR_CHECK(err_code);
        
        /* Configure sampling timer. */
        nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
        timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
    	
    	/* Initiliaze timer. */
        err_code = nrf_drv_timer_init(&m_timer, &timer_cfg, timer_handler);
        APP_ERROR_CHECK(err_code);
        
    	
        /* setup m_timer to sample every 160 uS */
        uint32_t ticks = nrf_drv_timer_us_to_ticks(&m_timer, 160);  
        nrf_drv_timer_extended_compare(&m_timer,
                                       NRF_TIMER_CC_CHANNEL0,
                                       ticks,
                                       NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                       false);
        /*Enable timer. */                               
        nrf_drv_timer_enable(&m_timer);
        /* Get Address of timer compare event. */
        uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer, NRF_TIMER_CC_CHANNEL0);
        /* Get ADC task. */
        uint32_t saadc_sample_task_addr   = nrf_drv_saadc_sample_task_get();
    
        /* setup ppi channel so that timer compare event is triggering sample task in SAADC */
        err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
                                              timer_compare_event_addr,
                                              saadc_sample_task_addr);
    																					
        APP_ERROR_CHECK(err_code);
    }
    

    Please note that I call this function in the event handler of GPIOTE interrupt, that comes from a PWM signal, every time this PWM signal completes a cycle I call this function, to start sampling. 

    	// Start sampling event.
    	err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAMPLES_IN_BUFFER);	//Non blocking call
    	APP_ERROR_CHECK(err_code);

    And then in the adc call back I process my data: 

    void saadc_callback(nrf_drv_saadc_evt_t const * p_event){
    	
    	
    	  if (p_event->type == NRF_DRV_SAADC_EVT_DONE){
    			
    			
    			/*
    			
    			
    			    In here I just process the data gathered, by the ADC.  
    			
    			
    			*/
    			
    			for(int i = 0; i < SAMPLES_IN_BUFFER; i++){
    			
    				  voltageAtADCs[i] = p_event->data.done.p_buffer[i];  
    
    			 }
    
    			
    		}
    
    }

    As far as I understood from the thread mentioned above, this is how implement the change: 

    By adding a ppi channel to  trigger a START task on an END event.

    void saadc_sampling_event_init(void)
    {
    	
        ret_code_t err_code;
    
        err_code = nrf_drv_ppi_init();
        APP_ERROR_CHECK(err_code);
    
        nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
        timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
    	
        err_code = nrf_drv_timer_init(&m_timer, &timer_cfg, timer_handler);
        APP_ERROR_CHECK(err_code);
    	  
    	
        /* setup m_timer for compare event every 160 uS*/
        uint32_t ticks = nrf_drv_timer_us_to_ticks(&m_timer, 160);  
        nrf_drv_timer_extended_compare(&m_timer,
                                       NRF_TIMER_CC_CHANNEL0,
                                       ticks,
                                       NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                       true);
        nrf_drv_timer_enable(&m_timer);
    
        uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer, NRF_TIMER_CC_CHANNEL0);
        uint32_t saadc_sample_task_addr   = nrf_drv_saadc_sample_task_get();
    
        /* setup ppi channel so that timer compare event is triggering sample task in SAADC */
        err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
        APP_ERROR_CHECK(err_code);
    		
    
    
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
                                              timer_compare_event_addr,
                                              saadc_sample_task_addr);
    																					
    		
    	err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel0);
        APP_ERROR_CHECK(err_code);
    	
    	
    	/* Use PPI to trigger START task on an END event. */
    	
    	err_code = nrf_drv_ppi_channel_assign(m_ppi_channel0, 
    										 nrf_saadc_event_address_get(NRF_SAADC_EVENT_END),
    										 nrf_saadc_task_address_get(NRF_SAADC_TASK_START));
    
        NRF_LOG_INFO("Error code PPI assignment -> %d", err_code);
    		
    		
    		
    																					
        APP_ERROR_CHECK(err_code);
    		
    		
    }

    What is the clock source for the SAADC peripheral ?

    Am I missing something in my implementation ?

    Thanks for your help. 

    Warm Regards

  • Hi,

    Thank you for the code. 

    To elaborate on my thoughts behind my previous reply: 

    As I've mentioned earlier, I wasn't 100% sure that your issue was indeed due to the START task being preempted. The ordering of the data in your buffer wasn't what I would have expected if the preempting was the case. It seems the data is ordered randomly, which is not quite what I've seen before with the other cases. Nevertheless, I figured that you should try to implement the workaround to see if it indeed solves your issue. 

    Jared said:
    • I see that you only use one buffer, and call 
      nrf_drv_saadc_buffer_convert() in the callback handler. This might be problematic if the SAMPLE task is triggered during the updating of the pointer to the result buffer. Try removing the function call from the callback handler. It shouldn't be necessary to call the function if you only use one buffer.
    • You can try to increase the SAMPLES_IN_BUFFER

     After a further evaluation behind these suggestions I've changed my mind. It would be better if you just tried to implement the PPI with the START and END events. But, a prerequisite of using that suggestion is that you're using double buffering, which you are not using. Is there a specific reason for why you're not using it? 

    Could you implement a double buffer and a PPI channel that links the START and END event together? You've implemented the PPI channel correctly, the only remaining part is to actually activate the channel by a call to nrf_drv_ppi_channel_enable(). Also, remember to comment out/remove the call to the START task in the IRQ handler when you implement this:

     

    felan said:
    What is the clock source for the SAADC peripheral ?

    See this thread.

    Also, Are you using a development kit or a custom board?

    regards

    Jared 

Related