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?