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

Constant Delay of a SAADC channel

Hello,

Project Description

in our project we have multiple nRF52DK and one nRF52840DK. We are using the SAADC on the nRF52DK. The SAADC is configured with 4 inputs in differential mode. The sample rate is 1 kHz (1 ms) and the resolution is 10 Bit. The samples get transmitted to the nRF52840DK via NUS. The nRF52840DK prints the received values into the virtual COM port over USB for further processing on the PC.

Test Scenario

A few days ago we tested this setup. We used a function generator and applied a 1.6 V_pp Sinuswave with a frequency of 20 Hz to all 4 differential inputs of the SAADC. So we expected to get identical results for all four inputs/channels of the SAADC.

Test Results

The picture below show the results of this test. The event/trigger signal at the bottom of the graph is not relevant. As you can see, the SAADC channels/inputs 1, 2 and 3 are equal and fine. When you take a look to channel 4 you can see that this channel 4 has a constant 1 ms delay compared to channel 1,2 and 3. The black vertical marker shows this well.  

This is not the result what we expected.

SAADC configuration

The following code snippets show how the SAADC is configured and used:

#define SAADC_SAMPLES_IN_BUFFER         4
#define TRANSMISSION_OFFSET             512

static nrf_saadc_value_t                m_buffer_pool[2][SAADC_SAMPLES_IN_BUFFER]; 
static nrf_ppi_channel_t                m_ppi_channel;
static const nrfx_timer_t               m_timer = NRFX_TIMER_INSTANCE(1);

static uint16_t adc_value_ch1;
static uint16_t adc_value_ch2;
static uint16_t adc_value_ch3;
static uint16_t adc_value_ch4;

void timer_handler(nrf_timer_event_t event_type, void* p_context)
{

}

void saadc_sampling_event_init(void)
{
    ret_code_t err_code;
    err_code = nrf_drv_ppi_init();
    APP_ERROR_CHECK(err_code);
    
    nrfx_timer_config_t timer_config = NRFX_TIMER_DEFAULT_CONFIG;
    timer_config.frequency = NRF_TIMER_FREQ_16MHz;
    err_code = nrfx_timer_init(&m_timer, &timer_config, timer_handler);
    APP_ERROR_CHECK(err_code);

    /* setup m_timer for compare event */
    uint32_t ticks = nrfx_timer_ms_to_ticks(&m_timer,1);
    nrfx_timer_extended_compare(&m_timer, NRF_TIMER_CC_CHANNEL0, ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
    nrfx_timer_enable(&m_timer);

    uint32_t timer_compare_event_addr = nrfx_timer_compare_event_address_get(&m_timer, NRF_TIMER_CC_CHANNEL0);
    uint32_t saadc_sample_event_addr = nrfx_saadc_sample_task_get();

    /* setup ppi channel so that timer compare event is triggering sample task in SAADC */
    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
    APP_ERROR_CHECK(err_code);
    
    err_code = nrf_drv_ppi_channel_assign(m_ppi_channel, timer_compare_event_addr, saadc_sample_event_addr);
    APP_ERROR_CHECK(err_code);
}

void saadc_sampling_event_enable(void)
{
    ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
    APP_ERROR_CHECK(err_code);
}

void saadc_init(void)
{
    ret_code_t err_code;
	
    nrfx_saadc_config_t saadc_config = NRFX_SAADC_DEFAULT_CONFIG;
    saadc_config.resolution = NRF_SAADC_RESOLUTION_10BIT;
    
    nrf_saadc_channel_config_t channel_0_config =
        NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_DIFFERENTIAL(NRF_SAADC_INPUT_AIN4,NRF_SAADC_INPUT_AIN0);      
    channel_0_config.gain = NRF_SAADC_GAIN1_2;
    channel_0_config.reference = NRF_SAADC_REFERENCE_INTERNAL;
	
    nrf_saadc_channel_config_t channel_1_config =
        NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_DIFFERENTIAL(NRF_SAADC_INPUT_AIN5,NRF_SAADC_INPUT_AIN0); 
    channel_1_config.gain = NRF_SAADC_GAIN1_2;
    channel_1_config.reference = NRF_SAADC_REFERENCE_INTERNAL;

    nrf_saadc_channel_config_t channel_2_config =
        NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_DIFFERENTIAL(NRF_SAADC_INPUT_AIN6,NRF_SAADC_INPUT_AIN0);    
    channel_2_config.gain = NRF_SAADC_GAIN1_2;
    channel_2_config.reference = NRF_SAADC_REFERENCE_INTERNAL;

    nrf_saadc_channel_config_t channel_3_config =
        NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_DIFFERENTIAL(NRF_SAADC_INPUT_AIN7,NRF_SAADC_INPUT_AIN0);  
    channel_3_config.gain = NRF_SAADC_GAIN1_2;
    channel_3_config.reference = NRF_SAADC_REFERENCE_INTERNAL;	
	
    err_code = nrfx_saadc_init(&saadc_config, saadc_callback);
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_saadc_channel_init(0, &channel_0_config);
    APP_ERROR_CHECK(err_code);
    err_code = nrfx_saadc_channel_init(1, &channel_1_config);
    APP_ERROR_CHECK(err_code);
    err_code = nrfx_saadc_channel_init(2, &channel_2_config);
    APP_ERROR_CHECK(err_code);
    err_code = nrfx_saadc_channel_init(3, &channel_3_config);
    APP_ERROR_CHECK(err_code);	

    err_code = nrfx_saadc_buffer_convert(m_buffer_pool[0],SAADC_SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);   
    err_code = nrfx_saadc_buffer_convert(m_buffer_pool[1],SAADC_SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);

}

void saadc_callback(nrfx_saadc_evt_t const * p_event)
{

    if (p_event->type == NRFX_SAADC_EVT_DONE)
    {
        ret_code_t err_code;

        // set buffers
        err_code = nrfx_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);
        
        adc_value_ch1 = p_event->data.done.p_buffer[0] + TRANSMISSION_OFFSET;
        adc_value_ch2 = p_event->data.done.p_buffer[1] + TRANSMISSION_OFFSET;
        adc_value_ch3 = p_event->data.done.p_buffer[2] + TRANSMISSION_OFFSET;
        adc_value_ch4 = p_event->data.done.p_buffer[3] + TRANSMISSION_OFFSET;
        
        // send the adc_values every 40ms via NUS
        ...
    }
...
}


