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.

Parents
  • 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

  • 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.

  • 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.

Reply
  • 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.

Children
  • ftjandra said:
    I am now successfully triggering the sample via PPI.

    I am glad to hear that, well done!

    ftjandra said:
    lets say every 1000 samples?

    Is there a particular reason for why you would like a calibration to be done every 1000 samples? Is the temperature changing during this time?

    ftjandra said:
    What is now the correct process to trigger a calibration

    Which nRF52840 SoC revision are you working with?
    As I mentioned before, calibration should be done when you enable the SAADC and when the temperature has changed significantly.
    You will also need to make sure that CTASKS_ALIBRATEOFFSET is never called between TASKS_START and EVENT_END, due to Errata 237.
    So, to calibrate every 1000 sample you will need to make a counter that keeps track of the sampled number, and when it reaches 1000 samples the START task PPI timer is stopped, and when the END event is generated calibration can start.

    Best regards,
    Karl

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

    I did not see this question when writing my last post, sorry.
    The timer handler can be empty since the CC event is directly tied to the SAADC START task through PPI, which is all it is meant to do.

    Best regards,
    Karl

  • My nRF52840 is rev2. Eventually I will trigger calibration on temperature change, but for testing it's easier to just do it every xx samples.

    I modified the saadc_event_handler() as follows:

    static void saadc_event_handler(nrf_drv_saadc_evt_t const * p_event) {
        static uint16_t saadc_event_counter = 1;
    
        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) {
                saadc_event_counter = 0;    //reset
                nrf_drv_timer_disable(&m_saadc_timer);
                nrf_drv_saadc_abort();
                while(nrf_drv_saadc_calibrate_offset() != NRF_SUCCESS); //trigger calibration task
            }
            else {
                //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_current[0], saadc_value_current[1], saadc_value_current[2], saadc_value_current[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));
    
            nrf_drv_timer_enable(&m_saadc_timer);
        }
    }

    Do I need to disable the timer, ppi channel, both? Seems like disabling the timer should be enough. I also moved enabling the timer to the NRF_DRV_SAADC_EVT_CALIBRATEDONE event instead of in saadc_init(). So the timer gets started after the initial calibration and after any re-calibration.

    But with the above code, it gets stuck in the while(nrf_drv_saadc_calibrate_offset() != NRF_SUCCESS);

    Do I need the nrf_drv_saadc_abort()?

    I know you mentioned that I need to wait for the END event before calibrating, how do I do that?

    Edit: After trying the above, I followed this thread and stopped and restarted everything so it would start from the very beginning with the initial calibration, but the buffer was out of order again!

    https://devzone.nordicsemi.com/f/nordic-q-a/32982/can-t-stop-saadc/127979#127979

    static void saadc_event_handler(nrf_drv_saadc_evt_t const * p_event) {
        static uint16_t saadc_event_counter = 1;
    
        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) {
                saadc_event_counter = 0;    //reset
                nrf_drv_timer_disable(&m_saadc_timer);
                nrf_drv_timer_uninit(&m_saadc_timer);
                nrf_drv_ppi_channel_disable(m_saadc_ppi_channel);
                nrf_drv_ppi_uninit();
                nrf_drv_saadc_abort();
                nrf_drv_saadc_uninit();
                while(nrf_drv_saadc_is_busy());
                saadc_init();
                saadc_sampling_event_init();
                saadc_sampling_event_enable();
            }
            else {
                //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_current[0], saadc_value_current[1], saadc_value_current[2], saadc_value_current[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));
    
            nrf_drv_timer_enable(&m_saadc_timer);
        }
    }

    Thanks.

  • Hello again,

    ftjandra said:
    My nRF52840 is rev2.

    Great, thank you for confirming this. There is only 2 SAADC erratas for the rev 2, one of which is not the issue in question here, and the other one which we have already spoken about.

    ftjandra said:
    Eventually I will trigger calibration on temperature change, but for testing it's easier to just do it every xx samples.

    I understand, this is all right during the testing and development phase.

    ftjandra said:
    Do I need to disable the timer, ppi channel, both? Seems like disabling the timer should be enough.

    This is correct, by disabling the task trigger you have disabled sampling.
    However, it seems you might be using the _abort incorrectly - please see the nrfx_saadc_abort documentation, it reads:

    Note
            NRFX_SAADC_EVT_DONE event will be generated if there is a conversion in progress. Event will contain number of words in the sample buffer.
    Warning
            This function must not be called from the context of event handler of the SAADC driver or from the context of interrupt with priority equal to or higher than priority of the SAADC interrupt.

    You can not call _abort from the event handler of the SAADC, or from an interrupt with priority equal to or higher than the SAADC interrupt.
    So, for instance, if you call it from main ( or anywhere else with lower priority ) then it will work.

    ftjandra said:
    Do I need the nrf_drv_saadc_abort()?

    Yes, but it can not be used as you are using it now.

    ftjandra said:
    I know you mentioned that I need to wait for the END event before calibrating, how do I do that?

    My apologies, when using the NRFX driver it is the DONE event you will have to wait for.
    The END event is from the HAL, which you should not use directly together with the NRFX driver, since this may place the driver in an invalid state.

    ftjandra said:
    After trying the above, I followed this thread and stopped and restarted everything so it would start from the very beginning with the initial calibration, but the buffer was out of order again!

    As I said, you can not call abort from the SAADC event handler context.
    Please resolve this, and let me know if this resolves the issue of getting stuck in the calibrate loop for earlier.

    Looking forward to resolving this issue together!

    Best regards,
    Karl 

  • Ok, still no luck.

    I am now calling nrf_drv_timer_disable(&m_saadc_timer) and setting a calibrate flag from within the SAADC event handler.

    Then in main() when the calibrate flag is set, I call nrf_drv_saadc_abort().

    Then 2 seconds later also from main I re-initiate the calibration with while(nrf_drv_saadc_calibrate_offset() != NRF_SUCCESS). I picked two seconds to account for any pending NRFX_SAADC_EVT_DONE events.

    After the calibration everything in the buffer is still shifted by spot.

Related