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

NRF9160 DK ADC long sample times

I'm working with the NRF9160 DK and have found the sample times for the ADC to be abnormally long. I am sampling three channels and seeing a total time for the ADC read of 80+us when I'm expecting something on the order of 15us. I have experimented with many different settings and configurations and not found any way to improve it. 

Here is my configuration

#include <hal/nrf_saadc.h>
#define ADC_DEVICE_NAME DT_ADC_0_NAME
#define ADC_RESOLUTION 10
#define ADC_GAIN ADC_GAIN_1_6
#define ADC_REFERENCE ADC_REF_INTERNAL
#define ADC_ACQUISITION_TIME ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 3)
#define ADC_1ST_CHANNEL_ID 0
#define ADC_1ST_CHANNEL_INPUT NRF_SAADC_INPUT_AIN0
#define ADC_2ND_CHANNEL_ID 2
#define ADC_2ND_CHANNEL_INPUT NRF_SAADC_INPUT_AIN2
#define ADC_3RD_CHANNEL_ID 3
#define ADC_3RD_CHANNEL_INPUT NRF_SAADC_INPUT_AIN3

static const struct adc_channel_cfg m_1st_channel_cfg = {
.gain = ADC_GAIN,
.reference = ADC_REFERENCE,
.acquisition_time = NRF_SAADC_ACQTIME_3US,
.channel_id = ADC_1ST_CHANNEL_ID,
#if defined(CONFIG_ADC_CONFIGURABLE_INPUTS)
.input_positive = ADC_1ST_CHANNEL_INPUT,
#endif
};

static const struct adc_channel_cfg m_2nd_channel_cfg = {
.gain = ADC_GAIN,
.reference = ADC_REFERENCE,
.acquisition_time = NRF_SAADC_ACQTIME_3US,
.channel_id = ADC_2ND_CHANNEL_ID,
#if defined(CONFIG_ADC_CONFIGURABLE_INPUTS)
.input_positive = ADC_2ND_CHANNEL_INPUT,
#endif
};

static const struct adc_channel_cfg m_3rd_channel_cfg = {
.gain = ADC_GAIN,
.reference = ADC_REFERENCE,
.acquisition_time = NRF_SAADC_ACQTIME_3US,
.channel_id = ADC_3RD_CHANNEL_ID,
#if defined(CONFIG_ADC_CONFIGURABLE_INPUTS)
.input_positive = ADC_3RD_CHANNEL_INPUT,
#endif
};

Here is the channel set up:

adc_dev = device_get_binding("ADC_0");
if (!adc_dev) {
printk("device_get_binding ADC_0 failed\n");
}
err = adc_channel_setup(adc_dev, &m_1st_channel_cfg);
if (err) {
printk("Error in adc setup: %d\n", err);
}

err = adc_channel_setup(adc_dev, &m_2nd_channel_cfg);
if (err) {
printk("Error in adc setup: %d\n", err);
}

err = adc_channel_setup(adc_dev, &m_3rd_channel_cfg);
if (err) {
printk("Error in adc setup: %d\n", err);
}

And here is where I'm sampling:

static int adc_sample(void)
{
int ret;


const struct adc_sequence_options options = {
.interval_us = 0,
.extra_samplings = 0,

};

const struct adc_sequence sequence = {
.channels = BIT(ADC_1ST_CHANNEL_ID) | BIT(ADC_2ND_CHANNEL_ID) | BIT(ADC_3RD_CHANNEL_ID),
.buffer = m_sample_buffer,
.buffer_size = sizeof(m_sample_buffer),
.resolution = ADC_RESOLUTION,
.oversampling = 0,
.calibrate = false,
.options = &options,
};

if (!adc_dev) {
return -1;
}
gpio_pin_write(gpio_dev, 10, 1);
ret = adc_read(adc_dev, &sequence);
gpio_pin_write(gpio_dev, 10, 0);

/*printk("ADC read err: %d\n", ret);

/* Print the AIN0 values */
/*for (int i = 0; i < BUFFER_SIZE; i++) {
float adc_voltage = 0;
adc_voltage = (float)(((float)m_sample_buffer[i] / 1023.0f) *
3600.0f);
printk("ADC raw value: %d\n", m_sample_buffer[i]);
printf("Measured voltage: %f mV\n", adc_voltage);
}
*/
return ret;
}