int main(void)
{
...
    saadc_sampling_event_init();
    saadc_init();
    saadc_sampling_event_enable();
...
}

Please let me know if you need any further information.

Own Findings

When I use defined values (which I increase every 1 second for example) for the adc_value_ch variables:

adc_value_ch1 = defined_value + TRANSMISSION_OFFSET;
adc_value_ch2 = defined_value + TRANSMISSION_OFFSET;
adc_value_ch3 = defined_value + TRANSMISSION_OFFSET;
adc_value_ch4 = defined_value + TRANSMISSION_OFFSET;

instead of:

        adc_value_ch1 = p_event->data.done.p_buffer[0] + TRANSMISSION_OFFSET;
        adc_value_ch2 = p_event->data.done.p_buffer[1] + TRANSMISSION_OFFSET;
        adc_value_ch3 = p_event->data.done.p_buffer[2] + TRANSMISSION_OFFSET;
        adc_value_ch4 = p_event->data.done.p_buffer[3] + TRANSMISSION_OFFSET;

the 1 ms offset of the channel 4 is gone. Maybe there is an issue when collection the samples from the buffer when calling "p_event->data.done.p_buffer[...]".

Questions

1. Do you have any idea what could cause this behaviour?

2. Is there a systematic error in using the SAADC?

3. Can you provide us some help, how to solve this issue?

Thank you very much in advance.

Best regards,

Michael

  • Hi Michael

    It's a bit hard to say what is happening here. My initial thought was that the buffer setup was incorrect and that the ADC would write to the buffer one position off, so that an old sample is left for one of the channels, but just by looking at your code I can't spot any errors. 

    If I understand your code correctly you will get interrupts from the ADC driver every 1ms, which could be problematic if there are a lot of other interrupts in the system, but if this was the case I would expect to see more drastic errors than the one you are seeing. In general I would recommend increasing the size of the buffers in order to reduce the CPU overhead, as long as you can accept a bit higher latency. 

    Have you tried disabling the handover of ADC data to the UART and the NUS service to see if the problem still remains? 
    You should be able to check the values using the debugger in the case where you don't send the ADC data anywhere. 

    Best regards
    Torbjørn

Related