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

nrf52 SAADC - NRFX_SAADC_EVT_DONE never called

using nRF5SDK 160098a08e2

I have the following code:

#define ADC_NOF_SAMPLES 16u
static int16_t s_sample_buffer[ ADC_NOF_SAMPLES ];
static nrf_saadc_channel_config_t const s_adc_channel0 = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE( NRF_SAADC_INPUT_AIN5 );
static void saadc_event_isr( nrfx_saadc_evt_t const *p_event )
{
    volatile uint32_t numOfSamples = 0;
    switch ( p_event->type )
    {
    case NRFX_SAADC_EVT_DONE:  // Event generated when the buffer is filled with samples.
        numOfSamples = p_event->data.done.size;
        break;
    case NRFX_SAADC_EVT_LIMIT: // Event generated after one of the limits is reached.
        break;
    case NRFX_SAADC_EVT_CALIBRATEDONE: // Event generated when the calibration is complete.
        {
            nrfx_err_t err;
            err = nrfx_saadc_buffer_convert( &s_sample_buffer[0], ADC_NOF_SAMPLES );
            APP_ERROR_CHECK(err);
            /*err = nrfx_saadc_sample();
            APP_ERROR_CHECK(err);*/
        }
        break;
    default:
        break;
    }
}

    nrfx_err_t err;
    static nrfx_saadc_config_t const s_saadc_config = {
            .resolution = NRF_SAADC_RESOLUTION_12BIT,
            .oversample = NRF_SAADC_OVERSAMPLE_256X,
            .interrupt_priority = NRFX_SAADC_CONFIG_IRQ_PRIORITY,
            .low_power_mode = false
    };

    // ADC driver is initialized in non-blocking mode, since an event handler is given
    err = nrfx_saadc_init( &s_saadc_config, saadc_event_isr );

    if ( err == NRFX_SUCCESS )
    {
        // Initialize the ADC channel 0
        err = nrfx_saadc_channel_init( 0, &s_adc_channel0 );

        if ( err == NRFX_SUCCESS )
        {
            /*
            * Calibrate the offset with a call to nrfx_saadc_calibrate_offset.
            * You need to wait for the SAADCs DONE event before you can proceed.
            * */
            do
            {
                err = nrfx_saadc_calibrate_offset();
            } while ( err == NRFX_ERROR_BUSY );

            rc = 1u;
        }
    }



I never receive NRFX_SAADC_EVT_DONE. Please advise

