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

Multiple SAADC Samples

I'm having issue where after booting I can immediately do three SAADC samples as follows:

    for (uint8_t i = 0; i < SAMPLES_IN_BUFFER; i++)
    {
	nrfx_saadc_sample();
	nrf_delay_ms(SAADC_SAMPLE_DELAY);
    }

This works as expected where I get the callback immediately after the third call to nrfx_saadc_sample(). I needed to put in a 1ms delay, otherwise the first sample is 0; However, when I do the same thing in a timer handler, I cannot do three samples in a row. The timer needs to timeout three times in order to get the three samples. I doesn't matter how long the delay time is (I've put in up to 1 second), the only way to get three samples is for the handler to trigger three times. I'm using an app_timer. Here is the handler code:

static void advertise_timeout_handler(void * p_context)
{
    for (uint8_t i = 0; i < SAMPLES_IN_BUFFER; i++)
    {
	nrfx_saadc_sample();
	nrf_delay_ms(SAADC_SAMPLE_DELAY);
    }
}

Here is the SAADC initialization code:

static void saadc_init(void)
{
    ret_code_t              err_code;
    nrf_drv_saadc_config_t  saadc_config;

    saadc_config.low_power_mode = NRFX_SAADC_CONFIG_LP_MODE;
    saadc_config.resolution = NRF_SAADC_RESOLUTION_10BIT;	//Set SAADC resolution to 10-bit. This will make the SAADC output values from 0 (when input voltage is 0V) to 2^10=1024 (when input voltage is 3.6V for channel gain setting of 1/6).
    saadc_config.oversample = NRF_SAADC_OVERSAMPLE_DISABLED;    //Disable oversample. Can't oversample with more than one channel active.
    saadc_config.interrupt_priority = NRFX_SAADC_CONFIG_IRQ_PRIORITY;
    nrf_saadc_channel_config_t channel_config =
    NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_VDD);

    err_code = nrf_drv_saadc_init(NULL, saadc_callback);
    APP_ERROR_CHECK(err_code);

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

    err_code = nrfx_saadc_buffer_convert(m_buffer_pool, SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);
}

Here is the callback code:

static void saadc_callback(nrf_drv_saadc_evt_t const *p_event)
{
    static uint32_t m_adc_evt_counter;
    if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
    {
	ret_code_t err_code;
	uint32_t voltage;
	uint16_t average_sample = 0;
	err_code = nrfx_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLES_IN_BUFFER);
	APP_ERROR_CHECK(err_code);

	SEGGER_RTT_printf(0, "\nADC event number: %d\n", m_adc_evt_counter);

	for (uint8_t i = 0; i < SAMPLES_IN_BUFFER; i++)
	{
	    voltage = p_event->data.done.p_buffer[i] * 3600 / 1024;

	    SEGGER_RTT_printf(0, "%d\n", voltage);
	    average_sample += voltage;
	}

	if((++m_adc_evt_counter % SAADC_CALIBRATION_INTERVAL) == 0)
	    m_saadc_calibrate = true;

	battery_voltage = average_sample / SAMPLES_IN_BUFFER;
	change_manuf_specific_data();
    }
}

Why does the timer handler have to trigger three times in order to get three samples when outside of the hander after booting, I can do three samples in row and get a valid result?

Parents
  • Hello,

    The timer needs to timeout three times in order to get the three samples. I doesn't matter how long the delay time is (I've put in up to 1 second), the only way to get three samples is for the handler to trigger three times. I'm using an app_timer. Here is the handler code:

    On a general note, I can not recommend that you add delay's to any interrupt handling. Interrupts should complete quickly, as to let lower-priority processes have a chance at completing.
    It might function for now, in this particular project, but as the project gets bigger it is probable that it will cause some issue.

    Please make sure to add a APP_ERROR_CHECK to the error code returned by nrfx_saadc_sample - if you do not check the returned error codes you will not have any way of knowing whether the function call was successful or not.

    Furthermore I notice that the timeout function is called advertising_* - is this process in any way related to BLE? Please know that the SoftDevice takes priority over any application-layer task to do timing-critical operations, such as advertising at a specific interval or maintaining a connection. Since you are using the nrfx_saadc_sample function you will need the CPU to start this sampling, which might be busy with the SoftDevice.
    Instead, you might want to look into connecting the sampling task directly to your timer event by PPI. This way, it will not require CPU intervention to begin.

    Looking forward to resolving this issue together,

    Best regards,
    Karl

  • I can look at using the PPI channel again. This is a very low-power application, so I would need to use the app_timer.

    The timeout function handler named "advertising" initiates the beacon advertising. This actually done through the SAADC callback function by calling change_manuf_specific_data(). nrfx_saadc_sample() starts the process, the advertising is not active when this call is made.

  • fackbell said:
    I can look at using the PPI channel again. This is a very low-power application, so I would need to use the app_timer.

    Having your sampling trigger through PPI will likely reduce your applications power consumption, since it allows the CPU to spend its time completing other tasks or going to SYSTEM ON sleep.

    fackbell said:
    The timeout function handler named "advertising" initiates the beacon advertising. This actually done through the SAADC callback function by calling change_manuf_specific_data(). nrfx_saadc_sample() starts the process, the advertising is not active when this call is made.

    Thank you for clarifying.
    Did you add the APP_ERROR_CHECK to your sample call, to see if the samplings were actually successful in your previous test?

    Best regards,
    Karl

