/* * 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)); }