drivers: sensors: smart-battery with SBS 1.1 compliant fuel gauge

Implementation of a SBS 1.1 compliant fuel gauge driver

Signed-off-by: MORGER Patrick <patrick.morger@leica-geosystems.com>
This commit is contained in:
MORGER Patrick 2021-04-08 08:46:34 +02:00 committed by Kumar Gala
commit 0e1d16bf03
17 changed files with 620 additions and 4 deletions

View file

@ -67,6 +67,7 @@ add_subdirectory_ifdef(CONFIG_PMS7003 pms7003)
add_subdirectory_ifdef(CONFIG_QDEC_NRFX qdec_nrfx)
add_subdirectory_ifdef(CONFIG_QDEC_SAM qdec_sam)
add_subdirectory_ifdef(CONFIG_TEMP_NRF5 nrf5)
add_subdirectory_ifdef(CONFIG_SBS_GAUGE sbs_gauge)
add_subdirectory_ifdef(CONFIG_SHT3XD sht3xd)
add_subdirectory_ifdef(CONFIG_SI7006 si7006)
add_subdirectory_ifdef(CONFIG_SI7055 si7055)

View file

@ -174,6 +174,8 @@ source "drivers/sensor/qdec_nrfx/Kconfig"
source "drivers/sensor/qdec_sam/Kconfig"
source "drivers/sensor/sbs_gauge/Kconfig"
source "drivers/sensor/sht3xd/Kconfig"
source "drivers/sensor/si7006/Kconfig"

View file

@ -0,0 +1,3 @@
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_SBS_GAUGE sbs_gauge.c)

View file

@ -0,0 +1,8 @@
# Copyright (c) 2021 Leica Geosystems AG
# SPDX-License-Identifier: Apache-2.0
config SBS_GAUGE
bool "Smart Battery Fuel Gauge"
depends on I2C
help
Enable I2C-based/SMBus-based driver for a Smart Battery Fuel Gauge.

View file

