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

  • Hi Eyalasko

    What Acquisition time did you set?

    I made a FW with 8 channels & 220us period scan and it works well

    But I set minimal Acquisition time

    /* Bits 18..16 : Acquisition time, the time the SAADC uses to sample the input voltage */
    #define SAADC_CH_CONFIG_TACQ_Pos (16UL) /*!< Position of TACQ field. */
    #define SAADC_CH_CONFIG_TACQ_Msk (0x7UL << SAADC_CH_CONFIG_TACQ_Pos) /*!< Bit mask of TACQ field. */
    #define SAADC_CH_CONFIG_TACQ_3us (0UL) /*!< 3 us */
    #define SAADC_CH_CONFIG_TACQ_5us (1UL) /*!< 5 us */
    #define SAADC_CH_CONFIG_TACQ_10us (2UL) /*!< 10 us */
    #define SAADC_CH_CONFIG_TACQ_15us (3UL) /*!< 15 us */
    #define SAADC_CH_CONFIG_TACQ_20us (4UL) /*!< 20 us */
    #define SAADC_CH_CONFIG_TACQ_40us (5UL) /*!< 40 us */

  • I've set chan0 (ain4) to 20us and Chan 1-3 (ain5-7) to 3us (also tried 10us per errata212)

  • Set to 3us and try. Or you have to set a longer period

  • 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();
    }
    

Related