Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs

SAADC samples missing

Hi,

I'm implementing a hard real-time system (few us jitter) on nRF52840, SDK17.1, based on FreeRTOS project.

In the project, I need to sync GPIOs toggling with ADC sampling. There is heavy EGU-Timers-PPI-GPIOTE/SAADC work done (see attached picture)

However, I can't get the SAADC work reliably, and I get samples missed.

The setup is as follows:

I need to sample 4 adc channels (AIN4-7) at 4 specific times t1-t4 (200-250us apart)

For that I use PPI channel and tie T3_COMPARE1 event to SAADC_SAMPLE task.

I set up T3CCR1 = t1(=200us) and enable T3 interrupts

As T3 needs to run in sync with T4 (that toggles 5 GPIOTEs), both are started using another PPI channel that ties EGU1.1->T4_START and T3_START (as fork)

When T3CC1 gets to t1=200us, SAADC_SAMPLE is triggered (by the PPI) and the saadc samples 4 channels in SCAN mode

Concurrently T3 interrupt is triggered in which I set T3CC1 = t2 (& t3 & t4=850us on subsequent interrupts)

The same trick is used on T4 to generate a sequence of signals (T4CC0 used to generate interrupts).

The last T4 interrupt is triggered at t4+100us=950us, at this point I should get 4*4=16 adc samples in the buffer

(side note - converting AIn4-7 consume 35us, far less than 100us span from tast T3CC1 interrupt to T4CC0 interrupt)

For debug I use 2 counters - SamplesCounter incremented in saadc_hadler() & SequncesCounter incremented at the last interrupt of T4 (t=950us)

On each 'burst's last interrupt (t=950us) I check that these counters are identical.

Now, occasionally I find that these counters do not match and the SamplesCounter was not incremented.

Looking at SAADC.AMOUNT register shows 0 or another small number of samples actually taken (e.g. 2 samples) instead of the expected 0x10 (see image)

To debug I replaced the T3_COMPARE1 --> SAADC_SAMPLE ppi trigger with nrfx_saadc_sample() call in T3CC1 interrupt and count these invocation as well.

This way I can count the actual SAMPLE tasks triggered, which should be 4*SamplesCounter but this verification also fails.

Looked though the erratas as well. I noticed Errata 212, not sure if its relevant but I've extended acquisition times to 10us or more on all 4 channels (and pushed 200us forward the 950us interrupt just to give some room for the last conversion). Still not helping.

I know its complicated description, but it's a complicated situation ;)

Thanks for reading this far.

Any advice is appreciated as I'm scratching the bottom of the ideas bucket...

Thanks

