155 lines
3.5 KiB
C
155 lines
3.5 KiB
C
/*
|
|
* Copyright (c) 2018-2019 Peter Bigot Consulting, LLC
|
|
* Copyright (c) 2019-2020 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/adc.h>
|
|
#include <zephyr/drivers/sensor.h>
|
|
#include <zephyr/logging/log.h>
|
|
|
|
#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;
|
|
}
|