Core: RAK4631
Base: RAK19010
Power: RAK19012
nRF Connect v3.2.1
Zephyr
I am trying to read the current battery voltage on my setup. The problem is that the value being read from AIN2 always seems to float around 4000mv (after conversion).
My relevant devicetree:
zephyr,user
{
io-channels = <&adc 0>, <&adc 1>, <&adc 2>, <&adc 3>, <&adc 4>, <&adc 5>, <&adc 6>, <&adc 7>;
io-channel-names = “ain0”, “ain1”, “ain2”, “ain3”, “ain4”, “ain5”, “ain6”, “ain7”;
};
&adc {
#address-cells = <1>;
#size-cells = <0>;
status=“okay”;
channel@0
{
reg = <0>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)>;
zephyr,input-positive = <(NRF_SAADC_AIN0)>;
zephyr,resolution = <12>;
};
channel@1
{
reg = <1>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)>;
zephyr,input-positive = <NRF_SAADC_AIN1>;
zephyr,resolution = <12>;
};
channel@2
{
reg = <2>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)>;
zephyr,input-positive = <NRF_SAADC_AIN2>;
zephyr,resolution = <12>;
zephyr,oversampling = <4>; // Optional: Enable oversampling for better accuracy
};
channel@3
{
reg = <3>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)>;
zephyr,input-positive = <NRF_SAADC_AIN3>;
zephyr,resolution = <12>;
};
channel@4
{
reg = <4>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)>;
zephyr,input-positive = <NRF_SAADC_AIN4>;
zephyr,resolution = <12>;
};
channel@5
{
reg = <5>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)>;
zephyr,input-positive = <NRF_SAADC_AIN5>;
zephyr,resolution = <12>;
};
channel@6
{
reg = <6>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)>;
zephyr,input-positive = <NRF_SAADC_AIN6>;
zephyr,resolution = <12>;
};
channel@7
{
reg = <7>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)>;
zephyr,input-positive = <NRF_SAADC_AIN7>;
zephyr,resolution = <12>;
};
};
*I have all the AIN inputs defined as part of a test to read from all channels to see if AIN2 was the correct channel.
C Code:
DataStructs:
static const struct adc_dt_spec adc_channels = {
ADC_DT_SPEC_GET_BY_NAME(DT_PATH(zephyr_user), ain0),
ADC_DT_SPEC_GET_BY_NAME(DT_PATH(zephyr_user), ain1),
ADC_DT_SPEC_GET_BY_NAME(DT_PATH(zephyr_user), ain2),
ADC_DT_SPEC_GET_BY_NAME(DT_PATH(zephyr_user), ain3),
ADC_DT_SPEC_GET_BY_NAME(DT_PATH(zephyr_user), ain4),
ADC_DT_SPEC_GET_BY_NAME(DT_PATH(zephyr_user), ain5),
ADC_DT_SPEC_GET_BY_NAME(DT_PATH(zephyr_user), ain6),
ADC_DT_SPEC_GET_BY_NAME(DT_PATH(zephyr_user), ain7)
};
static int16_t sample_buffer = {0, 0, 0, 0, 0, 0, 0, 0};
static struct adc_sequence sequence = {
{
.buffer = &sample_buffer[0],
.buffer_size = sizeof(sample_buffer[0]),
},
{
.buffer = &sample_buffer[1],
.buffer_size = sizeof(sample_buffer[0]),
},
{
.buffer = &sample_buffer[2],
.buffer_size = sizeof(sample_buffer[0]),
},
{
.buffer = &sample_buffer[3],
.buffer_size = sizeof(sample_buffer[0]),
},
{
.buffer = &sample_buffer[4],
.buffer_size = sizeof(sample_buffer[0]),
},
{
.buffer = &sample_buffer[5],
.buffer_size = sizeof(sample_buffer[0]),
},
{
.buffer = &sample_buffer[6],
.buffer_size = sizeof(sample_buffer[0]),
},
{
.buffer = &sample_buffer[7],
.buffer_size = sizeof(sample_buffer[0]),
}
};
Initialization:
for (int ch=0; ch < sizeof(adc_channels)/sizeof(adc_channels[0]); ch++)
{
if (!adc_is_ready_dt(&adc_channels[ch]))
{
LOG_ERR(“ADC channel %d device not ready\n”, ch);
return -ENODEV;
}
err = adc_channel_setup_dt(&adc_channels[ch]);
if (err < 0)
{
LOG_ERR("ADC channel %d setup failed: %d\n", ch, err);
return err;
}
err = adc_sequence_init_dt(&adc_channels[ch], &sequence[ch]);
if (err < 0)
{
LOG_ERR("ADC channel %d sequence init failed: %d\n", ch, err);
return err;
}
}
Read:
static int read_adc_channel(int ch, int32_t* out_mv)
{
int err;
err = adc_read(adc_channels[ch].dev, &sequence[ch]);
if (err < 0)
{
return err;
}
*out_mv = (int32_t)sample_buffer[ch];
return 0;
}
Conversion:
static int read_battery_millivolts(int32_t* out_mv)
{
int err;
int32_t adc_val_mv;
int32_t val_mv;
// set_3v3_power(false);
err = read_adc_channel(2, &adc_val_mv); // Assuming channel 2 is the battery voltage channel
// set_3v3_power(true); // re-enable power after reading
if (err < 0)
{
LOG_ERR("Read battery failed: %d", err);
return err;
}
// Manual conversion to ADC Pin Voltage
val_mv = ((int64_t)adc_val_mv * REFERENCE_VOLTAGE_MV) / MAX_ADC_VALUE;
// Apply divider to get to Battery Voltage
int32_t battery_mv = (int64_t)val_mv * VOLTAGE_DIVIDER_RATIO;
LOG_INF("adc_read(): Raw ADC: %d, ADC pin voltage: %d mV, Battery: %d mV",
adc_val_mv,
val_mv,
battery_mv);
*out_mv = battery_mv;
return 0;
}
I read from the channel once every 10s. On battery only, the battery mv value drifts ±30mv. When measuring the battery directly on the battery terminals after 8h, it is down 100mv. After 24h it is down 200mv. The reading from the AIN2 seems to float around 4.000mv.
ALSO, in v3.2.1 of the SDK, the value of (devicetree binding) NRF_SAADC_AIN2 == 2, but the value NRF_SAADC_INPUT_AIN2 == 3, the enum is off by 1. I don’t know if that is an issue or not, but it is confusing.
This looks like a h/w config issue to me, I just cannot find where.
jk