Parents Reply Children
  • I tried working with nrfx_saadc.h and including it and had lots of trouble. I'm not sure how to get it to build with SES and for the build to find the file. I get errors like:

    1> C:\Nordic\ncs\nrf\samples\nrf9160\adc\build_nrf9160_pca10090ns/../src/main.c:68: undefined reference to `nrfx_saadc_init'

    I tried going through the Zephyr menuconfig to get it to find the nrfx_saadc.h file but to no avail. Searching around I found plenty of examples of how to work with the old SDK but since this is 9160, they need modification. I also found this example: 

    https://devzone.nordicsemi.com/nordic/cellular-iot-guides/b/getting-started-cellular/posts/nrf-connect-sdk-tutorial

    It is using 

    #include <hal/nrf_saadc.h>

    So, the question is, What is the right way to go about this? I see that nrfx_saadc.h includes hal/nrf_saadc.h. But then I see in the Zephyr structure that it is including adc_nrfx_saadc.h. I'm new to this whole Zephyr structure and want to work with best practices. Could you provide a little guidance?

  • If you keep the project "as-is", meaning that all the zephyr related ADC drivers are enabled, then all the nrfx-saadc components are also enabled (sources + headers). Remove the zephyr adc driver functions in your application, then include nrfx_saadc.h in your main.c file. This should then not give you any undefined references.

    If you want to use the event handler, you have to add the interrupt wrapper for the OS, as done here for the PDM:

    https://devzone.nordicsemi.com/f/nordic-q-a/51164/pdm-hardware-exception/204916#204916

     

    Its not very easy to use nrfx directly, and this is something that I'm reporting internally so that we can improve this in the future.

     

    Kind regards,

    Håkon

  • I'm not sure why I'm getting undefined references. Here is my main.c file. Maybe there's something obvious? 

    /*
     * Copyright (c) 2018 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic
     */
    
    #include <gpio.h>
    #include <net/socket.h>
    #include <nrf9160.h>
    #include <stdio.h>
    #include <string.h>
    #include <uart.h>
    #include <adc.h>
    #include <zephyr.h>
    #include <nrfx.h>
    #include <nrfx_saadc.h>
    //#include <hal/nrf_saadc.h>
    //#include <nrfx_dppi.h> 
    
    
    struct device *gpio_dev;
    
    #define SAADC_DEFAULT_CONFIG                                               \
    {                                                                               \
        .resolution         = (nrf_saadc_resolution_t)NRFX_SAADC_CONFIG_RESOLUTION, \
        .oversample         = (nrf_saadc_oversample_t)NRFX_SAADC_CONFIG_OVERSAMPLE, \
        .interrupt_priority = NRFX_SAADC_CONFIG_IRQ_PRIORITY,                       \
        .low_power_mode     = NRFX_SAADC_CONFIG_LP_MODE                             \
    }
    
    #define SAADC_DEFAULT_CHANNEL_CONFIG_SE(PIN_P) \
    {                                                   \
        .resistor_p = NRF_SAADC_RESISTOR_DISABLED,      \
        .resistor_n = NRF_SAADC_RESISTOR_DISABLED,      \
        .gain       = NRF_SAADC_GAIN1_6,                \
        .reference  = NRF_SAADC_REFERENCE_INTERNAL,     \
        .acq_time   = NRF_SAADC_ACQTIME_3US,           \
        .mode       = NRF_SAADC_MODE_SINGLE_ENDED,      \
        .burst      = NRF_SAADC_BURST_DISABLED,         \
        .pin_p      = (nrf_saadc_input_t)(PIN_P),       \
        .pin_n      = NRF_SAADC_INPUT_DISABLED          \
    }
    
    nrf_saadc_channel_config_t saadc_channel_config = SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);
    static bool adcCalibrateDone;
    
    
    #define BUFFER_SIZE 3
    static nrf_saadc_value_t m_sample_buffer[BUFFER_SIZE];
    
    static void saadc_callback(nrfx_saadc_evt_t const * p_event){
    
        if ( p_event->type == NRFX_SAADC_EVT_DONE )
        {
           printk("ADC raw value: %d\n", m_sample_buffer[0]);
        }
        else if( p_event->type == NRFX_SAADC_EVT_CALIBRATEDONE )
        {
          adcCalibrateDone = true;
        }
    }
    
    void InitAdcModule(void)
    {
        nrfx_err_t err_code;
        nrfx_saadc_config_t drv_saadc_cfg = SAADC_DEFAULT_CONFIG;
    		
        err_code = nrfx_saadc_init( &drv_saadc_cfg, saadc_callback );
        if(err_code != NRFX_SUCCESS){
            //Blah error
        }
    	
        adcCalibrateDone = false;
        err_code = nrfx_saadc_calibrate_offset( );
        if(err_code != NRFX_SUCCESS){
            //Blah error
        }
    	
        if( err_code == NRFX_SUCCESS )
        {
    	err_code = 0;
    	while( !adcCalibrateDone );  
        }
    	
    }
    
    //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);
    //
    //    nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
    //    timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
    //    err_code = nrf_drv_timer_init(&m_timer, &timer_cfg, timer_handler);
    //    APP_ERROR_CHECK(err_code);
    //
    //    /* setup m_timer for compare event every 400ms */
    //    uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_timer, 400);
    //    nrf_drv_timer_extended_compare(&m_timer,
    //                                   NRF_TIMER_CC_CHANNEL0,
    //                                   ticks,
    //                                   NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
    //                                   false);
    //    nrf_drv_timer_enable(&m_timer);
    //
    //    uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_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 */
    //    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_task_addr);
    //    APP_ERROR_CHECK(err_code);
    //}
    
    static int adc_sample(void)
    {
    	int ret;
    
    
    	/*printk("ADC read err: %d\n", ret);
    
    	/* Print the AIN0 values */
    	/*for (int i = 0; i < BUFFER_SIZE; i++) {
    		float adc_voltage = 0;
    		adc_voltage = (float)(((float)m_sample_buffer[i] / 1023.0f) *
    				      3600.0f);
    		printk("ADC raw value: %d\n", m_sample_buffer[i]);
    		printf("Measured voltage: %f mV\n", adc_voltage);
    	}
    */
    	return ret;
    }
    
    
    static inline void nrf_delay_us(u32_t microsec)
    {
    	NRF_TIMER1_NS->TASKS_CLEAR = 1;
    	if (microsec < 2 )
    		return;
    	NRF_TIMER1_NS->CC[0] = (microsec << 4) - 26;
    	NRF_TIMER1_NS->PRESCALER = 0;
    	NRF_TIMER1_NS->TASKS_START = 1;
    	while (NRF_TIMER1_NS->EVENTS_COMPARE[0] == 0)
    		;
    	NRF_TIMER1_NS->EVENTS_COMPARE[0] = 0;
    	NRF_TIMER1_NS->TASKS_STOP = 1;	
    }
    
    int main(void)
    {
    	nrfx_err_t err;
            nrf_saadc_value_t  adc_value;
    
    	printk("nrf91 saadc sampling AIN0 (P0.13)\n");
    	printk("Example requires secure_boot to have ");
    	printk("SAADC set to non-secure!\n");
    	printk("If not; BusFault/UsageFault will be triggered\n");
    
    //        gpio_dev = device_get_binding(DT_GPIO_P0_DEV_NAME);
    //
    //        if (!gpio_dev) {
    //		printk("Cannot bind gpio device");
    //		return -ENODEV;
    //	}
    //
    //        err = gpio_pin_configure(gpio_dev, 10, GPIO_DIR_OUT);//Set pin 0 to output
    
    
            //Startup the high frequency clock
            NRF_CLOCK_NS->TASKS_HFCLKSTART = 1;
    	while (NRF_CLOCK_NS->EVENTS_HFCLKSTARTED == 0);
    
            //Configure the timer
    //        NRF_TIMER1_NS->TASKS_CLEAR = 1;
    //	NRF_TIMER1_NS->PRESCALER = 0;//Run at 16MHz
    
            InitAdcModule();
    
    	while (1) {
    
    		err = nrfx_saadc_sample();
    
    		if (err) {
    			printk("Error in adc sampling: %d\n", err);
    		}
    		k_sleep(500);
    
    	}
    }
    

    Maybe it's my prj.conf? I started with the asset tracker application and haven't changed the prj.conf much? Maybe I should just start over?

    CONFIG_BSD_LIBRARY=y
    CONFIG_GPIO=y
    CONFIG_SERIAL=y
    CONFIG_STDOUT_CONSOLE=y
    CONFIG_UART_INTERRUPT_DRIVEN=y
    CONFIG_TEST_RANDOM_GENERATOR=y
    CONFIG_NETWORKING=y
    CONFIG_NET_BUF_USER_DATA_SIZE=1
    CONFIG_NET_SOCKETS_OFFLOAD=y
    CONFIG_NET_SOCKETS=y
    CONFIG_NET_SOCKETS_POSIX_NAMES=y
    CONFIG_NET_RAW_MODE=y
    CONFIG_TRUSTED_EXECUTION_NONSECURE=y
    CONFIG_LOG=n
    CONFIG_LOG_DEFAULT_LEVEL=4
    CONFIG_HEAP_MEM_POOL_SIZE=1024
    CONFIG_LTE_LINK_CONTROL=n
    CONFIG_ADC=y
    CONFIG_ADC_0=y
    CONFIG_ADC_NRFX_SAADC=y
    
    
    CONFIG_MAIN_STACK_SIZE=4096

  • Here's something interesting. 

    If I use functions from hal/nrf_saadc.h, I can get it to build. But if I use functions from nrfx_saadc.h, i'll get undefined references.

  • Hi,

     

    The driver does not use the nrfx_saadc.c file, but rather the API in the header file.

    To force the usage of nrfx_saadc.c, you need to do these two things.

    1. Create a Kconfig file in your application directory (ie: nrf/samples/nrf9160/adc/) that contains this:

    mainmenu "NRFX SAADC"
    
    config FORCE_NRFX_SAADC
    	bool "Test to force SAADC"
    	select NRFX_SAADC    
        default y
    
    source "$ZEPHYR_BASE/Kconfig.zephyr"

    2. Since the file is guarded by a "ENABLE" macro, we need to add this to the prj.conf file:

    CONFIG_COMPILER_OPT="-DNRFX_SAADC_ENABLED=1"

     

    I apologize for the inconvenience regarding using the nrfx drivers directly. I have been in contact with developers to make it easier to enable these modules directly, and I can assure you that we are working on improving this.

     

    Kind regards,

    Håkon

Related