Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions boards/msba2/Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ ifneq (,$(filter netdev_default gnrc_netdev_default,$(USEMODULE)))
endif

ifneq (,$(filter saul_default,$(USEMODULE)))
USEMODULE += ltc4150
USEMODULE += sht11
endif
10 changes: 10 additions & 0 deletions drivers/Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,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
Expand Down
4 changes: 4 additions & 0 deletions drivers/Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,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
Expand Down
236 changes: 236 additions & 0 deletions drivers/include/ltc4150.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/*
* Copyright 2018 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this detailed text should go below the first @brief and before @{ to be directly visible in the doxygen html output.

* 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 module `ltc4150_bidirectional` should also be
* added and 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"

#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 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;
} ltc4150_params_t;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding a ltc4150_params_t member to ltc4150_dev_t would allow to deduplicate a bit


/**
* @brief Data structure storing the number of pulses for (dis-)charging
*
* In addition to the total number of pulses the accumulated pulses are stored
* for:
* - Every second in the last minute
* - The last minute
*/
typedef struct {
uint32_t total; /**< Total number of pulses */
uint8_t ringbuf[6]; /**< Ring buffer to allow O(1) update of the `last_min` */
uint16_t last_min; /**< Charge transferred in the last minute */
uint8_t counter; /**< Temporary counter used as source for the ring buffer */
} ltc4150_data_t;

/**
* @brief LTC4150 coulomb counter
*/
typedef struct {
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 */
#ifdef MODULE_LTC4150_BIDIRECTIONAL
ltc4150_data_t charged; /**< # of pulses for charging (POL=high) */
#endif
ltc4150_data_t discharged; /**< # of pulses for discharging (POL=low) */
uint8_t position; /**< Offset of the first element in the ring buf */
} ltc4150_dev_t;

/**
* @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 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 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_total_charge(ltc4150_dev_t *dev,
int32_t *charged, int32_t *discharged);

/**
* @brief Get the accumulated charge of the last minute 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. Divide the result by 60 to get the average
* current in milliampere.
*/
int ltc4150_last_minute_charge(ltc4150_dev_t *dev, int32_t *charged,
int32_t *discharged);

/**
* @brief Get the average current drawn in E-01 milliampere
*
* @param dev The LTC4150 device to read data from
* @param[out] dest The result is stored here
*
* @retval 0 Success
* @retval -EINVAL Called with an invalid argument
*/
int ltc4150_avg_current(ltc4150_dev_t *dev, int16_t *dest);
#ifdef __cplusplus
}
#endif

#endif /* LTC4150_H */
/** @} */
2 changes: 2 additions & 0 deletions drivers/include/saul.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ enum {
SAUL_SENSE_OCCUP = 0x91, /**< sensor: occupancy */
SAUL_SENSE_PROXIMITY= 0x92, /**< sensor: proximity */
SAUL_SENSE_RSSI = 0x93, /**< sensor: RSSI */
SAUL_SENSE_CHARGE = 0x94, /**< sensor: coulomb counter */
SAUL_SENSE_CURRENT = 0x95, /**< sensor: ammeter */
SAUL_CLASS_ANY = 0xff /**< any device - wildcard */
/* extend this list as needed... */
};
Expand Down
1 change: 1 addition & 0 deletions drivers/ltc4150/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base
90 changes: 90 additions & 0 deletions drivers/ltc4150/include/ltc4150_params.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright (C) 2018 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"
#include "saul_reg.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))
Comment thread
smlng marked this conversation as resolved.
#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 (45700)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here parentheses are good, but add better write (45700UL) to explicitly make it unsigned

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not agree on the parentheses remarks. In my opinion there should be a reasonable rule to where to add them. A reasonable rule to could be one of the following to me:

  1. add enclosing parentheses in every preprocessor macro, just to be safe
  2. add enclosing parentheses when order of precedence bugs can be introduced

A bug could be introduced e.g. in:

#define FOO 3 + 4

int bar = 2 * FOO; /* (2 * 3) + 4 not 2 * (3 + 4) */

But a precedence bug cannot be introduced by an integer constant. So unless adding enclosing parentheses everywhere, no parentheses are needed here. But if enclosing parentheses would be needed everywhere, they should also be placed around GPIO_PIN(0, 4).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fine, but then leave parentheses in place as is.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR: go for rule 1

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the UL suffix I also disagree. According to the C99 standard in section 6.4.4.1 a decimal constant without any suffix will have the smallest integer type of the following list: int, long int, long long int. Suffixes will only be needed in two cases:

  1. The decimal number does fit into unsigned long long int, but not into long long int
  2. The decimal number is used in expressions in which could overflow the automatically determent type of the number, e.g. uint64_t foo = 42 << 32;

(Explaining 2.: The 42 would fit into int, thus this would be the determent type. However, shifting any int by 32 bits to the left will result in 0. Thus, foo will have the value 0 here. Using uint64_t foo = 42ULL << 32 would have the expected result - so here the suffix would be needed for correct operation.)

Suffixes are really rarely needed. But they make reading the code harder. Adding them only were actually harm could occur will be more readable in my opinion and raise awareness of the actual reason, why the suffixes are needed in some places.

@smlng smlng Jan 8, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to clarify (from my side) on the suggested UL suffix:

  1. we had lots of issue with signed/unsigned comparison errors/warnings, hence the U
  2. RIOT also supports 8bit platforms such as the atmega arduinos and (IIRC) it was mostly required to explicitly state UL, bc w/o any suffix or just U 45700 will not fit into a 16bit int.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe U only would also do for the arduinos, but not sure about that

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point with the signed/unsigned comparison. There should be no issue with the Arduino, as the compiler will (according to the standard) just fall back to long int or even long long int if the decimal number is too big.

But honestly: I believe if the compiler does complain about signed/unsigned comparison with a constant decimal, I would consider this as a compiler bug. The compiler is aware of the value of the constant decimal number and of the type of the other side of the comparison. This should be enough to tell for sure if the comparison will work as expected or not.

#endif
#ifndef LTC4150_PARAM_FLAGS
#define LTC4150_PARAM_FLAGS LTC4150_EXT_PULL_UP
#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 }
#endif
/** @} */

/**
* @name Set default SAUL info text for the LTC4150
* @{
*/
#ifndef LTC4150_SAULINFO
#define LTC4150_SAULINFO { .name = "LTC4150 total charge" }, \
{ .name = "LTC4150 last minute charge" }, \
{ .name = "LTC4150 average current" }
#endif
/** @} */

/**
* @brief Configure LTC4150 devices
*/
static const ltc4150_params_t ltc4150_params[] =
{
LTC4150_PARAMS
};

/**
* @brief Allocate and configure entries to the SAUL registry
*/
static const saul_reg_info_t ltc4150_saul_info[] =
{
LTC4150_SAULINFO
};

#ifdef __cplusplus
}
#endif

#endif /* LTC4150_PARAMS_H */
/** @} */
Loading