drivers: Added fuel gauge max17048
Added support for fuel gauge max17048 Signed-off-by: Alvaro Garcia <maxpowel@gmail.com>
This commit is contained in:
parent
8fb4a18667
commit
2380710020
|
@ -1,5 +1,6 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
add_subdirectory_ifdef(CONFIG_SBS_GAUGE_NEW_API sbs_gauge)
|
||||
add_subdirectory_ifdef(CONFIG_MAX17048 max17048)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_USERSPACE fuel_gauge_syscall_handlers.c)
|
||||
|
|
|
@ -19,6 +19,8 @@ config FUEL_GAUGE_INIT_PRIORITY
|
|||
help
|
||||
Battery fuel gauge initialization priority.
|
||||
|
||||
source "drivers/fuel_gauge/max17048/Kconfig"
|
||||
|
||||
source "drivers/fuel_gauge/sbs_gauge/Kconfig"
|
||||
|
||||
endif # FUEL_GAUGE
|
||||
|
|
8
drivers/fuel_gauge/max17048/CMakeLists.txt
Normal file
8
drivers/fuel_gauge/max17048/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_library()
|
||||
|
||||
zephyr_library_sources(max17048.c)
|
||||
|
||||
zephyr_include_directories_ifdef(CONFIG_EMUL_MAX17048 .)
|
||||
zephyr_library_sources_ifdef(CONFIG_EMUL_MAX17048 ./emul_max17048.c)
|
20
drivers/fuel_gauge/max17048/Kconfig
Normal file
20
drivers/fuel_gauge/max17048/Kconfig
Normal file
|
@ -0,0 +1,20 @@
|
|||
# MAX17048 Li-Ion battery fuel gauge
|
||||
|
||||
# Copyright (c) 2023, Alvaro Garcia <maxpowel@gmail.com>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
||||
config MAX17048
|
||||
bool "MAX17048 Li-Po fuel gauge"
|
||||
default y
|
||||
depends on DT_HAS_MAXIM_MAX17048_ENABLED
|
||||
select I2C
|
||||
help
|
||||
Enable driver for the MAX17048 fuel gauge device.
|
||||
|
||||
config EMUL_MAX17048
|
||||
bool "Emulate an MAX17048 fuel gague"
|
||||
depends on EMUL
|
||||
help
|
||||
It provides readings which follow a simple sequence, thus allowing
|
||||
test code to check that things are working as expected.
|
152
drivers/fuel_gauge/max17048/emul_max17048.c
Normal file
152
drivers/fuel_gauge/max17048/emul_max17048.c
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* Copyright 2023, Alvaro Garcia <maxpowel@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Emulator for max17048 fuel gauge
|
||||
*/
|
||||
|
||||
|
||||
#define DT_DRV_COMPAT maxim_max17048
|
||||
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(maxim_max17048);
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/emul.h>
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
#include <zephyr/drivers/i2c_emul.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
|
||||
#include "max17048.h"
|
||||
|
||||
/** Static configuration for the emulator */
|
||||
struct max17048_emul_cfg {
|
||||
/** I2C address of emulator */
|
||||
uint16_t addr;
|
||||
};
|
||||
|
||||
static int emul_max17048_reg_write(const struct emul *target, int reg, int val)
|
||||
{
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int emul_max17048_reg_read(const struct emul *target, int reg, int *val)
|
||||
{
|
||||
|
||||
switch (reg) {
|
||||
case REGISTER_VERSION:
|
||||
*val = 0x1000;
|
||||
break;
|
||||
case REGISTER_CRATE:
|
||||
*val = 0x4000;
|
||||
break;
|
||||
case REGISTER_SOC:
|
||||
*val = 0x3525;
|
||||
break;
|
||||
case REGISTER_VCELL:
|
||||
*val = 0x4387;
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Unknown register 0x%x read", reg);
|
||||
return -EIO;
|
||||
}
|
||||
LOG_INF("read 0x%x = 0x%x", reg, *val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max17048_emul_transfer_i2c(const struct emul *target, struct i2c_msg *msgs,
|
||||
int num_msgs, int addr)
|
||||
{
|
||||
/* Largely copied from emul_bmi160.c */
|
||||
struct max17048_emul_data *data;
|
||||
unsigned int val;
|
||||
int reg;
|
||||
int rc;
|
||||
|
||||
data = target->data;
|
||||
|
||||
__ASSERT_NO_MSG(msgs && num_msgs);
|
||||
|
||||
i2c_dump_msgs_rw("emul", msgs, num_msgs, addr, false);
|
||||
switch (num_msgs) {
|
||||
case 2:
|
||||
if (msgs->flags & I2C_MSG_READ) {
|
||||
LOG_ERR("Unexpected read");
|
||||
return -EIO;
|
||||
}
|
||||
if (msgs->len != 1) {
|
||||
LOG_ERR("Unexpected msg0 length %d", msgs->len);
|
||||
return -EIO;
|
||||
}
|
||||
reg = msgs->buf[0];
|
||||
|
||||
/* Now process the 'read' part of the message */
|
||||
msgs++;
|
||||
if (msgs->flags & I2C_MSG_READ) {
|
||||
switch (msgs->len - 1) {
|
||||
case 1:
|
||||
rc = emul_max17048_reg_read(target, reg, &val);
|
||||
if (rc) {
|
||||
/* Return before writing bad value to message buffer */
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* SBS uses SMBus, which sends data in little-endian format. */
|
||||
sys_put_le16(val, msgs->buf);
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Unexpected msg1 length %d", msgs->len);
|
||||
return -EIO;
|
||||
}
|
||||
} else {
|
||||
/* We write a word (2 bytes by the SBS spec) */
|
||||
if (msgs->len != 2) {
|
||||
LOG_ERR("Unexpected msg1 length %d", msgs->len);
|
||||
}
|
||||
uint16_t value = sys_get_le16(msgs->buf);
|
||||
|
||||
rc = emul_max17048_reg_write(target, reg, value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Invalid number of messages: %d", num_msgs);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct i2c_emul_api max17048_emul_api_i2c = {
|
||||
.transfer = max17048_emul_transfer_i2c,
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up a new emulator (I2C)
|
||||
*
|
||||
* @param emul Emulation information
|
||||
* @param parent Device to emulate
|
||||
* @return 0 indicating success (always)
|
||||
*/
|
||||
static int emul_max17048_init(const struct emul *target, const struct device *parent)
|
||||
{
|
||||
ARG_UNUSED(target);
|
||||
ARG_UNUSED(parent);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Main instantiation macro.
|
||||
*/
|
||||
#define MAX17048_EMUL(n) \
|
||||
static const struct max17048_emul_cfg max17048_emul_cfg_##n = { \
|
||||
.addr = DT_INST_REG_ADDR(n), \
|
||||
}; \
|
||||
EMUL_DT_INST_DEFINE(n, emul_max17048_init, NULL, \
|
||||
&max17048_emul_cfg_##n, &max17048_emul_api_i2c, NULL)
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(MAX17048_EMUL)
|
293
drivers/fuel_gauge/max17048/max17048.c
Normal file
293
drivers/fuel_gauge/max17048/max17048.c
Normal file
|
@ -0,0 +1,293 @@
|
|||
/* max17048.c - Driver for max17048 battery fuel gauge */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2023 Alvaro Garcia Gomez <maxpowel@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT maxim_max17048
|
||||
|
||||
#include "max17048.h"
|
||||
|
||||
#include <zephyr/drivers/fuel_gauge.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/init.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/pm/device.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <zephyr/sys/__assert.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_REGISTER(MAX17048);
|
||||
|
||||
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 0
|
||||
#warning "MAX17048 driver enabled without any devices"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Storage for the fuel gauge basic information
|
||||
*/
|
||||
struct max17048_data {
|
||||
/* Charge as percentage */
|
||||
uint8_t charge;
|
||||
/* Voltage as mV */
|
||||
uint16_t voltage;
|
||||
|
||||
/* Time in minutes */
|
||||
uint16_t time_to_full;
|
||||
uint16_t time_to_empty;
|
||||
/* True if battery chargin, false if discharging */
|
||||
bool charging;
|
||||
};
|
||||
|
||||
/**
|
||||
* I2C communication
|
||||
* The way we read a value is first writing the address we want to read and then
|
||||
* wait for 2 bytes containing the data.
|
||||
*/
|
||||
int max17048_read_register(const struct device *dev, uint8_t registerId, uint16_t *response)
|
||||
{
|
||||
uint8_t max17048_buffer[2];
|
||||
const struct max17048_config *cfg = dev->config;
|
||||
int rc = i2c_write_read_dt(&cfg->i2c, ®isterId, sizeof(registerId), max17048_buffer,
|
||||
sizeof(max17048_buffer));
|
||||
if (rc != 0) {
|
||||
LOG_ERR("Unable to read register, error %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
*response = sys_get_be16(max17048_buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw value from the internal ADC
|
||||
*/
|
||||
int max17048_adc(const struct device *i2c_dev, uint16_t *response)
|
||||
{
|
||||
return max17048_read_register(i2c_dev, REGISTER_VCELL, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Battery voltage
|
||||
*/
|
||||
int max17048_voltage(const struct device *i2c_dev, uint16_t *response)
|
||||
{
|
||||
int rc = max17048_adc(i2c_dev, response);
|
||||
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
/**
|
||||
* Once the value is read, it has to be converted to volts. The datasheet
|
||||
* https://www.analog.com/media/en/technical-documentation/data-sheets/
|
||||
* MAX17048-MAX17049.pdf
|
||||
* Page 10, Table 2. Register Summary: 78.125µV/cell
|
||||
* Max17048 only supports one cell so we just have to multiply the value by 78.125 to
|
||||
* obtain µV and then divide the value to obtain V.
|
||||
* But to avoid floats, instead of using 78.125 we will use 78125 and use this value as
|
||||
* milli volts instead of volts.
|
||||
*/
|
||||
|
||||
*response = *response * 78125 / 1000000;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Battery percentage still available
|
||||
*/
|
||||
int max17048_percent(const struct device *i2c_dev, uint8_t *response)
|
||||
{
|
||||
uint16_t data;
|
||||
int rc = max17048_read_register(i2c_dev, REGISTER_SOC, &data);
|
||||
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
/**
|
||||
* Once the value is read, it has to be converted to percentage. The datasheet
|
||||
* https://www.analog.com/media/en/technical-documentation/data-she4ets/
|
||||
* MAX17048-MAX17049.pdf
|
||||
* Page 10, Table 2. Register Summary: 1%/256
|
||||
* So to obtain the total percentaje we just divide the read value by 256
|
||||
*/
|
||||
*response = data / 256;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Percentage of the total battery capacity per hour, positive is charging or
|
||||
* negative if discharging
|
||||
*/
|
||||
int max17048_crate(const struct device *i2c_dev, int16_t *response)
|
||||
{
|
||||
int rc = max17048_read_register(i2c_dev, REGISTER_CRATE, response);
|
||||
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Once the value is read, it has to be converted to something useful. The datasheet
|
||||
* https://www.analog.com/media/en/technical-documentation/data-sheets/
|
||||
* MAX17048-MAX17049.pdf
|
||||
* Page 11, Table 2. Register Summary (continued): 0.208%/hr
|
||||
* To avoid floats, the value will be multiplied by 208 instead of 0.208, taking into
|
||||
* account that the value will be 1000 times higher
|
||||
*/
|
||||
*response = *response * 208;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize and verify the chip. The datasheet says that the version register
|
||||
* should be 0x10. If not, or the chip is malfunctioning or it is not a MAX17048 at all
|
||||
*/
|
||||
static int max17048_init(const struct device *dev)
|
||||
{
|
||||
const struct max17048_config *cfg = dev->config;
|
||||
uint16_t version;
|
||||
int rc = max17048_read_register(dev, REGISTER_VERSION, &version);
|
||||
|
||||
if (!device_is_ready(cfg->i2c.bus)) {
|
||||
LOG_ERR("Bus device is not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Cannot read from I2C");
|
||||
return rc;
|
||||
}
|
||||
|
||||
version = version & 0xFFF0;
|
||||
if (version != 0x10) {
|
||||
LOG_ERR("Something found at the provided I2C address, but it is not a MAX17048");
|
||||
LOG_ERR("The version registers should be 0x10 but got %x. Maybe your wiring is "
|
||||
"wrong or it is a fake chip\n",
|
||||
version);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single property from the fuel gauge
|
||||
*/
|
||||
static int max17048_get_prop(const struct device *dev, struct fuel_gauge_get_property *prop)
|
||||
{
|
||||
struct max17048_data *data = dev->data;
|
||||
int rc = 0;
|
||||
|
||||
switch (prop->property_type) {
|
||||
case FUEL_GAUGE_RUNTIME_TO_EMPTY:
|
||||
prop->value.runtime_to_empty = data->time_to_empty;
|
||||
break;
|
||||
case FUEL_GAUGE_RUNTIME_TO_FULL:
|
||||
prop->value.runtime_to_full = data->time_to_full;
|
||||
break;
|
||||
case FUEL_GAUGE_STATE_OF_CHARGE:
|
||||
prop->value.state_of_charge = data->charge;
|
||||
break;
|
||||
case FUEL_GAUGE_VOLTAGE:
|
||||
prop->value.voltage = data->voltage;
|
||||
break;
|
||||
default:
|
||||
rc = -ENOTSUP;
|
||||
}
|
||||
|
||||
prop->status = rc;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all possible properties from the fuel gague
|
||||
*/
|
||||
static int max17048_get_props(const struct device *dev, struct fuel_gauge_get_property *props,
|
||||
size_t len)
|
||||
{
|
||||
int err_count = 0;
|
||||
struct max17048_data *data = dev->data;
|
||||
int rc = max17048_percent(dev, &data->charge);
|
||||
int16_t crate;
|
||||
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Error while reading battery percentage");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = max17048_voltage(dev, &data->voltage);
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Error while reading battery voltage");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crate (current rate) is the current percentage of the battery charged or drained
|
||||
* per hour
|
||||
*/
|
||||
rc = max17048_crate(dev, &crate);
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Error while reading battery current rate");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* May take some time until the chip detects the change between discharging to charging
|
||||
* (and vice versa) especially if your device consumes little power
|
||||
*/
|
||||
data->charging = crate > 0;
|
||||
|
||||
|
||||
/**
|
||||
* In the following code, we multiply by 1000 the charge to increase the precision. If we
|
||||
* just truncate the division without this multiplier, the precision lost is very
|
||||
* significant when converting it into minutes (the value given is in hours)
|
||||
*
|
||||
* The value coming from crate is already 1000 times higher (check the function
|
||||
* max17048_crate to
|
||||
* see the reason) so the multiplier for the charge
|
||||
* will be 1000000
|
||||
*/
|
||||
if (data->charging) {
|
||||
uint8_t percentage_pending = 100 - data->charge;
|
||||
uint32_t hours_pending = percentage_pending * 1000000 / crate;
|
||||
|
||||
data->time_to_empty = 0;
|
||||
data->time_to_full = hours_pending * 60 / 1000;
|
||||
} else {
|
||||
/* Discharging */
|
||||
uint32_t hours_pending = data->charge * 1000000 / -crate;
|
||||
|
||||
data->time_to_empty = hours_pending * 60 / 1000;
|
||||
data->time_to_full = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
int ret = max17048_get_prop(dev, props + i);
|
||||
|
||||
err_count += ret ? 1 : 0;
|
||||
}
|
||||
|
||||
err_count = (err_count == len) ? -1 : err_count;
|
||||
|
||||
return err_count;
|
||||
}
|
||||
|
||||
static const struct fuel_gauge_driver_api max17048_driver_api = {
|
||||
.get_property = &max17048_get_props,
|
||||
};
|
||||
|
||||
#define MAX17048_DEFINE(inst) \
|
||||
static struct max17048_data max17048_data_##inst; \
|
||||
\
|
||||
static const struct max17048_config max17048_config_##inst = { \
|
||||
.i2c = I2C_DT_SPEC_INST_GET(inst)}; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(inst, &max17048_init, NULL, &max17048_data_##inst, \
|
||||
&max17048_config_##inst, POST_KERNEL, \
|
||||
CONFIG_FUEL_GAUGE_INIT_PRIORITY, &max17048_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(MAX17048_DEFINE)
|
34
drivers/fuel_gauge/max17048/max17048.h
Normal file
34
drivers/fuel_gauge/max17048/max17048.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Alvaro Garcia Gomez <maxpowel@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
#ifndef ZEPHYR_DRIVERS_SENSOR_MAX17048_MAX17048_H_
|
||||
#define ZEPHYR_DRIVERS_SENSOR_MAX17048_MAX17048_H_
|
||||
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
|
||||
#define REGISTER_VCELL 0x02
|
||||
#define REGISTER_SOC 0x04
|
||||
#define REGISTER_MODE 0x06
|
||||
#define REGISTER_VERSION 0x08
|
||||
#define REGISTER_HIBRT 0x0A
|
||||
#define REGISTER_CONFIG 0x0C
|
||||
#define REGISTER_VALRT 0x14
|
||||
#define REGISTER_CRATE 0x16
|
||||
#define REGISTER_VRESET 0x18
|
||||
#define REGISTER_CHIP_ID 0x19
|
||||
#define REGISTER_STATUS 0x1A
|
||||
#define REGISTER_TABLE 0x40
|
||||
#define REGISTER_COMMAND 0xFE
|
||||
|
||||
#define RESET_COMMAND 0x5400
|
||||
#define QUICKSTART_MODE 0x4000
|
||||
|
||||
struct max17048_config {
|
||||
struct i2c_dt_spec i2c;
|
||||
};
|
||||
|
||||
#endif /* ZEPHYR_DRIVERS_SENSOR_MAX17048_MAX17048_H_ */
|
10
dts/bindings/fuel-gauge/maxim,max17048.yaml
Normal file
10
dts/bindings/fuel-gauge/maxim,max17048.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Copyright (c) 2023 Alvaro Garcia Gomez <maxpowel@gmail.com>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: |
|
||||
Maxim MAX17048 Fuel Gauge with ModelGauge. See more info at:
|
||||
https://www.maximintegrated.com/en/products/power/battery-management/MAX17048.html
|
||||
|
||||
compatible: "maxim,max17048"
|
||||
|
||||
include: [i2c-device.yaml, fuel-gauge.yaml]
|
10
samples/fuel_gauge/fuel_gauge.rst
Normal file
10
samples/fuel_gauge/fuel_gauge.rst
Normal file
|
@ -0,0 +1,10 @@
|
|||
.. _fuel_gauge-samples:
|
||||
|
||||
Fuel Gauge Samples
|
||||
##################
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
**/*
|
8
samples/fuel_gauge/max17048/CMakeLists.txt
Normal file
8
samples/fuel_gauge/max17048/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(max17048)
|
||||
|
||||
FILE(GLOB app_sources src/*.c)
|
||||
target_sources(app PRIVATE ${app_sources})
|
51
samples/fuel_gauge/max17048/README.rst
Normal file
51
samples/fuel_gauge/max17048/README.rst
Normal file
|
@ -0,0 +1,51 @@
|
|||
.. _MAX17048_sample:
|
||||
|
||||
MAX17048 Li-Ion battery fuel gauge
|
||||
###################################
|
||||
|
||||
Overview
|
||||
********
|
||||
|
||||
This sample shows how to use the Zephyr :ref:`fuel_gauge_api` API driver for the `MAX17048` fuel gauge.
|
||||
|
||||
.. _MAX17048: https://www.maximintegrated.com/en/products/power/battery-management/MAX17048.html
|
||||
|
||||
The sample periodically reads battery percentage and power status
|
||||
|
||||
Building and Running
|
||||
********************
|
||||
|
||||
The sample can be configured to support MAX17048 fuel gauge connected via either I2C. It only needs
|
||||
an I2C pin configuration
|
||||
|
||||
Features
|
||||
********
|
||||
By using this fuel gauge you can get the following information:
|
||||
* Battery charge status as percentage
|
||||
* Total time until battery is fully charged or discharged
|
||||
* Battery voltage
|
||||
* Charging state: if charging or discharging
|
||||
|
||||
|
||||
Notes
|
||||
*****
|
||||
The charging state and the time to full/empty are estimated and based on the last consumption average. That means that
|
||||
if you plug/unplug a charger it will take some time until it is actually detected by the chip. Don't try to plug/unplug
|
||||
to see in real time the charging status change because it will not work. If you really need to know exactly the moment
|
||||
when the battery is being charged you will need other method.
|
||||
|
||||
Sample output
|
||||
*************
|
||||
|
||||
```
|
||||
*** Booting Zephyr OS build 16043f62a40a ***
|
||||
Found device "max17048@36", getting fuel gauge data
|
||||
Time to empty 1911
|
||||
Time to full 0
|
||||
Charge 72%
|
||||
Voltage 3968
|
||||
Time to empty 1911
|
||||
Time to full 0
|
||||
Charge 72%
|
||||
Voltage 3968
|
||||
```
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Alvaro Garcia Gomez <maxpowel@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
&i2c0 {
|
||||
status = "okay";
|
||||
compatible = "nordic,nrf-twim";
|
||||
pinctrl-0 = <&i2c0_default>;
|
||||
pinctrl-1 = <&i2c0_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
max17048:max17048@36 {
|
||||
compatible = "maxim,max17048";
|
||||
status = "ok";
|
||||
reg = <0x36 >;
|
||||
};
|
||||
};
|
1
samples/fuel_gauge/max17048/prj.conf
Normal file
1
samples/fuel_gauge/max17048/prj.conf
Normal file
|
@ -0,0 +1 @@
|
|||
CONFIG_FUEL_GAUGE=y
|
9
samples/fuel_gauge/max17048/sample.yaml
Normal file
9
samples/fuel_gauge/max17048/sample.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
sample:
|
||||
name: MAX17048 Sensor sample
|
||||
tests:
|
||||
sample.sensor.max17048:
|
||||
build_only: true
|
||||
platform_allow: nrf52dk_nrf52832
|
||||
integration_platforms:
|
||||
- nrf52dk_nrf52832
|
||||
tags: fuel_gauge
|
104
samples/fuel_gauge/max17048/src/main.c
Normal file
104
samples/fuel_gauge/max17048/src/main.c
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Alvaro Garcia Gomez <maxpowel@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/devicetree.h>
|
||||
#include <zephyr/drivers/fuel_gauge.h>
|
||||
|
||||
|
||||
|
||||
void main(void)
|
||||
{
|
||||
const struct device *const dev = DEVICE_DT_GET_ANY(maxim_max17048);
|
||||
int ret = 0;
|
||||
|
||||
if (dev == NULL) {
|
||||
printk("\nError: no device found.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!device_is_ready(dev)) {
|
||||
printk("\nError: Device \"%s\" is not ready; "
|
||||
"check the driver initialization logs for errors.\n",
|
||||
dev->name);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
printk("Found device \"%s\", getting fuel gauge data\n", dev->name);
|
||||
|
||||
if (dev == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
|
||||
struct fuel_gauge_get_property props[] = {
|
||||
{
|
||||
.property_type = FUEL_GAUGE_RUNTIME_TO_EMPTY,
|
||||
},
|
||||
{
|
||||
.property_type = FUEL_GAUGE_RUNTIME_TO_FULL,
|
||||
},
|
||||
{
|
||||
.property_type = FUEL_GAUGE_STATE_OF_CHARGE,
|
||||
},
|
||||
{
|
||||
.property_type = FUEL_GAUGE_VOLTAGE,
|
||||
}
|
||||
};
|
||||
|
||||
ret = fuel_gauge_get_prop(dev, props, ARRAY_SIZE(props));
|
||||
if (ret < 0) {
|
||||
printk("Error: cannot get properties\n");
|
||||
} else {
|
||||
if (ret != 0) {
|
||||
printk("Warning: Some properties failed\n");
|
||||
}
|
||||
|
||||
if (props[0].status == 0) {
|
||||
printk("Time to empty %d\n", props[0].value.runtime_to_empty);
|
||||
} else {
|
||||
printk(
|
||||
"Property FUEL_GAUGE_RUNTIME_TO_EMPTY failed with error %d\n",
|
||||
props[0].status
|
||||
);
|
||||
}
|
||||
|
||||
if (props[1].status == 0) {
|
||||
printk("Time to full %d\n", props[1].value.runtime_to_full);
|
||||
} else {
|
||||
printk(
|
||||
"Property FUEL_GAUGE_RUNTIME_TO_FULL failed with error %d\n",
|
||||
props[1].status
|
||||
);
|
||||
}
|
||||
|
||||
if (props[2].status == 0) {
|
||||
printk("Charge %d%%\n", props[2].value.state_of_charge);
|
||||
} else {
|
||||
printk(
|
||||
"Property FUEL_GAUGE_STATE_OF_CHARGE failed with error %d\n",
|
||||
props[2].status
|
||||
);
|
||||
}
|
||||
|
||||
if (props[3].status == 0) {
|
||||
printk("Voltage %d\n", props[3].value.voltage);
|
||||
} else {
|
||||
printk(
|
||||
"Property FUEL_GAUGE_VOLTAGE failed with error %d\n",
|
||||
props[3].status
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
k_sleep(K_MSEC(5000));
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ Samples and Demos
|
|||
tfm_integration/tfm_integration.rst
|
||||
modules/*
|
||||
compression/*
|
||||
fuel_gauge/*
|
||||
|
||||
.. comment
|
||||
To add a new sample document, please use the template available under
|
||||
|
|
8
tests/drivers/fuel_gauge/max17048/CMakeLists.txt
Normal file
8
tests/drivers/fuel_gauge/max17048/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(device)
|
||||
|
||||
FILE(GLOB app_sources src/test_max17048.c)
|
||||
target_sources(app PRIVATE ${app_sources})
|
|
@ -0,0 +1,6 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
CONFIG_EMUL=y
|
||||
CONFIG_EMUL_MAX17048=y
|
||||
CONFIG_I2C=y
|
||||
CONFIG_I2C_EMUL=y
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
&i2c0 {
|
||||
max17048: max17048@36 {
|
||||
compatible = "maxim,max17048";
|
||||
reg = <0x36>;
|
||||
status = "okay";
|
||||
};
|
||||
};
|
7
tests/drivers/fuel_gauge/max17048/prj.conf
Normal file
7
tests/drivers/fuel_gauge/max17048/prj.conf
Normal file
|
@ -0,0 +1,7 @@
|
|||
CONFIG_ZTEST=y
|
||||
CONFIG_ZTEST_NEW_API=y
|
||||
CONFIG_I2C=y
|
||||
CONFIG_TEST_USERSPACE=y
|
||||
CONFIG_LOG=y
|
||||
|
||||
CONFIG_FUEL_GAUGE=y
|
114
tests/drivers/fuel_gauge/max17048/src/test_max17048.c
Normal file
114
tests/drivers/fuel_gauge/max17048/src/test_max17048.c
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Alvaro Garcia Gomez <maxpowel@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/fuel_gauge.h>
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
#include <zephyr/ztest.h>
|
||||
#include <zephyr/ztest_assert.h>
|
||||
|
||||
struct max17048_fixture {
|
||||
const struct device *dev;
|
||||
const struct fuel_gauge_driver_api *api;
|
||||
};
|
||||
|
||||
static void *max17048_setup(void)
|
||||
{
|
||||
static ZTEST_DMEM struct max17048_fixture fixture;
|
||||
|
||||
fixture.dev = DEVICE_DT_GET_ANY(maxim_max17048);
|
||||
|
||||
k_object_access_all_grant(fixture.dev);
|
||||
|
||||
zassert_true(device_is_ready(fixture.dev), "Fuel Gauge not found");
|
||||
|
||||
return &fixture;
|
||||
}
|
||||
|
||||
ZTEST_USER_F(max17048, test_get_all_props_failed_returns_negative)
|
||||
{
|
||||
struct fuel_gauge_get_property props[] = {
|
||||
{
|
||||
/* Invalid property */
|
||||
.property_type = FUEL_GAUGE_PROP_MAX,
|
||||
},
|
||||
};
|
||||
|
||||
int ret = fuel_gauge_get_prop(fixture->dev, props, ARRAY_SIZE(props));
|
||||
|
||||
zassert_equal(props[0].status, -ENOTSUP, "Getting bad property %d has a good status.",
|
||||
props[0].property_type);
|
||||
|
||||
zassert_true(ret < 0);
|
||||
}
|
||||
|
||||
ZTEST_USER_F(max17048, test_get_some_props_failed_returns_failed_prop_count)
|
||||
{
|
||||
struct fuel_gauge_get_property props[] = {
|
||||
{
|
||||
/* First invalid property */
|
||||
.property_type = FUEL_GAUGE_PROP_MAX,
|
||||
},
|
||||
{
|
||||
/* Second invalid property */
|
||||
.property_type = FUEL_GAUGE_PROP_MAX,
|
||||
},
|
||||
{
|
||||
/* Valid property */
|
||||
.property_type = FUEL_GAUGE_VOLTAGE,
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
int ret = fuel_gauge_get_prop(fixture->dev, props, ARRAY_SIZE(props));
|
||||
|
||||
zassert_equal(props[0].status, -ENOTSUP, "Getting bad property %d has a good status.",
|
||||
props[0].property_type);
|
||||
|
||||
zassert_equal(props[1].status, -ENOTSUP, "Getting bad property %d has a good status.",
|
||||
props[1].property_type);
|
||||
|
||||
zassert_ok(props[2].status, "Property %d getting %d has a bad status.", 2,
|
||||
props[2].property_type);
|
||||
|
||||
zassert_equal(ret, 2);
|
||||
}
|
||||
|
||||
|
||||
ZTEST_USER_F(max17048, test_get_props__returns_ok)
|
||||
{
|
||||
/* Validate what props are supported by the driver */
|
||||
|
||||
struct fuel_gauge_get_property props[] = {
|
||||
{
|
||||
.property_type = FUEL_GAUGE_RUNTIME_TO_EMPTY,
|
||||
},
|
||||
{
|
||||
.property_type = FUEL_GAUGE_RUNTIME_TO_FULL,
|
||||
},
|
||||
{
|
||||
.property_type = FUEL_GAUGE_STATE_OF_CHARGE,
|
||||
},
|
||||
{
|
||||
.property_type = FUEL_GAUGE_VOLTAGE,
|
||||
}
|
||||
};
|
||||
|
||||
int ret = fuel_gauge_get_prop(fixture->dev, props, ARRAY_SIZE(props));
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(props); i++) {
|
||||
zassert_ok(props[i].status, "Property %d getting %d has a bad status.", i,
|
||||
props[i].property_type);
|
||||
}
|
||||
|
||||
zassert_ok(ret);
|
||||
}
|
||||
|
||||
|
||||
ZTEST_SUITE(max17048, NULL, max17048_setup, NULL, NULL, NULL);
|
5
tests/drivers/fuel_gauge/max17048/testcase.yaml
Normal file
5
tests/drivers/fuel_gauge/max17048/testcase.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
tests:
|
||||
# section.subsection
|
||||
drivers.max17048:
|
||||
filter: dt_compat_enabled("maxim,max17048")
|
||||
platform_allow: native_posix
|
Loading…
Reference in a new issue