From a6de43e665ded4831d79159495660ac3a7d48e99 Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Sat, 3 Apr 2021 01:00:51 -0500 Subject: [PATCH] feat: Add nrf VDDH battery driver Added a driver which uses the nRF52's ADC channel on the VDDH pin to read the battery voltage when using high voltage mode. --- app/drivers/sensor/CMakeLists.txt | 4 +- app/drivers/sensor/Kconfig | 2 +- app/drivers/sensor/battery/CMakeLists.txt | 10 ++ app/drivers/sensor/battery/Kconfig | 21 ++++ app/drivers/sensor/battery/battery_common.c | 43 +++++++ app/drivers/sensor/battery/battery_common.h | 21 ++++ app/drivers/sensor/battery/battery_nrf_vddh.c | 116 ++++++++++++++++++ .../battery_voltage_divider.c | 50 ++------ .../battery_voltage_divider/CMakeLists.txt | 6 - .../sensor/battery_voltage_divider/Kconfig | 8 -- .../bindings/sensor/zmk,battery-nrf-vddh.yaml | 11 ++ 11 files changed, 236 insertions(+), 56 deletions(-) create mode 100644 app/drivers/sensor/battery/CMakeLists.txt create mode 100644 app/drivers/sensor/battery/Kconfig create mode 100644 app/drivers/sensor/battery/battery_common.c create mode 100644 app/drivers/sensor/battery/battery_common.h create mode 100644 app/drivers/sensor/battery/battery_nrf_vddh.c rename app/drivers/sensor/{battery_voltage_divider => battery}/battery_voltage_divider.c (82%) delete mode 100644 app/drivers/sensor/battery_voltage_divider/CMakeLists.txt delete mode 100644 app/drivers/sensor/battery_voltage_divider/Kconfig create mode 100644 app/drivers/zephyr/dts/bindings/sensor/zmk,battery-nrf-vddh.yaml diff --git a/app/drivers/sensor/CMakeLists.txt b/app/drivers/sensor/CMakeLists.txt index a4c2ba89..b549320f 100644 --- a/app/drivers/sensor/CMakeLists.txt +++ b/app/drivers/sensor/CMakeLists.txt @@ -1,5 +1,5 @@ -# Copyright (c) 2020 The ZMK Contributors +# Copyright (c) 2020-2021 The ZMK Contributors # SPDX-License-Identifier: MIT -add_subdirectory_ifdef(CONFIG_ZMK_BATTERY_VOLTAGE_DIVIDER battery_voltage_divider) +add_subdirectory_ifdef(CONFIG_ZMK_BATTERY battery) add_subdirectory_ifdef(CONFIG_EC11 ec11) \ No newline at end of file diff --git a/app/drivers/sensor/Kconfig b/app/drivers/sensor/Kconfig index 7b6a0d08..a828f6c6 100644 --- a/app/drivers/sensor/Kconfig +++ b/app/drivers/sensor/Kconfig @@ -1,5 +1,5 @@ # Copyright (c) 2020 The ZMK Contributors # SPDX-License-Identifier: MIT -rsource "battery_voltage_divider/Kconfig" +rsource "battery/Kconfig" rsource "ec11/Kconfig" \ No newline at end of file diff --git a/app/drivers/sensor/battery/CMakeLists.txt b/app/drivers/sensor/battery/CMakeLists.txt new file mode 100644 index 00000000..1203e53a --- /dev/null +++ b/app/drivers/sensor/battery/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2020-2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +zephyr_include_directories(.) + +zephyr_library() + +zephyr_library_sources(battery_common.c) +zephyr_library_sources_ifdef(CONFIG_ZMK_BATTERY_NRF_VDDH battery_nrf_vddh.c) +zephyr_library_sources_ifdef(CONFIG_ZMK_BATTERY_VOLTAGE_DIVIDER battery_voltage_divider.c) \ No newline at end of file diff --git a/app/drivers/sensor/battery/Kconfig b/app/drivers/sensor/battery/Kconfig new file mode 100644 index 00000000..fd8cd26d --- /dev/null +++ b/app/drivers/sensor/battery/Kconfig @@ -0,0 +1,21 @@ +# Copyright (c) 2020-2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config ZMK_BATTERY + bool "ZMK battery monitoring" + help + Enable battery monitoring + +config ZMK_BATTERY_NRF_VDDH + bool "ZMK nRF VDDH battery monitoring" + select ADC + select ZMK_BATTERY + help + Enable ZMK nRF VDDH voltage driver for battery monitoring. + +config ZMK_BATTERY_VOLTAGE_DIVIDER + bool "ZMK battery voltage divider" + select ADC + select ZMK_BATTERY + help + Enable ZMK battery voltage divider driver for battery monitoring. diff --git a/app/drivers/sensor/battery/battery_common.c b/app/drivers/sensor/battery/battery_common.c new file mode 100644 index 00000000..36e98aff --- /dev/null +++ b/app/drivers/sensor/battery/battery_common.c @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include "battery_common.h" + +int battery_channel_get(const struct battery_value *value, enum sensor_channel chan, + struct sensor_value *val_out) { + switch (chan) { + case SENSOR_CHAN_GAUGE_VOLTAGE: + val_out->val1 = value->millivolts / 1000; + val_out->val2 = (value->millivolts % 1000) * 1000U; + break; + + case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE: + val_out->val1 = value->state_of_charge; + val_out->val2 = 0; + break; + + default: + return -ENOTSUP; + } + + return 0; +} + +uint8_t lithium_ion_mv_to_pct(int16_t bat_mv) { + // Simple linear approximation of a battery based off adafruit's discharge graph: + // https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages + + if (bat_mv >= 4200) { + return 100; + } else if (bat_mv <= 3450) { + return 0; + } + + return bat_mv * 2 / 15 - 459; +} \ No newline at end of file diff --git a/app/drivers/sensor/battery/battery_common.h b/app/drivers/sensor/battery/battery_common.h new file mode 100644 index 00000000..d81c39e2 --- /dev/null +++ b/app/drivers/sensor/battery/battery_common.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +struct battery_value { + uint16_t adc_raw; + uint16_t millivolts; + uint8_t state_of_charge; +}; + +int battery_channel_get(const struct battery_value *value, enum sensor_channel chan, + struct sensor_value *val_out); + +uint8_t lithium_ion_mv_to_pct(int16_t bat_mv); diff --git a/app/drivers/sensor/battery/battery_nrf_vddh.c b/app/drivers/sensor/battery/battery_nrf_vddh.c new file mode 100644 index 00000000..90890344 --- /dev/null +++ b/app/drivers/sensor/battery/battery_nrf_vddh.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + * + * This is a simplified version of battery_voltage_divider.c which always reads + * the VDDHDIV5 channel of the &adc node and multiplies it by 5. + */ + +#define DT_DRV_COMPAT zmk_battery_nrf_vddh + +#include +#include +#include +#include +#include + +#include "battery_common.h" + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#define VDDHDIV (5) + +static const struct device *adc = DEVICE_DT_GET(DT_NODELABEL(adc)); + +struct vddh_data { + struct adc_channel_cfg acc; + struct adc_sequence as; + struct battery_value value; +}; + +static int vddh_sample_fetch(const struct device *dev, enum sensor_channel chan) { + // Make sure selected channel is supported + if (chan != SENSOR_CHAN_GAUGE_VOLTAGE && chan != SENSOR_CHAN_GAUGE_STATE_OF_CHARGE && + chan != SENSOR_CHAN_ALL) { + LOG_DBG("Selected channel is not supported: %d.", chan); + return -ENOTSUP; + } + + struct vddh_data *drv_data = dev->data; + struct adc_sequence *as = &drv_data->as; + + int rc = adc_read(adc, as); + as->calibrate = false; + + if (rc != 0) { + LOG_ERR("Failed to read ADC: %d", rc); + return rc; + } + + int32_t val = drv_data->value.adc_raw; + rc = adc_raw_to_millivolts(adc_ref_internal(adc), drv_data->acc.gain, as->resolution, &val); + if (rc != 0) { + LOG_ERR("Failed to convert raw ADC to mV: %d", rc); + return rc; + } + + drv_data->value.millivolts = val * VDDHDIV; + drv_data->value.state_of_charge = lithium_ion_mv_to_pct(drv_data->value.millivolts); + + LOG_DBG("ADC raw %d ~ %d mV => %d%%", drv_data->value.adc_raw, drv_data->value.millivolts, + drv_data->value.state_of_charge); + + return rc; +} + +static int vddh_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) { + struct vddh_data const *drv_data = dev->data; + return battery_channel_get(&drv_data->value, chan, val); +} + +static const struct sensor_driver_api vddh_api = { + .sample_fetch = vddh_sample_fetch, + .channel_get = vddh_channel_get, +}; + +static int vddh_init(const struct device *dev) { + struct vddh_data *drv_data = dev->data; + + if (!device_is_ready(adc)) { + LOG_ERR("ADC device is not ready %s", adc->name); + return -ENODEV; + } + + drv_data->as = (struct adc_sequence){ + .channels = BIT(0), + .buffer = &drv_data->value.adc_raw, + .buffer_size = sizeof(drv_data->value.adc_raw), + .oversampling = 4, + .calibrate = true, + }; + +#ifdef CONFIG_ADC_NRFX_SAADC + drv_data->acc = (struct adc_channel_cfg){ + .gain = ADC_GAIN_1_5, + .reference = ADC_REF_INTERNAL, + .acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40), + .input_positive = SAADC_CH_PSELN_PSELN_VDDHDIV5, + }; + + drv_data->as.resolution = 12; +#else +#error Unsupported ADC +#endif + + const int rc = adc_channel_setup(adc, &drv_data->acc); + LOG_DBG("VDDHDIV5 setup returned %d", rc); + + return rc; +} + +static struct vddh_data vddh_data; + +DEVICE_DT_INST_DEFINE(0, &vddh_init, device_pm_control_nop, &vddh_data, NULL, POST_KERNEL, + CONFIG_SENSOR_INIT_PRIORITY, &vddh_api); diff --git a/app/drivers/sensor/battery_voltage_divider/battery_voltage_divider.c b/app/drivers/sensor/battery/battery_voltage_divider.c similarity index 82% rename from app/drivers/sensor/battery_voltage_divider/battery_voltage_divider.c rename to app/drivers/sensor/battery/battery_voltage_divider.c index 9efd7fbd..8981fb3a 100644 --- a/app/drivers/sensor/battery_voltage_divider/battery_voltage_divider.c +++ b/app/drivers/sensor/battery/battery_voltage_divider.c @@ -7,11 +7,14 @@ #define DT_DRV_COMPAT zmk_battery_voltage_divider #include +#include #include #include #include #include +#include "battery_common.h" + LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); struct io_channel_config { @@ -37,24 +40,9 @@ struct bvd_data { const struct device *gpio; struct adc_channel_cfg acc; struct adc_sequence as; - uint16_t adc_raw; - uint16_t voltage; - uint8_t state_of_charge; + struct battery_value value; }; -static uint8_t lithium_ion_mv_to_pct(int16_t bat_mv) { - // Simple linear approximation of a battery based off adafruit's discharge graph: - // https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages - - if (bat_mv >= 4200) { - return 100; - } else if (bat_mv <= 3450) { - return 0; - } - - return bat_mv * 2 / 15 - 459; -} - static int bvd_sample_fetch(const struct device *dev, enum sensor_channel chan) { struct bvd_data *drv_data = dev->data; const struct bvd_config *drv_cfg = dev->config; @@ -87,18 +75,18 @@ static int bvd_sample_fetch(const struct device *dev, enum sensor_channel chan) as->calibrate = false; if (rc == 0) { - int32_t val = drv_data->adc_raw; + int32_t val = drv_data->value.adc_raw; adc_raw_to_millivolts(adc_ref_internal(drv_data->adc), drv_data->acc.gain, as->resolution, &val); uint16_t millivolts = val * (uint64_t)drv_cfg->full_ohm / drv_cfg->output_ohm; - LOG_DBG("ADC raw %d ~ %d mV => %d mV", drv_data->adc_raw, val, millivolts); + LOG_DBG("ADC raw %d ~ %d mV => %d mV", drv_data->value.adc_raw, val, millivolts); uint8_t percent = lithium_ion_mv_to_pct(millivolts); LOG_DBG("Percent: %d", percent); - drv_data->voltage = millivolts; - drv_data->state_of_charge = percent; + drv_data->value.millivolts = millivolts; + drv_data->value.state_of_charge = percent; } else { LOG_DBG("Failed to read ADC: %d", rc); } @@ -119,23 +107,7 @@ static int bvd_sample_fetch(const struct device *dev, enum sensor_channel chan) static int bvd_channel_get(const struct device *dev, enum sensor_channel chan, struct sensor_value *val) { struct bvd_data *drv_data = dev->data; - - switch (chan) { - case SENSOR_CHAN_GAUGE_VOLTAGE: - val->val1 = drv_data->voltage / 1000; - val->val2 = (drv_data->voltage % 1000) * 1000U; - break; - - case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE: - val->val1 = drv_data->state_of_charge; - val->val2 = 0; - break; - - default: - return -ENOTSUP; - } - - return 0; + return battery_channel_get(&drv_data->value, chan, val); } static const struct sensor_driver_api bvd_api = { @@ -173,8 +145,8 @@ static int bvd_init(const struct device *dev) { drv_data->as = (struct adc_sequence){ .channels = BIT(0), - .buffer = &drv_data->adc_raw, - .buffer_size = sizeof(drv_data->adc_raw), + .buffer = &drv_data->value.adc_raw, + .buffer_size = sizeof(drv_data->value.adc_raw), .oversampling = 4, .calibrate = true, }; diff --git a/app/drivers/sensor/battery_voltage_divider/CMakeLists.txt b/app/drivers/sensor/battery_voltage_divider/CMakeLists.txt deleted file mode 100644 index 4b7f042c..00000000 --- a/app/drivers/sensor/battery_voltage_divider/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) 2020 The ZMK Contributors -# SPDX-License-Identifier: MIT - -zephyr_library() - -zephyr_library_sources(battery_voltage_divider.c) \ No newline at end of file diff --git a/app/drivers/sensor/battery_voltage_divider/Kconfig b/app/drivers/sensor/battery_voltage_divider/Kconfig deleted file mode 100644 index 18c4ea3a..00000000 --- a/app/drivers/sensor/battery_voltage_divider/Kconfig +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2020 The ZMK Contributors -# SPDX-License-Identifier: MIT - -config ZMK_BATTERY_VOLTAGE_DIVIDER - bool "ZMK battery voltage divider" - select ADC - help - Enable ZMK battery voltage divider driver for battery monitoring. \ No newline at end of file diff --git a/app/drivers/zephyr/dts/bindings/sensor/zmk,battery-nrf-vddh.yaml b/app/drivers/zephyr/dts/bindings/sensor/zmk,battery-nrf-vddh.yaml new file mode 100644 index 00000000..a8904360 --- /dev/null +++ b/app/drivers/zephyr/dts/bindings/sensor/zmk,battery-nrf-vddh.yaml @@ -0,0 +1,11 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Battery SoC monitoring using nRF VDDH + +compatible: "zmk,battery-nrf-vddh" + +properties: + label: + required: true + type: string