Parents Reply Children
  • I tried 3us on all channels and tried to extend 950-->1150us.

    Problem still occurs

  • It is hard to analise without code

    So look at this reference.

    Don`t forget to set SAADC and TIMER proirity to 0

    /**
     * Copyright (c) 2014 - 2017, Nordic Semiconductor ASA
     * 
     * All rights reserved.
     * 
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     * 
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     * 
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     * 
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     * 
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     * 
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     * 
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     * 
     */
    
    #include <stdbool.h>
    #include <stdint.h>
    #include <nrfx_saadc.h>
    #include "nrfx_timer.h"
    #include "nrfx_ppi.h"
    #include "nrf_delay.h"
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #include "nrf_uart.h"
    #include "app_uart.h"
    
    
    #define ADC_CHANNELS_IN_USE     6   // Note: If changed, the logging during the NRFX_SAADC_EVT_DONE must be updated.
    #define SAADC_BUF_SIZE          ADC_CHANNELS_IN_USE
    #define SAADC_BUF_COUNT         2000
    #define SAADC_SAMPLE_FREQUENCY  10000
    
    static nrf_saadc_value_t samples1[SAADC_BUF_COUNT][SAADC_BUF_SIZE];
    static nrf_saadc_value_t samples2[SAADC_BUF_COUNT][SAADC_BUF_SIZE];
    static const nrfx_timer_t m_sample_timer = NRFX_TIMER_INSTANCE(1);
    static nrf_ppi_channel_t m_timer_saadc_ppi_channel;
    static nrf_ppi_channel_t m_saadc_internal_ppi_channel;
    static const uint32_t saadc_sampling_rate = 1000; // milliseconds (ms)
    static uint32_t buffer_index;
    static uint32_t buf_select = 0;
    static const nrf_saadc_input_t ANALOG_INPUT_MAP[NRF_SAADC_CHANNEL_COUNT] = {
        NRF_SAADC_INPUT_AIN0, NRF_SAADC_INPUT_AIN1, NRF_SAADC_INPUT_AIN2, NRF_SAADC_INPUT_AIN3,
        NRF_SAADC_INPUT_AIN4, NRF_SAADC_INPUT_AIN5, NRF_SAADC_INPUT_AIN6, NRF_SAADC_INPUT_AIN7};
    
    
    // Simple function to provide an index to the next input buffer
    // Will simply alernate between 0 and 1 when SAADC_BUF_COUNT is 2
    static uint32_t next_free_buf_index(void)
    {
        //buffer_index = -1;
        buffer_index = (buffer_index + 1) % SAADC_BUF_COUNT;
        //NRF_LOG_INFO("buffer_index: %d",buffer_index);
        return buffer_index;
    }
     
    
    static void timer_handler(nrf_timer_event_t event_type, void * p_context)
    {
    }
    
    bool alarm = false;
    
    static void event_handler(nrfx_saadc_evt_t const * p_event)
    {
        ret_code_t err_code;
        switch (p_event->type)
        {
            case NRFX_SAADC_EVT_DONE:
            NRF_LOG_INFO("ALARM!!! %d");
                break;
    
            case NRFX_SAADC_EVT_BUF_REQ:
                // Set up the next available buffer
                if(buf_select == 0)
                {
                    err_code = nrfx_saadc_buffer_set(&samples1[next_free_buf_index()][0], SAADC_BUF_SIZE);
                    
                }
                else
                {
                    err_code = nrfx_saadc_buffer_set(&samples2[next_free_buf_index()][0], SAADC_BUF_SIZE);
    
                }
                APP_ERROR_CHECK(err_code);
                break;
                    default:
                NRF_LOG_INFO("SAADC evt %d", p_event->type);
                break;
        }
    }
    
    
    static void timer_init(void)
    {
        nrfx_err_t err_code;
    
        nrfx_timer_config_t timer_config = NRFX_TIMER_DEFAULT_CONFIG;
        timer_config.frequency = NRF_TIMER_FREQ_31250Hz;
        err_code = nrfx_timer_init(&m_sample_timer, &timer_config, timer_handler);
        APP_ERROR_CHECK(err_code);
        nrfx_timer_extended_compare(&m_sample_timer,
                                    NRF_TIMER_CC_CHANNEL0,
                                    nrfx_timer_ms_to_ticks(&m_sample_timer, saadc_sampling_rate),
                                    NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                    false);
    
        nrfx_timer_resume(&m_sample_timer);
    }
    
    
    
    static void ppi_init(void)
    {
        // Trigger task sample from timer
        nrfx_err_t err_code = nrfx_ppi_channel_alloc(&m_timer_saadc_ppi_channel);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_ppi_channel_assign(m_timer_saadc_ppi_channel, 
                                           nrfx_timer_event_address_get(&m_sample_timer, NRF_TIMER_EVENT_COMPARE0),
                                           nrf_saadc_task_address_get(NRF_SAADC_TASK_SAMPLE));
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_ppi_channel_enable(m_timer_saadc_ppi_channel);
        APP_ERROR_CHECK(err_code);
    }
    
    
    static void adc_configure(void)
    {
        ret_code_t err_code;
    
        nrfx_saadc_adv_config_t saadc_adv_config = NRFX_SAADC_DEFAULT_ADV_CONFIG;
        saadc_adv_config.internal_timer_cc = 0;
        saadc_adv_config.start_on_end = true;
    
        err_code = nrfx_saadc_init(NRFX_SAADC_CONFIG_IRQ_PRIORITY);
        APP_ERROR_CHECK(err_code);
    
        static nrfx_saadc_channel_t channel_configs[ADC_CHANNELS_IN_USE];
    
        uint8_t channel_mask = 0;
        for(int i = 0; i < ADC_CHANNELS_IN_USE; i++) {
            nrf_saadc_input_t pin = ANALOG_INPUT_MAP[i];
            // Apply default config to each channel
            nrfx_saadc_channel_t config = NRFX_SAADC_DEFAULT_CHANNEL_SE(pin, i);
    
            // Replace some parameters in default config
            config.channel_config.reference = NRF_SAADC_REFERENCE_INTERNAL;          
            config.channel_config.gain = NRF_SAADC_GAIN1;
            config.channel_config.acq_time   = NRF_SAADC_ACQTIME_40US,
    
            // Copy to list of channel configs
            memcpy(&channel_configs[i], &config, sizeof(config));
    
            // Update channel mask
            channel_mask |= 1 << i;
        }
    
        err_code = nrfx_saadc_channels_config(channel_configs, ADC_CHANNELS_IN_USE);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_saadc_advanced_mode_set(channel_mask,
                                                NRF_SAADC_RESOLUTION_14BIT,
                                                &saadc_adv_config,
                                                event_handler);
        APP_ERROR_CHECK(err_code);
                                                
        // Configure two buffers to ensure double buffering of samples, to avoid data loss when the sampling frequency is high
        err_code = nrfx_saadc_buffer_set(&samples1[next_free_buf_index()][0], SAADC_BUF_SIZE);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_saadc_buffer_set(&samples2[next_free_buf_index()][0], SAADC_BUF_SIZE);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_saadc_mode_trigger();
        APP_ERROR_CHECK(err_code);
    }
    
    void uart_event_handle(app_uart_evt_t * p_event)
    {}
    
    static void uart_init(void)
    {
        uint32_t                     err_code;
        app_uart_comm_params_t const comm_params =
        {
            .rx_pin_no    = 31,
            .tx_pin_no    = 32,
            .rts_pin_no   = 6,
            .cts_pin_no   = 8,
            .flow_control = APP_UART_FLOW_CONTROL_DISABLED,
            .use_parity   = false,
            .baud_rate    = NRF_UART_BAUDRATE_460800//NRF_UART_BAUDRATE_1000000
        };
    
        APP_UART_FIFO_INIT(&comm_params,
                           255,
                           255,
                           uart_event_handle,
                           APP_IRQ_PRIORITY_LOWEST,
                           err_code);
        APP_ERROR_CHECK(err_code);
    }
    
    int main(void)
    {
        ret_code_t err_code = 1;
    
        // Configure Logging. LOGGING is used to show the SAADC sampled result.
        err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);                       
        NRF_LOG_DEFAULT_BACKENDS_INIT();
        NRF_LOG_INFO("nrfx_saadc_api2 simple SAADC Continuous Sampling Example using timer and PPI.");	
        while(NRF_LOG_PROCESS() != NRF_SUCCESS);
        adc_configure();
        ppi_init();
        timer_init();
        uart_init();
    }
    

  • Hey ,

    In reality,  the sequence shown above is only part of the full EGU--Timers-PPI-Saadc sequences that are going on.

    In practice, AIN0-3 are being sampled @ 1KHz with PPI channel T2CC0 ->SAADC_SAMPLE

    The sampled data is fed into an algorithm that decides when to generate the burst mentioned above.

    When its time to generate the burst, saad is being uninitialized and reinitialized with AIN4-7 as input.

    Then the software requests a ~1200us timeslot from the SD, once its  granted T3 & T4 are triggered bu EGU and the aforementioned sequence begins.

    This sequence of sequences goes no and on.

    This is depicted in the attached image.

    UPDATE -

    I followed errata 212 workaround each time saadc_init() runs (before saadc_channel_init()) and it seems to overcome the problem above (at least at first glance). I will keep testing and will update if I find something new.

    @Nordic_engineers@ - I suspect that errata 212 appears in a broader set of conditions than described in the errata sheet. As described above, I experienced the problem when switching from one set of channels to another set of channels back and forth. All channels were 10us (for testing), and BURST was off.

  • Hi,

    Do you have a minimal code example without BURST enabled, and T_acq >= 10 μs that will trigger errata 212?

    Best regards,
    Jørgen

  • Hi

    The code is quite extensive, I'll try to summarize it -

    This is a freertos application with some hard real time requirement.

    There are two modules involved: Analog.c & Sequencer.c - Analog.c constantly sample Ain0-Ain3 @ 1KHz, 10us acq time, driven by T2 & PPI.. Each sample is send to freertos algo task queue.

    The algo runs in task context. Once it decides a GPIOTE sequence  is requested it signals the sequencer task.

    The sequencer task:

    1. uninits the current saadc (that was controlled in the analog module)
    2. requesters 1150us timeslot from SD

      Now sequencer task is blocked and the rest is happening in realtime (PPIs & ISR)

    3. A radio callback is fired with  NRF_RADIO_CALLBACK_SIGNAL_TYPE_START signal
    4. In the callback EGU1.1 is triggered. It was previously tied to T4 & T3 start tasks hence timers begin to run synchronously.
    5. T4CC0 generates interrupts, al other T4CCRs (T4CCR1-5, ) are tied to toggling GPIOTE and T3CC1 is tied to SAADC_START
    6. On the first 'state' interrupt @100us, I initialize the saadc (AIN4-7, 10usacq time)
    7. T4CC0 intr is used as a state machine arbiter. On each T4CC0 interrupt, the other CC registers are modified as needed (according to the current state) to generate the sequence.
    8. Note that SAADC_START is triggered 4 times in the sequence (@200,400,600,850us) total of 16 samples need to be stored in the buffer
    9. On the last intr (@950us) I inspect guard variables, EGU flags and/or SAADC.AMOUNT register.
      1. If I find mismatch (EGU Flag tied to SAADC_END not set or AMOUNT != 0x10 or guard variable counting saadc_handler() invocations) I breakpoint and inspect.
    10. If everything is OKI stop T3, T4, saadc_unint() and restarts saadc_init() with the analog module's and reenable the PPI

    This way the saadc repeatedly : saadc_init() - samples several (of several hndereds) of samples - saadc_uninit() and the switch 'owner' to the other module.

    Now, occasionally branch to step 9a and brealpoint, Inspecting the EGU flags, AMOIUNt register and the variables or show that though 4 START tasks were triggered (=16 samples), no END task triggered. AMOUNT register display a relatively small number when this happens 0/1/2 instead of 0x10.

    The problem was resolved only when I added errata 212 workaround code right before the saadc_init()  command in both modules

    I Hope this helps to recreate the problem.

Related