From 195379a9b83573f203f48c1757957ed1e540d77b Mon Sep 17 00:00:00 2001 From: fabian18 Date: Tue, 5 Nov 2019 12:29:42 +0100 Subject: [PATCH 1/3] drivers/ina3221: INA3221 driver implementation --- drivers/Makefile.dep | 10 + drivers/Makefile.include | 4 + drivers/ina3221/Makefile | 7 + drivers/ina3221/alerts.c | 57 + drivers/ina3221/ina3221.c | 686 ++++++++++++ drivers/ina3221/include/ina3221_defines.h | 60 ++ drivers/ina3221/include/ina3221_internal.h | 133 +++ drivers/ina3221/include/ina3221_params.h | 144 +++ drivers/ina3221/include/ina3221_regs.h | 58 + drivers/include/ina3221.h | 1117 ++++++++++++++++++++ makefiles/pseudomodules.inc.mk | 3 +- 11 files changed, 2278 insertions(+), 1 deletion(-) create mode 100644 drivers/ina3221/Makefile create mode 100644 drivers/ina3221/alerts.c create mode 100644 drivers/ina3221/ina3221.c create mode 100644 drivers/ina3221/include/ina3221_defines.h create mode 100644 drivers/ina3221/include/ina3221_internal.h create mode 100644 drivers/ina3221/include/ina3221_params.h create mode 100644 drivers/ina3221/include/ina3221_regs.h create mode 100644 drivers/include/ina3221.h diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 8b15fbd4b792..4607c762813d 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -214,6 +214,16 @@ ifneq (,$(filter ina220,$(USEMODULE))) FEATURES_REQUIRED += periph_i2c endif +ifneq (,$(filter ina3221,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio + FEATURES_REQUIRED += periph_i2c +endif + +ifneq (,$(filter ina3221_alerts,$(USEMODULE))) + USEMODULE += ina3221 + USEMODULE += periph_gpio_irq +endif + ifneq (,$(filter io1_xplained,$(USEMODULE))) FEATURES_REQUIRED += periph_gpio FEATURES_REQUIRED += periph_adc diff --git a/drivers/Makefile.include b/drivers/Makefile.include index 5cc2aa3b3b6a..3b8ea0fe1d54 100644 --- a/drivers/Makefile.include +++ b/drivers/Makefile.include @@ -118,6 +118,10 @@ ifneq (,$(filter ina220,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/drivers/ina220/include endif +ifneq (,$(filter ina3221,$(USEMODULE))) + USEMODULE_INCLUDES += $(RIOTBASE)/drivers/ina3221/include +endif + ifneq (,$(filter io1_xplained,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/drivers/io1_xplained/include endif diff --git a/drivers/ina3221/Makefile b/drivers/ina3221/Makefile new file mode 100644 index 000000000000..85e9f95fef54 --- /dev/null +++ b/drivers/ina3221/Makefile @@ -0,0 +1,7 @@ +# enable submodules +SUBMODULES := 1 + +# Always build base module and SAUL integration (rely on linker to garbage collect SAUL) +SRC := ina3221.c ina3221_saul.c + +include $(RIOTBASE)/Makefile.base diff --git a/drivers/ina3221/alerts.c b/drivers/ina3221/alerts.c new file mode 100644 index 000000000000..68af79396f19 --- /dev/null +++ b/drivers/ina3221/alerts.c @@ -0,0 +1,57 @@ +/* + * 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_ina3221 + * @{ + * + * @file + * @brief Functions to enable/disable GPIO alerts + * for INA3221 + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include "periph/gpio.h" +#include "ina3221.h" + +int _ina3221_enable_alert(ina3221_t *dev, ina3221_alert_t alert, + ina3221_alert_cb_t cb, void *arg) +{ + if (alert >= INA3221_NUM_ALERTS) { + return -ERANGE; + } + if (dev->params.upins.apins.alert_pins[alert] == GPIO_UNDEF) { + return -ENOTSUP; + } + dev->alert_callbacks[alert] = cb; + dev->alert_callback_arguments[alert] = arg; + int check = gpio_init_int( + dev->params.upins.apins.alert_pins[alert], + (dev->params.gpio_config & (1 << alert)) ? GPIO_IN_PU : GPIO_IN, + GPIO_FALLING, + cb, + arg + ); + return check ? check : INA3221_OK; +} + +int _ina3221_disable_alert(ina3221_t *dev, ina3221_alert_t alert) +{ + if (alert >= INA3221_NUM_ALERTS) { + return -ERANGE; + } + if (dev->params.upins.apins.alert_pins[alert] == GPIO_UNDEF) { + return -ENOTSUP; + } + gpio_irq_disable(dev->params.upins.apins.alert_pins[alert]); + return INA3221_OK; +} diff --git a/drivers/ina3221/ina3221.c b/drivers/ina3221/ina3221.c new file mode 100644 index 000000000000..269c5423900e --- /dev/null +++ b/drivers/ina3221/ina3221.c @@ -0,0 +1,686 @@ +/* + * 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_ina3221 + * @{ + * + * @file + * @brief Device driver implementation for Texas Instruments INA3221 + * three-channel, high-side current and bus voltage + * monitor + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include +#include "byteorder.h" +#include "ina3221_internal.h" +#include "ina3221_params.h" +#include "ina3221_regs.h" +#include "ina3221.h" +#define ENABLE_DEBUG (0) +#include "debug.h" + +typedef struct { + uint8_t chi_reg_shunt; + uint8_t chi_reg_bus; + uint8_t chi_reg_crit_alert_limit; + uint8_t chi_reg_warn_alert_limit; +} ina3221_channel_info_t; + +static ina3221_channel_info_t _chi[INA3221_NUM_CH] = { + { + .chi_reg_shunt = INA3221_REG_CH1_SHUNT_VOLTAGE, + .chi_reg_bus = INA3221_REG_CH1_BUS_VOLTAGE, + .chi_reg_crit_alert_limit = INA3221_REG_CH1_CRIT_ALERT_LIMIT, + .chi_reg_warn_alert_limit = INA3221_REG_CH1_WARN_ALERT_LIMIT + }, + { + .chi_reg_shunt = INA3221_REG_CH2_SHUNT_VOLTAGE, + .chi_reg_bus = INA3221_REG_CH2_BUS_VOLTAGE, + .chi_reg_crit_alert_limit = INA3221_REG_CH2_CRIT_ALERT_LIMIT, + .chi_reg_warn_alert_limit = INA3221_REG_CH2_WARN_ALERT_LIMIT + }, + { + .chi_reg_shunt = INA3221_REG_CH3_SHUNT_VOLTAGE, + .chi_reg_bus = INA3221_REG_CH3_BUS_VOLTAGE, + .chi_reg_crit_alert_limit = INA3221_REG_CH3_CRIT_ALERT_LIMIT, + .chi_reg_warn_alert_limit = INA3221_REG_CH3_WARN_ALERT_LIMIT + } +}; + +/** + * @brief Read register value + * + * @param[in] dev Device handle + * @param[in] reg Register address + * @param[out] out Output register value + * + * @post @p out is in host byte order + * + * @return 0, on success + * @return -INA3221_I2C_ERROR, if i2c bus acquirement failed + * @return @see i2c_read_regs + */ +static int _read_reg(const ina3221_t *dev, uint8_t reg, uint16_t *out) +{ + if (i2c_acquire(dev->params.i2c)) { + return -INA3221_I2C_ERROR; + } + int status = i2c_read_regs(dev->params.i2c, dev->params.addr, reg, out, + INA3221_REG_LEN, 0); + i2c_release(dev->params.i2c); + if (status < 0) { + return status; + } + *out = ntohs(*out); + return 0; +} + +/** + * @brief Write register value + * + * @param[in] dev Device handle + * @param[in] reg Register address + * @param[out] in Input register value + * + * @pre @p in must be in host byte order + * + * @return 0, on success + * @return -INA3221_I2C_ERROR, if i2c bus acquirement failed + * @return @see i2c_write_regs + */ +static int _write_reg(const ina3221_t *dev, uint8_t reg, uint16_t in) +{ + in = htons(in); + if (i2c_acquire(dev->params.i2c)) { + return -INA3221_I2C_ERROR; + } + int status = i2c_write_regs(dev->params.i2c, dev->params.addr, reg, &in, + INA3221_REG_LEN, 0); + i2c_release(dev->params.i2c); + if (status < 0) { + return status; + } + return 0; +} + +int ina3221_reset(ina3221_t *dev) +{ + uint16_t config; + int status = _write_reg(dev, INA3221_REG_CONFIGURATION, INA3221_RESET); + + if (status < 0) { + return status; + } + /* Check if default config is present after reset */ + status = _read_reg(dev, INA3221_REG_CONFIGURATION, &config); + if (status < 0) { + return status; + } + if (config != INA3221_DEFCONFIG) { + return -INA3221_RESET_FAILED; + } + dev->params.config = INA3221_DEFCONFIG; + return INA3221_OK; +} + +int ina3221_init(ina3221_t *dev, const ina3221_params_t *params) +{ + int status; + + if (!dev || !params) { + return -EINVAL; + } + dev->params = *params; + uint16_t id; + status = _read_reg(dev, INA3221_REG_MANUFACTURER_ID, &id); + if (status < 0) { + return status; + } + if (id != INA3221_MANUFACTURER_ID) { + return -INA3221_BAD_MANUF_ID; + } + status = _read_reg(dev, INA3221_REG_DIE_ID, &id); + if (status < 0) { + return status; + } + if (id != INA3221_DIE_ID) { + return -INA3221_BAD_DIE_ID; + } + if (ina3221_reset(dev) != INA3221_OK) { + return -INA3221_RESET_FAILED; + } + if (_ina3221_set_config(dev, params->config) != INA3221_OK) { + return -INA3221_CONFIG_FAILED; + } + uint16_t cfg; + if (_ina3221_get_config(dev, &cfg) != INA3221_OK || cfg != params->config) { + return -INA3221_CONFIG_FAILED; + } +#if defined(MODULE_INA3221_ALERTS) || defined(DOXYGEN) + memset(dev->alert_callbacks, 0, sizeof(dev->alert_callbacks)); + memset(dev->alert_callback_arguments, 0, + sizeof(dev->alert_callback_arguments)); +#endif + return INA3221_OK; +} + +int _ina3221_set_config(ina3221_t *dev, uint16_t cfg) +{ + cfg &= ~INA3221_RESET; /* prevent accidental reset */ + int status = _write_reg(dev, INA3221_REG_CONFIGURATION, cfg); + if (status < 0) { + return status; + } + dev->params.config = cfg; + return INA3221_OK; +} + +int _ina3221_get_config(const ina3221_t *dev, uint16_t *cfg) +{ + *cfg = dev->params.config; + *cfg &= ~INA3221_RESET; /* clear reset flag */ + return INA3221_OK; +} + +int _ina3221_set_enable_channel(ina3221_t *dev, ina3221_enable_ch_t ech) +{ + uint16_t cfg; + int status = _read_reg(dev, INA3221_REG_CONFIGURATION, &cfg); + + if (status < 0) { + return status; + } + cfg &= ~INA3221_ENABLE_CH_MASK; + cfg |= (ech & INA3221_ENABLE_CH_MASK); + status = _write_reg(dev, INA3221_REG_CONFIGURATION, cfg); + if (status < 0) { + return status; + } + dev->params.config = cfg; + return INA3221_OK; +} + +int _ina3221_get_enable_channel(const ina3221_t *dev, ina3221_enable_ch_t *ech) +{ + *ech = dev->params.config & INA3221_ENABLE_CH_MASK; + return ((*ech & INA3221_ENABLE_CH1) ? 1 : 0) + + ((*ech & INA3221_ENABLE_CH2) ? 1 : 0) + + ((*ech & INA3221_ENABLE_CH3) ? 1 : 0); +} + +int ina3221_set_num_samples(ina3221_t *dev, ina3221_num_samples_t ns) +{ + uint16_t cfg; + int status = _read_reg(dev, INA3221_REG_CONFIGURATION, &cfg); + + if (status < 0) { + return status; + } + cfg &= ~INA3221_NUM_SAMPLES_MASK; + cfg |= (ns & INA3221_NUM_SAMPLES_MASK); + status = _write_reg(dev, INA3221_REG_CONFIGURATION, cfg); + if (status < 0) { + return status; + } + dev->params.config = cfg; + return INA3221_OK; +} + +int ina3221_get_num_samples(const ina3221_t *dev, ina3221_num_samples_t *ns) +{ + *ns = dev->params.config & INA3221_NUM_SAMPLES_MASK; + return INA3221_OK; +} + +int ina3221_set_conv_time_bus_adc(ina3221_t *dev, + ina3221_conv_time_bus_adc_t ctb) +{ + uint16_t cfg; + int status = _read_reg(dev, INA3221_REG_CONFIGURATION, &cfg); + + if (status < 0) { + return status; + } + cfg &= ~INA3221_CONV_TIME_BADC_MASK; + cfg |= (ctb & INA3221_CONV_TIME_BADC_MASK); + status = _write_reg(dev, INA3221_REG_CONFIGURATION, cfg); + if (status < 0) { + return status; + } + dev->params.config = cfg; + return INA3221_OK; +} + +int ina3221_get_conv_time_bus_adc(const ina3221_t *dev, + ina3221_conv_time_bus_adc_t *ctb) +{ + *ctb = dev->params.config & INA3221_CONV_TIME_BADC_MASK; + return INA3221_OK; +} + +int ina3221_set_conv_time_shunt_adc(ina3221_t *dev, + ina3221_conv_time_shunt_adc_t ctb) +{ + uint16_t cfg; + int status = _read_reg(dev, INA3221_REG_CONFIGURATION, &cfg); + + if (status < 0) { + return status; + } + cfg &= ~INA3221_CONV_TIME_SADC_MASK; + cfg |= (ctb & INA3221_CONV_TIME_SADC_MASK); + status = _write_reg(dev, INA3221_REG_CONFIGURATION, cfg); + if (status < 0) { + return status; + } + dev->params.config = cfg; + return INA3221_OK; +} + +int ina3221_get_conv_time_shunt_adc(const ina3221_t *dev, + ina3221_conv_time_shunt_adc_t *ctb) +{ + *ctb = dev->params.config & INA3221_CONV_TIME_SADC_MASK; + return INA3221_OK; +} + +int ina3221_set_mode(ina3221_t *dev, ina3221_mode_t mode) +{ + uint16_t cfg; + int status = _read_reg(dev, INA3221_REG_CONFIGURATION, &cfg); + + if (status < 0) { + return status; + } + cfg &= ~INA3221_MODE_MASK; + cfg |= (mode & INA3221_MODE_MASK); + status = _write_reg(dev, INA3221_REG_CONFIGURATION, cfg); + if (status < 0) { + return status; + } + dev->params.config = cfg; + return INA3221_OK; +} + +int ina3221_get_mode(const ina3221_t *dev, ina3221_mode_t *mode) +{ + *mode = dev->params.config & INA3221_MODE_MASK; + return INA3221_OK; +} + +int _ina3221_set_enable_sum_channel(const ina3221_t *dev, + ina3221_enable_sum_ch_t esch) +{ + uint16_t mask_en; + int status = _read_reg(dev, INA3221_REG_MASK_ENABLE, &mask_en); + + if (status < 0) { + return status; + } + mask_en &= ~INA3221_ENABLE_SUM_CH_MASK; + mask_en |= (esch & INA3221_ENABLE_SUM_CH_MASK); + status = _write_reg(dev, INA3221_REG_MASK_ENABLE, mask_en); + if (status < 0) { + return status; + } + return INA3221_OK; +} + +int _ina3221_get_enable_sum_channel(const ina3221_t *dev, + ina3221_enable_sum_ch_t *esch) +{ + uint16_t mask_en; + int status = _read_reg(dev, INA3221_REG_MASK_ENABLE, &mask_en); + + if (status < 0) { + return status; + } + *esch = mask_en & (INA3221_ENABLE_SUM_CH_MASK); + return ((*esch & INA3221_ENABLE_SUM_CH1) ? 1 : 0) + + ((*esch & INA3221_ENABLE_SUM_CH2) ? 1 : 0) + + ((*esch & INA3221_ENABLE_SUM_CH3) ? 1 : 0); +} + +int ina3221_set_latch(const ina3221_t *dev, ina3221_enable_latch_t latch) +{ + uint16_t mask_en; + int status = _read_reg(dev, INA3221_REG_MASK_ENABLE, &mask_en); + + if (status < 0) { + return status; + } + mask_en &= ~INA3221_ENABLE_LATCH_MASK; + mask_en |= (latch & INA3221_ENABLE_LATCH_MASK); + status = _write_reg(dev, INA3221_REG_MASK_ENABLE, mask_en); + if (status < 0) { + return status; + } + return INA3221_OK; +} + +int ina3221_get_latch(const ina3221_t *dev, ina3221_enable_latch_t *latch) +{ + uint16_t mask_en; + int status = _read_reg(dev, INA3221_REG_MASK_ENABLE, &mask_en); + + if (status < 0) { + return status; + } + *latch = mask_en & INA3221_ENABLE_LATCH_MASK; + return INA3221_OK; +} + +int ina3221_set_crit_alert_limit(const ina3221_t *dev, ina3221_channel_t ch, + int32_t in_uv) +{ + if (in_uv < INA3221_MIN_SHUNT_UV || in_uv > INA3221_MAX_SHUNT_UV) { + return -ERANGE; + } + int16_t reg_val = shunt_voltage_uv_to_reg_val(in_uv); + int status = INA3221_OK; + int i, j; + for (i = 0, j = 0; i < INA3221_NUM_CH; i++) { + if (ch & (1 << i)) { + status = _write_reg(dev, _chi[i].chi_reg_crit_alert_limit, reg_val); + if (status < 0) { + break; + } + j++; + } + } + return j ? j : status; +} + +int ina3221_get_crit_alert_limit(const ina3221_t *dev, ina3221_channel_t ch, + int32_t *out_uv) +{ + uint16_t reg_val; + int status = INA3221_OK; + int i, j; + + for (i = 0, j = 0; i < INA3221_NUM_CH; i++) { + if (ch & (1 << i)) { + status = _read_reg(dev, _chi[i].chi_reg_crit_alert_limit, ®_val); + if (status < 0) { + break; + } + out_uv[j++] = reg_val_to_shunt_voltage_uv(reg_val); + } + } + return j ? j : status; +} + +int ina3221_set_warn_alert_limit(const ina3221_t *dev, ina3221_channel_t ch, + int32_t in_uv) +{ + if (in_uv < INA3221_MIN_SHUNT_UV || in_uv > INA3221_MAX_SHUNT_UV) { + return -ERANGE; + } + int16_t reg_val = shunt_voltage_uv_to_reg_val(in_uv); + int status = INA3221_OK; + int i, j; + for (i = 0, j = 0; i < INA3221_NUM_CH; i++) { + if (ch & (1 << i)) { + status = _write_reg(dev, _chi[i].chi_reg_warn_alert_limit, reg_val); + if (status < 0) { + break; + } + j++; + } + } + return j ? j : status; +} + +int ina3221_get_warn_alert_limit(const ina3221_t *dev, ina3221_channel_t ch, + int32_t *out_uv) +{ + uint16_t reg_val; + int status = INA3221_OK; + int i, j; + + for (i = 0, j = 0; i < INA3221_NUM_CH; i++) { + if (ch & (1 << i)) { + status = _read_reg(dev, _chi[i].chi_reg_warn_alert_limit, ®_val); + if (status < 0) { + break; + } + out_uv[j++] = reg_val_to_shunt_voltage_uv(reg_val); + } + } + return j ? j : status; +} + +int ina3221_set_shunt_voltage_sum_alert_limit(const ina3221_t *dev, + int32_t in_uv) +{ + if (in_uv < INA3221_MIN_SHUNT_SUM_UV || in_uv > INA3221_MAX_SHUNT_SUM_UV) { + return -ERANGE; + } + int16_t reg_val = sum_shunt_voltage_uv_to_reg_val(in_uv); + int status = _write_reg(dev, INA3221_REG_SHUNT_VOLTAGE_SUM_LIMIT, reg_val); + if (status < 0) { + return status; + } + return INA3221_OK; +} + +int ina3221_get_shunt_voltage_sum_alert_limit(const ina3221_t *dev, + int32_t *out_uv) +{ + uint16_t reg_val; + int status = _read_reg(dev, INA3221_REG_SHUNT_VOLTAGE_SUM_LIMIT, ®_val); + + if (status < 0) { + return status; + } + *out_uv = sum_reg_val_to_shunt_voltage_uv(reg_val); + return INA3221_OK; +} + +int ina3221_set_power_valid_upper_limit(const ina3221_t *dev, int32_t in_mv) +{ + if (in_mv < INA3221_MIN_BUS_MV || in_mv > INA3221_MAX_BUS_MV) { + return -ERANGE; + } + int16_t reg_val = bus_voltage_mv_to_reg_val(in_mv); + int status = _write_reg(dev, INA3221_REG_PV_UPPER_LIMIT, reg_val); + if (status < 0) { + return status; + } + return INA3221_OK; +} + +int ina3221_get_power_valid_upper_limit(const ina3221_t *dev, int32_t *out_mv) +{ + uint16_t reg_val; + int status = _read_reg(dev, INA3221_REG_PV_UPPER_LIMIT, ®_val); + + if (status < 0) { + return status; + } + *out_mv = reg_val_to_bus_voltage_mv(reg_val); + return INA3221_OK; +} + +int ina3221_set_power_valid_lower_limit(const ina3221_t *dev, int32_t in_mv) +{ + if (in_mv < INA3221_MIN_BUS_MV || in_mv > INA3221_MAX_BUS_MV) { + return -ERANGE; + } + int16_t reg_val = bus_voltage_mv_to_reg_val(in_mv); + int status = _write_reg(dev, INA3221_REG_PV_LOWER_LIMIT, reg_val); + if (status < 0) { + return status; + } + return INA3221_OK; +} + +int ina3221_get_power_valid_lower_limit(const ina3221_t *dev, int32_t *out_mv) +{ + uint16_t reg_val; + int status = _read_reg(dev, INA3221_REG_PV_LOWER_LIMIT, ®_val); + + if (status < 0) { + return status; + } + *out_mv = reg_val_to_bus_voltage_mv(reg_val); + return INA3221_OK; +} + +int ina3221_read_flags(const ina3221_t *dev, uint16_t *flags) +{ + uint16_t reg_val; + int status = _read_reg(dev, INA3221_REG_MASK_ENABLE, ®_val); + + if (status < 0) { + return status; + } + *flags = reg_val & INA3221_FLAGS_MASK; + return INA3221_OK; +} + +int ina3221_read_shunt_sum_uv(const ina3221_t *dev, int32_t *out_uv, + uint16_t *flags) +{ + uint16_t reg_val; + int status = _read_reg(dev, INA3221_REG_SHUNT_VOLTAGE_SUM, ®_val); + + if (status < 0) { + return status; + } + *out_uv = sum_reg_val_to_shunt_voltage_uv(reg_val); + if (flags) { + status = _read_reg(dev, INA3221_REG_MASK_ENABLE, flags); + if (status < 0) { + *flags = 0; + DEBUG("ina3221_read_shunt_sum_uv: Reading flags failed\n"); + } + else { + *flags &= INA3221_FLAGS_MASK; + } + } + return INA3221_OK; +} + +int ina3221_read_shunt_uv(const ina3221_t *dev, ina3221_channel_t ch, + int32_t *out_uv, uint16_t *flags) +{ + uint16_t reg_val; + int status = INA3221_OK; + int i, j; + + for (i = 0, j = 0; i < INA3221_NUM_CH; i++) { + if (ch & (1 << i)) { + status = _read_reg(dev, _chi[i].chi_reg_shunt, ®_val); + if (status < 0) { + break; + } + out_uv[j++] = reg_val_to_shunt_voltage_uv(reg_val); + } + } + if (j && flags) { + status = _read_reg(dev, INA3221_REG_MASK_ENABLE, flags); + if (status < 0) { + *flags = 0; + DEBUG("ina3221_read_shunt_uv: Reading flags failed\n"); + } + else { + *flags &= INA3221_FLAGS_MASK; + } + } + return j ? j : status; +} + +int ina3221_read_bus_mv(const ina3221_t *dev, ina3221_channel_t ch, + int16_t *out_mv, uint16_t *flags) +{ + uint16_t reg_val; + int status = INA3221_OK; + int i, j = 0; + + for (i = 0; i < INA3221_NUM_CH; i++) { + if (ch & (1 << i)) { + status = _read_reg(dev, _chi[i].chi_reg_bus, ®_val); + if (status < 0) { + break; + } + out_mv[j++] = reg_val_to_bus_voltage_mv(reg_val); + } + } + if (j && flags) { + status = _read_reg(dev, INA3221_REG_MASK_ENABLE, flags); + if (status < 0) { + *flags = 0; + DEBUG("ina3221_read_bus_mv: Reading flags failed\n"); + } + else { + *flags &= INA3221_FLAGS_MASK; + } + } + return j ? j : status; +} + +int ina3221_calculate_current_ua(const ina3221_t *dev, ina3221_channel_t ch, + int32_t *in_uv, int32_t *out_ua) +{ + int i, j = 0; + + for (i = 0; i < INA3221_NUM_CH; i++) { + if (ch & (1 << i)) { + out_ua[j] = in_uv[j] * 1000 / dev->params.rshunt_mohm[i]; + j++; + } + } + return j; +} + +int ina3221_calculate_power_uw(int16_t *in_mv, int32_t *in_ua, uint8_t num, + int32_t *out_uw) +{ + if (num > INA3221_NUM_CH) { + return -ERANGE; + } + int i; + for (i = 0; i < num; i++) { + /* max 26V bus voltage */ + /* (2^31)-1 resolution; 2.147483647 Watt in Nanowatt resolutiona */ + /* 2.147483647 / 26000 = 82595.525 */ + if (in_ua[i] < (82596 - 500)) { + out_uw[i] = (in_ua[i] * in_mv[i] + 500) / 1000; + } + else { + out_uw[i] = (in_ua[i] + 500) / 1000 * in_mv[i]; + } + } + return i; +} + +void ina3221_ch_align(ina3221_channel_t ch, const void *in_res, void *out_res, + size_t res_val_size) +{ + uint8_t *in = (uint8_t *)in_res; + uint8_t tmp_out[INA3221_NUM_CH][res_val_size]; + int j = 0; + + for (int i = 0; i < INA3221_NUM_CH; i++) { + if (ch & (1 << i)) { + memcpy(&tmp_out[i][0], in + j * res_val_size, res_val_size); + j++; + } + else { + memset(&tmp_out[i][0], 0, res_val_size); + } + } + memcpy(out_res, tmp_out, sizeof(tmp_out)); +} diff --git a/drivers/ina3221/include/ina3221_defines.h b/drivers/ina3221/include/ina3221_defines.h new file mode 100644 index 000000000000..92397cb041ea --- /dev/null +++ b/drivers/ina3221/include/ina3221_defines.h @@ -0,0 +1,60 @@ +/* + * 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_ina3221 + * @{ + * + * @file + * @brief Internal definitions for Texas Instruments INA3221 + * three-channel, high-side current and bus voltage + * monitor + * + * @author Fabian Hüßler + */ + +#ifndef INA3221_DEFINES_H +#define INA3221_DEFINES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define INA3221_REG_ADDR_LEN (1) /**< Register address length */ +#define INA3221_REG_LEN (2) /**< Register length */ + +#define INA3221_SHUNT_VOLTAGE_PRECISION_UV (40) /**< Shunt voltage measurement precision */ +#define INA3221_BUS_VOLTAGE_PRECISION_MV (8) /**< Bus voltage measurement precision */ + +#define INA3221_MANUFACTURER_ID (0x5449) /**< INA3221 manufacturer ID */ +#define INA3221_DIE_ID (0x3220) /**< INA3221 DIE ID */ + +/** + * @name INA3221 Limits + * @{ + */ +#define INA3221_MAX_SHUNT_REG_VAL ((int16_t)(0x7FF8)) /**< 0111 1111 1111 1000 */ +#define INA3221_MIN_SHUNT_REG_VAL ((int16_t)(0x8000)) /**< 1000 0000 0000 0000 */ +#define INA3221_MAX_BUS_REG_VAL ((int16_t)(0x7FF8)) /**< 0111 1111 1111 1000 */ +#define INA3221_MIN_BUS_REG_VAL ((int16_t)(0x8000)) /**< 1000 0000 0000 0000 */ +#define INA3221_MAX_SHUNT_SUM_REG_VAL ((int16_t)(0x7FFE)) /**< 0111 1111 1111 1110 */ +#define INA3221_MIN_SHUNT_SUM_REG_VAL ((int16_t)(0x8000)) /**< 1000 0000 0000 0000 */ +#define INA3221_MAX_SHUNT_UV (163800) /**< Max. measurable shunt voltage value */ +#define INA3221_MIN_SHUNT_UV (-163840) /**< Min. measurable shunt voltage value */ +#define INA3221_MAX_BUS_MV (26000) /**< Max. measurable bus voltage value */ +#define INA3221_MIN_BUS_MV (0) /**< Min. measurable bus voltage value */ +#define INA3221_MAX_SHUNT_SUM_UV (655320) /**< Max. measurable shunt sum voltage value */ +#define INA3221_MIN_SHUNT_SUM_UV (-655360) /**< Min. measurable shunt sum voltage value */ +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* INA3221_DEFINES_H */ +/** @} */ diff --git a/drivers/ina3221/include/ina3221_internal.h b/drivers/ina3221/include/ina3221_internal.h new file mode 100644 index 000000000000..0075c1d9a00d --- /dev/null +++ b/drivers/ina3221/include/ina3221_internal.h @@ -0,0 +1,133 @@ +/* + * 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_ina3221 + * @{ + * + * @file + * @brief Functions to convert register values of INA3221 + * to actual voltage values and vice versa + * + * @author Fabian Hüßler + */ + +#ifndef INA3221_INTERNAL_H +#define INA3221_INTERNAL_H + +#include +#include "ina3221_defines.h" +#include "ina3221.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Convert register value to shunt voltage in uV + * + * @param[in] reg_val Register value + * + * @pre reg_val must be in host byte order + * + * @return Shunt voltage in uV + */ +static inline int32_t reg_val_to_shunt_voltage_uv(int16_t reg_val) +{ + assert(reg_val <= INA3221_MAX_SHUNT_REG_VAL); + + return reg_val / 8 * INA3221_SHUNT_VOLTAGE_PRECISION_UV; +} + +/** + * @brief Convert shunt voltage in uV to register value + * + * @param[in] s_uv Shunt voltage in uV + * + * @post Output register value is in host byte order + * + * @return Register value + */ +static inline int16_t shunt_voltage_uv_to_reg_val(int32_t s_uv) +{ + assert(s_uv <= INA3221_MAX_SHUNT_UV); + + return s_uv / INA3221_SHUNT_VOLTAGE_PRECISION_UV * 8; +} + +/** + * @brief Convert register value to bus voltage in mV + * + * @param[in] reg_val Register value + * + * @pre reg_val must be in host byte order + * + * @return Shunt voltage in mV + */ +static inline int16_t reg_val_to_bus_voltage_mv(int16_t reg_val) +{ + assert(reg_val <= INA3221_MAX_BUS_REG_VAL); + + return reg_val / 8 * INA3221_BUS_VOLTAGE_PRECISION_MV; +} + +/** + * @brief Convert bus voltage in mV to register value + * + * @param[in] b_mv Bus voltage in mV + * + * @post Output register value is in host byte order + * + * @return Register value + */ +static inline int16_t bus_voltage_mv_to_reg_val(int16_t b_mv) +{ + assert(b_mv >= INA3221_MIN_BUS_MV); + assert(b_mv <= INA3221_MAX_BUS_MV); + + return b_mv / INA3221_BUS_VOLTAGE_PRECISION_MV * 8; +} + +/** + * @brief Convert register value to shunt voltage sum value in mV + * + * @param[in] sum_reg_val Register value + * + * @pre sum_reg_val must be in host byte order + * + * @return Shunt voltage sum value in mV + */ +static inline int32_t sum_reg_val_to_shunt_voltage_uv(int16_t sum_reg_val) +{ + assert(sum_reg_val <= INA3221_MAX_SHUNT_SUM_REG_VAL); + + return sum_reg_val / 2 * INA3221_SHUNT_VOLTAGE_PRECISION_UV; +} + +/** + * @brief Convert shunt voltage sum value in uV to register value + * + * @param[in] sum_suv shunt voltage sum value in uV + * + * @post Output register value is in host byte order + * + * @return Register value + */ +static inline int16_t sum_shunt_voltage_uv_to_reg_val(int32_t sum_suv) +{ + assert(sum_suv <= INA3221_MAX_SHUNT_SUM_UV); + + return sum_suv / INA3221_SHUNT_VOLTAGE_PRECISION_UV * 2; +} + +#ifdef __cplusplus +} +#endif + +#endif /* INA3221_INTERNAL_H */ +/** @} */ diff --git a/drivers/ina3221/include/ina3221_params.h b/drivers/ina3221/include/ina3221_params.h new file mode 100644 index 000000000000..7534ac3543ff --- /dev/null +++ b/drivers/ina3221/include/ina3221_params.h @@ -0,0 +1,144 @@ +/* + * 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_ina3221 + * @{ + * + * @file + * @brief Driver configuration parameters for Texas Instruments INA3221 + * three-channel, high-side current and bus voltage + * monitor + * + * @author Fabian Hüßler + */ + +#ifndef INA3221_PARAMS_H +#define INA3221_PARAMS_H + +#include "board.h" +#include "ina3221.h" +#include "saul_reg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef INA3221_PARAM_I2C +#define INA3221_PARAM_I2C (I2C_DEV(0)) /**< I2C bus index */ +#endif + +#ifndef INA3221_PARAM_ADDR +#define INA3221_PARAM_ADDR (INA3221_ADDR_00) /**< I2C device address */ +#endif + +#ifndef INA3221_PARAM_PIN_WRN +#define INA3221_PARAM_PIN_WRN (GPIO_UNDEF) /**< Warning alert pin */ +#endif + +#ifndef INA3221_PARAM_PIN_CRT +#define INA3221_PARAM_PIN_CRT (GPIO_UNDEF) /**< Critical alert pin */ +#endif + +#ifndef INA3221_PARAM_PIN_PV +#define INA3221_PARAM_PIN_PV (GPIO_UNDEF) /**< Power valid alert pin */ +#endif + +#ifndef INA3221_PARAM_PIN_TC +#define INA3221_PARAM_PIN_TC (GPIO_UNDEF) /**< Timing control alert pin */ +#endif + +#ifndef INA3221_PARAM_INT_PU_PIN_WRN +#define INA3221_PARAM_INT_PU_PIN_WRN (0) /**< Enable or disable internal pull up resistor for pin WRN */ +#endif + +#ifndef INA3221_PARAM_INT_PU_PIN_CRT +#define INA3221_PARAM_INT_PU_PIN_CRT (0) /**< Enable or disable internal pull up resistor for pin CRT */ +#endif + +#ifndef INA3221_PARAM_INT_PU_PIN_PV +#define INA3221_PARAM_INT_PU_PIN_PV (0) /**< Enable or disable internal pull up resistor for pin PV */ +#endif + +#ifndef INA3221_PARAM_INT_PU_PIN_TC +#define INA3221_PARAM_INT_PU_PIN_TC (0) /**< Enable or disable internal pull up resistor for pin TC */ +#endif + +#ifndef INA3221_PARAM_CONFIG +#define INA3221_PARAM_CONFIG ( \ + INA3221_ENABLE_CH1 | \ + INA3221_ENABLE_CH2 | \ + INA3221_ENABLE_CH3 | \ + INA3221_NUM_SAMPLES_4 | \ + INA3221_CONV_TIME_BADC_4156US | \ + INA3221_CONV_TIME_SADC_4156US | \ + INA3221_MODE_CONTINUOUS_SHUNT_BUS \ + ) /**< Configuration register value */ +#endif + +#ifndef INA3221_PARAM_RSHUNT_MOHM_CH1 +#define INA3221_PARAM_RSHUNT_MOHM_CH1 (100) /**< Channel 1 shunt resistance */ +#endif + +#ifndef INA3221_PARAM_RSHUNT_MOHM_CH2 +#define INA3221_PARAM_RSHUNT_MOHM_CH2 (100) /**< Channel 2 shunt resistance */ +#endif + +#ifndef INA3221_PARAM_RSHUNT_MOHM_CH3 +#define INA3221_PARAM_RSHUNT_MOHM_CH3 (100) /**< Channel 3 shunt resistance */ +#endif + +#ifndef INA3221_PARAMS +#define INA3221_PARAMS { \ + .i2c = INA3221_PARAM_I2C, \ + .addr = INA3221_PARAM_ADDR, \ + .upins.pins = { \ + .pin_warn = INA3221_PARAM_PIN_WRN, \ + .pin_crit = INA3221_PARAM_PIN_CRT, \ + .pin_tc = INA3221_PARAM_PIN_TC, \ + .pin_pv = INA3221_PARAM_PIN_PV \ + }, \ + .gpio_config = (INA3221_PARAM_INT_PU_PIN_WRN << INA3221_ALERT_WRN) | \ + (INA3221_PARAM_INT_PU_PIN_CRT << INA3221_ALERT_CRT) | \ + (INA3221_PARAM_INT_PU_PIN_TC << INA3221_ALERT_TC) | \ + (INA3221_PARAM_INT_PU_PIN_PV << INA3221_ALERT_PV), \ + .config = INA3221_PARAM_CONFIG, \ + .rshunt_mohm = { \ + INA3221_PARAM_RSHUNT_MOHM_CH1, \ + INA3221_PARAM_RSHUNT_MOHM_CH2, \ + INA3221_PARAM_RSHUNT_MOHM_CH3 \ + } \ +} /**< Default device initialization parameters */ +#endif + +#ifndef INA3221_SAUL_INFO +#define INA3221_SAUL_INFO { .name = "INA3221 bus voltage" }, \ + { .name = "INA3221 current" }, \ + { .name = "INA3221 power" }, \ + { .name = "INA3221 shunt voltage sum" } /**< SAUL driver information */ +#endif + +/** + * @brief INA3221 array of device configurations + */ +static const ina3221_params_t ina3221_params[] = { + INA3221_PARAMS +}; + +/** + * @brief INA3221 array of SAUL driver information + */ +static const saul_reg_info_t ina3221_saul_info[] = { + INA3221_SAUL_INFO +}; +#ifdef __cplusplus +} +#endif + +#endif /* INA3221_PARAMS_H */ +/** @} */ diff --git a/drivers/ina3221/include/ina3221_regs.h b/drivers/ina3221/include/ina3221_regs.h new file mode 100644 index 000000000000..f9601522931a --- /dev/null +++ b/drivers/ina3221/include/ina3221_regs.h @@ -0,0 +1,58 @@ +/* + * 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_ina3221 + * @{ + * + * @file + * @brief Register definitions for Texas Instruments INA3221 + * three-channel, high-side current and bus voltage + * monitor + * + * @author Fabian Hüßler + */ + +#ifndef INA3221_REGS_H +#define INA3221_REGS_H + +#ifdef __cplusplus +extern "C" { +#endif +/** + * @brief INA3221 internal register addresses + */ +typedef enum ina3221_reg { + INA3221_REG_CONFIGURATION = 0x00, /**< Configuration register (R/W) */ + INA3221_REG_CH1_SHUNT_VOLTAGE = 0x01, /**< Channel 1 shunt voltage register (R) */ + INA3221_REG_CH1_BUS_VOLTAGE = 0x02, /**< Channel 1 bus voltage register (R) */ + INA3221_REG_CH2_SHUNT_VOLTAGE = 0x03, /**< Channel 2 shunt voltage register (R) */ + INA3221_REG_CH2_BUS_VOLTAGE = 0x04, /**< Channel 2 bus voltage register (R) */ + INA3221_REG_CH3_SHUNT_VOLTAGE = 0x05, /**< Channel 3 shunt voltage register (R) */ + INA3221_REG_CH3_BUS_VOLTAGE = 0x06, /**< Channel 3 bus voltage register (R) */ + INA3221_REG_CH1_CRIT_ALERT_LIMIT = 0x07, /**< Channel 1 voltage thresholt for critical alert (R/W) */ + INA3221_REG_CH1_WARN_ALERT_LIMIT = 0x08, /**< Channel 1 voltage thresholt for warning alert (R/W) */ + INA3221_REG_CH2_CRIT_ALERT_LIMIT = 0x09, /**< Channel 2 voltage thresholt for critical alert (R/W) */ + INA3221_REG_CH2_WARN_ALERT_LIMIT = 0x0A, /**< Channel 2 voltage thresholt for warning alert (R/W) */ + INA3221_REG_CH3_CRIT_ALERT_LIMIT = 0x0B, /**< Channel 3 voltage thresholt for critical alert (R/W) */ + INA3221_REG_CH3_WARN_ALERT_LIMIT = 0x0C, /**< Channel 3 voltage thresholt for warning alert (R/W) */ + INA3221_REG_SHUNT_VOLTAGE_SUM = 0x0D, /**< Shunt voltage sum register over all channels (R) */ + INA3221_REG_SHUNT_VOLTAGE_SUM_LIMIT = 0x0E, /**< Shunt voltage sum threshold for critical alert (R/W) */ + INA3221_REG_MASK_ENABLE = 0x0F, /**< Channel enable and status register (R/W) */ + INA3221_REG_PV_UPPER_LIMIT = 0x10, /**< Pover valid upper limit register (R/W) */ + INA3221_REG_PV_LOWER_LIMIT = 0x11, /**< Power valid lower limit register (R/W) */ + INA3221_REG_MANUFACTURER_ID = 0xFE, /**< Manufacturer ID (=0x5449) (R) */ + INA3221_REG_DIE_ID = 0xFF /**< Die ID (=0x3220) (R) */ +} ina3221_reg_t; + +#ifdef __cplusplus +} +#endif + +#endif /* INA3221_REGS_H */ +/** @} */ diff --git a/drivers/include/ina3221.h b/drivers/include/ina3221.h new file mode 100644 index 000000000000..11ddb0bc3072 --- /dev/null +++ b/drivers/include/ina3221.h @@ -0,0 +1,1117 @@ +/* + * 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. + */ + +/** + * @defgroup drivers_ina3221 INA3221 current/power monitor + * @ingroup drivers_sensors + * @brief Device driver for Texas Instruments INA3221 + * three-channel ,high-side current and bus voltage + * monitor + * @{ + * + * @file + * @brief Device driver interface for Texas Instruments INA3221 + * three-channel ,high-side current and bus voltage + * monitor. + * + * The alert pins WRN, CRT, TC, PV are open drain + * output pins, so they should be pulled high with + * internal or external pull-ups. They pull low, if + * the alert condition becomes true. If they become + * pulled low, the configured input pins in + * @see ina3221_params_t will read a low flank and + * the related interrupt callback will be executed, + * if configured with @see ina3221_enable_alert. + * WRN condition: + * Any channel´s measured shunt + * voltage drop was higher than the configured value in + * INA3221_REG_CHx_WARN_ALERT_LIMIT, + * CRT condition: + * Any channel´s measured shunt + * voltage drop was higher than the configured value in + * INA3221_REG_CHx_CRIT_ALERT_LIMIT, + * or the sum of all channel´s voltage drops was higher + * than the configured value in + * INA3221_REG_SHUNT_VOLTAGE_SUM_LIMIT. Note that the + * sum of voltage drops only makes sense, if all + * channels are using the same shunt resistance value! + * TC condition: + * After a 1.2-V level is detected on channel 1, if the + * INA3221 does not detecta 1.2-V value or greater on + * the bus voltage measurement following four complete + * cycles of all three channels, the timing control + * (TC)alert pin pulls low. + * PV condition: + * If any channel´s measured bus voltage was not within + * the configured values in INA3221_REG_PV_LOWER_LIMIT + * and INA3221_REG_PV_UPPER_LIMIT, the PV pin is pulled + * low. + * + * For all enabled channels (1 <= i <= INA3221_NUM_CH), + * the time to complete a full measurment cycle is: + * + * num_samples * (shunt_conv_time_ch_i + bus_voltage_conv_time_ch_i) + * + * @author Fabian Hüßler + */ + +#ifndef INA3221_H +#define INA3221_H + +#include + +#include "periph/gpio.h" +#include "periph/i2c.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define INA3221_NUM_CH (3) /**< Number of channels */ + +#define INA3221_NUM_ALERTS (4) /**< Number of alerts */ + +/** + * @brief INA3221 alert callback for alert pins + * CRT, WRN, TC, PV + */ +typedef void (*ina3221_alert_cb_t)(void *arg); + +/** + * @brief Return codes + */ +typedef enum ina3221_ret_val { + INA3221_OK, /**< Everything is fine */ + INA3221_I2C_ERROR, /**< I2C bus acquirenment failed */ + INA3221_BAD_MANUF_ID, /**< Read device manufacturer ID failed */ + INA3221_BAD_DIE_ID, /**< Read device DIE ID failed */ + INA3221_RESET_FAILED, /**< Device reset failed */ + INA3221_CONFIG_FAILED /**< Device configuration failed */ +} ina3221_ret_val_t; + +/** + * @brief I2C device addresses + */ +typedef enum ina3221_dev_addr { + INA3221_ADDR_00 = 0x40, /**< A0 connected to GND */ + INA3221_ADDR_01 = 0x41, /**< A0 connected to VS */ + INA3221_ADDR_10 = 0x42, /**< A0 connected to SDA */ + INA3221_ADDR_11 = 0x43, /**< A0 connected to SCL */ +} ina3221_dav_addr_t; + +/** + * @brief Channel flags + */ +typedef enum ina3221_channel { + INA3221_CH1 = (1 << 0), /**< Channel 1 */ + INA3221_CH2 = (1 << 1), /**< Channel 2 */ + INA3221_CH3 = (1 << 2) /**< Channel 3 */ +} ina3221_channel_t; + +/** + * @brief Alert indices + */ +typedef enum ina3221_alert { + INA3221_ALERT_WRN = 0, /**< Warning alert */ + INA3221_ALERT_CRT = 1, /**< Critical alert */ + INA3221_ALERT_TC = 2, /**< Timing control */ + INA3221_ALERT_PV = 3 /**< Power valid alert */ +} ina3221_alert_t; + +/** + * @brief Device operation modes + */ +typedef enum ina3221_mode { + INA3221_MODE_POWER_DOWN = 0x0000, /**< Sleep mode */ + INA3221_MODE_TRIGGER_SHUNT_ONLY = 0x0001, /**< Trigger shunt voltage measurement only */ + INA3221_MODE_TRIGGER_BUS_ONLY = 0x0002, /**< Trigger bus voltage measurement only */ + INA3221_MODE_TRIGGER_SHUNT_BUS = 0x0003, /**< Trigger shunt and bus voltage measurement */ + INA3221_MODE_POWER_DOWN_ = 0x0004, /**< Also power down (see datasheet)*/ + INA3221_MODE_CONTINUOUS_SHUNT_ONLY = 0x0005, /**< Continuous shunt voltage measurement only*/ + INA3221_MODE_CONTINUOUS_BUS_ONLY = 0x0006, /**< Continuous bus voltage measurement only*/ + INA3221_MODE_CONTINUOUS_SHUNT_BUS = 0x0007 /**< Continuous shunt and bus voltage measurement */ +} ina3221_mode_t; +#define INA3221_MODE_MASK (0x0007) /**< Device operation mode bit mask */ + +/** + * @brief ADC shunt voltage conversion times + */ +typedef enum ina3221_conv_time_shunt_adc { + INA3221_CONV_TIME_SADC_140US = 0x0000, /**< 140 us */ + INA3221_CONV_TIME_SADC_204US = 0x0008, /**< 204 us */ + INA3221_CONV_TIME_SADC_332US = 0x0010, /**< 332 us */ + INA3221_CONV_TIME_SADC_588US = 0x0018, /**< 588 us */ + INA3221_CONV_TIME_SADC_1100US = 0x0020, /**< 1100 us */ + INA3221_CONV_TIME_SADC_2116US = 0x0028, /**< 2116 us */ + INA3221_CONV_TIME_SADC_4156US = 0x0030, /**< 4156 us */ + INA3221_CONV_TIME_SADC_8244US = 0x0038 /**< 8244 us */ +} ina3221_conv_time_shunt_adc_t; +#define INA3221_CONV_TIME_SADC_MASK (0x0038) /**< ADC shunt voltage conversion times bit mask*/ + +/** + * @brief ADC bus voltage conversion times + */ +typedef enum ina3221_conv_time_bus_adc { + INA3221_CONV_TIME_BADC_140US = 0x0000, /**< 140 us */ + INA3221_CONV_TIME_BADC_204US = 0x0040, /**< 204 us */ + INA3221_CONV_TIME_BADC_332US = 0x0080, /**< 332 us */ + INA3221_CONV_TIME_BADC_588US = 0x00C0, /**< 588 us */ + INA3221_CONV_TIME_BADC_1100US = 0x0100, /**< 1100 us */ + INA3221_CONV_TIME_BADC_2116US = 0x0140, /**< 2116 us */ + INA3221_CONV_TIME_BADC_4156US = 0x0180, /**< 4156 us */ + INA3221_CONV_TIME_BADC_8244US = 0x01C0 /**< 8244 us */ +} ina3221_conv_time_bus_adc_t; +#define INA3221_CONV_TIME_BADC_MASK (0x01C0) /**< ADC bus voltage conversion times bit mask*/ + +/** + * @brief Number of samples to calculate average shunt/bus voltage value + */ +typedef enum ina3221_num_samples { + INA3221_NUM_SAMPLES_1 = 0x0000, /**< 1 */ + INA3221_NUM_SAMPLES_4 = 0x0200, /**< 4 */ + INA3221_NUM_SAMPLES_16 = 0x0400, /**< 16 */ + INA3221_NUM_SAMPLES_64 = 0x0600, /**< 64 */ + INA3221_NUM_SAMPLES_128 = 0x0800, /**< 128 */ + INA3221_NUM_SAMPLES_256 = 0x0A00, /**< 256 */ + INA3221_NUM_SAMPLES_512 = 0x0C00, /**< 512 */ + INA3221_NUM_SAMPLES_1024 = 0x0E00 /**< 1024 */ +} ina3221_num_samples_t; +#define INA3221_NUM_SAMPLES_MASK (0x0E00) /**< Number of samples to calculate average shunt/bus voltage value bit mask */ + +/** + * @brief Enable channel flags + */ +typedef enum ina3221_enable_ch { + INA3221_ENABLE_CH1 = 0x4000, /**< Enable channel 1 */ + INA3221_ENABLE_CH2 = 0x2000, /**< Enable channel 2 */ + INA3221_ENABLE_CH3 = 0x1000 /**< Enable channel 3 */ +} ina3221_enable_ch_t; +#define INA3221_ENABLE_CH_MASK (INA3221_ENABLE_CH1 \ + | INA3221_ENABLE_CH2 \ + | INA3221_ENABLE_CH3) /**< Enable channel bit mask */ + +/** + * @brief Channel state enabled/disabled + */ +typedef enum ina3221_channel_state { + INA3221_CH_DISABLE, /**< Channel disabled */ + INA3221_CH_ENABLE /**< Channel enabled */ +} ina3221_channel_state_t; + +/** + * @brief Enable shunt voltage sum calculation channel flags + */ +typedef enum ina3221_enable_sum_ch { + INA3221_ENABLE_SUM_CH1 = 0x4000, /**< Enable sum channel 1 */ + INA3221_ENABLE_SUM_CH2 = 0x2000, /**< Enable sum channel 2 */ + INA3221_ENABLE_SUM_CH3 = 0x1000 /**< Enable sum channel 3 */ +} ina3221_enable_sum_ch_t; +#define INA3221_ENABLE_SUM_CH_MASK (INA3221_ENABLE_SUM_CH1 \ + | INA3221_ENABLE_SUM_CH2 \ + | INA3221_ENABLE_SUM_CH3) /**< Enable shunt voltage sum calculation channel bit mask */ + +/** + * @brief Enable latch flags + */ +typedef enum ina3221_enable_latch { + INA3221_ENABLE_WARN_LATCH = 0x0800, /**< Enable warning alert latch */ + INA3221_ENABLE_CRIT_LATCH = 0x0400 /**< Enable critical alert latch */ +} ina3221_enable_latch_t; +#define INA3221_ENABLE_LATCH_MASK (INA3221_ENABLE_WARN_LATCH \ + | INA3221_ENABLE_CRIT_LATCH) /**< Enable latch bit mask */ +/** + * @brief INA3221 device parameters + */ +typedef struct { + i2c_t i2c; /**< I2C bus */ + uint8_t addr; /**< I2C address */ + union { + struct { + gpio_t pin_warn; /**< Warning alert pin, becomes configured as input*/ + gpio_t pin_crit; /**< Critical alert pin, becomes configured as input*/ + gpio_t pin_tc; /**< Timing control alert pin, becomes configured as input*/ + gpio_t pin_pv; /**< Power valid alert pin, becomes configured as input*/ + } pins; /**< Struct of alert pins */ + struct { + gpio_t alert_pins[INA3221_NUM_ALERTS]; /**< Array to access alert pins via an index */ + } apins; /**< Struct of array of alert pins */ + } upins; /**< Union of alert pins as array and as single members */ + /* + For each pin 1 bit states whether to use internal pull up resistors. + d = don´t care + [d][d][d][d][pu PV][pu TC][pu CRT][pu WRN] + */ + uint8_t gpio_config; /**< Configure pull up resistors for gpio pins */ + uint16_t config; /**< Configuration */ + uint16_t rshunt_mohm[INA3221_NUM_CH]; /**< Shunt resistances im mOhm for each channel */ +} ina3221_params_t; + +/* ( INA3221_ENABLE_CH_1 + | INA3221_ENABLE_CH_2 + | INA3221_ENABLE_CH_2 + | INA3221_NUM_SAMPLES_1 + | INA3221_CONV_TIME_BADC_1100US + | INA3221_CONV_TIME_SADC_1100US + | INA3221_MODE_CONTINUOUS_SHUNT_BUS ) */ +#define INA3221_DEFCONFIG (0x7127) /**< Default configuration register value */ + +#define INA3221_RESET (0x8000) /**< Reset configuration register value*/ + +#define INA3221_FLAG_CRIT_ALERT_CH1 (0x0200) /**< Critical alert channel 1 flag */ +#define INA3221_FLAG_CRIT_ALERT_CH2 (0x0100) /**< Critical alert channel 2 flag */ +#define INA3221_FLAG_CRIT_ALERT_CH3 (0x0080) /**< Critical alert channel 3 flag */ + +#define INA3221_FLAG_SHUNT_SUM_ALERT (0x0040) /**< Alert channel sum flag */ + +#define INA3221_FLAG_WARN_ALERT_CH1 (0x0020) /**< Warning alert channel 1 flag */ +#define INA3221_FLAG_WARN_ALERT_CH2 (0x0010) /**< Warning alert channel 2 flag */ +#define INA3221_FLAG_WARN_ALERT_CH3 (0x0008) /**< Warning alert channel 3 flag */ + +#define INA3221_FLAG_POWER_VALID (0x0004) /**< Power valid flag */ + +#define INA3221_FLAG_TIMING_CONTROL (0x0002) /**< Timing Control flag */ + +#define INA3221_FLAG_CONV_READY (0x0001) /**< Conversion ready flag */ + +#define INA3221_FLAGS_MASK (INA3221_FLAG_CRIT_ALERT_CH1 \ + | INA3221_FLAG_CRIT_ALERT_CH2 \ + | INA3221_FLAG_CRIT_ALERT_CH3 \ + | INA3221_FLAG_SHUNT_SUM_ALERT \ + | INA3221_FLAG_WARN_ALERT_CH1 \ + | INA3221_FLAG_WARN_ALERT_CH2 \ + | INA3221_FLAG_WARN_ALERT_CH3 \ + | INA3221_FLAG_POWER_VALID \ + | INA3221_FLAG_TIMING_CONTROL \ + | INA3221_FLAG_CONV_READY) /**< Flags bit mask*/ + +/** + * @brief INA3221 device handle struct + */ +typedef struct { + ina3221_params_t params; /**< Device parameters */ +#if defined(MODULE_INA3221_ALERTS) || defined(DOXYGEN) + union { + struct { + ina3221_alert_cb_t warn_cb; /**< Warning alert callback: executed when WRN is pulled low */ + ina3221_alert_cb_t crit_cb; /**< Critical alert callback: executed when CRT is pulled low */ + ina3221_alert_cb_t tc_cb; /**< Timing control alert callback: executed when TC is pulled low */ + ina3221_alert_cb_t pv_cb; /**< Power Valid alert callback: executed when PV is pulled low */ + }; + struct { + ina3221_alert_cb_t alert_callbacks[INA3221_NUM_ALERTS]; /**< Array to access alert callbacks via an index */ + }; + }; + union { + struct { + void *arg_warn_cb; /**< Argument for Warning alert callback */ + void *arg_crit_cb; /**< Argument for Critical alert callback */ + void *arg_tc_cb; /**< Argument for Timing control alert callback */ + void *arg_pv_cb; /**< Argument for Power Valid alert callback */ + }; + struct { + void *alert_callback_arguments[INA3221_NUM_ALERTS]; /**< Array to access alert callback arguments via an index */ + }; + }; +#endif /* MODULE_INA3221_ALERTS */ +} ina3221_t; + +/** + * @brief Reset device, i.e. apply default configuration + * + * @param[in,out] dev Device handle + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return -INA3221_RESET_FAILED, if device could not be reset + * @return @see i2c_read_regs + * @return @see i2c_write_regs + */ +int ina3221_reset(ina3221_t *dev); + +/** + * @brief Initialize device + * + * @param[out] dev Device handle + * @param[in] params Device parameters + * + * @return INA3221_OK, on success + * @return -EINVAL, if NULL pointer was passed + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return -INA3221_BAD_MANUF_ID, if manufacturer ID does not match + * @return -INA3221_DIE_ID, if DIE ID does not match + * @return -INA3221_RESET_FAILED, if reset failed + * @return -INA3221_CONFIG_FAILED, if configuration could not be applied + * @return @see i2c_read_regs + * @return @see i2c_write_regs + */ +int ina3221_init(ina3221_t *dev, const ina3221_params_t *params); + +#if defined(MODULE_INA3221_ALERTS) || defined(DOXYGEN) +/** + * @brief Enable alert callback and argument for alert @p alert + * + * @param[in,out] dev Device handle + * @param[in] alert Alert index + * @param[in] cb Alert callback + * @param[in] arg Alert callback argument + * + * @return INA3221_OK, on success + * @return -ERANGE, if @p alert was out of bounds + * @return -ENUTSUP, if alert pin was initialized with GPIO_UNDEF + */ +int _ina3221_enable_alert(ina3221_t *dev, ina3221_alert_t alert, + ina3221_alert_cb_t cb, void *arg); + +/** + * @brief Wrapper around @see _ina3221_enable_alert, to + * enble warning alert + * + * @param[in,out] dev Device handle + * @param[in] cb Alert callback + * @param[in] arg Alert callback argument + * + * @return @see _ina3221_enable_alert + */ +static inline int ina3221_enable_warning_alert(ina3221_t *dev, + ina3221_alert_cb_t cb, void *arg) +{ + return _ina3221_enable_alert(dev, INA3221_ALERT_WRN, cb, arg); +} + +/** + * @brief Wrapper around @see _ina3221_enable_alert, to + * enable critical alert + * + * @param[in,out] dev Device handle + * @param[in] cb Alert callback + * @param[in] arg Alert callback argument + * + * @return @see _ina3221_enable_alert + */ +static inline int ina3221_enable_critical_alert(ina3221_t *dev, + ina3221_alert_cb_t cb, + void *arg) +{ + return _ina3221_enable_alert(dev, INA3221_ALERT_CRT, cb, arg); +} + +/** + * @brief Wrapper around @see _ina3221_enable_alert, to + * enable timing control alert + * + * @param[in,out] dev Device handle + * @param[in] cb Alert callback + * @param[in] arg Alert callback argument + * + * @return @see _ina3221_enable_alert + */ +static inline int ina3221_enable_timing_control_alert(ina3221_t *dev, + ina3221_alert_cb_t cb, + void *arg) +{ + return _ina3221_enable_alert(dev, INA3221_ALERT_TC, cb, arg); +} + +/** + * @brief Wrapper around @see _ina3221_enable_alert, to + * enable power valid alert + * + * @param[in,out] dev Device handle + * @param[in] cb Alert callback + * @param[in] arg Alert callback argument + * + * @return @see _ina3221_enable_alert + */ +static inline int ina3221_enable_power_valid_alert(ina3221_t *dev, + ina3221_alert_cb_t cb, + void *arg) +{ + return _ina3221_enable_alert(dev, INA3221_ALERT_PV, cb, arg); +} + +/** + * @brief Disable alert callback and argument for alert @p alert + * + * @param[in,out] dev Device handle + * @param[in] alert Alert index + * + * @return INA3221_OK, on success + * @return -ERANGE, if @p alert was out of bounds + * @return -ENUTSUP, if alert pin was initialized with GPIO_UNDEF + */ +int _ina3221_disable_alert(ina3221_t *dev, ina3221_alert_t alert); + +/** + * @brief Wrapper around @see _ina3221_disable_alert, to + * disable warning alert + * + * @param[in,out] dev Device handle + * + * @return @see _ina3221_disable_alert + */ +static inline int ina3221_disable_warning_alert(ina3221_t *dev) +{ + return _ina3221_disable_alert(dev, INA3221_ALERT_WRN); +} + +/** + * @brief Wrapper around @see _ina3221_disable_alert, to + * disable critical alert + * + * @param[in,out] dev Device handle + * + * @return @see _ina3221_disable_alert + */ +static inline int ina3221_disable_critical_alert(ina3221_t *dev) +{ + return _ina3221_disable_alert(dev, INA3221_ALERT_CRT); +} + +/** + * @brief Wrapper around @see _ina3221_disable_alert, to + * disable timing control alert + * + * @param[in,out] dev Device handle + * + * @return @see _ina3221_disable_alert + */ +static inline int ina3221_disable_timing_control_alert(ina3221_t *dev) +{ + return _ina3221_disable_alert(dev, INA3221_ALERT_TC); +} + +/** + * @brief Wrapper around @see _ina3221_disable_alert, to + * disable power valid alert + * + * @param[in,out] dev Device handle + * + * @return @see _ina3221_disable_alert + */ +static inline int ina3221_disable_power_valid_alert(ina3221_t *dev) +{ + return _ina3221_disable_alert(dev, INA3221_ALERT_PV); +} + +#endif /* MODULE_INA3221_ALERTS */ + +/** + * @brief Write configuration register value to configuration register + * + * @param[in,out] dev Device handle + * @param[in] cfg Configuration register value in host byte order + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_write_regs + */ +int _ina3221_set_config(ina3221_t *dev, uint16_t cfg); + +/** + * @brief Wrapper around @see _ina3221_set_config + * + * @param[in, out] dev Device handle + * @param[in] chs Channel enable flags + * @param[in] ns Number of samples + * @param[in] ctbadc Conversion time for bus voltage ADC + * @param[in] ctsadc Conversion time for shunt voltage ADC + * @param[in] mode Device operation mode + * + * @return @see _ina3221_set_config + */ +static inline int ina3221_set_config(ina3221_t *dev, + ina3221_enable_ch_t chs, + ina3221_num_samples_t ns, + ina3221_conv_time_bus_adc_t ctbadc, + ina3221_conv_time_shunt_adc_t ctsadc, + ina3221_mode_t mode) +{ + return _ina3221_set_config(dev, chs | ns | ctbadc | ctsadc | mode); +} + +/** + * @brief Read currently saved configuration register value + * + * @param[in] dev Device handle + * @param[out] cfg Pointer to configuration register output value in host byte order + * + * @return INA3221_OK + */ +int _ina3221_get_config(const ina3221_t *dev, uint16_t *cfg); + +/** + * @brief Wrapper around @see _ina3221_get_config + * + * @param[in] dev Device handle + * @param[out] chs Pointer to enabled channels variable + * @param[out] ns Pointer to number of samples variable + * @param[out] ctbadc Pointer to conversion time bus adc variable + * @param[out] ctsadc Pointer to conversion time shunt adc variable + * @param[out] mode Pointer to measurement mode variable + * + * @return @see _ina3221_get_config + */ +static inline int ina3221_get_config(const ina3221_t *dev, + ina3221_enable_ch_t *chs, + ina3221_num_samples_t *ns, + ina3221_conv_time_bus_adc_t *ctbadc, + ina3221_conv_time_shunt_adc_t *ctsadc, + ina3221_mode_t *mode) +{ + uint16_t cfg = 0; + int ret = _ina3221_get_config(dev, &cfg); + + *chs = cfg & INA3221_ENABLE_CH_MASK; + *ns = cfg & INA3221_NUM_SAMPLES_MASK; + *ctbadc = cfg & INA3221_CONV_TIME_BADC_MASK; + *ctsadc = cfg & INA3221_CONV_TIME_SADC_MASK; + *mode = cfg & INA3221_MODE_MASK; + return ret; +} + +/** + * @brief Enable channels + * + * @param[in,out] dev Device handle + * @param[in] ech Channel enable flags + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + * @return @see i2c_write_regs + */ +int _ina3221_set_enable_channel(ina3221_t *dev, ina3221_enable_ch_t ech); + +/** + * @brief Wrapper around @see _ina3221_set_enable_channel + * + * @param[in, out] dev Device handle + * @param[in] ch1 Channel 1 state + * @param[in] ch2 Channel 2 state + * @param[in] ch3 Channel 3 state + * + * @return @see _ina3221_set_enable_channel + */ +static inline int ina3221_set_channel_state(ina3221_t *dev, + ina3221_channel_state_t ch1, + ina3221_channel_state_t ch2, + ina3221_channel_state_t ch3) +{ + return _ina3221_set_enable_channel(dev, + (ch1 ? INA3221_ENABLE_CH1 : 0) | + (ch2 ? INA3221_ENABLE_CH2 : 0) | + (ch3 ? INA3221_ENABLE_CH3 : 0)); +} + +/** + * @brief Read which channels are currently enabled + * + * @param[in] dev Device handle + * @param[out] ech Pointer to enabled channels output variable + * + * @return Number of enabled channels + */ +int _ina3221_get_enable_channel(const ina3221_t *dev, ina3221_enable_ch_t *ech); + +/** + * @brief Wrapper around _ina3221_get_enable_channel + * + * @param[in] dev Device handle + * @param[out] ch1 Pointer to channel 1 state variable + * @param[out] ch2 Pointer to channel 2 state variable + * @param[out] ch3 Pointer to channel 3 state variable + * + * @return @see _ina3221_get_enable_channel + */ +static inline int ina3221_get_channel_state(const ina3221_t *dev, + ina3221_channel_state_t *ch1, + ina3221_channel_state_t *ch2, + ina3221_channel_state_t *ch3) +{ + ina3221_enable_ch_t ech = 0; + int ret = _ina3221_get_enable_channel(dev, &ech); + + *ch1 = (ech & INA3221_ENABLE_CH1) ? INA3221_CH_ENABLE : INA3221_CH_DISABLE; + *ch2 = (ech & INA3221_ENABLE_CH2) ? INA3221_CH_ENABLE : INA3221_CH_DISABLE; + *ch3 = (ech & INA3221_ENABLE_CH3) ? INA3221_CH_ENABLE : INA3221_CH_DISABLE; + return ret; +} + +/** + * @brief Update number of samples and write to configuration register + * + * @param[in, out] dev Device handle + * @param[in] ns Number of samples + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + * @return @see i2c_write_regs + */ +int ina3221_set_num_samples(ina3221_t *dev, ina3221_num_samples_t ns); + +/** + * @brief Read currently applied number of samples + * + * @param[in] dev Device handle + * @param[out] ns Pointer to number of samples output variable + * + * @return INA3221_OK + */ +int ina3221_get_num_samples(const ina3221_t *dev, ina3221_num_samples_t *ns); + +/** + * @brief Update conversion time of bus voltage ADC and write to configuration register + * + * @param[in,out] dev Device handle + * @param[in] ctb Bus voltage conversion time + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + * @return @see i2c_write_regs + */ +int ina3221_set_conv_time_bus_adc(ina3221_t *dev, + ina3221_conv_time_bus_adc_t ctb); + +/** + * @brief Read currently applied bus voltage ADC conversion time + * + * @param[in] dev Device handle + * @param[out] ctb Pointer to bus voltage ADC conversion time output variable + * + * @return INA3221_OK + */ +int ina3221_get_conv_time_bus_adc(const ina3221_t *dev, + ina3221_conv_time_bus_adc_t *ctb); + +/** + * @brief Update conversion time of shunt voltage ADC and write to configuration register + * + * @param[in,out] dev Device handle + * @param[in] cts Shunt voltage conversion time value + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + * @return @see i2c_write_regs + */ +int ina3221_set_conv_time_shunt_adc(ina3221_t *dev, + ina3221_conv_time_shunt_adc_t cts); + +/** + * @brief Read currently applied shunt voltage ADC conversion time + * + * @param[in] dev Device handle + * @param[out] cts Pointer to shunt voltage ADC conversion time output variable + * + * @return INA3221_OK + */ +int ina3221_get_conv_time_shunt_adc(const ina3221_t *dev, + ina3221_conv_time_shunt_adc_t *cts); + +/** + * @brief Update device operation mode + * + * @param[in,out] dev Device handle + * @param[in] mode Operation mode value in host byte order + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + * @return @see i2c_write_regs + */ +int ina3221_set_mode(ina3221_t *dev, ina3221_mode_t mode); + +/** + * @brief Read currently applied device operation mode + * + * @param[in] dev Device handle + * @param[out] mode Pointer to device operation mode output variable + * + * @return INA3221_OK + */ +int ina3221_get_mode(const ina3221_t *dev, ina3221_mode_t *mode); + +/** + * @brief Enable channels for shunt voltage sum calculation + * + * @param[in] dev Device handle + * @param[in] esch Enable channel shunt voltage sum flags in host byte order + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + * @return @see i2c_write_regs + */ +int _ina3221_set_enable_sum_channel(const ina3221_t *dev, + ina3221_enable_sum_ch_t esch); + +/** + * @brief Wrapper around @see _ina3221_set_enable_sum_channel + * + * @param[in] dev Device handle + * @param[in] ch1 Channel 1 state for sum voltage drop calculation + * @param[in] ch2 Channel 2 state for sum voltage drop calculation + * @param[in] ch3 Channel 3 state for sum voltage drop calculation + * + * @return @see _ina3221_set_enable_sum_channel + */ +static inline int ina3221_set_enable_sum_channel(const ina3221_t *dev, + ina3221_channel_state_t ch1, + ina3221_channel_state_t ch2, + ina3221_channel_state_t ch3) +{ + return _ina3221_set_enable_sum_channel(dev, + (ch1 ? INA3221_ENABLE_SUM_CH1 : 0) | + (ch2 ? INA3221_ENABLE_SUM_CH2 : 0) | + (ch3 ? INA3221_ENABLE_SUM_CH3 : 0)); +} + +/** + * @brief Read enabled channels for shunt voltage sum calculation + * + * @param[in] dev Device handle + * @param[out] esch Pointer to enabled channels for shunt voltage sum calculation output variable + * + * @return Number of enabled channels for shunt voltage sum calculation, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + */ +int _ina3221_get_enable_sum_channel(const ina3221_t *dev, + ina3221_enable_sum_ch_t *esch); + +/** + * @brief Wrapper for @see _ina3221_get_enable_sum_channel + * + * @param[in] dev Device handle + * @param[out] ch1 Pointer to channel 1 state variable for sum voltage drop calculation + * @param[out] ch2 Pointer to channel 2 state variable for sum voltage drop calculation + * @param[out] ch3 Pointer to channel 3 state variable for sum voltage drop calculation + * + * @return @see _ina3221_get_enable_sum_channel + */ +static inline int ina3221_get_enable_sum_channel(const ina3221_t *dev, + ina3221_channel_state_t *ch1, + ina3221_channel_state_t *ch2, + ina3221_channel_state_t *ch3) +{ + ina3221_enable_sum_ch_t esch = 0; + int ret = _ina3221_get_enable_sum_channel(dev, &esch); + + *ch1 = + (esch & + INA3221_ENABLE_SUM_CH1) ? INA3221_CH_ENABLE : INA3221_CH_DISABLE; + *ch2 = + (esch & + INA3221_ENABLE_SUM_CH2) ? INA3221_CH_ENABLE : INA3221_CH_DISABLE; + *ch3 = + (esch & + INA3221_ENABLE_SUM_CH3) ? INA3221_CH_ENABLE : INA3221_CH_DISABLE; + return ret; +} + +/** + * @brief Enable latches for critical/warning alert pins + * + * @param[in] dev Device handle + * @param[in] latch Enable latch flags + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + * @return @see i2c_write_regs + */ +int ina3221_set_latch(const ina3221_t *dev, ina3221_enable_latch_t latch); + +/** + * @brief Read enabled latches for critical/warning alert pins + * + * @param[in] dev Device handle + * @param[in] latch Pointer to latch flags output variable + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + */ +int ina3221_get_latch(const ina3221_t *dev, ina3221_enable_latch_t *latch); + +/** + * @brief Set critical shunt voltage alert limit to @p in_uv for each channel in @p ch + * + * The function exits on the first channel that could not be updated. + * + * @param[in] dev Device handle + * @param[in] ch Channel flags + * @param[in] in_uv Critical shunt voltage limit in uv + * + * @return Number of channels whose critical shunt voltage alert limit could be set, if any + * @return -ERANGE, if @p in_uv was not in [INA3221_MIN_SHUNT_UV; INA3221_MAX_SHUNT_UV] + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_write_regs + */ +int ina3221_set_crit_alert_limit(const ina3221_t *dev, ina3221_channel_t ch, + int32_t in_uv); + +/** + * @brief Read critical shunt voltage alert limit for each channel in @p ch + * + * The function exits on the first channel whose critical alert limit could not be read. + * + * @param[in] dev Device handle + * @param[in] ch Channel flags + * @param[out] out_uv Output array of shunt voltage alert limits in uV + * + * @return Number of channels whose critical shunt voltage alert limits could be read, if any + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + */ +int ina3221_get_crit_alert_limit(const ina3221_t *dev, ina3221_channel_t ch, + int32_t *out_uv); + +/** + * @brief Set warning shunt voltage alert limit to @p in_uv for each channel in @p ch + * + * The function exits on the first channel that could not be updated. + * + * @param[in] dev Device handle + * @param[in] ch Channel flags + * @param[in] in_uv Warning shunt voltage limit in uV in host byte order + * + * @return Number of channels whose warning shunt voltage alert limit could be set, if any + * @return -ERANGE, if @p in_uv was not in [INA3221_MIN_SHUNT_UV; INA3221_MAX_SHUNT_UV] + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_write_regs + */ +int ina3221_set_warn_alert_limit(const ina3221_t *dev, ina3221_channel_t ch, + int32_t in_uv); + +/** + * @brief Read warning shunt voltage alert limit for each channel in @p ch + * + * The function exits on the first channel whose warning alert limit could not be read. + * + * @param[in] dev Device handle + * @param[in] ch Channel flags + * @param[out] out_uv Output array of shunt voltage alert limits in uV + * + * @return Number of channels whose warning shunt voltage alert limits could be read, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + */ +int ina3221_get_warn_alert_limit(const ina3221_t *dev, ina3221_channel_t ch, + int32_t *out_uv); + +/** + * @brief Set shunt voltage sum alert limit to @p in_uv + * + * @param[in] dev Device handle + * @param[in] in_uv shunt voltage sum limit in uv + * + * @return INA3221_OK, on success + * @return -ERANGE, if @p in_uv was not in [INA3221_MIN_SHUNT_SUM_UV; INA3221_MAX_SHUNT_SUM_UV] + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_write_regs + */ +int ina3221_set_shunt_voltage_sum_alert_limit(const ina3221_t *dev, + int32_t in_uv); + +/** + * @brief Read shunt voltage sum alert limit + * + * @param[in] dev Device handle + * @param[out] out_uv Pointer to sum voltage sum alert limit output variable + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + */ +int ina3221_get_shunt_voltage_sum_alert_limit(const ina3221_t *dev, + int32_t *out_uv); + +/** + * @brief Set bus voltage power valid upper limit to @p in_mv + * + * @param[in] dev Device handle + * @param[in] in_mv bus voltage power valid upper limit in mv + * + * @return INA3221_OK, on success + * @return -ERANGE, if @p in_mv was not in [INA3221_MIN_BUS_MV; INA3221_MAX_BUS_MV] + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_write_regs + */ +int ina3221_set_power_valid_upper_limit(const ina3221_t *dev, int32_t in_mv); + +/** + * @brief Read bus voltage power valid upper limit + * + * @param[in] dev Device handle + * @param[out] out_mv Pointer to bus voltage power valid upper limit output variable + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + */ +int ina3221_get_power_valid_upper_limit(const ina3221_t *dev, int32_t *out_mv); + +/** + * @brief Set bus voltage power valid lower limit to @p in_mv + * + * @param[in] dev Device handle + * @param[in] in_mv bus voltage power valid lower limit in mV + * + * @return INA3221_OK, on success + * @return -ERANGE, if @p in_mv was not in [INA3221_MIN_BUS_MV; INA3221_MAX_BUS_MV] + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_write_regs + */ +int ina3221_set_power_valid_lower_limit(const ina3221_t *dev, int32_t in_mv); + +/** + * @brief Read bus voltage power valid lower limit + * + * @param[in] dev Device handle + * @param[out] out_mv Pointer to bus voltage power valid lower limit output variable + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + */ +int ina3221_get_power_valid_lower_limit(const ina3221_t *dev, int32_t *out_mv); + +/** + * @brief Read status flags + * + * @param[in] dev Device handle + * @param[out] flags Pointer to flags output variable + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + */ +int ina3221_read_flags(const ina3221_t *dev, uint16_t *flags); + +/** + * @brief Read sum of shunt voltages + * + * @param[in] dev Device handle + * @param[out] out_uv Pointer to shunt voltage sum output variable + * @param[out] flags Pointer to flags output variable, may be NULL + * + * @return INA3221_OK, on success + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + */ +int ina3221_read_shunt_sum_uv(const ina3221_t *dev, int32_t *out_uv, + uint16_t *flags); + +/** + * @brief Read shunt voltages for each channel in @p ch + * + * The function exits on the first channel whose shunt voltage could not be read + * + * @param[in] dev Device handle + * @param[in] ch Channel flags + * @param[out] out_uv Array of output shunt voltage values in uV + * @param[out] flags Pointer to flags output variable, may be NULL + * + * @return Number of channels whose shunt voltages could be read, if any + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + */ +int ina3221_read_shunt_uv(const ina3221_t *dev, ina3221_channel_t ch, + int32_t *out_uv, uint16_t *flags); + +/** + * @brief Read bus voltages for each channel in @p ch + * + * The function exits on the first channel whose bus voltage could not be read + * + * @param[in] dev Device handle + * @param[in] ch Channel flags + * @param[out] out_mv Array of output bus voltage values in mV + * @param[out] flags Pointer to flags output variable, may be NULL + * + * @return Number of channels whose bus voltages could be read, if any + * @return -INA3221_I2C_ERROR, if I2C bus acquirement failed + * @return @see i2c_read_regs + */ +int ina3221_read_bus_mv(const ina3221_t *dev, ina3221_channel_t ch, + int16_t *out_mv, uint16_t *flags); + +/** + * @brief Calculate current for each channel in @p ch + * + * @param[in] dev Device handle + * @param[in] ch Channel flags + * @param[in] in_uv Array of input shunt voltage values + * @param[out] out_ma Array of output current values + * + * @return Number of clculated current values + */ +int ina3221_calculate_current_ua(const ina3221_t *dev, ina3221_channel_t ch, + int32_t *in_uv, int32_t *out_ma); + +/** + * @brief Calculate power from bus voltage and current values + * + * @param[in] in_mv Array of input bus voltage values in mV + * @param[in] in_ua Array of input current values in uA + * @param[in] num Number of values in @p in_mv + * @param[out] out_mw Array of output power values in uW + * + * @return Number of calculated power values, on success + * @return -ERANGE, if @p num is greater than INA3221_NUM_CH + */ +int ina3221_calculate_power_uw(int16_t *in_mv, int32_t *in_ua, uint8_t num, + int32_t *out_mw); + +/** + * @brief Align @p in_res to the number of channels + * For example: @p ch = (INA3221_CH1 | INA3221_CH3) + * @p in_res = {value_ch1, value_ch3}, then + * @p out_res will be {value_ch1, 0, value_ch3} + * + * @param[in] ch Channel flags + * @param[in] in_res Output of e.g. @see ina3221_calculate_current_ua + * @param[out] out_res Channel aligned result + * @param[in] res_val_size Size of a value in @p in_res in bytes + */ +void ina3221_ch_align(ina3221_channel_t ch, const void *in_res, void *out_res, + size_t res_val_size); + +/** + * @brief Set operation mode to INA3221_MODE_TRIGGER_SHUNT_ONLY to trigger shunt voltage measurement + * + * @param dev Device handle + */ +#define INA3221_TRIGGER_SHUNT(dev) \ + ina3221_set_mode(dev, INA3221_MODE_TRIGGER_SHUNT_ONLY) + +/** + * @brief Set operation mode to INA3221_MODE_TRIGGER_BUS_ONLY to trigger bus voltage measurement + * + * @param dev Device handle + */ +#define INA3221_TRIGGER_BUS(dev) \ + ina3221_set_mode(dev, INA3221_MODE_TRIGGER_BUS_ONLY) + +/** + * @brief Set operation mode to INA3221_MODE_TRIGGER_SHUNT_BUS to trigger shunt and bus voltage measurement + * + * @param dev Device handle + */ +#define INA3221_TRIGGER_SHUNT_AND_BUS(dev) \ + ina3221_set_mode(dev, INA3221_MODE_TRIGGER_SHUNT_BUS) + +#ifdef __cplusplus +} +#endif + +#endif /* INA3221_H */ +/** @} */ diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index b11af0f9397c..f54c5a2993a2 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -40,8 +40,9 @@ PSEUDOMODULES += gnrc_sixlowpan_router PSEUDOMODULES += gnrc_sixlowpan_router_default PSEUDOMODULES += gnrc_sock_check_reuse PSEUDOMODULES += gnrc_txtsnd -PSEUDOMODULES += i2c_scan PSEUDOMODULES += heap_cmd +PSEUDOMODULES += i2c_scan +PSEUDOMODULES += ina3221_alerts PSEUDOMODULES += l2filter_blacklist PSEUDOMODULES += l2filter_whitelist PSEUDOMODULES += lis2dh12_i2c From 5311de713145f71320a333c743cc535d469e866d Mon Sep 17 00:00:00 2001 From: fabian18 Date: Tue, 5 Nov 2019 12:33:06 +0100 Subject: [PATCH 2/3] tests/driver_ina3221: Test for INA3221 --- tests/driver_ina3221/Makefile | 21 ++ tests/driver_ina3221/README.md | 29 +++ tests/driver_ina3221/main.c | 418 +++++++++++++++++++++++++++++++++ 3 files changed, 468 insertions(+) create mode 100644 tests/driver_ina3221/Makefile create mode 100644 tests/driver_ina3221/README.md create mode 100644 tests/driver_ina3221/main.c diff --git a/tests/driver_ina3221/Makefile b/tests/driver_ina3221/Makefile new file mode 100644 index 000000000000..a40bc8947973 --- /dev/null +++ b/tests/driver_ina3221/Makefile @@ -0,0 +1,21 @@ +include ../Makefile.tests_common + +DEBUG ?= 0 + +USEMODULE += ina3221 +USEMODULE += fmt +USEMODULE += xtimer + +INA3221_ADDR ?= INA3221_ADDR_00 +INA3221_PIN_WRN ?= GPIO_UNDEF +INA3221_PIN_CRT ?= GPIO_UNDEF +INA3221_PIN_PV ?= GPIO_UNDEF +INA3221_PIN_TC ?= GPIO_UNDEF + +CFLAGS += -DINA3221_PARAM_ADDR=$(INA3221_ADDR) +CFLAGS += -DINA3221_PARAM_PIN_WRN=$(INA3221_PIN_WRN) +CFLAGS += -DINA3221_PARAM_PIN_CRT=$(INA3221_PIN_CRT) +CFLAGS += -DINA3221_PARAM_PIN_PV=$(INA3221_PIN_PV) +CFLAGS += -DINA3221_PARAM_PIN_TC=$(INA3221_PIN_TC) + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_ina3221/README.md b/tests/driver_ina3221/README.md new file mode 100644 index 000000000000..2f445e7519b9 --- /dev/null +++ b/tests/driver_ina3221/README.md @@ -0,0 +1,29 @@ +# About +This is a manual test application for the INA3221 current and power +monitor driver. + +# Usage +This test application will initialize the sensor with the following parameters: +- INA3221_PARAM_I2C: I2C_DEV(0) +- INA3221_PARAM_ADDR: INA3221_ADDR_00 +- INA3221_PARAM_PIN_WRN: GPIO_UNDEF, or given WRN input pin +- INA3221_PARAM_PIN_CRT: GPIO_UNDEF, or given CRT input pin +- INA3221_PARAM_PIN_TC: GPIO_UNDEF, or given TC input pin +- INA3221_PARAM_PIN_PV: GPIO_UNDEF, or given PV input pin +- INA3221_PARAM_CONFIG: ( + INA3221_ENABLE_CH1 | INA3221_ENABLE_CH2 | INA3221_ENABLE_CH3, + INA3221_NUM_SAMPLES_4 | + INA3221_CONV_TIME_BADC_4156US | + INA3221_CONV_TIME_SADC_4156US | + INA3221_MODE_CONTINUOUS_SHUNT_BUS + ) +- INA3221_PARAM_RSHUNT_MOHM_CH1: 100 +- INA3221_PARAM_RSHUNT_MOHM_CH2: 100 +- INA3221_PARAM_RSHUNT_MOHM_CH3: 100 + +After initialization, the application will perform basic read/write +functionality tests. If the preceding static tests succeed, the program +will enter an endless loop and check if a full conversion cycle has +completed. If new values for shunt voltage and bus voltage are available, +current and power are calculated and each value for every channel is +printed to stdio in a neat table, with available status flags. diff --git a/tests/driver_ina3221/main.c b/tests/driver_ina3221/main.c new file mode 100644 index 000000000000..af2a5551da1f --- /dev/null +++ b/tests/driver_ina3221/main.c @@ -0,0 +1,418 @@ +/* + * 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 tests + * @{ + * + * @file + * @brief Test application for the INA3221 sensor driver + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include "periph/gpio.h" +#include "xtimer.h" +#include "fmt.h" +#include "ina3221_internal.h" +#include "ina3221_params.h" +#include "ina3221.h" + +/* Issue critical alert if shunt voltage is above thist value */ +#define CRIT_ALERT_LIM_UV (2500) +/* Issue warning alert if shunt voltage is above this value */ +#define WARN_ALERT_LIM_UV (1600) +/* Issue power valid alert if bus voltage is below this value */ +#define PV_LOWER_LIM_MV (4500) +/* Issue power valid alert if bus voltage is above this value */ +#define PV_UPPER_LIM_MV (5500) +/* Issue critical alert if the sum of all shunt voltages is above this value */ +#define SUM_SHUNT_ALERT_LIM_UV (8000) + +#define COL (" ") +#define THEAD ( \ + "channel | shunt [uV] | bus [mV] | current [uA] | power [uW] ") +#define HLINE ( \ + "--------------+--------------+--------------+--------------+--------------") + +#ifdef MODULE_INA3221_ALERTS +static void warning_alert(void *arg) +{ + ina3221_t *dev = (ina3221_t *)arg; + + (void)dev; + puts("WARNING"); +} + +static void critical_alert(void *arg) +{ + ina3221_t *dev = (ina3221_t *)arg; + + (void)dev; + puts("CRITICAL"); +} + +static void timing_control_alert(void *arg) +{ + ina3221_t *dev = (ina3221_t *)arg; + + (void)dev; + puts("TIMING CONTROL"); +} + +static void power_valid_alert(void *arg) +{ + ina3221_t *dev = (ina3221_t *)arg; + + (void)dev; + puts("POWER VALID"); +} +#endif + +int main(void) +{ + xtimer_init(); + + int status; + ina3221_t dev; + + for (int32_t i = INA3221_MIN_SHUNT_UV; i <= INA3221_MAX_SHUNT_UV; + i += INA3221_SHUNT_VOLTAGE_PRECISION_UV) { + int16_t reg = shunt_voltage_uv_to_reg_val(i); + int32_t sv = reg_val_to_shunt_voltage_uv(reg); + int16_t _reg = shunt_voltage_uv_to_reg_val(sv); + if (reg != _reg || i != sv) { + printf( + "[FAILURE] shunt V conversiom: shunt V: %" PRId32 ", REG: %" PRId16 ", shunt V: %" PRId32 ", REG: %" PRId16 "\n", i, reg, sv, + _reg); + return 1; + } + } + for (int32_t i = INA3221_MIN_BUS_MV; i <= INA3221_MAX_BUS_MV; + i += INA3221_BUS_VOLTAGE_PRECISION_MV) { + int16_t reg = bus_voltage_mv_to_reg_val(i); + int32_t bv = reg_val_to_bus_voltage_mv(reg); + int16_t _reg = bus_voltage_mv_to_reg_val(bv); + if (reg != _reg || i != bv) { + printf( + "[FAILURE] bus V conversion: bus V: %" PRId32 ", REG: %" PRId16 ", bus V: %" PRId32 ", REG: %" PRId16 "\n", i, reg, bv, + _reg); + return 1; + } + } + for (int32_t i = INA3221_MIN_SHUNT_SUM_UV; i <= INA3221_MAX_SHUNT_SUM_UV; + i += INA3221_SHUNT_VOLTAGE_PRECISION_UV) { + int16_t reg = sum_shunt_voltage_uv_to_reg_val(i); + int32_t sv = sum_reg_val_to_shunt_voltage_uv(reg); + int16_t _reg = sum_shunt_voltage_uv_to_reg_val(sv); + if (reg != _reg || i != sv) { + printf( + "[FAILURE] shunt sum V conversion: sum V: %" PRId32 ", REG: %" PRId16 ", sum V: %" PRId32 ", REG: %" PRId16 "\n", i, reg, sv, + _reg); + return 1; + } + } + + status = ina3221_init(&dev, &ina3221_params[0]); + if (status != INA3221_OK) { + printf("[FAILURE] ina3221_init: %d\n", status); + return 1; + } + else { + puts("[SUCCESS] ina3221_init"); + } + +#ifdef MODULE_INA3221_ALERTS + status = ina3221_enable_warning_alert(&dev, warning_alert, &dev); + if (status == -ENOTSUP) { + puts("[WARNING] INA3221_ALERT_WRN not supported"); + } + else if (status != INA3221_OK) { + printf("[FAILURE] ina3221_enable_alert INA3221_ALERT_WRN: %d\n", + status); + return 1; + } + + status = ina3221_enable_critical_alert(&dev, critical_alert, &dev); + if (status == -ENOTSUP) { + puts("[WARNING] INA3221_ALERT_CRT not supported"); + } + else if (status != INA3221_OK) { + printf("[FAILURE] ina3221_enable_alert INA3221_ALERT_CRT: %d\n", + status); + return 1; + } + + status = ina3221_enable_timing_control_alert(&dev, timing_control_alert, + &dev); + if (status == -ENOTSUP) { + puts("[WARNING] INA3221_ALERT_TC not supported"); + } + else if (status != INA3221_OK) { + printf("[FAILURE] ina3221_enable_alert INA3221_ALERT_TC: %d\n", status); + return 1; + } + + status = ina3221_enable_power_valid_alert(&dev, power_valid_alert, &dev); + if (status == -ENOTSUP) { + puts("[WARNING] INA3221_ALERT_PV not supported"); + } + else if (status != INA3221_OK) { + printf("[FAILURE] ina3221_enable_alert INA3221_ALERT_PV: %d\n", status); + return 1; + } + puts("[SUCCESS] ina3221_enable_alert"); +#endif + + int32_t shunt_uv[INA3221_NUM_CH] = { 0 }; + int16_t bus_mv[INA3221_NUM_CH] = { 0 }; + int32_t sum_shunt_uv = 0; + int32_t current_ua[INA3221_NUM_CH] = { 0 }; + int32_t power_uw[INA3221_NUM_CH] = { 0 }; + uint16_t flags = 0; + + status = ina3221_read_shunt_uv(&dev, + INA3221_CH1 | INA3221_CH2 | INA3221_CH3, + shunt_uv, &flags); + if (status != INA3221_NUM_CH) { + printf("[FAILURE] ina3221_read_shunt_uv: %d\n", status); + return 1; + } + else { + puts("[SUCCESS] ina3221_read_shunt_uv"); + } + + status = ina3221_calculate_current_ua(&dev, + INA3221_CH1 | INA3221_CH2 | INA3221_CH3, shunt_uv, + current_ua); + if (status != INA3221_NUM_CH) { + printf("[FAILURE] ina3221_calculate_current_ua: %d\n", status); + return 1; + } + else { + puts("[SUCCESS] ina3221_calculate_current_ua"); + } + + status = ina3221_read_bus_mv(&dev, INA3221_CH1 | INA3221_CH2 | INA3221_CH3, + bus_mv, &flags); + if (status != INA3221_NUM_CH) { + printf("[FAILURE] ina3221_read_bus_mv: %d\n", status); + return 1; + } + else { + puts("[SUCCESS] ina3221_read_bus_mv"); + } + + status = ina3221_calculate_power_uw(bus_mv, current_ua, status, power_uw); + if (status != INA3221_NUM_CH) { + printf("[FAILURE] ina3221_calculate_power_uw: %d\n", status); + return 1; + } + else { + puts("[SUCCESS] ina3221_calculate_power_uw"); + } + + status = ina3221_set_enable_sum_channel(&dev, INA3221_CH_ENABLE, + INA3221_CH_ENABLE, + INA3221_CH_ENABLE); + if (status < 0) { + printf("[FAILURE] ina3221_set_enable_sum_channel: %d\n", status); + return 1; + } + else { + printf("[SUCCESS] ina3221_set_enable_sum_channel"); + } + + status = ina3221_read_shunt_sum_uv(&dev, &sum_shunt_uv, &flags); + if (status != INA3221_OK) { + printf("[FAILURE] ina3221_read_shunt_sum_uv: %d\n", status); + return 1; + } + else { + puts("[SUCCESS] ina3221_read_shunt_sum_uv"); + } + + status = INA3221_TRIGGER_SHUNT_AND_BUS(&dev); + if (status < 0) { + printf("[FAILURE] INA3221_TRIGGER_SHUNT_AND_BUS: %d\n", status); + return 1; + } + else { + puts("[SUCCESS] INA3221_TRIGGER_SHUNT_AND_BUS"); + } + flags = 0; + while (!(flags & INA3221_FLAG_CONV_READY)) { + status = ina3221_read_flags(&dev, &flags); + if (status < 0) { + printf("[FAILURE] ina3221_read_flags: %d\n", status); + return 1; + } + } + status = ina3221_read_shunt_uv(&dev, + INA3221_CH1 | INA3221_CH2 | INA3221_CH3, + shunt_uv, &flags); + if (status != INA3221_NUM_CH) { + printf("[FAILURE] ina3221_read_shunt_uv (triggered): %d\n", status); + return 1; + } + else { + puts("[SUCCESS] ina3221_read_shunt_uv (triggered)"); + } + + status = ina3221_read_bus_mv(&dev, INA3221_CH1 | INA3221_CH2 | INA3221_CH3, + bus_mv, &flags); + if (status != INA3221_NUM_CH) { + printf("[FAILURE] ina3221_read_bus_mv (triggered): %d\n", status); + return 1; + } + else { + puts("[SUCCESS] ina3221_read_bus_mv (triggered)"); + } + + int32_t crit_alert_lim = CRIT_ALERT_LIM_UV; + status = ina3221_set_crit_alert_limit(&dev, + INA3221_CH1 | INA3221_CH2 | INA3221_CH3, + crit_alert_lim); + if (status != INA3221_NUM_CH) { + printf("[FAILURE] ina3221_set_crit_alert_limit: %d\n", status); + return 1; + } + else { + puts("[SUCCESS] ina3221_set_crit_alert_limit"); + } + + int32_t warn_alert_lim = WARN_ALERT_LIM_UV; + status = ina3221_set_warn_alert_limit(&dev, + INA3221_CH1 | INA3221_CH2 | INA3221_CH3, + warn_alert_lim); + if (status != INA3221_NUM_CH) { + printf("[FAILURE] ina3221_set_warn_alert_limit: %d\n", status); + return 1; + } + else { + puts("[SUCCESS] ina3221_set_warn_alert_limit"); + } + + int32_t pv_lower_lim = PV_LOWER_LIM_MV; + status = ina3221_set_power_valid_lower_limit(&dev, pv_lower_lim); + if (status != INA3221_OK) { + printf("[FAILURE] ina3221_set_power_valid_lower_limit: %d\n", status); + return 1; + } + else { + puts("[SUCCESS] ina3221_set_power_valid_lower_limit"); + } + + int32_t pv_upper_lim = PV_UPPER_LIM_MV; + status = ina3221_set_power_valid_upper_limit(&dev, pv_upper_lim); + if (status != INA3221_OK) { + printf("[FAILURE] ina3221_set_power_valid_upper_limit: %d\n", status); + return 1; + } + else { + puts("[SUCCESS] ina3221_set_power_valid_upper_limit"); + } + + int32_t sum_shunt_alert_lim = SUM_SHUNT_ALERT_LIM_UV; + status = + ina3221_set_shunt_voltage_sum_alert_limit(&dev, sum_shunt_alert_lim); + if (status != INA3221_OK) { + printf("[FAILURE] ina3221_set_shunt_voltage_sum_alert_limit: %d\n", + status); + return 1; + } + else { + puts("[SUCCESS] ina3221_set_shunt_voltage_sum_alert_limit"); + } + + status = ina3221_set_mode(&dev, INA3221_MODE_CONTINUOUS_SHUNT_BUS); + if (status != INA3221_OK) { + printf("[FAILURE] ina3221_set_mode: %d\n", status); + return 1; + } + char line_buffer[strlen(THEAD) + 1]; + char col_buffer[strlen(COL) + 1]; + while (1) { + status = ina3221_read_flags(&dev, &flags); + if (status != INA3221_OK) { + printf("[FAILURE] ina3221_read_flags: %d\n", status); + return 1; + } + if (!(flags & INA3221_FLAG_CONV_READY)) { + xtimer_sleep(2); + continue; + } + status = ina3221_read_shunt_uv(&dev, + INA3221_CH1 | INA3221_CH2 | INA3221_CH3, + shunt_uv, NULL); + if (status != INA3221_NUM_CH) { + printf("[FAILURE] ina3221_read_shunt_uv: %d\n", status); + return 1; + } + status = ina3221_read_bus_mv(&dev, + INA3221_CH1 | INA3221_CH2 | INA3221_CH3, + bus_mv, NULL); + if (status != INA3221_NUM_CH) { + printf("[FAILURE] ina3221_read_bus_mv: %d\n", status); + return 1; + } + status = ina3221_calculate_current_ua(&dev, + INA3221_CH1 | INA3221_CH2 | INA3221_CH3, shunt_uv, + current_ua); + if (status != INA3221_NUM_CH) { + printf("[FAILURE] ina3221_calculate_current_ua: %d\n", status); + return 1; + } + status = ina3221_calculate_power_uw(bus_mv, current_ua, INA3221_NUM_CH, + power_uw); + if (status != INA3221_NUM_CH) { + printf("[FAILURE] ina3221_calculate_power_uw: %d\n", status); + return 1; + } + puts(THEAD); + puts(HLINE); + for (int8_t i = 0; i < INA3221_NUM_CH; i++) { + char *p = line_buffer; + strcpy(col_buffer, COL); + fmt_char(col_buffer, '0' + i); + p += sprintf(p, "%s|", col_buffer); + strcpy(col_buffer, COL); + fmt_s32_dec(col_buffer, shunt_uv[i]); + p += sprintf(p, "%s|", col_buffer); + strcpy(col_buffer, COL); + fmt_s16_dec(col_buffer, bus_mv[i]); + p += sprintf(p, "%s|", col_buffer); + strcpy(col_buffer, COL); + fmt_s32_dec(col_buffer, current_ua[i]); + p += sprintf(p, "%s|", col_buffer); + strcpy(col_buffer, COL); + fmt_s32_dec(col_buffer, power_uw[i]); + p += sprintf(p, "%s", col_buffer); + printf("%s\n", line_buffer); + } + puts(""); + snprintf(line_buffer, sizeof(line_buffer), + "FLAGS: CF1:%d CF2:%d CF3:%d SF:%d WF1:%d WF2:%d WF3:%d PVF:%d TCF:%d CVRF:%d", + (flags & INA3221_FLAG_CRIT_ALERT_CH1) ? 1 : 0, + (flags & INA3221_FLAG_CRIT_ALERT_CH2) ? 1 : 0, + (flags & INA3221_FLAG_CRIT_ALERT_CH3) ? 1 : 0, + (flags & INA3221_FLAG_SHUNT_SUM_ALERT) ? 1 : 0, + (flags & INA3221_FLAG_WARN_ALERT_CH1) ? 1 : 0, + (flags & INA3221_FLAG_WARN_ALERT_CH2) ? 1 : 0, + (flags & INA3221_FLAG_WARN_ALERT_CH3) ? 1 : 0, + (flags & INA3221_FLAG_POWER_VALID) ? 1 : 0, + (flags & INA3221_FLAG_TIMING_CONTROL) ? 1 : 0, + (flags & INA3221_FLAG_CONV_READY) ? 1 : 0); + printf("%s\n", line_buffer); + puts(HLINE); + } + return 0; +} From 7e8cce875ad42a3566e8a36dd75cf8ffbcb0d931 Mon Sep 17 00:00:00 2001 From: fabian18 Date: Tue, 5 Nov 2019 12:37:41 +0100 Subject: [PATCH 3/3] drivers/ina3221: Added SAUL integration --- drivers/ina3221/ina3221_saul.c | 178 +++++++++++++++++++++++++ drivers/include/saul.h | 1 + drivers/saul/saul_str.c | 1 + sys/auto_init/auto_init.c | 4 + sys/auto_init/saul/auto_init_ina3221.c | 65 +++++++++ sys/include/phydat.h | 1 + sys/phydat/phydat_str.c | 1 + 7 files changed, 251 insertions(+) create mode 100644 drivers/ina3221/ina3221_saul.c create mode 100644 sys/auto_init/saul/auto_init_ina3221.c diff --git a/drivers/ina3221/ina3221_saul.c b/drivers/ina3221/ina3221_saul.c new file mode 100644 index 000000000000..b17361ca29d8 --- /dev/null +++ b/drivers/ina3221/ina3221_saul.c @@ -0,0 +1,178 @@ +/* + * 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_ina3221 + * @{ + * + * @file + * @brief SAUL adaption for INA3221 device + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include "phydat.h" +#include "saul.h" +#include "ina3221.h" +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define SAUL_INA3221_NO_VALUE (0) + +static int read_bus_voltage(const void *dev, phydat_t *res) +{ + ina3221_t *_dev = (ina3221_t *)dev; + ina3221_enable_ch_t ench = 0; + int16_t voltage[INA3221_NUM_CH] = { 0 }; + int num_ch = _ina3221_get_enable_channel(_dev, &ench); + + if (!num_ch) { + return -ECANCELED; + } + ina3221_channel_t ch = (((ench & INA3221_ENABLE_CH1) ? INA3221_CH1 : 0) | + ((ench & INA3221_ENABLE_CH2) ? INA3221_CH2 : 0) | + ((ench & INA3221_ENABLE_CH3) ? INA3221_CH3 : 0)); + ina3221_read_bus_mv(_dev, ch, voltage, NULL); + res->scale = -3; /* mV to V*/ + res->unit = UNIT_V; + for (int i = 0, j = 0; i < INA3221_NUM_CH; i++) { + res->val[i] = (ch & (1 << i)) + ? voltage[j++] : SAUL_INA3221_NO_VALUE; + } + return INA3221_NUM_CH; +} + +static int read_current(const void *dev, phydat_t *res) +{ + ina3221_t *_dev = (ina3221_t *)dev; + ina3221_enable_ch_t ench = 0; + int32_t shunt_uv[INA3221_NUM_CH] = { 0 }; + int32_t current[INA3221_NUM_CH] = { 0 }; + int num_ch = _ina3221_get_enable_channel(_dev, &ench); + + if (!num_ch) { + return -ECANCELED; + } + ina3221_channel_t ch = (((ench & INA3221_ENABLE_CH1) ? INA3221_CH1 : 0) | + ((ench & INA3221_ENABLE_CH2) ? INA3221_CH2 : 0) | + ((ench & INA3221_ENABLE_CH3) ? INA3221_CH3 : 0)); + ina3221_read_shunt_uv(_dev, ch, shunt_uv, NULL); + ina3221_calculate_current_ua(_dev, ch, shunt_uv, current); + res->scale = -6; /* uA to A*/ + res->unit = UNIT_A; + for (int i = 0, j = 0; i < INA3221_NUM_CH; i++) { + res->val[i] = (ch & (1 << i)) + ? (int16_t)current[j++] : SAUL_INA3221_NO_VALUE; + } + ina3221_ch_align(ch, current, current, sizeof(int32_t)); + phydat_fit(res, current, 3); + return INA3221_NUM_CH; +} + +static int read_power(const void *dev, phydat_t *res) +{ + ina3221_t *_dev = (ina3221_t *)dev; + ina3221_enable_ch_t ench = 0; + int32_t shunt_uv[INA3221_NUM_CH] = { 0 }; + int32_t current_ua[INA3221_NUM_CH] = { 0 }; + int16_t bus_mv[INA3221_NUM_CH] = { 0 }; + int32_t power[INA3221_NUM_CH] = { 0 }; + int num_ch = _ina3221_get_enable_channel(_dev, &ench); + + if (!num_ch) { + return -ECANCELED; + } + ina3221_channel_t ch = (((ench & INA3221_ENABLE_CH1) ? INA3221_CH1 : 0) | + ((ench & INA3221_ENABLE_CH2) ? INA3221_CH2 : 0) | + ((ench & INA3221_ENABLE_CH3) ? INA3221_CH3 : 0)); + int num_ch_shunt_uv = ina3221_read_shunt_uv(_dev, ch, shunt_uv, NULL); + ina3221_calculate_current_ua(_dev, ch, shunt_uv, current_ua); + ina3221_read_bus_mv(_dev, ch, bus_mv, NULL); + ina3221_calculate_power_uw(bus_mv, current_ua, num_ch_shunt_uv, power); + res->scale = -6; /* uW to W*/ + res->unit = UNIT_W; + for (int i = 0, j = 0; i < INA3221_NUM_CH; i++) { + res->val[i] = (ch & (1 << i)) + ? (int16_t)power[j++] : SAUL_INA3221_NO_VALUE; + } + ina3221_ch_align(ch, power, power, sizeof(int32_t)); + phydat_fit(res, power, 3); + return INA3221_NUM_CH; +} + +static int read_shunt_voltage_sum(const void *dev, phydat_t *res) +{ + ina3221_t *_dev = (ina3221_t *)dev; + ina3221_enable_ch_t ench = 0; + int32_t shunt_voltage_sum = SAUL_INA3221_NO_VALUE; + int num_ch = _ina3221_get_enable_channel(_dev, &ench); + + if (!num_ch) { + return -ECANCELED; + } + ina3221_read_shunt_sum_uv(_dev, &shunt_voltage_sum, NULL); + res->scale = -6; /* uV to V*/ + res->unit = UNIT_V; + res->val[0] = shunt_voltage_sum; + phydat_fit(res, &shunt_voltage_sum, 1); + return 1; +} + +static int configure_channel(const void *dev, phydat_t *data) +{ + ina3221_t *_dev = (ina3221_t *)dev; + ina3221_enable_ch_t ench = + ((data->val[0] & INA3221_CH1) ? INA3221_ENABLE_CH1 : 0) | + ((data->val[0] & INA3221_CH2) ? INA3221_ENABLE_CH2 : 0) | + ((data->val[0] & INA3221_CH3) ? INA3221_ENABLE_CH3 : 0); + + if (_ina3221_set_enable_channel(_dev, ench) != INA3221_OK) { + return -ECANCELED; + } + return INA3221_NUM_CH; +} + +static int configure_channel_sum(const void *dev, phydat_t *data) +{ + ina3221_t *_dev = (ina3221_t *)dev; + ina3221_enable_sum_ch_t esch = + ((data->val[0] & INA3221_CH1) ? INA3221_ENABLE_SUM_CH1 : 0) | + ((data->val[0] & INA3221_CH2) ? INA3221_ENABLE_SUM_CH2 : 0) | + ((data->val[0] & INA3221_CH3) ? INA3221_ENABLE_SUM_CH3 : 0); + + if (_ina3221_set_enable_sum_channel(_dev, esch) != INA3221_OK) { + return -ECANCELED; + } + return INA3221_NUM_CH; +} + +const saul_driver_t ina3221_saul_drivers[] = { + { + .read = read_bus_voltage, + .write = configure_channel, + .type = SAUL_SENSE_VOLTAGE + }, + { + .read = read_current, + .write = configure_channel, + .type = SAUL_SENSE_CURRENT + }, + { + .read = read_power, + .write = configure_channel, + .type = SAUL_SENSE_POWER + }, + { + .read = read_shunt_voltage_sum, + .write = configure_channel_sum, + .type = SAUL_SENSE_VOLTAGE + } +}; diff --git a/drivers/include/saul.h b/drivers/include/saul.h index 7cace775a128..a5e6b55d3ad3 100644 --- a/drivers/include/saul.h +++ b/drivers/include/saul.h @@ -105,6 +105,7 @@ enum { SAUL_SENSE_CAPACITANCE = 0x97, /**< sensor: capacitance */ SAUL_SENSE_VOLTAGE = 0x98, /**< sensor: voltage */ SAUL_SENSE_PH = 0x99, /**< sensor: pH */ + SAUL_SENSE_POWER = 0x9A, /**< sensor: (electrical) power */ SAUL_CLASS_ANY = 0xff /**< any device - wildcard */ /* extend this list as needed... */ }; diff --git a/drivers/saul/saul_str.c b/drivers/saul/saul_str.c index 12150c0e9d32..29fe6721997d 100644 --- a/drivers/saul/saul_str.c +++ b/drivers/saul/saul_str.c @@ -62,6 +62,7 @@ const char *saul_class_to_str(const uint8_t class_id) case SAUL_SENSE_CAPACITANCE: return "SENSE_CAPACITANCE"; case SAUL_SENSE_VOLTAGE: return "SENSE_VOLTAGE"; case SAUL_SENSE_PH: return "SENSE_PH"; + case SAUL_SENSE_POWER: return "SENSE_POWER"; case SAUL_CLASS_ANY: return "CLASS_ANY"; default: return "CLASS_UNKNOWN"; } diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index e8704c8ffb48..4b7c7d1d3cc0 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -422,6 +422,10 @@ void auto_init(void) extern void auto_init_hts221(void); auto_init_hts221(); #endif +#ifdef MODULE_INA3221 + extern void auto_init_ina3221(void); + auto_init_ina3221(); + #endif #ifdef MODULE_IO1_XPLAINED extern void auto_init_io1_xplained(void); auto_init_io1_xplained(); diff --git a/sys/auto_init/saul/auto_init_ina3221.c b/sys/auto_init/saul/auto_init_ina3221.c new file mode 100644 index 000000000000..7dfa246693c4 --- /dev/null +++ b/sys/auto_init/saul/auto_init_ina3221.c @@ -0,0 +1,65 @@ +/* + * 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 sys_auto_init_saul + * @{ + * + * @file + * @brief Auto initialization for HTS221 devices + * + * @author Fabian Hüßler + * + * @} + */ + +#ifdef MODULE_INA3221 + +#include "assert.h" +#include "log.h" +#include "saul_reg.h" +#include "ina3221_params.h" +#include "ina3221.h" + +#define INA3221_NUM ARRAY_SIZE(ina3221_params) + +#define INA3221_SAUL_INFO_NUM ARRAY_SIZE(ina3221_saul_info) + +#define INA3221_SAUL_DRIVERS_NUM (4) +extern const saul_driver_t ina3221_saul_drivers[INA3221_SAUL_DRIVERS_NUM]; + +static ina3221_t ina3221_devs[INA3221_NUM]; + +static saul_reg_t saul_entries[INA3221_NUM * INA3221_SAUL_DRIVERS_NUM]; + +void auto_init_ina3221(void) +{ + assert(INA3221_SAUL_INFO_NUM == INA3221_SAUL_DRIVERS_NUM * INA3221_NUM); + + for (unsigned i = 0; i < INA3221_NUM; i++) { + LOG_DEBUG("[auto_init_saul] initializing ina3221 #%u\n", i); + int status = ina3221_init(&ina3221_devs[i], &ina3221_params[i]); + if (status != INA3221_OK) { + LOG_ERROR("[auto_init_saul] error(%d) initializing ina3221 #%u\n", + status, i); + continue; + } + for (unsigned j = 0; j < INA3221_SAUL_DRIVERS_NUM; j++) { + saul_entries[i * INA3221_SAUL_DRIVERS_NUM + j] = (saul_reg_t) { + .dev = &ina3221_devs[i], + .name = + ina3221_saul_info[i * INA3221_SAUL_DRIVERS_NUM + j].name, + .driver = &ina3221_saul_drivers[j] + }; + saul_reg_add(&saul_entries[i * INA3221_SAUL_DRIVERS_NUM + j]); + } + } +} +#else +typedef int dont_be_pedantic; +#endif /* MODULE_INA3221 */ diff --git a/sys/include/phydat.h b/sys/include/phydat.h index 9c611caac4ff..3d9c2e65fd3f 100644 --- a/sys/include/phydat.h +++ b/sys/include/phydat.h @@ -94,6 +94,7 @@ enum { /* electricity */ UNIT_A, /**< Ampere */ UNIT_V, /**< Volts */ + UNIT_W, /**< Watt */ UNIT_GS, /**< gauss */ UNIT_DBM, /**< decibel-milliwatts */ UNIT_COULOMB, /**< coulomb */ diff --git a/sys/phydat/phydat_str.c b/sys/phydat/phydat_str.c index 1d847ac62404..956cedb6f26f 100644 --- a/sys/phydat/phydat_str.c +++ b/sys/phydat/phydat_str.c @@ -92,6 +92,7 @@ const char *phydat_unit_to_str(uint8_t unit) case UNIT_GR: return "G"; case UNIT_A: return "A"; case UNIT_V: return "V"; + case UNIT_W: return "W"; case UNIT_DBM: return "dBm"; case UNIT_GS: return "Gs"; case UNIT_BAR: return "Bar";