I'm trying to use the sample here: github.com/.../battery but I had some questions around how the voltage divider logic is handled. I'm getting weird readings.
In my final design, I'll have a battery hooked up like this:
But, until that's ready, I've been putting 400 mV into this pin using an ESP32. I've confirmed this with a multimeter.
Here is the relevant section of my .dts file:
```
// Voltage Divider Resistors:
// 2M Ohm
// 845k Ohm
vbatt {
compatible = "voltage-divider";
io-channels = <&adc 5>;
output-ohms = <845000>;
full-ohms = <(845000 + 2000000)>;
};
```
But, the output looks like this:
```
[00:03:08.021,392] <inf> app_name: raw 16380 ~ 599 mV => 2016 mV
[00:03:11.021,606] <inf> app_name: raw 16380 ~ 599 mV => 2016 mV
[00:03:14.021,820] <inf> app_name: raw 16380 ~ 599 mV => 2016 mV
[00:03:17.022,033] <inf> app_name: raw 16380 ~ 599 mV => 2016 mV
```
I've tweaked the gain and the resolution but the values are always wildly off what I would expect. When I change the voltage I'm supplying, the values change, but they never seem to be accurate. I would expect to see ~420 mV here.
I am using a BMD-300 Dev Kit that's based on the nRF52832. I'm using nRF Connect v2.1.2.
Apologies if this is a basic question - I'm very new to this!
Here is the full code below. It's very close to the sample:
#include "cx_battery.h" #include <zephyr/kernel.h> #include <zephyr/init.h> #include <zephyr/drivers/adc.h> #include <zephyr/drivers/sensor.h> #include <drivers/gpio.h> #include <logging/log.h> #include <stdio.h> #define LOG_MODULE_NAME app_name_battery LOG_MODULE_REGISTER(LOG_MODULE_NAME); #define VBATT DT_PATH(vbatt) #define ZEPHYR_USER DT_PATH(zephyr_user) #define BATTERY_ADC_GAIN ADC_GAIN_1 #define STACKSIZE 1024 #define THREAD0_PRIORITY 7 // Battery Voltage pin is the voltage level of the battery static bool battery_ok; int battery_measure_enable(bool enable); static const struct battery_level_point levels[] = { /* "Curve" here eyeballed from captured data for the [Adafruit * 3.7v 2000 mAh](https://www.adafruit.com/product/2011) LIPO * under full load that started with a charge of 3.96 V and * dropped about linearly to 3.58 V over 15 hours. It then * dropped rapidly to 3.10 V over one hour, at which point it * stopped transmitting. * * Based on eyeball comparisons we'll say that 15/16 of life * goes between 3.95 and 3.55 V, and 1/16 goes between 3.55 V * and 3.1 V. */ { 10000, 3950 }, { 625, 3550 }, { 0, 3100 }, }; static const char *now_str(void) { static char buf[16]; /* ...HH:MM:SS.MMM */ uint32_t now = k_uptime_get_32(); unsigned int ms = now % MSEC_PER_SEC; unsigned int s; unsigned int min; unsigned int h; now /= MSEC_PER_SEC; s = now % 60U; now /= 60U; min = now % 60U; now /= 60U; h = now; snprintf(buf, sizeof(buf), "%u:%02u:%02u.%03u", h, min, s, ms); return buf; } struct io_channel_config { uint8_t channel; }; struct divider_config { struct io_channel_config io_channel; struct gpio_dt_spec power_gpios; uint32_t output_ohm; uint32_t full_ohm; }; static const struct divider_config divider_config = { .io_channel = { DT_IO_CHANNELS_INPUT(VBATT), }, .output_ohm = DT_PROP(VBATT, output_ohms), .full_ohm = DT_PROP(VBATT, full_ohms), }; struct divider_data { const struct device *adc; struct adc_channel_cfg adc_cfg; struct adc_sequence adc_seq; int16_t raw; }; static struct divider_data divider_data = { #if DT_NODE_HAS_STATUS(VBATT, okay) .adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(VBATT)), #else .adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(ZEPHYR_USER)), #endif }; int cx_battery_init(void) { int rc = battery_measure_enable(true); if (rc != 0) { LOG_ERR("Failed initialize battery measurement: %d\n", rc); // TODO: Call a global function to halt initialization? return -1; } LOG_INF("Finished initializing Battery module successfully"); return 0; } static int divider_setup(void) { const struct divider_config *cfg = ÷r_config; const struct io_channel_config *iocp = &cfg->io_channel; const struct gpio_dt_spec *gcp = &cfg->power_gpios; struct divider_data *ddp = ÷r_data; struct adc_sequence *asp = &ddp->adc_seq; struct adc_channel_cfg *accp = &ddp->adc_cfg; int rc; if (!device_is_ready(ddp->adc)) { // LOG_ERR("ADC device is not ready %s", ddp->adc->name); return -ENOENT; } if (gcp->port) { if (!device_is_ready(gcp->port)) { LOG_ERR("%s: device not ready", gcp->port->name); return -ENOENT; } rc = gpio_pin_configure_dt(gcp, GPIO_OUTPUT_INACTIVE); if (rc != 0) { LOG_ERR("Failed to control feed %s.%u: %d", gcp->port->name, gcp->pin, rc); return rc; } } *asp = (struct adc_sequence){ .channels = BIT(0), .buffer = &ddp->raw, .buffer_size = sizeof(ddp->raw), .oversampling = 4, .calibrate = true, }; *accp = (struct adc_channel_cfg){ .gain = BATTERY_ADC_GAIN, .reference = ADC_REF_INTERNAL, .acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40), }; if (cfg->output_ohm != 0) { accp->input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0 + iocp->channel; } else { accp->input_positive = SAADC_CH_PSELP_PSELP_VDD; } asp->resolution = 14; LOG_INF("Reference voltage: %d", ADC_REF_INTERNAL); rc = adc_channel_setup(ddp->adc, accp); // LOG_INF("Setup AIN%u got %d", iocp->channel, rc); return rc; } static int battery_setup(const struct device *arg) { int rc = divider_setup(); battery_ok = (rc == 0); if (battery_ok) { LOG_INF("Battery setup successfully"); } else { LOG_ERR("Battery setup failed"); } return rc; } SYS_INIT(battery_setup, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); int battery_measure_enable(bool enable) { int rc = -ENOENT; if (battery_ok) { const struct gpio_dt_spec *gcp = ÷r_config.power_gpios; rc = 0; if (gcp->port) { rc = gpio_pin_set_dt(gcp, enable); } } return rc; } int battery_sample(void) { int rc = -ENOENT; if (battery_ok) { struct divider_data *ddp = ÷r_data; const struct divider_config *dcp = ÷r_config; struct adc_sequence *sp = &ddp->adc_seq; rc = adc_read(ddp->adc, sp); sp->calibrate = false; if (rc == 0) { int32_t val = ddp->raw; adc_raw_to_millivolts(adc_ref_internal(ddp->adc), ddp->adc_cfg.gain, sp->resolution, &val); if (dcp->output_ohm != 0) { rc = val * (uint64_t)dcp->full_ohm / dcp->output_ohm; LOG_INF("raw %u ~ %u mV => %d mV", ddp->raw, val, rc); } else { rc = val; LOG_INF("raw %u ~ %u mV", ddp->raw, val); } } } return rc; } void check_battery_life_indefinitely() { k_msleep(5000); while (!battery_ok) { k_msleep(1000); } LOG_INF("Finished waiting for battery setup"); while (true) { int batt_mV = battery_sample(); if (batt_mV < 0) { printk("Failed to read battery voltage: %d\n", batt_mV); break; } k_msleep(3000); } printk("Disable: %d\n", battery_measure_enable(false)); } K_THREAD_DEFINE(thread0_id, STACKSIZE, check_battery_life_indefinitely, NULL, NULL, NULL, THREAD0_PRIORITY, 0, 0);