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?