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

SAADC scan + burst?

On SDK16.0.0 + Mesh SDK 4.1.0

I know that oversampling when using the SADDC in scan mode on multiple channels does not work because the buffer gets all out of order.

But I believe burst mode can be used in scan mode where it samples the channel multiple times as fast as possible, averages it, and then puts the result in the buffer in the right order. Burst mode uses the oversample setting for setting how many readings to average. Is this all correct?

Does this work in SDK16.0.0 by simply setting nrf_drv_saadc_config_t .oversample value and nrf_saadc_channel_config_t .burst value? I did this and everything seems to be working, but I don't know if it's actually doing it. I initially tried to use nrf_saadc_burst_set(channel, NRF_SAADC_BURST_ENABLED) to enable burst for each channel, but that did not work and the readings were all wrong.

Or do some modifications need to be made like in this thread? https://devzone.nordicsemi.com/f/nordic-q-a/26659/saacd-scan-oversample/

Thanks.

  • I think all the relevant code should be in my first post (plus the modification to saadc_init().

    1. saadc_init(). Does initial calibration.

    2. On NRF_DRV_SAADC_EVT_CALIBRATEDONE set up double-buffer.

    3. App timer: Every second call nrf_drv_saadc_sample() if calibrate flag is not set. If calibrate flag is set call nrf_drv_saadc_calibrate_offset()

    4. On NRF_DRV_SAADC_EVT_DONE, copy out readings and either:

        set up next buffer, nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_SAMPLES_IN_BUFFER)

        - or -

        every 4096 samples do a calibration by nrf_drv_saadc_abort() and setting calibrate flag.

    Thanks.

  • Hello again,

    Thank you for providing the additional information.

    ftjandra said:
    I think all the relevant code should be in my first post

    Reading the code and hearing your intentions are both useful.

    I see that you are using an app_timer to call the nrf_drv_saadc_sample function.
    The nrf_drv_saadc_sample is a call that is handled by the CPU. I suspect that a higher priority task could interrupt the SAADC sample handling at an inopportune time, potentially causing the shift in the buffered samples.
    Is there a particular reason for why you are having the CPU start each sampling, or could you instead set up the timer to trigger the sample task through PPI?
    This would greatly reduce the CPU time necessary to perform each sample.

    Additionally, what is your NRFX_SAADC_CONFIG_IRQ_PRIORITY defined as?

    Best regards,
    Karl

  • NRFX_SAADC_CONFIG_IRQ_PRIORITY is set to 6 in sdk_config.h

    I could set it up through PPI if you show me how. I haven't used PPI.

    So far it has only messed up immediately after it calibrates. If I never re-calibrate after the initial calibration it has been fine (24 hours). But, if there is a risk from using the app_timer, I want to fix that.

    Thanks.

  • Hello,

    ftjandra said:
    NRFX_SAADC_CONFIG_IRQ_PRIORITY is set to 6 in sdk_config.h

    With your current solution - using CPU to trigger sampling - I would suggest increasing the priority of the SAADC, to see if this may prevent it from being interrupted by some other peripheral.

    ftjandra said:
    I could set it up through PPI if you show me how. I haven't used PPI.

    I would highly recommend making use of the PPI peripheral. The PPI and easyDMA is two very powerful features that drastically reduce the necessary CPU intervention to perform sampling.
    In essence, what the PPI does is create a hardware connection between two peripherals. In your case, that would be to connect the timer interrupt directly to the SAADC sample task, so that there is no need for the CPU to do anything between processing of the buffers.

    A clean example of how you may use the PPI to connect a timer to your SAADC is demonstrated in the SAADC peripheral example. Please have a look at this example, and let me know if anything should be unclear, or if you have any questions!

    ftjandra said:
    So far it has only messed up immediately after it calibrates. If I never re-calibrate after the initial calibration it has been fine (24 hours). But, if there is a risk from using the app_timer, I want to fix that.

    Using the app_timer is fine. You might switch to using the app_timer v2 if you are not already, but none of them should be causing the SAADC buffer troubles, they are very well behaved.

    Looking forward to resolving this issue together!

    Best regards,
    Karl

  • Ok, I am now successfully triggering the sample via PPI. All my SAADC code is below. What is now the correct process to trigger a calibration, lets say every 1000 samples?

    static void saadc_event_handler(nrf_drv_saadc_evt_t const * p_event) {
        if(p_event->type == NRF_DRV_SAADC_EVT_DONE) {
            //Read buffer
            for(int i=0; i<SAADC_SAMPLES_IN_BUFFER; i++) {
                saadc_value_current[i] = p_event->data.done.p_buffer[i];
            }
            
            //Set buffer, double-buffering
            ERROR_CHECK(nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_SAMPLES_IN_BUFFER));
    
            //Print samples
            __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "\nsaadc_value_current[0]: %u\nsaadc_value_current[1]: %u\nsaadc_value_current[2] %u\nsaadc_value_current[3]: %u\n", saadc_value[0], saadc_value[1], saadc_value[2], saadc_value[3]);
        }
        else if(p_event->type == NRF_DRV_SAADC_EVT_CALIBRATEDONE) {
            __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "SAADC calibration complete\n");
    
            //Need to setup both buffers, as they were both removed with the call to nrf_drv_saadc_abort before calibration
            ERROR_CHECK(nrf_drv_saadc_buffer_convert(saadc_buffer_pool[0], SAADC_SAMPLES_IN_BUFFER));
            ERROR_CHECK(nrf_drv_saadc_buffer_convert(saadc_buffer_pool[1], SAADC_SAMPLES_IN_BUFFER));
        }
    }
    
    static void saadc_timer_handler(nrf_timer_event_t event_type, void * p_context) {
        __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "saadc_timer_handler\n");
    }
    
    static void saadc_init(void) {
        nrf_drv_saadc_config_t saadc_config = NRF_DRV_SAADC_DEFAULT_CONFIG;
        saadc_config.resolution = NRF_SAADC_RESOLUTION_12BIT;
        saadc_config.oversample = (nrf_saadc_oversample_t)NRF_SAADC_OVERSAMPLE_256X;
        saadc_config.interrupt_priority = NRFX_SAADC_CONFIG_IRQ_PRIORITY;
        saadc_config.low_power_mode = NRFX_SAADC_CONFIG_LP_MODE;
    
        nrf_saadc_channel_config_t channel_0_config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);
        channel_0_config.gain = NRF_SAADC_GAIN1_6;
        channel_0_config.reference = NRF_SAADC_REFERENCE_INTERNAL;
        channel_0_config.burst = NRF_SAADC_BURST_ENABLED;
    
        nrf_saadc_channel_config_t channel_1_config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN1);
        channel_1_config.gain = NRF_SAADC_GAIN1_6;
        channel_1_config.reference = NRF_SAADC_REFERENCE_INTERNAL;
        channel_1_config.burst = NRF_SAADC_BURST_ENABLED;
    
        nrf_saadc_channel_config_t channel_2_config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN5);
        channel_2_config.gain = NRF_SAADC_GAIN1_6;
        channel_2_config.reference = NRF_SAADC_REFERENCE_INTERNAL;
        channel_2_config.burst = NRF_SAADC_BURST_ENABLED;
    
        nrf_saadc_channel_config_t channel_3_config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN4);
        channel_3_config.gain = NRF_SAADC_GAIN1_6;
        channel_3_config.reference = NRF_SAADC_REFERENCE_INTERNAL;
        channel_3_config.burst = NRF_SAADC_BURST_ENABLED;
    
        ERROR_CHECK(nrf_drv_saadc_init(&saadc_config, saadc_event_handler));
    
        ERROR_CHECK(nrf_drv_saadc_channel_init(0, &channel_0_config));
        ERROR_CHECK(nrf_drv_saadc_channel_init(1, &channel_1_config));
        ERROR_CHECK(nrf_drv_saadc_channel_init(2, &channel_2_config));
        ERROR_CHECK(nrf_drv_saadc_channel_init(3, &channel_3_config));
    
        //https://devzone.nordicsemi.com/f/nordic-q-a/63074/buffer-order-swap-of-saadc-used-with-nrf-mesh
        nrf_ppi_channel_t saadc_buffer_swap_ppi_channel;
        ERROR_CHECK(nrf_drv_ppi_init());
        ERROR_CHECK(nrf_drv_ppi_channel_alloc(&saadc_buffer_swap_ppi_channel));
        ERROR_CHECK(nrf_drv_ppi_channel_assign(saadc_buffer_swap_ppi_channel,
                                               (uint32_t)&NRF_SAADC->EVENTS_END,
                                               (uint32_t)&NRF_SAADC->TASKS_START));
        ERROR_CHECK(nrf_drv_ppi_channel_enable(saadc_buffer_swap_ppi_channel));
    
        __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "Starting initial SAADC calibration\n");
        while(nrf_drv_saadc_calibrate_offset() != NRF_SUCCESS); //trigger calibration task
        saadc_calibrate = false;
    }
    
    static void saadc_sampling_event_init(void) {
        //ERROR_CHECK(nrf_drv_ppi_init());
    
        nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
        timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
        ERROR_CHECK(nrf_drv_timer_init(&m_saadc_timer, &timer_cfg, saadc_timer_handler));
    
        // setup m_saadc_timer for compare event every 1000ms
        uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_saadc_timer, 1000);
        nrf_drv_timer_extended_compare(&m_saadc_timer,
                                       NRF_TIMER_CC_CHANNEL0,
                                       ticks,
                                       NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                       false);
        nrf_drv_timer_enable(&m_saadc_timer);
    
        uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_saadc_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
        ERROR_CHECK(nrf_drv_ppi_channel_alloc(&m_saadc_ppi_channel));
        ERROR_CHECK(nrf_drv_ppi_channel_assign(m_saadc_ppi_channel,
                                               timer_compare_event_addr,
                                               saadc_sample_task_addr));
    }
    
    static void saadc_sampling_event_enable(void) {
        ERROR_CHECK(nrf_drv_ppi_channel_enable(m_saadc_ppi_channel));
    }

    Also, when does saadc_timer_handler() get called. In the example it was empty and I see that it never gets called.

    Thanks.

Related