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 spoke too soon. I let it run for a day and the readings are now out-of-sync in the buffer.

    Edit #1: I think it gets all messed up after calling nrf_drv_saadc_calibrate_offset() which I do periodically. I think I remember reading about this issue, but I can't find it now.

    Edit #2: Right now on power-up I am initializing the saadc and doing an initial calibration:

    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_burst_set(0, 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_burst_set(1, 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_burst_set(2, 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;
        //nrf_saadc_burst_set(3, 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));
    
        __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;
    }

    Then when I get the NRF_DRV_SAADC_EVT_CALIBRATEDONE event in the event handler:

    static void saadc_event_handler(nrf_drv_saadc_evt_t const * p_event) {
        static uint16_t saadc_event_counter = 1;  //used for recalibrating adc, start at 1 otherwise will calibrate right away
    
        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];
            }
    
            if((saadc_event_counter % SAADC_CALIBRATION_INTERVAL) == 0) {
                nrf_drv_saadc_abort();      //abort all ongoing conversions because calibration cannot be run if SAADC is busy
                saadc_calibrate = true;     //set flag to trigger calibration in main context when SAADC is stopped
                saadc_event_counter = 0;    //reset
            }
    
            if(saadc_calibrate == false) {
                //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]);
            }
            
            saadc_event_counter++;
        }
        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));
        }
    }

    Then every second via an app_timer:

        //SAADC
        if(saadc_calibrate) {
            __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "Starting interval SAADC calibration\n");
            while(nrf_drv_saadc_calibrate_offset() != NRF_SUCCESS); //trigger calibration task
            saadc_calibrate = false;
    
            /*
            //PAN-212 workaround: https://devzone.nordicsemi.com/f/nordic-q-a/45339/saadc-burst-problems-in-scan-vs-non-scan-acquisitions/
            //Above three lines commented out
            __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "Power-cycling and re-initalizing SAADC for calibration\n");
            nrf_drv_saadc_uninit();
            *(volatile uint32_t *)0x40007FFC = 0;
            *(volatile uint32_t *)0x40007FFC = 1;
            saadc_init();
            saadc_timeout_counter = 0; //reset
            */
        }
        else if((saadc_timeout_counter % SAADC_SAMPLE_INTERVAL) == 0) {
            ERROR_CHECK(nrf_drv_saadc_sample());
            saadc_timeout_counter = 0; //reset
        }
        
        saadc_timeout_counter++; 

    Everything seems to be fine until it calibrates, after that everything is out of whack.

    I found this thread: https://devzone.nordicsemi.com/f/nordic-q-a/45339/saadc-burst-problems-in-scan-vs-non-scan-acquisitions

    I tried that, but it didn't work either. You can see what I tried in the code above (commented out).

    Thanks.

  • Hello,

    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.

    This is correct - using scan mode with burst ( with oversampling ) will make the SAADC sample a single channel 2^OVERSAMPLING times as fast as it can, average it, and place it in RAM. This will happen for each channel.

    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?

    If you are not using nrfx v.2 - which SDK 16 does not - then you will have to modify an assert of the driver to allow this.
    Particularly line 294 in nrfx_saadc.c, which asserts if oversampling is enabled with multiple channels.

    NRFX_ASSERT((nrf_saadc_oversample_get() == NRF_SAADC_OVERSAMPLE_DISABLED) ||
                    (m_cb.active_channels == 0));



    You must also implement the changes that Jørgen detailed in the answer to the ticket you linked, for best performance.

    Best regards,
    Karl

  • Hello again,

    ftjandra said:
    I let it run for a day and the readings are now out-of-sync in the buffer.

    The out-of-order part is a separate problem from the burst + oversampling configuration for SAADC in scan mode.
    Could you implement the solution provided by Jørgen in this ticket? Please let me know if this does not resolve your samples-out-of-order issue.

    ftjandra said:
    Right now on power-up I am initializing the saadc and doing an initial calibration:

    This is good practice. You should also perform a calibration if the temperature has changed significantly since last calibration, at least for every 10 C change.

    Best regards,
    Karl

  • Ok, so I added Jørgen's changes to the end of saadc_init():

    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_burst_set(0, 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_burst_set(1, 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_burst_set(2, 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;
        //nrf_saadc_burst_set(3, 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;
        nrf_drv_ppi_channel_alloc(&saadc_buffer_swap_ppi_channel);
        nrf_drv_ppi_channel_assign(saadc_buffer_swap_ppi_channel,
                                   (uint32_t)&NRF_SAADC->EVENTS_END,
                                   (uint32_t)&NRF_SAADC->TASKS_START);
        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;
    }

    I commented out the NRF_SAADC_TASK_START on line 128 of nrfx_saadc.c

    Line 294 or nrfx_saadc.c that you mentioned doesn't seem to affect me, it's not asserting with that line as-is.

    It's fine after the initial calibration that I do, but after I trigger another one, the buffer is still out of sync.

    Before these modifications, I ran it for a whole day without having it do any periodic calibration and everything was fine. Are you saying that even if I don't do periodic calibrations that the buffer will eventually mess up?

    Thanks.

  • Hello,

    ftjandra said:
    Line 294 or nrfx_saadc.c that you mentioned doesn't seem to affect me, it's not asserting with that line as-is.

    Strange, I would expect it to assert when you combine scan mode with oversampling.

    ftjandra said:
    It's fine after the initial calibration that I do, but after I trigger another one, the buffer is still out of sync.

    I see. Could you elaborate on how and when you perform calibrations?
    If you could share the code snippets of the calibration that would be beneficial as well.

    ftjandra said:
    Are you saying that even if I don't do periodic calibrations that the buffer will eventually mess up?

    No, absolutely not. The buffer-swap issue was an older issue, which is resolved by the modification you added to your saadc_init - the buffer swaps you are seeing now is therefore likely caused by something else, perhaps an incorrectly aborted conversion or similar. Lets look further into how you perform the calibrations, and what else is going on in the application. 

    Looking forward to resolving this issue together!

    Best regards,
    Karl

Related