ADC value 20% too low.

I am working on a project using the nrf9160DK for initial firmware development. Trying out the SAADC i encountered that the measured values were always about 20% lower then expected.

For instance, when the measured inout is connected to 50% vdd, then the next code

#include <zephyr.h>
#include <sys/printk.h>
#include <drivers/gpio.h>
#include <drivers/adc.h>
#include <drivers/sensor.h>
#include <nrfx_saadc.h>

#define MY_AIN1 DT_PATH(ain1)

#define MY_ADC DT_NODELABEL(adc)

#define ADC_PIN NRF_SAADC_INPUT_AIN3 // P0.16
#define ADC_RESOLUTION 12
#define ADC_OVERSAMPLE 0

static struct adc_channel_cfg adc_pin_ref_vdd_cfg = 
{
	.gain = ADC_GAIN_1_4,
	.reference = ADC_REF_VDD_1_4, // reference direct to ADC_REF_VDD_1 isnot supported by nrf9160
	.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10),
	.input_positive = ADC_PIN
};

static struct adc_channel_cfg adc_vdd_ref_vdd_cfg = 
{
	.gain = ADC_GAIN_1_4,
	.reference = ADC_REF_VDD_1_4, // reference direct to ADC_REF_VDD_1 isnot supported by nrf9160
	.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10),
	.input_positive = NRF_SAADC_INPUT_VDD
};

static struct adc_channel_cfg adc_pin_ref_internal_cfg = 
{
	.gain = ADC_GAIN_1_4,
	.reference = ADC_REF_INTERNAL,
	.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10),
	.input_positive = ADC_PIN
};

const char* err_str(int err)
{
	switch (-err)
	{
		case 0: return "OK";
		case EINVAL: return "EINVAL";
		case ENOMEM: return "ENOMEM";
		case ENOTSUP: return "ENOTSUP";
		case EBUSY: return "EBUSY";
		default: return "?";
	}
}


int read_adc(const struct device *device, 
		const struct adc_channel_cfg *cfg, 
		uint8_t resolution, nrf_saadc_oversample_t oversample)
{
	int16_t sample_buffer[1];

	struct adc_channel_cfg my_cfg = *cfg;
	my_cfg.channel_id = 0; // just use the first logical channel
	my_cfg.differential = 0;
	my_cfg.input_negative = NRF_SAADC_INPUT_DISABLED;

	struct adc_sequence sequence = 
	{
		.options = NULL,
		.channels = 0b00000001, // only measuring my_cfg.channel_id = 0
		.buffer = sample_buffer,
		.buffer_size = sizeof(sample_buffer),
		.resolution = resolution,
		.oversampling = oversample, // hw can oversample up to 256
		.calibrate = false
	};

	static unsigned count=0;
	if( count++ % 10 == 0 )
	{
		sequence.calibrate = true;
		printk("DEBUG Calibrate before measure\n");
	}

	// choosing the correct resolution for the channel is important for accuracy!
	// but changing the resolution between readings can cause offset deviations!
	int32_t err = adc_channel_setup(device, &my_cfg);
	if( err != 0 )
	{
		printk("ADC channel setup failed with %d: %s.\n", err, err_str(err));
		return -1;
	}

	// Read sequence of channels (fails if not supported by MCU)
	err = adc_read(device, &sequence);
	if (err != 0) 
	{
		printk("ADC reading failed with error %d: %s.\n", err, err_str(err));
		return -2;
	}

	return sample_buffer[0];
}



void main(void)
{
	// get board attributes of this analog input voltage divider:
	// get io-channels attribute from ain1
	uint8_t input = DT_IO_CHANNELS_INPUT(MY_AIN1);
	uint32_t output_ohm = DT_PROP(MY_AIN1, output_ohms);
	uint32_t full_ohm = DT_PROP(MY_AIN1, full_ohms);

	char hw_rev[8];
	memcpy( hw_rev, &NRF_FICR->INFO.VARIANT, 4);
	hw_rev[4] = 0;
	char hw_type[8];
	memcpy( hw_type, &NRF_FICR->INFO.DEVICETYPE, 4);
	hw_type[4] = 0;

	printk("nrf91 FICR HW revision: %s, type: ", hw_rev, hw_type);
	printk("ain1 channel = %d, output_ohm=%u, full_ohm=%u, factor=%f\n", input, output_ohm, full_ohm, (float)full_ohm/output_ohm);

	// see: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/2.0.0/zephyr/build/dts/howtos.html
	const struct device *dev_adc = DEVICE_DT_GET(MY_ADC);
	// or runtime
	//const struct device *dev_adc = device_get_binding("ADC_0");

	if (!device_is_ready(dev_adc)) 
	{
		printk("ADC device not found\n");
		return;
	}

	while (1) 
	{
		int vdd_ref_vdd = read_adc(dev_adc, &adc_vdd_ref_vdd_cfg, ADC_RESOLUTION, NRF_SAADC_OVERSAMPLE_DISABLED);
		int pin_ref_vdd = read_adc(dev_adc, &adc_pin_ref_vdd_cfg, ADC_RESOLUTION, NRF_SAADC_OVERSAMPLE_DISABLED);
		int pin_ref_int = read_adc(dev_adc, &adc_pin_ref_internal_cfg, ADC_RESOLUTION, NRF_SAADC_OVERSAMPLE_DISABLED);

		int32_t mv_value = pin_ref_int;
		adc_raw_to_millivolts(600, adc_pin_ref_internal_cfg.gain, ADC_RESOLUTION, &mv_value);


		printk("ADC reading:{range:%d, pin:{ref_int:{adc:%d, Vpin:%0.3f}, ref_vdd:{adc:%d, _%%:%0.1f}, vdd:{adc:%d}}}\n", 
			0x01<<ADC_RESOLUTION, 
			pin_ref_int, mv_value/1000.0,
			pin_ref_vdd, 100.0*pin_ref_vdd/(float)(0x01<<ADC_RESOLUTION),
			vdd_ref_vdd);


		k_sleep(K_MSEC(1000));
	}
}

outputs the following values:

ADC reading:{range:4096, pin:{ref_int:{adc:1415, Vpin:0.829}, ref_vdd:{adc:1658, _%:40.5}, vdd:{adc:3307}}}
ADC reading:{range:4096, pin:{ref_int:{adc:1420, Vpin:0.832}, ref_vdd:{adc:1631, _%:39.8}, vdd:{adc:3320}}}

However, i expected vdd:{adc:4096} and corresponding values for the ref_vdd:0.9V and ref_int:50%

What is going wrong here?

Parents Reply
  • Hello again,

    Thank you for your patience with this. The summer holiday season has begun here in Norway, and so the DevZone forum is operating at reduced capacity for the next couple of weeks.

    Thank you for providing the additional information as well. Did you try to explicitly trigger an offset calibration before you begin the sampling, to see if this had any effect?
    Do you have this as a minimal project you could send me, so that I could reproduce this on my end and take a closer look?

    Best regards,
    Karl

Children
Related