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

  • Hello again, Damien

    DamoL said:
    Thanks for the Reply.

    No problem at all, I am happy to help!
    Thank you for clarifying with the diagram and updating us on your findings.

    DamoL said:
    I followed the Errata 86 document, and added the below into my code on setup. 

    This is almost correct, except you should not write to the events but instead wait for them to be generated as a result of the triggered task.
    So, you should only write to the TASKS registers to trigger them, and not write to the EVENTS registers but instead wait for them to be set on their own as a result of the triggered TASK finishing. You should also add a STOP task trigger before the calibrate offset task, as shown in the workaround.
    The result could look similar to this:

    ..
        NRF_SAADC_NS->TASKS_STOP = 1;
        while(!nrf_saadc_event_check(NRF_SAADC_EVENT_STOPPED));
        NRF_SAADC_NS->TASKS_CALIBRATEOFFSET=1;
        while(!nrf_saadc_event_check(EVENTS_CALIBRATEDONE));
    	NRF_SAADC_NS->TASKS_STOP=1;
    	while(!nrf_saadc_event_check(NRF_SAADC_EVENT_STOPPED));
    ..

    And then you can proceed to start the SAADC and its regular operation afterwards.
    You may of course replace the busy waits with something more productive, or have the CPU go into a SYSTEM_ON sleep / idling to reduce power, if you intend on calibrating the SAADC somewhat frequently.

    Could you try it with this change, and see if the non-debug mode works as when stepping through the code?
    I look forward to hearing what you observe with these changes implemented!

    Best regards,
    Karl

  • Hi,

    We have had the similar problem like this one. I tried what  suggested and it gave some errors on missing arguments. So I did it like this:

    ...
    	LOG_INF("nrf_saadc_task_trigger NRF_SAADC_TASK_STOP");
    	nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_STOP);
    	LOG_INF("nrf_saadc_event_check NRF_SAADC_EVENT_STOPPED");
    	while(!nrf_saadc_event_check(NRF_SAADC, NRF_SAADC_EVENT_STOPPED));
    	LOG_INF("nrf_saadc_task_trigger NRF_SAADC_TASK_CALIBRATEOFFSET");
    	nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_CALIBRATEOFFSET);
    	LOG_INF("nrf_saadc_event_check NRF_SAADC_EVENT_CALIBRATEDONE");
    	while(!nrf_saadc_event_check(NRF_SAADC, NRF_SAADC_EVENT_CALIBRATEDONE));
    	LOG_INF("nrf_saadc_task_trigger NRF_SAADC_TASK_STOP");
    	nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_STOP);
    	LOG_INF("nrf_saadc_event_check NRF_SAADC_EVENT_STOPPED");
    	while(!nrf_saadc_event_check(NRF_SAADC, NRF_SAADC_EVENT_STOPPED));
    	LOG_INF("done");
    ...

    And it's getting stuck on NRF_SAADC_EVENT_CALIBRATEDONE.

    [00:00:01.773,071] [1B][0m<inf> adc_control: nrf_saadc_task_trigger NRF_SAADC_TASK_STOP[1B][0m
    [00:00:01.781,768] [1B][0m<inf> adc_control: nrf_saadc_event_check NRF_SAADC_EVENT_STOPPED[1B][0m
    [00:00:01.790,740] [1B][0m<inf> adc_control: nrf_saadc_task_trigger NRF_SAADC_TASK_CALIBRATEOFFSET[1B][0m
    [00:00:01.800,506] [1B][0m<inf> adc_control: nrf_saadc_event_check NRF_SAADC_EVENT_CALIBRATEDONE[1B][0m

    Earlier I had it like below and it was working but I'm not sure if I did it at the right way.

    ...
    	nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_CALIBRATEOFFSET);
    	nrf_saadc_event_clear(NRF_SAADC, NRF_SAADC_EVENT_CALIBRATEDONE);
    	nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_STOP);
    	nrf_saadc_event_clear(NRF_SAADC, NRF_SAADC_EVENT_STOPPED);
    	nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_START);
    ...

    I hope we could solve this issue.

    Regards,
    Tero

  • Hi Karl and Tero. 

    As Tero said, it does get stuck on the NRF_SAADC_EVENT_CALIBRATEDONE line. I tried what Tero had previously and that didn't work for me either. 

    Thanks, 

    Damien

  • Hello again Damien and Tero,

    Thank you for your patience with this.

    anicare-tero said:
    We have had the similar problem like this one. I tried what Karl Ylvisaker suggested and it gave some errors on missing arguments. So I did it like this:

    Yes, I wrote the section with the nRF5 SDK in mind, my mistake.

    anicare-tero said:
    Earlier I had it like below and it was working but I'm not sure if I did it at the right way.

    I am not sure that this will work, since the program counter will move on immediately after having triggered the task to cleared the event - regardless of the event actually having happened.

    DamoL said:
    As Tero said, it does get stuck on the NRF_SAADC_EVENT_CALIBRATEDONE line. I tried what Tero had previously and that didn't work for me either. 

    I just created a minimal NCS ADC example to test this on my end as well, and I notice that the same thing - the CALIBRATEDONE event never seems to occur. I am not immediately sure why this is happening - perhaps the calibrate done event is cleared somewhere else with a higher priority, so that the section here never sees the event, or similar.

    I will have a look into what is happening behind the scenes here tomorrow.

    Best regards,
    Karl

  • Hello again Damien and Tero,

    As mentioned I previously had the nRF5 SDK in mind when discussing this issue earlier. Looking into the NCS SAADC's irq handler it is apparent that the CALIBRATEDONE event is indeed cleared before the main context ever sees it. While this explains how the application is getting stuck waiting for the event, it does not unambiguously explain why you saw a change in the behavior after triggering the stop tasks before and after the calibrate done triggering.

    Could you try to add a delay in between your call to trigger calibration and the first sampling, to see if this then gives the SAADC sufficient time to finish calibration and the STOP task, before it is started again?
    If it is the Errata 86 at play, the driver's implementation of the workaround should take care of it as long as it is given time to finish before the first call.

    Best regards,
    Karl

Related