/* * Copyright (c) 2018-2019 Peter Bigot Consulting, LLC * Copyright (c) 2019-2020 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include "battery.h" LOG_MODULE_REGISTER(BATTERYVDD, CONFIG_ADC_LOG_LEVEL); #define ZEPHYR_USER DT_PATH(zephyr_user) /* Other boards may use dividers that only reduce battery voltage to * the maximum supported by the hardware (3.6 V) */ #define BATTERY_ADC_GAIN ADC_GAIN_1_6 struct io_channel_config { uint8_t channel; }; struct divider_config { struct io_channel_config io_channel; struct gpio_dt_spec power_gpios; /* output_ohm is used as a flag value: if it is nonzero then * the battery is measured through a voltage divider; * otherwise it is assumed to be directly connected to Vdd. */ uint32_t output_ohm; uint32_t full_ohm; }; static const struct divider_config dividervdd_config = { .io_channel = { DT_IO_CHANNELS_INPUT(ZEPHYR_USER), }, }; 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 dividervdd_data = { .adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(ZEPHYR_USER)), }; static int dividervdd_setup(void) { const struct divider_config *cfg = ÷rvdd_config; const struct io_channel_config *iocp = &cfg->io_channel; const struct gpio_dt_spec *gcp = &cfg->power_gpios; struct divider_data *ddp = ÷rvdd_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, }; #ifdef CONFIG_ADC_NRFX_SAADC *accp = (struct adc_channel_cfg){ .gain = BATTERY_ADC_GAIN, .reference = ADC_REF_INTERNAL, .acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40), }; accp->input_positive = SAADC_CH_PSELP_PSELP_VDD; asp->resolution = 14; #else /* CONFIG_ADC_var */ #error Unsupported ADC #endif /* CONFIG_ADC_var */ rc = adc_channel_setup(ddp->adc, accp); LOG_INF("Setup VDD AIN%u got %d", iocp->channel, rc); return rc; } static bool battery_ok; int batteryvdd_setup() { int rc = dividervdd_setup(); battery_ok = (rc == 0); LOG_INF("Battery VDD setup: %d %d", rc, battery_ok); return rc; } //SYS_INIT(batteryvdd_setup, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); int16_t batteryvdd_sample(void) { int16_t rc = -ENOENT; if (battery_ok) { struct divider_data *ddp = ÷rvdd_data; //const struct divider_config *dcp = ÷rvdd_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); rc = val; LOG_INF("raw %u ~ %u mV", ddp->raw, val); } } return rc; }