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 Children
Related