Parents
  • Hello,

    Could you tell me, are you working out of the provided SAADC example from the SDK?
    Please provide the entire source code, as the root cause for your error might be located elsewhere.

    For future reference, when inserting code as part of a question, I ask that you format it with the code option to significantly increase readability.

    Best regards,
    Karl

  • So I tried to add the following code:

    			nrfx_err_t err;
    			err = nrfx_saadc_buffer_convert( &s_sample_buffer[0], ADC_NOF_SAMPLES );
    			APP_ERROR_CHECK(err);
    			err = nrfx_saadc_sample();
    			APP_ERROR_CHECK(err);
    
    			while(1)
    			{
    				while( nrfx_saadc_is_busy() ) {}
    				err = nrfx_saadc_sample();
    				APP_ERROR_CHECK(err);
    			}

    I never gets past nrfx_saadc_is_busy(). So the SAADC never gets into idle state. What is wrong with my initialization of this module ?

        nrfx_err_t err;
        static nrfx_saadc_config_t const s_saadc_config = {
        		.resolution = NRF_SAADC_RESOLUTION_12BIT,
        		.oversample = NRF_SAADC_OVERSAMPLE_DISABLED,//NRF_SAADC_OVERSAMPLE_128X,//NRF_SAADC_OVERSAMPLE_256X,
        		.interrupt_priority = NRFX_SAADC_CONFIG_IRQ_PRIORITY,
        		.low_power_mode = false
        };
    
        // ADC driver is initialized in non-blocking mode, since an event handler is given
        err = nrfx_saadc_init( &s_saadc_config, saadc_event_isr );
    
        if ( err == NRFX_SUCCESS )
        {
        	// Initialize the ADC channel 0
        	err = nrfx_saadc_channel_init( 0, &s_adc_channel0 );
    
        	if ( err == NRFX_SUCCESS )
        	{
        		/*
        		 * Calibrate the offset with a call to nrfx_saadc_calibrate_offset.
        		 * You need to wait for the SAADCs DONE event before you can proceed.
        		 * */
        		do
        		{
        			/*
    					The SAADC has a temperature dependent offset.
    					Therefore, it is recommended to calibrate the SAADC at least once before use,
    					and to re-run calibration every time the ambient temperature has changed by more than 10 °C.
    					Offset calibration is started by triggering the CALIBRATEOFFSET task, and the CALIBRATEDONE
    					event is generated when calibration is done.
        			 */
        			err = nrfx_saadc_calibrate_offset();
        		} while ( err == NRFX_ERROR_BUSY );
    
        		rc = 1u;
        	}
        }

  • BardenDK said:
    I do, here:

    My mistake, I had a hard time reading your initially posted code.
    Thank you for formatting all the code in your comments!

    BardenDK said:
    What is wrong with my initialization of this module ?

    Your initialization looks fine from what I can gather, I suspect this is rather a problem with the timing of the operations.

    Looking back at the code you posted originally, what is your intention when the NRFX_SAADC_EVT_DONE event occurs?
    Also, how often or at what event do you intend for the ADC to sample? I see from your first comment that you are showing me a call to app_timer_init, but I see no mention of it anywhere else.

    It is hard to get a good overview and understanding of your application without seeing all the relevant code.
    Let me know, if you would like me to make your ticket private at any time.

    Best regards,
    Karl

  • Alternatively, could you provide me with a stripped down version of your application - only the parts concerning the setup and use of the SAADC?
    So that I am able to replicate and debug on my part.

  • Hi Karl,

    I have had succes in getting the event to occur. But why i have to do, what i did, i do not fully understand when reading the API description. And it is also not a working solution for my project. Here goes, i did the follwoing:

     nrfx_err_t err;
     static nrf_saadc_channel_config_t const s_adc_channel0 = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE( NRF_SAADC_INPUT_AIN5 );
        static nrfx_saadc_config_t const s_saadc_config = {
        		.resolution = NRF_SAADC_RESOLUTION_12BIT,
        		.oversample = NRF_SAADC_OVERSAMPLE_DISABLED,//NRF_SAADC_OVERSAMPLE_128X,//NRF_SAADC_OVERSAMPLE_256X,
        		.interrupt_priority = NRFX_SAADC_CONFIG_IRQ_PRIORITY,
        		.low_power_mode = false
        };
    
        // ADC driver is initialized in non-blocking mode, since an event handler is given
        err = nrfx_saadc_init( &s_saadc_config, saadc_event_isr );
    
        if ( err == NRFX_SUCCESS )
        {
        	// Initialize the ADC channel 0
        	err = nrfx_saadc_channel_init( 0u, &s_adc_channel0 );
    
        	if ( err == NRFX_SUCCESS )
        	{
        		/*
        		 * Calibrate the offset with a call to nrfx_saadc_calibrate_offset.
        		 * You need to wait for the SAADCs DONE event before you can proceed.
        		 * */
        		do
        		{
        			/*
    					The SAADC has a temperature dependent offset.
    					Therefore, it is recommended to calibrate the SAADC at least once before use,
    					and to re-run calibration every time the ambient temperature has changed by more than 10 °C.
    					Offset calibration is started by triggering the CALIBRATEOFFSET task, and the CALIBRATEDONE
    					event is generated when calibration is done.
        			 */
        			err = nrfx_saadc_calibrate_offset();
        		} while ( err == NRFX_ERROR_BUSY );
    
        		// The code below is from:
        		// https://devzone.nordicsemi.com/f/nordic-q-a/52512/look-like-a-bug-inside-the-function-nrfx_saadc_calibrate_offset-in-sdk-15-3-0-nrf5_sdk_15-3-0_59ac345
        		while ( nrfx_saadc_is_busy() ) // Wait for calibration to complete
        		{
        		    __WFE(); //
        		    __SEV(); //
        		    __WFE(); // This sequence puts the system to sleep (SystemON) while waiting
        		}
    
        		err = nrfx_saadc_buffer_convert( &s_sample_buffer[ 0u ], ADC_NOF_SAMPLES );
        		APP_ERROR_CHECK(err);
        		err = nrfx_saadc_sample();
        		APP_ERROR_CHECK(err);
    
        		while ( nrfx_saadc_is_busy() ) // Wait for calibration to complete
    			{
    				__WFE(); //
    				__SEV(); //
    				__WFE(); // This sequence puts the system to sleep (SystemON) while waiting
    
    				( void ) nrfx_saadc_sample();
    			}
    
        		rc = 1u;

    In the while loop from line 50 I am calling nrfx_saadc_sample(); and it looks like that for each call i get one sample.

    But this is not at all the behavior i have expected. I do not want a solution where I need to call a function for each sample. I expected it to work in a way where I could start the sampling and get the DONE event when the SAADC module had made the number of samples i had requested.

  • BardenDK said:
    But this is not at all the behavior i have expected. I do not want a solution where I need to call a function for each sample. I expected it to work in a way where I could start the sampling and get the DONE event when the SAADC module had made the number of samples i had requested.

    The most common use of the SAADC is to trigger it on a interrupt, for example generated by the RTC or any other timer. That way, you can get a sample every time a certain event happens or at a certain time interval.
    I still see no mention of where you use the timer you mentioned in your first comment, and you have not told me how often you intend for the ADC to sample.
    The buffer will not fill on its own, if the sampling happens too seldom or not at all. If this is the case, it could be the reason for the missing done event.
    Please tell me how often you intend for the sampling to happen, and if possible please also show me the code for how you have implemented that timing with the SAADC.

    I will get back to you on the code workaround that you have implemented, but my immediate thought is that the multiple WFE calls are unnecessary for your application.
    I will look closer on the code you have provided within a couple of hours. Please respond to the above questions in the meantime.

    Best regards,
    Karl

  • Hi Karl,

    Thank you for your support and patience Slight smile

    No i do not use a timer. I did not expect that it was needed. The app_timer is initialized and never used. I run FreeRTOS as RTOS and this uses RTC2 for ticks.

    All this ADC i have tested is run through calls from main.

    I need to at least make 128 samples pr. second and calculate all samples when done. So (128 / 16) pr second.

    I dont want to load the CPU with interriputs and thats why i wanted to DMA and expected the system to be able to handle this in the way it is described in API documentation.

    I want to start the ADC sequence and get an interrupt/event when 16 samples have been made, i.e. the sample buffer is full.

    Is this possible ?

Reply
  • Hi Karl,

    Thank you for your support and patience Slight smile

    No i do not use a timer. I did not expect that it was needed. The app_timer is initialized and never used. I run FreeRTOS as RTOS and this uses RTC2 for ticks.

    All this ADC i have tested is run through calls from main.

    I need to at least make 128 samples pr. second and calculate all samples when done. So (128 / 16) pr second.

    I dont want to load the CPU with interriputs and thats why i wanted to DMA and expected the system to be able to handle this in the way it is described in API documentation.

    I want to start the ADC sequence and get an interrupt/event when 16 samples have been made, i.e. the sample buffer is full.

    Is this possible ?

Children
  • Hello,

    BardenDK said:
    Thank you for your support and patience

    It is no problem at all, I am happy to help! :) 

    BardenDK said:
    No i do not use a timer. I did not expect that it was needed. The app_timer is initialized and never used. I run FreeRTOS as RTOS and this uses RTC2 for ticks

    I understand. If you want the sampling to happen on a certain interval, you will have to use a timer or event of some kind. Otherwise, the SAADC will not have any reference as to when and how often it should be sampling.

    BardenDK said:
    All this ADC i have tested is run through calls from main.

    Is the results as expected when you call the sample function multiple times from main? Does the events trigger as expected?
    If this is the case, I suspect that the issue is with the aforementioned need for a timer or event.

    BardenDK said:
    I dont want to load the CPU with interriputs and thats why i wanted to DMA and expected the system to be able to handle this in the way it is described in API documentation.

    Please link me the API documentation you are referring to, and tell me what parts did not work as expected.

    BardenDK said:
    I need to at least make 128 samples pr. second and calculate all samples when done. So (128 / 16) pr second.

    This seems very reasonable, and a perfect task for a timer based SAADC. For reference, the SAADC example I have linked you earlier does exactly this; the example demonstrates the setup and use of a timer with the SAADC. You seem uninterested in this example, is this due to a constraint on your application that I am unaware of?

    BardenDK said:

    I want to start the ADC sequence and get an interrupt/event when 16 samples have been made, i.e. the sample buffer is full.

    Is this possible ?

    Yes, this is possible. As you have seen in the documentation, the DONE event will trigger when the buffer is full. However, the problem seems to me to stem from a lack of sampling being done. If the sampling is not tied to an event or timer, then it will not happen(unless you call it manually), and as such the buffer will not fill up - which means you will not get the DONE event.

    Bear in mind, I might be wrong in my suspicion since I have only seen snippets from different places in the code, but I hope this is helpful to you.


    Best regards,
    Karl

     

  • Hi Karl,

    Thank you for your reply.

    This seems very reasonable, and a perfect task for a timer based SAADC. For reference, the SAADC example I have linked you earlier does exactly this; the example demonstrates the setup and use of a timer with the SAADC. You seem uninterested in this example, is this due to a constraint on your application that I am unaware of?

    I think I will refer to it again. I had hoped it was possible to achieve the ADC conversion in a less CPU intensive manor. So i tried to chase a solution that could make use of DMA transfer and just give an interrupt when completed. This is the behavior of the system i am porting our application from.

    Please link me the API documentation you are referring to, and tell me what parts did not work as expected.
    nrfx_saadc_buffer_convert()
    Quote: "This function is non-blocking. The application is notified about filling the buffer by the event handler. Conversion will be done on all enabled channels. If the SAADC is in idle state, the function will set up EasyDMA for the conversion. The SAADC will be ready for sampling and wait for the SAMPLE task. It can be triggered manually by the nrfx_saadc_sample function or by PPI using the NRF_SAADC_TASK_SAMPLE task "
    and this:
    nrfx_saadc_sample()
    Quote: "Function for starting the SAADC sampling."
    It is not stated in the above that i must call nrfx_saadc_sample() for each sample i want. This is further confused by the fact that i thought this function would behave differently than nrfx_saadc_sample_convert() which only does 1 sample.
    quote: "Blocking function for executing a single SAADC conversion. "
    So my advise is that you expand the API description to mention, that only 1 sample is done.
  • Hi,

    BardenDK said:
    I think I will refer to it again. I had hoped it was possible to achieve the ADC conversion in a less CPU intensive manor. So i tried to chase a solution that could make use of DMA transfer and just give an interrupt when completed. This is the behavior of the system i am porting our application from.

    Yes, please refer to it again and let me know if you found it useful for you implementation. As mentioned, the SAADC example pretty much demonstrates exactly the functionality you are describing - it sets up a timed interval for sampling, and outputs the data on the DONE event.
    If you are afraid of missing samples during the DONE event handling, you could make use of the double-buffer feature(which is also demonstrated in the SAADC example).
    In that case, the second buffer will start filling immediately upon DONE event for the first buffer.

    In any case, the buffer_convert function makes use of the EasyDMA functionality. That is to say, if you in each callback from the DONE event call the saadc_buffer_convert again, with a new/incremented buffer, that buffer will be filled with the following conversions done by saadc_sample(). Is this perhaps the functionality you were looking for? 

    BardenDK said:
    It is not stated in the above that i must call nrfx_saadc_sample() for each sample i want. This is further confused by the fact that i thought this function would behave differently than nrfx_saadc_sample_convert() which only does 1 sample.

    You do not have to call nrfx_saadc_sample in your main() for each sample, if the SAADC is connected to trigger on a certain interval or event, such as a timer, or any other interrupt. Please see the SAADC examples function saadc_sampling_event_init(), it demonstrates how to set up a timer to trigger at a given frequency, enable PPI,  and finally: how to connect the saadc sampling to the created timer using PPI. It sounds to me like this is exactly this functionality you are missing.

    BardenDK said:
    So my advise is that you expand the API description to mention, that only 1 sample is done.

    I can understand why you would think that. The nrfx_saadc_buffer_convert actually does not do any conversions, but rather designates the location to which the nrfx_saadc_sample will output. I see how the naming and accompanying API reference could be interpreted ambiguously here, my apologies.

    nrfx_saadc_sample_convert however does behave quite differently than the nrfx_saadc_sample. As written in the API reference, they both perform a single sampling, but the difference is that nrfx_saadc_sample is non-blockingwhile nrfx_saadc_sample_convert is blocking.

    In short: nrfx_saadc_sample triggers a single non-blocking sampling, which is placed in the buffer specified by nrfx_saacd_buffer_convert.
    nrfx_saadc_sample_convert on the other hand is blocking, for one sample, to be placed in the location specified in its second function parameter.

    I hope this could provide some clarity, and I am looking forward to hearing how you progress with your application! :) 

    Best regards,
    Karl

Related