ADC - First read is always wrong

Hi, 

I have been playing around with the nRF9160 DK trying to get it reading a range of sensors, so far with success, however I have found an issue which has stumped me. I am using ADC0 to read a voltage. Although this is intended for a pressure transducer, for debugging I have just put the 5V out through a resistive divider (10K / 10K) and the ADC successfully reads 2.5V. 

However, when the program is first run, the very first ADC reading is around 50. The next one, and all instances after, it reads correctly (around 3500 - using 3V VDD as reference and gain of 1/4, 12-bit resolution). 

I assumed this may be due to acquisition time, but I tried a few different values and nothing helped. 

I wont post the full code, but here is the config and read code

#define ADC_RESOLUTION 12
#define ADC_GAIN ADC_GAIN_1_4
#define ADC_REFERENCE ADC_REF_VDD_1_4
#define ADC_ACQUISITION_TIME ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)
#define ADC_1ST_CHANNEL_ID 0  
#define ADC_1ST_CHANNEL_INPUT NRF_SAADC_INPUT_AIN0

#define BUFFER_SIZE 1
static uint16_t m_sample_buffer[BUFFER_SIZE];

static struct adc_channel_cfg channel_cfg = {
	.gain 				= ADC_GAIN,
	.reference 			= ADC_REFERENCE,
	.acquisition_time 	= ADC_ACQUISITION_TIME,
	.differential 		= 0,
	.channel_id			= ADC_1ST_CHANNEL_INPUT ,
	.input_positive 	= SAADC_CH_PSELP_PSELP_AnalogInput0,
};

static int adc_sample(void)
{
	int ret;

		struct adc_sequence sequence = {
		.channels    = BIT(channel_cfg.channel_id),
		.buffer      = &m_sample_buffer,
		.buffer_size = sizeof(m_sample_buffer),
		.resolution  = ADC_RESOLUTION,
	};

	if (!adc_dev) {
		return -1;
	}

	ret = adc_read(adc_dev, &sequence);
	if (ret) {
        printk("adc_read() failed with code %d\n", ret);
	}

	for (int i = 0; i < BUFFER_SIZE; i++) {
                printk("ADC raw value: %d\n", m_sample_buffer[i]);
				uint32_t mV = (m_sample_buffer[i] *1000)*3/4096;	//n*1000*Reference (3V) / 12bit adc
				printk("ADC mV after Divider: %u\n", mV);
				mV = mV *2;
				printk("Voltage Sensed: %u\n", mV);
	}

	return ret;
}



//WITHIN MAIN

adc_dev = device_get_binding("ADC_0");
	if (!adc_dev) {
        printk("device_get_binding ADC_0 failed\n");
    } 
    NRF_SAADC_NS->TASKS_CALIBRATEOFFSET=1;
    err = adc_channel_setup(adc_dev, &channel_cfg);
    if (err) {
	    printk("Error in adc setup: %d\n", err);
	}
	
//END

The adc_sample() function is called when button 1 is pressed, but I'm sure that shouldnt make a difference. 

Thanks, 

Damien

  • Hi Karl, 

    Apologies I have been away for a few days. I have tried your suggestion. But do not see a change in behaviour. My current workaround is just using printk() between adc_read() and looking at the buffer. 

    ret = adc_read(dev, &sequence);
    	if (ret) {
            printk("adc_read() failed with code %d\n", ret);
    	}
    	else{
    
    		for (int i = 0; i < BUFFER_SIZE; i++) {
    					printk("Reading ADC\n");    //THIS ADDS SUFFICIENT DELAY
    					uint32_t mV = (m_sample_buffer[i] *1000)*3/4096;	//n*1000*Reference (3V) / 12bit adc
    					printk("ADC raw value: %d\n", m_sample_buffer[i]);
    					printk("ADC mV after Divider: %u\n", mV);
    					mV = mV *2;
    					printk("Voltage Sensed: %u\n", mV);
    		}
    	}
    For some reason this gives enough time for the buffer to be filled. If I comment out that line it reads correctly about 30% of the time.
    Thanks, 
    Damien
  • Hello again, Damien

    DamoL said:
    Apologies I have been away for a few days.

    No need to apologize - we will continue this whenever you have the chance.

    DamoL said:
    I have tried your suggestion. But do not see a change in behaviour. My current workaround is just using printk() between adc_read() and looking at the buffer. 
    DamoL said:
    For some reason this gives enough time for the buffer to be filled. If I comment out that line it reads correctly about 30% of the time.

    Thank you for trying this, and updating us on the results. I find this very strange. I will need to look deeper into this, and once I have done so I will escalate it to the SAADC driver's developer team so that they may examine my findings and patch it. I will update you as soon as I have got anything to share.

    Thank you for bringing this to our attention!

    Best regards,
    Karl

  • Hello again Tero and Damien,

    Thank you for your extreme patience with this issue. I have been out of office for some time, but now I am back in office and will resume work with this investigation.

    A colleague has conducted some tests on this in my absence and come to the conclusion that the incorrect sample is as expected as per the 9160's SAADC documentation, which reads:

    The ADC has a temperature dependent offset. If the ADC is to operate over a large temperature range, we recommend running CALIBRATEOFFSET at regular intervals. The CALIBRATEDONE event will be fired when the calibration has been completed. Note that the DONE and RESULTDONE events will also be generated.

    Which implies that a junk DONE and RESULTDONE event will be returned following a successful calibration. I will have to test this some more, to see if this might have been what is causing the behavior you have seen on your end as well. I have also noted that the implementation of the Errata 86 workaround in the adc_nrfx_saadc.c driver does not exactly follow the workaround detailed in the mentioned Errata 86 (namely, it does not wait for the appropriate events to be generated), which I am unclear on whether has played into this issue.

    Best regards,
    Karl

  • Hello again, Tero and Damien

    It seems that the incorrect sample does indeed stem from the calibrateoffset procedure - at least in my minimal NCS ADC test application.

    Damien, could you confirm whether you meant that the unexpected sample happens periodically without the added kprint delay, or only during startup of the SAADC peripheral? If it is periodically, could you possibly send me the stripped down version of the project (only adc parts needed) that you tested with along with which NCS version you used when you saw this behavior?

    Tero, does the previous discussion not explain the similar issue you saw/are seeing?
    If your issue now diverges from this issue, please open a separate ticket where you detail your issue.

    In any case, I thought I should let you both know that I have made an internal ticket with the SAADC driver developers to have the adc_nrfx_saadc driver's implementation of the errata 86 workaround reviewed, and to have it re-implemented to instead exactly match the workaround described in the errata 86 documentation.

    I have also opened an internal request to have this elaborated on and clarified in the nRF9160's SAADC Product Specification section, akin to that of the nRF5340's SAADC section or better.

    Best regards,
    Karl

Related