1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-17 05:12:57 +01:00

drivers/ltc4150: (Re-)implemented LTC4150 driver

The LTC4150 is a coulomb counter (a.k.a. battery sensor or bidirectional
current sensor) that is used in the MSBA2 board and available for little money
as easy to use break out board.
This commit is contained in:
Marian Buschsieweke 2018-12-30 00:24:34 +01:00
parent 5acd87bf6f
commit c00886ac39
No known key found for this signature in database
GPG Key ID: 61F64C6599B1539F
6 changed files with 584 additions and 0 deletions

View File

@ -273,6 +273,16 @@ ifneq (,$(filter lsm6dsl,$(USEMODULE)))
USEMODULE += xtimer
endif
ifneq (,$(filter ltc4150_bidirectional,$(USEMODULE)))
USEMODULE += ltc4150
endif
ifneq (,$(filter ltc4150,$(USEMODULE)))
FEATURES_REQUIRED += periph_gpio
FEATURES_REQUIRED += periph_gpio_irq
USEMODULE += xtimer
endif
ifneq (,$(filter mag3110,$(USEMODULE)))
FEATURES_REQUIRED += periph_i2c
endif

View File

@ -162,6 +162,10 @@ ifneq (,$(filter lsm6dsl,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/lsm6dsl/include
endif
ifneq (,$(filter ltc4150,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/ltc4150/include
endif
ifneq (,$(filter mag3110,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/mag3110/include
endif

283
drivers/include/ltc4150.h Normal file
View File

@ -0,0 +1,283 @@
/*
* Copyright 2019 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup drivers_ltc4150 LTC4150 coulomb counter
* @ingroup drivers_sensors
* @brief Driver for the Linear Tech LTC4150 Coulomb Counter
* (a.k.a. battery gauge sensor or power consumption sensor)
*
* # Wiring the LTC4150
* Hint: M Grusin thankfully created an
* [open hardware breakout board](https://cdn.sparkfun.com/datasheets/BreakoutBoards/LTC4150_BOB_v10.pdf).
* As a result, virtually all LTC4150 breakout boards are using this schematic.
* Whenever this documentation refers to a breakout board, this open hardware
* board is meant. Of course, this driver works with the "bare" LTC4150 as well.
*
* Please note that this driver works interrupt driven and does not clear the
* signal. Thus, the /CLR and /INT pins on the LTC4150 need to be connected
* (in case of the breakout board: close solder jumper SJ1), so that the signal
* is automatically cleared.
*
* Hint: The breakout board uses external pull up resistors on /INT, POL and
* /SHDN. Therefore /SHDN can be left unconnected and no internal pull ups are
* required for /INT and POL. In case your board uses 3.3V logic the solder
* jumpers SJ2 and SJ3 have to be closed, in case of 5V they have to remain
* open. Connect the VIO pin to the logic level, GND to ground, IN+ and IN- to
* the power supply and use OUT+ and OUT- to power your board.
*
* In the easiest case only the /INT pin needs to be connected to a GPIO,
* and (in case of external pull ups) /SHDN and POL can be left unconnected.
* The GPIO /INT is connected to support for interrupts, /SHDN and POL
* (if connected) do not require interrupt support.
*
* In case a battery is used the POL pin connected to another GPIO. This allows
* to distinguish between charge drawn from the battery and charge transferred
* into the battery (used to load it).
*
* In case support to power off the LTC4150 is desired, the /SHDN pin needs to
* be connected to a third GPIO.
*
* # Things to keep in mind
* The LTC4150 creates pulses with a frequency depending on the current drawn.
* Thus, more interrupts need to be handled when more current is drawn, which
* in turn increases system load (and power consumption). The interrupt service
* routing is quite short and even when used outside of specification less than
* 20 ticks per second will occur. Hence, this effect should hopefully be
* negligible.
*
* @{
*
* @file
* @brief LTC4150 coulomb counter
*
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*/
#ifndef LTC4150_H
#define LTC4150_H
#include <stdint.h>
#include "mutex.h"
#include "periph/gpio.h"
#include "xtimer.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Configuration flags of the LTC4150 coulomb counter
*/
enum {
/**
* @brief External pull on the /INT pin is present
*/
LTC4150_INT_EXT_PULL_UP = 0x01,
/**
* @brief External pull on the /POL pin is present
*/
LTC4150_POL_EXT_PULL_UP = 0x02,
/**
* @brief External pull on the /INT *and* the /POL pin is present
*/
LTC4150_EXT_PULL_UP = LTC4150_INT_EXT_PULL_UP | LTC4150_POL_EXT_PULL_UP,
};
/**
* @brief Enumeration of directions in which the charge can be transferred
*/
typedef enum {
LTC4150_CHARGE, /**< The battery is charged */
LTC4150_DISCHARGE, /**< Charge is drawn from the battery */
} ltc4150_dir_t;
/**
* @brief LTC4150 coulomb counter
*/
typedef struct ltc4150_dev ltc4150_dev_t;
/**
* @brief Interface to allow recording of the drawn current in a user defined
* resolution
*
* @note Keep in mind that the data recording may be performed by the CPU of
* the system to monitor - thus keep power consumption for the recording
* low!
*
* The LTC4150 driver will only track total charge transferred (separately for
* charging in discharging direction). However, there are use cases that
* required more precise data recording, e.g. a rolling average of the last
* minute. This interface allows application developers to implement the ideal
* trade-off between RAM, ROM and runtime overhead for the data recording and
* the level of information they require.
*/
typedef struct {
/**
* @brief Function to call on every pulse received from the LTC4150
* @warning This function is called in interrupt context
*
* @param[in] dev The device the pulse was received from
* @param[in] dir Direction in which the charge is transferred
* @param[in] now_usec The system time the pulse was received in µs
* @param[in] arg (Optional) argument for this callback
*/
void (*pulse)(ltc4150_dev_t *dev, ltc4150_dir_t dir, uint64_t now_usec, void *arg);
/**
* @brief Function to call upon driver initialization or reset
*
* @see ltc4150_init
* @see ltc4150_reset_counters
*
* @param[in] dev The LTC4150 device to monitor
* @param[in] now_usec The system time the pulse was received in µs
* @param[in] arg (Optional) argument for this callback
*/
void (*reset)(ltc4150_dev_t *dev, uint64_t now_usec, void *arg);
} ltc4150_recorder_t;
/**
* @brief Parameters required to set up the LTC4150 coulomb counter
*/
typedef struct {
/**
* @brief Pin going LOW every time a specific charge is drawn, labeled INT
*/
gpio_t interrupt;
/**
* @brief Pin indicating (dis-)charging, labeled POL
*
* Set this pin to `GPIO_UNDEF` to tread every pulse as discharging. This
* pin is pulled low by the LTC4150 in case the battery is discharging.
*/
gpio_t polarity;
/**
* @brief Pin to power off the LTC4150 coulomb counter, labeled SHDN
*
* Set this pin to `GPIO_UNDEF` if the SHDN pin is not connected to the MCU
*/
gpio_t shutdown;
/**
* @brief Pulse per ampere hour of charge
*
* pulses = 3600 * 32.55 * R
*
* Where R is the resistance (in Ohm) between the SENSE+ and SENSE- pins.
* E.g. the MSBA2 has 0.390 Ohm (==> 45700 pulses), while most breakout
* boards for the LTC4150 have 0.050 Ohm (==> 5859 pulses).
*/
uint16_t pulses_per_ah;
/**
* @brief Configuration flags controlling if inter pull ups are required
*
* Most [breakout boards](https://cdn.sparkfun.com/datasheets/BreakoutBoards/LTC4150_BOB_v10.pdf)
* and the MSBA2 board use external pull up resistors, so no internal pull
* ups are required. Clear the flags to use internal pull ups instead.
*/
uint16_t flags;
/**
* @brief `NULL` or a `NULL`-terminated array of data recorders
* @pre If not `NULL`, the last element of the array must be `NULL`
*/
ltc4150_recorder_t **recorders;
/**
* @brief `NULL` or an array of the user defined data for each recorder
* @pre If @see ltc4150_params_t::recorders is not `NULL`, this must point
* to an array of `void`-Pointers of the same length.
* @note Unlike @see ltc4150_param_t::callback, this array does not need to
* be `NULL`-terminated
*/
void **recorder_data;
} ltc4150_params_t;
/**
* @brief LTC4150 coulomb counter
*/
struct ltc4150_dev {
ltc4150_params_t params; /**< Parameter of the LTC4150 coulomb counter */
uint32_t start_sec; /**< Time stamp when started counting */
uint32_t last_update_sec; /**< Time stamp of last pulse */
uint32_t charged; /**< # of pulses for charging (POL=high) */
uint32_t discharged; /**< # of pulses for discharging (POL=low) */
};
/**
* @brief Initialize the LTC4150 driver
*
* @param dev Device to initialize
* @param params Information on how the LTC4150 is conntected
*
* @retval 0 Success
* @retval -EINVAL Called with invalid argument(s)
* @retval -EIO IO failure (`gpio_init()`/`gpio_init_int()` failed)
*/
int ltc4150_init(ltc4150_dev_t *dev, const ltc4150_params_t *params);
/**
* @brief Clear current counters of the given LTC4150 device
* @param dev The LTC4150 device to clear current counters from
*
* @retval 0 Success
* @retval -EINVAL Called with an invalid argument
*/
int ltc4150_reset_counters(ltc4150_dev_t *dev);
/**
* @brief Disable the interrupt handler and turn the chip off
*
* @param dev Previously initialized device to power off
*
* @retval 0 Success
* @retval -EINVAL Called with invalid argument(s)
*
* The driver can be reinitialized to power on the LTC4150 chip again
*/
int ltc4150_shutdown(ltc4150_dev_t *dev);
/**
* @brief Get the measured charge since boot or last reset in
* millicoulomb
*
* @param dev The LTC4150 device to read data from
* @param[out] charged The charge transferred in charging direction
* @param[out] discharged The charge transferred in discharging direction
*
* @retval 0 Success
* @retval -EINVAL Called with an invalid argument
*
* Passing `NULL` for `charged` or `discharged` is allowed, if only one
* information is of interest.
*/
int ltc4150_charge(ltc4150_dev_t *dev, uint32_t *charged, uint32_t *discharged);
/**
* @brief Get the average current drawn in E-01 milliampere
*
* This will return the average current drawn since boot or last reset until the
* last pulse from the LTC4150 was received. The value might thus be a bit
* outdated (0.8 seconds for the breakout board and a current of 100mA, 79
* seconds for a current of 1mA).
*
* @param dev The LTC4150 device to read data from
* @param[out] dest Store the average current drawn in E-01 milliampere here
*
* @retval 0 Success
* @retval -EINVAL Called with an invalid argument
* @retval -EAGAIN Called before enough data samples have been acquired.
* (Wait for at least one second or one pulse from the
LTC4150, whichever takes longer.)
*/
int ltc4150_avg_current(ltc4150_dev_t *dev, int16_t *dest);
#ifdef __cplusplus
}
#endif
#endif /* LTC4150_H */
/** @} */

1
drivers/ltc4150/Makefile Normal file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2019 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_ltc4150
*
* @{
* @file
* @brief Default configuration for LTC4150 coulomb counters
*
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*/
#ifndef LTC4150_PARAMS_H
#define LTC4150_PARAMS_H
#include "board.h"
#include "ltc4150.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name Set default configuration parameters for the LTC4150
* @{
*/
#ifndef LTC4150_PARAM_INT
#define LTC4150_PARAM_INT (GPIO_PIN(0, 4))
#endif
#ifndef LTC4150_PARAM_POL
#define LTC4150_PARAM_POL (GPIO_UNDEF)
#endif
#ifndef LTC4150_PARAM_SHUTDOWN
#define LTC4150_PARAM_SHUTDOWN (GPIO_PIN(0, 5))
#endif
#ifndef LTC4150_PARAM_PULSES
#define LTC4150_PARAM_PULSES (45700U)
#endif
#ifndef LTC4150_PARAM_FLAGS
#define LTC4150_PARAM_FLAGS LTC4150_EXT_PULL_UP
#endif
#ifndef LTC4150_PARAM_RECS
#define LTC4150_PARAM_RECS NULL
#define LTC4150_PARAM_RECDATA NULL
#endif
#ifndef LTC4150_PARAMS
#define LTC4150_PARAMS { .interrupt = LTC4150_PARAM_INT, \
.polarity = LTC4150_PARAM_POL, \
.shutdown = LTC4150_PARAM_SHUTDOWN, \
.pulses_per_ah = LTC4150_PARAM_PULSES, \
.flags = LTC4150_PARAM_FLAGS, \
.recorders = LTC4150_PARAM_RECS, \
.recorder_data = LTC4150_PARAM_RECDATA }
#endif
/**@}*/
/**
* @brief Configure LTC4150 devices
*/
static const ltc4150_params_t ltc4150_params[] =
{
LTC4150_PARAMS
};
#ifdef __cplusplus
}
#endif
#endif /* LTC4150_PARAMS_H */
/** @} */

210
drivers/ltc4150/ltc4150.c Normal file
View File

@ -0,0 +1,210 @@
/*
* Copyright 2019 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_ltc4150
* @{
*
* @file
* @brief LTC4150 Device Driver
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* @}
*/
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include "ltc4150.h"
#include "xtimer.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
static void pulse_cb(void *_dev)
{
uint64_t now;
ltc4150_dir_t dir;
ltc4150_dev_t *dev = _dev;
if ((dev->params.polarity == GPIO_UNDEF) ||
(!gpio_read(dev->params.polarity))
) {
dev->discharged++;
dir = LTC4150_DISCHARGE;
}
else {
dev->charged++;
dir = LTC4150_CHARGE;
}
now = xtimer_now_usec64();
if (dev->params.recorders) {
assert(dev->params.recorder_data);
for (unsigned i = 0; dev->params.recorders[i] != NULL; i++) {
dev->params.recorders[i]->pulse(dev, dir, now,
dev->params.recorder_data[i]);
}
}
dev->last_update_sec = now / US_PER_SEC;
}
int ltc4150_init(ltc4150_dev_t *dev, const ltc4150_params_t *params)
{
if (!dev || !params) {
return -EINVAL;
}
memset(dev, 0, sizeof(ltc4150_dev_t));
dev->params = *params;
if (dev->params.shutdown != GPIO_UNDEF) {
/* Activate LTC4150 */
if (gpio_init(dev->params.shutdown, GPIO_OUT)) {
DEBUG("[ltc4150] Failed to initialize shutdown pin");
return -EIO;
}
gpio_set(dev->params.shutdown);
}
if (dev->params.polarity != GPIO_UNDEF) {
gpio_mode_t mode = (dev->params.flags & LTC4150_POL_EXT_PULL_UP) ?
GPIO_IN : GPIO_IN_PU;
if (gpio_init(dev->params.polarity, mode)) {
DEBUG("[ltc4150] Failed to initialize polarity pin");
return -EIO;
}
}
gpio_mode_t mode = (dev->params.flags & LTC4150_INT_EXT_PULL_UP) ?
GPIO_IN : GPIO_IN_PU;
if (gpio_init_int(dev->params.interrupt, mode, GPIO_FALLING,
pulse_cb, dev)
) {
DEBUG("[ltc4150] Failed to initialize interrupt pin");
return -EIO;
}
ltc4150_reset_counters(dev);
DEBUG("[ltc4150] Initialized successfully");
return 0;
}
int ltc4150_reset_counters(ltc4150_dev_t *dev)
{
uint64_t now = xtimer_now_usec64();
if (!dev) {
return -EINVAL;
}
gpio_irq_disable(dev->params.interrupt);
dev->charged = 0;
dev->discharged = 0;
dev->last_update_sec = dev->start_sec = now / US_PER_SEC;
if (dev->params.recorders) {
assert(dev->params.recorder_data);
for (unsigned i = 0; dev->params.recorders[i] != NULL; i++) {
dev->params.recorders[i]->reset(dev, now, dev->params.recorder_data[i]);
}
}
gpio_irq_enable(dev->params.interrupt);
return 0;
}
int ltc4150_shutdown(ltc4150_dev_t *dev)
{
if (!dev) {
return -EINVAL;
}
gpio_irq_disable(dev->params.interrupt);
if (dev->params.shutdown != GPIO_UNDEF) {
gpio_clear(dev->params.shutdown);
}
return 0;
}
/**
* @brief Convert the raw data (# pulses) acquired by the LTC4150 device to
* charge information in millicoulomb
*
* @param dev LTC4150 device the data was received from
* @param[out] charged Charge in charging direction is stored here
* @param[out] discharged Charge in discharging direction is stored here
* @param[in] raw_charged Number of pulses in charging direction
* @param[in] raw_discharged Number of pulses in discharging direction
*/
static void get_coulomb(const ltc4150_dev_t *dev,
uint32_t *charged, uint32_t *discharged,
uint32_t raw_charged,
uint32_t raw_discharged)
{
uint64_t tmp;
if (charged) {
tmp = raw_charged;
tmp *= 3600000;
tmp += dev->params.pulses_per_ah >> 1;
tmp /= dev->params.pulses_per_ah;
*charged = tmp;
}
if (discharged) {
tmp = raw_discharged;
tmp *= 3600000;
tmp += dev->params.pulses_per_ah >> 1;
tmp /= dev->params.pulses_per_ah;
*discharged = tmp;
}
}
int ltc4150_charge(ltc4150_dev_t *dev, uint32_t *charged, uint32_t *discharged)
{
if (!dev) {
return -EINVAL;
}
gpio_irq_disable(dev->params.interrupt);
get_coulomb(dev, charged, discharged, dev->charged, dev->discharged);
gpio_irq_enable(dev->params.interrupt);
return 0;
}
int ltc4150_avg_current(ltc4150_dev_t *dev, int16_t *dest)
{
int32_t duration, charged, discharged;;
int retval;
retval = ltc4150_charge(dev, (uint32_t *)&charged, (uint32_t *)&discharged);
if (retval) {
return retval;
}
duration = dev->last_update_sec - dev->start_sec;
if (!duration) {
/* Called before one second of date or one pulse acquired. Prevent
* division by zero by returning -EAGAIN.
*/
return -EAGAIN;
}
/* From millicoloumb (=mAs) to E-01 mA */
*dest = ((discharged - charged) * 10) / duration;
return 0;
}