Reply
  • fackbell said:
    I can look at using the PPI channel again. This is a very low-power application, so I would need to use the app_timer.

    Having your sampling trigger through PPI will likely reduce your applications power consumption, since it allows the CPU to spend its time completing other tasks or going to SYSTEM ON sleep.

    fackbell said:
    The timeout function handler named "advertising" initiates the beacon advertising. This actually done through the SAADC callback function by calling change_manuf_specific_data(). nrfx_saadc_sample() starts the process, the advertising is not active when this call is made.

    Thank you for clarifying.
    Did you add the APP_ERROR_CHECK to your sample call, to see if the samplings were actually successful in your previous test?

    Best regards,
    Karl

Children
  • Yes, I added APP_ERROR_CHECK to the call. There is no error.

    It seems as if the PPI channel needs a timer compare channel. How do I do this with the app_timer?

  • fackbell said:
    Yes, I added APP_ERROR_CHECK to the call. There is no error.

    So, you are receiving 3 NRF_SUCCESS's returned from the _sample triggering function, but you are not receiving the SAADC DONE event? What is the contents of your SAADC buffer after the third NRF_SUCCESS is returned from the sample trigger function?

    fackbell said:
    It seems as if the PPI channel needs a timer compare channel. How do I do this with the app_timer?

    The PPI channel will connect a hardware event directly to the sample TASK. The app_timer is a software implementation built on top of the RTC peripheral, and is therefore not the best match for using with PPI since you will get COMPARE events from all of the app_timer's channels.
    Instead, you could set up a separate RTC or TIMER instance, to provide these periodic interrupts for the SAADC.
    Setting up the SAADC to trigger from a TIMER's interrupts through PPI is demonstrated in the SAADC peripheral example in the SDK, I highly recommend taking a look at it to see how you may go about doing this in your own application.

    Best regards,
    Karl

  • Yes, I'm receiving three NRF_SUCCESS's each time the handler is executed, but the callback isn't triggered until the handler is executed three times. It's as if it the _sample triggering is only executed once and it ignores the other two. When the callback is finally triggered, I get three valid readings that are slightly different from each other as expected. Again, outside of the handler immediately after boot I do three successive _sample triggers and I get a callback right away, but the same exact method doesn't work in the handler.

    Yes, I've tried the SAADC peripheral example previously, but abandoned it as that timer uses much too much current. That's why I'm using the app_timer for all of my timers. 

  • I now have it working by pulling the _sample triggering out of the timer handler and just setting a flag within the handler. The successive _sample triggers are then executed in the main loop based on the flag. It seems as if the timer handler doesn't like multiple _sample triggers within the handler.

  • Hello,

    fackbell said:
    I now have it working by pulling the _sample triggering out of the timer handler and just setting a flag within the handler. The successive _sample triggers are then executed in the main loop based on the flag. It seems as if the timer handler doesn't like multiple _sample triggers within the handler.

    I am happy to hear that it is working now, but this behavior seems very strange to me still. If you are receiving NRF_SUCCESS from the _sample calls then the buffer should also be filled with 1 sample per success, and immediately after getting 3 you should get the DONE event.
    Do you have a stripped down version of the project, which showcases this particular behavior?
    If so, then I could take a look at it here on my end, and see if I cant figure out why it is behaving like this.

    fackbell said:
    Yes, I've tried the SAADC peripheral example previously, but abandoned it as that timer uses much too much current. That's why I'm using the app_timer for all of my timers. 

    I understand. Are you using the app_timer for any other tasks than the sampling? If not, you could tie it up to the SAADC as mentioned using PPI - since all Capture/compare events will be valid for the SAADC to begin sampling.

    Best regards,
    Karl 

Related