@ -0,0 +1,272 @@
/*
* Copyright (c) 2021 Leica Geosystems AG
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT sbs_sbs_gauge
#include <drivers/i2c.h>
#include <drivers/sensor.h>
#include <sys/byteorder.h>
#include "sbs_gauge.h"
#include <logging/log.h>
LOG_MODULE_REGISTER(sbs_gauge, CONFIG_SENSOR_LOG_LEVEL);
static int sbs_cmd_reg_read(const struct device *dev,
uint8_t reg_addr,
uint16_t *val)
{
const struct sbs_gauge_config *cfg;
uint8_t i2c_data[2];
int status;
cfg = (struct sbs_gauge_config *)dev->config;
status = i2c_burst_read(cfg->i2c_dev, cfg->i2c_addr, reg_addr,
i2c_data, ARRAY_SIZE(i2c_data));
if (status < 0) {
LOG_ERR("Unable to read register");
return status;
}
*val = sys_get_le16(i2c_data);
return 0;
}
/**
* @brief sensor value get
*
* @return -ENOTSUP for unsupported channels
*/
static int sbs_gauge_channel_get(const struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
struct sbs_gauge_data *data;
int32_t int_temp;
data = (struct sbs_gauge_data *)dev->data;
val->val2 = 0;
switch (chan) {
case SENSOR_CHAN_GAUGE_VOLTAGE:
val->val1 = data->voltage / 1000;
val->val2 = (data->voltage % 1000) * 1000;
break;
case SENSOR_CHAN_GAUGE_AVG_CURRENT:
val->val1 = data->avg_current / 1000;
val->val2 = (data->avg_current % 1000) * 1000;
break;
case SENSOR_CHAN_GAUGE_TEMP:
int_temp = (data->internal_temperature * 10);
int_temp = int_temp - 27315;
val->val1 = int_temp / 100;
val->val2 = (int_temp % 100) * 1000000;
break;
case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE:
val->val1 = data->state_of_charge;
break;
case SENSOR_CHAN_GAUGE_FULL_CHARGE_CAPACITY:
val->val1 = data->full_charge_capacity;
break;
case SENSOR_CHAN_GAUGE_REMAINING_CHARGE_CAPACITY:
val->val1 = data->remaining_charge_capacity;
break;
case SENSOR_CHAN_GAUGE_NOM_AVAIL_CAPACITY:
val->val1 = data->nom_avail_capacity;
break;
case SENSOR_CHAN_GAUGE_FULL_AVAIL_CAPACITY:
val->val1 = data->full_avail_capacity;
break;
case SENSOR_CHAN_GAUGE_TIME_TO_EMPTY:
val->val1 = data->time_to_empty;
break;
case SENSOR_CHAN_GAUGE_TIME_TO_FULL:
val->val1 = data->time_to_full;
break;
case SENSOR_CHAN_GAUGE_CYCLE_COUNT:
val->val1 = data->cycle_count;
break;
default:
return -ENOTSUP;
}
return 0;
}
static int sbs_gauge_sample_fetch(const struct device *dev,
enum sensor_channel chan)
{
struct sbs_gauge_data *data;
int status = 0;
data = (struct sbs_gauge_data *)dev->data;
switch (chan) {
case SENSOR_CHAN_GAUGE_VOLTAGE:
status = sbs_cmd_reg_read(dev,
SBS_GAUGE_CMD_VOLTAGE,
&data->voltage);
if (status < 0) {
LOG_ERR("Failed to read voltage");
}
break;
case SENSOR_CHAN_GAUGE_AVG_CURRENT:
status = sbs_cmd_reg_read(dev,
SBS_GAUGE_CMD_AVG_CURRENT,
&data->avg_current);
if (status < 0) {
LOG_ERR("Failed to read average current ");
}
break;
case SENSOR_CHAN_GAUGE_TEMP:
status = sbs_cmd_reg_read(dev,
SBS_GAUGE_CMD_TEMP,
&data->internal_temperature);
if (status < 0) {
LOG_ERR("Failed to read internal temperature");
}
break;
case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE:
status = sbs_cmd_reg_read(dev,
SBS_GAUGE_CMD_ASOC,
&data->state_of_charge);
if (status < 0) {
LOG_ERR("Failed to read state of charge");
}
break;
case SENSOR_CHAN_GAUGE_FULL_CHARGE_CAPACITY:
status = sbs_cmd_reg_read(dev,
SBS_GAUGE_CMD_FULL_CAPACITY,
&data->full_charge_capacity);
if (status < 0) {
LOG_ERR("Failed to read full charge capacity");
}
break;
case SENSOR_CHAN_GAUGE_REMAINING_CHARGE_CAPACITY:
status = sbs_cmd_reg_read(dev,
SBS_GAUGE_CMD_REM_CAPACITY,
&data->remaining_charge_capacity);
if (status < 0) {
LOG_ERR("Failed to read remaining charge capacity");
}
break;
case SENSOR_CHAN_GAUGE_NOM_AVAIL_CAPACITY:
status = sbs_cmd_reg_read(dev,
SBS_GAUGE_CMD_NOM_CAPACITY,
&data->nom_avail_capacity);
if (status < 0) {
LOG_ERR("Failed to read nominal available capacity");
}
break;
case SENSOR_CHAN_GAUGE_FULL_AVAIL_CAPACITY:
status = sbs_cmd_reg_read(dev,
SBS_GAUGE_CMD_FULL_CAPACITY,
&data->full_avail_capacity);
if (status < 0) {
LOG_ERR("Failed to read full available capacity");
}
break;
case SENSOR_CHAN_GAUGE_TIME_TO_EMPTY:
status = sbs_cmd_reg_read(dev,
SBS_GAUGE_CMD_AVG_TIME2EMPTY,
&data->time_to_empty);
data->time_to_empty = (data->time_to_empty) & 0x00FF;
if (status < 0) {
LOG_ERR("Failed to read time to empty");
}
break;
case SENSOR_CHAN_GAUGE_TIME_TO_FULL:
status = sbs_cmd_reg_read(dev,
SBS_GAUGE_CMD_AVG_TIME2FULL,
&data->time_to_full);
data->time_to_full = (data->time_to_full) & 0x00FF;
if (status < 0) {
LOG_ERR("Failed to read time to full");
}
break;
case SENSOR_CHAN_GAUGE_CYCLE_COUNT:
status = sbs_cmd_reg_read(dev,
SBS_GAUGE_CMD_CYCLE_COUNT,
&data->cycle_count);
data->cycle_count = (data->cycle_count) & 0x00FF;
if (status < 0) {
LOG_ERR("Failed to read cycle count");
}
break;
default:
return -ENOTSUP;
}
return status;
}
/**
* @brief initialize the fuel gauge
*
* @return 0 for success
*/
static int sbs_gauge_init(const struct device *dev)
{
struct sbs_gauge_config *cfg;
cfg = (struct sbs_gauge_config *)dev->config;
if (!device_is_ready(cfg->i2c_dev)) {
LOG_ERR("%s device is not ready", cfg->i2c_dev->name);
return -ENODEV;
}
return 0;
}
static const struct sensor_driver_api sbs_gauge_driver_api = {
.sample_fetch = sbs_gauge_sample_fetch,
.channel_get = sbs_gauge_channel_get,
};
#define SBS_GAUGE_INIT(index) \
static struct sbs_gauge_data sbs_gauge_driver_##index; \
\
static const struct sbs_gauge_config sbs_gauge_config_##index = { \
.i2c_dev = DEVICE_DT_GET(DT_INST_BUS(index)), \
.i2c_addr = DT_INST_REG_ADDR(index), \
}; \
\
DEVICE_DT_INST_DEFINE(index, \
&sbs_gauge_init, \
NULL, \
&sbs_gauge_driver_##index, \
&sbs_gauge_config_##index, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&sbs_gauge_driver_api);
DT_INST_FOREACH_STATUS_OKAY(SBS_GAUGE_INIT)

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2021 Leica Geosystems AG
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_SBS_GAUGE_H_
#define ZEPHYR_DRIVERS_SENSOR_SBS_GAUGE_H_
#include <stdint.h>
/*** Standard Commands ***/
#define SBS_GAUGE_CMD_MANUFACTURER_ACCESS 0x00 /* ManufacturerAccess */
#define SBS_GAUGE_CMD_REM_CAPACITY_ALARM 0x01 /* LowCapacityAlarmThreshold */
#define SBS_GAUGE_CMD_REM_TIME_ALARM 0x02 /* RemainingTimeToEmptyThreshold */
#define SBS_GAUGE_CMD_BATTERY_MODE 0x03 /* BatteryOperatingMode */
#define SBS_GAUGE_CMD_AR 0x04 /* AtRate */
#define SBS_GAUGE_CMD_ARTTF 0x05 /* AtRateTimeToFull */
#define SBS_GAUGE_CMD_ARTTE 0x06 /* AtRateTimeToEmpty */
#define SBS_GAUGE_CMD_AROK 0x07 /* AtRateOK */
#define SBS_GAUGE_CMD_TEMP 0x08 /* Temperature */
#define SBS_GAUGE_CMD_VOLTAGE 0x09 /* Voltage */
#define SBS_GAUGE_CMD_CURRENT 0x0A /* Current */
#define SBS_GAUGE_CMD_AVG_CURRENT 0x0B /* AverageCurrent */
#define SBS_GAUGE_CMD_MAX_ERROR 0x0C /* MaxError */
#define SBS_GAUGE_CMD_RSOC 0x0D /* RelativeStateOfCharge */
#define SBS_GAUGE_CMD_ASOC 0x0E /* AbsoluteStateOfCharge */
#define SBS_GAUGE_CMD_REM_CAPACITY 0x0F /* RemainingCapacity */
#define SBS_GAUGE_CMD_FULL_CAPACITY 0x10 /* FullChargeCapacity */
#define SBS_GAUGE_CMD_RUNTIME2EMPTY 0x11 /* RunTimeToEmpty */
#define SBS_GAUGE_CMD_AVG_TIME2EMPTY 0x12 /* AverageTimeToEmpty */
#define SBS_GAUGE_CMD_AVG_TIME2FULL 0x13 /* AverageTimeToFull */
#define SBS_GAUGE_CMD_CHG_CURRENT 0x14 /* ChargeCurrent */
#define SBS_GAUGE_CMD_CHG_VOLTAGE 0x15 /* ChargeVoltage */
#define SBS_GAUGE_CMD_FLAGS 0x16 /* BatteryStatus */
#define SBS_GAUGE_CMD_CYCLE_COUNT 0x17 /* CycleCount */
#define SBS_GAUGE_CMD_NOM_CAPACITY 0x18 /* DesignCapacity */
#define SBS_GAUGE_CMD_DESIGN_VOLTAGE 0x19 /* DesignVoltage */
#define SBS_GAUGE_CMD_SPECS_INFO 0x1A /* SpecificationInfo */
#define SBS_GAUGE_CMD_MANUFACTURER_DATE 0x1B /* ManufacturerDate */
#define SBS_GAUGE_CMD_SN 0x1C /* SerialNumber */
#define SBS_GAUGE_CMD_MANUFACTURER_NAME 0x20 /* ManufacturerName */
#define SBS_GAUGE_CMD_DEVICE_NAME 0x21 /* DeviceName */
#define SBS_GAUGE_CMD_DEVICE_CHEM 0x22 /* DeviceChemistry */
#define SBS_GAUGE_CMD_MANUFACTURER_DATA 0x23 /* ManufacturerData */
#define SBS_GAUGE_CMD_DESIGN_MAX_POWER 0x24 /* DesignMaxPower */
#define SBS_GAUGE_CMD_START_TIME 0x25 /* StartTime */
#define SBS_GAUGE_CMD_TOTAL_RUNTIME 0x26 /* TotalRuntime */
#define SBS_GAUGE_CMD_FC_TEMP 0x27 /* FCTemp */
#define SBS_GAUGE_CMD_FC_STATUS 0x28 /* FCStatus */
#define SBS_GAUGE_CMD_FC_MODE 0x29 /* FCMode */
#define SBS_GAUGE_CMD_AUTO_SOFT_OFF 0x2A /* AutoSoftOff */
#define SBS_GAUGE_CMD_AUTHENTICATE 0x2F /* Authenticate */
#define SBS_GAUGE_CMD_CELL_V4 0x3C /* CellVoltage4 */
#define SBS_GAUGE_CMD_CELL_V3 0x3D /* CellVoltage3 */
#define SBS_GAUGE_CMD_CELL_V2 0x3E /* CellVoltage2 */
#define SBS_GAUGE_CMD_CELL_V1 0x3F /* CellVoltage1 */
#define SBS_GAUGE_DELAY 1000
struct sbs_gauge_data {
uint16_t voltage;
int16_t avg_current;
uint16_t state_of_charge;
uint16_t internal_temperature;
uint16_t full_charge_capacity;
uint16_t remaining_charge_capacity;
uint16_t nom_avail_capacity;
uint16_t full_avail_capacity;
uint16_t time_to_empty;
uint16_t time_to_full;
uint16_t cycle_count;
};
struct sbs_gauge_config {
const struct device *i2c_dev;
uint8_t i2c_addr;
};
#endif

View file

@ -0,0 +1,5 @@
description: SBS 1.1 compliant fuel gauge (http://www.sbs-forum.org/specs)
compatible: "sbs,sbs-gauge"
include: i2c-device.yaml

View file

@ -12,11 +12,11 @@ Tests
drivers:
build all drivers
sensors_a_m:
build sensors with name beginning a through m.
sensors_a_h:
build sensors with name beginning a through h.
sensors_n_z:
build sensors with name beginning n through z.
sensors_i_z:
build sensors with name beginning i through z.
sensors_trigger:
build sensors with trigger option enabled

View file

@ -633,3 +633,9 @@ test_i2c_bmp388: bmp388@4c {
reg = <0x4c>;
int-gpios = <&test_gpio 0 0>;
};
test_i2c_sbc_gauge: sbsgauge@4d {
compatible = "sbs,sbs-gauge";
label = "SBSGAUGE";
reg = <0x4d>;
};

View file

@ -40,6 +40,7 @@ CONFIG_SM351LT=y
CONFIG_SX9500=y
CONFIG_TACH_NPCX=y
CONFIG_TEMP_NRF5=y
CONFIG_SBS_GAUGE=y
CONFIG_TH02=y
CONFIG_TI_HDC=y
CONFIG_TMP007=y

View file

@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(device)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2021 Leica Geosystems AG
*
* SPDX-License-Identifier: Apache-2.0
*/
&i2c1 {
clock-frequency = <I2C_BITRATE_STANDARD>;
timings = <32000000 I2C_BITRATE_STANDARD 0x10E03E52>;
smartbattery0: smartbattery@b {
compatible = "sbs,sbs-gauge";
reg = <0x0B>;
label = "SMARTBATTERY";
status = "okay";
};
};

View file

@ -0,0 +1,6 @@
CONFIG_ZTEST=y
CONFIG_SENSOR=y
CONFIG_I2C=y
CONFIG_SBS_GAUGE=y
CONFIG_TEST_USERSPACE=y
CONFIG_LOG=y

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2021 Leica Geosystems AG
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ztest.h>
#include "test_sbs_gauge.h"
void test_main(void)
{
ztest_test_suite(framework_tests,
ztest_unit_test(test_get_gauge_voltage),
ztest_unit_test(test_get_gauge_current),
ztest_unit_test(test_get_stdby_current),
ztest_unit_test(test_get_max_load_current),
ztest_unit_test(test_get_temperature),
ztest_unit_test(test_get_soc),
ztest_unit_test(test_get_full_charge_capacity),
ztest_unit_test(test_get_rem_charge_capacity),
ztest_unit_test(test_get_nom_avail_capacity),
ztest_unit_test(test_get_full_avail_capacity),
ztest_unit_test(test_get_average_power),
ztest_unit_test(test_get_average_time_to_empty),
ztest_unit_test(test_get_average_time_to_full),
ztest_unit_test(test_get_cycle_count),
ztest_unit_test(test_get_design_voltage),
ztest_unit_test(test_get_desired_voltage),
ztest_unit_test(test_get_desired_chg_current),
ztest_unit_test(test_not_supported_channel)
);
ztest_run_test_suite(framework_tests);
}

View file

@ -0,0 +1,130 @@
/*
* Copyright (c) 2021 Leica Geosystems AG
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <ztest.h>
#include <drivers/sensor.h>
const struct device *get_fuel_gauge_device(void)
{
const struct device *dev = DEVICE_DT_GET_ANY(sbs_sbs_gauge);
zassert_true(device_is_ready(dev), "Fuel Gauge not found");
return dev;
}
void test_get_sensor_value(int16_t channel)
{
struct sensor_value value;
const struct device *dev = get_fuel_gauge_device();
zassert_true(sensor_sample_fetch_chan(dev, channel) < 0, "Sample fetch failed");
zassert_true(sensor_channel_get(dev, channel, &value) < 0, "Get sensor value failed");
}
void test_get_sensor_value_not_supp(int16_t channel)
{
const struct device *dev = get_fuel_gauge_device();
zassert_true(sensor_sample_fetch_chan(dev, channel) == -ENOTSUP, "Invalid function");
}
void test_get_gauge_voltage(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_VOLTAGE);
}
void test_get_gauge_current(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_AVG_CURRENT);
}
void test_get_stdby_current(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_STDBY_CURRENT);
}
void test_get_max_load_current(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_MAX_LOAD_CURRENT);
}
void test_get_temperature(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_TEMP);
}
void test_get_soc(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_STATE_OF_CHARGE);
}
void test_get_full_charge_capacity(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_FULL_CHARGE_CAPACITY);
}
void test_get_rem_charge_capacity(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_REMAINING_CHARGE_CAPACITY);
}
void test_get_nom_avail_capacity(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_NOM_AVAIL_CAPACITY);
}
void test_get_full_avail_capacity(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_FULL_AVAIL_CAPACITY);
}
void test_get_average_power(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_AVG_POWER);
}
void test_get_average_time_to_empty(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_TIME_TO_EMPTY);
}
void test_get_average_time_to_full(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_TIME_TO_FULL);
}
void test_get_cycle_count(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_CYCLE_COUNT);
}
void test_get_design_voltage(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_DESIGN_VOLTAGE);
}
void test_get_desired_voltage(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_DESIRED_VOLTAGE);
}
void test_get_desired_chg_current(void)
{
test_get_sensor_value(SENSOR_CHAN_GAUGE_DESIRED_CHARGING_CURRENT);
}
void test_not_supported_channel(void)
{
uint8_t channel;
for (channel = SENSOR_CHAN_ACCEL_X; channel <= SENSOR_CHAN_RPM; channel++) {
test_get_sensor_value_not_supp(channel);
}
/* SOH is not defined in the SBS 1.1 specifications */
test_get_sensor_value_not_supp(SENSOR_CHAN_GAUGE_STATE_OF_HEALTH);
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2021 Leica Geosystems AG
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __TEST_SBS_GAUGE_H_
#define __TEST_SBS_GAUGE_H_
const struct device *get_fuel_gauge_device(void);
void test_get_sensor_value(int16_t channel);
void test_get_sensor_value_not_supp(int16_t channel);
void test_get_gauge_voltage(void);
void test_get_gauge_current(void);
void test_get_stdby_current(void);
void test_get_max_load_current(void);
void test_get_temperature(void);
void test_get_soc(void);
void test_get_full_charge_capacity(void);
void test_get_rem_charge_capacity(void);
void test_get_nom_avail_capacity(void);
void test_get_full_avail_capacity(void);
void test_get_average_power(void);
void test_get_average_time_to_empty(void);
void test_get_average_time_to_full(void);
void test_get_cycle_count(void);
void test_get_design_voltage(void);
void test_get_desired_voltage(void);
void test_get_desired_chg_current(void);
void test_not_supported_channel(void);
#endif /* __TEST_SBS_GAUGE_H_ */

View file

@ -0,0 +1,8 @@
tests:
# section.subsection
testing.drivers.sbs_gauge:
build_only: true
tags: test_framework
filter: dt_compat_enabled("sbs,sbs-gauge")
integration_platforms:
- nucleo_f070rb