From 8e60aaec54fc7c1ec71e55a5f88554ea641a2796 Mon Sep 17 00:00:00 2001 From: Tinyu Date: Fri, 5 Aug 2022 17:48:26 +0800 Subject: [PATCH] Fix multiple compilation warnings --- .gitignore | 5 + LICENSE | 21 + README.md | 2 + examples/get_started/get_started.ino | 45 ++ .../offset_compensation.ino | 72 +++ library.json | 16 + library.properties | 9 + src/INA3221.cpp | 518 ++++++++++++++++++ src/INA3221.h | 311 +++++++++++ 9 files changed, 999 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 examples/get_started/get_started.ino create mode 100644 examples/offset_compensation/offset_compensation.ino create mode 100755 library.json create mode 100644 library.properties create mode 100644 src/INA3221.cpp create mode 100644 src/INA3221.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2f527e3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Tinyu Zhao + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b62fe2a --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# INA3221 Library +Arduino library for INA3221 current and voltage sensor. diff --git a/examples/get_started/get_started.ino b/examples/get_started/get_started.ino new file mode 100644 index 0000000..31a7371 --- /dev/null +++ b/examples/get_started/get_started.ino @@ -0,0 +1,45 @@ +#include +#include + +#define SERIAL_SPEED 115200 // serial baud rate +#define PRINT_DEC_POINTS 3 // decimal points to print + +// Set I2C address to 0x41 (A0 pin -> VCC) +INA3221 ina_0(INA3221_ADDR40_GND); +INA3221 ina_1(INA3221_ADDR41_VCC); + +void current_measure_init() { + ina_0.begin(&Wire); + ina_0.reset(); + ina_0.setShuntRes(10, 10, 10); + ina_1.begin(&Wire); + ina_1.reset(); + ina_1.setShuntRes(10, 10, 10); +} + +void setup() { + Serial.begin(SERIAL_SPEED); + current_measure_init(); + + while (!Serial) { + delay(1); + } + // Set shunt resistors to 10 mOhm for all channels +} + +void loop() { + Serial.printf( + "A1%3.0fma %1.1fV A2%3.0fma %1.1fV\r\n", + ina_0.getCurrent(INA3221_CH1) * 1000, ina_0.getVoltage(INA3221_CH1), + ina_0.getCurrent(INA3221_CH2) * 1000, ina_0.getVoltage(INA3221_CH2)); + Serial.printf( + "B1%3.0fma %1.1fV B2%3.0fma %1.1fV\r\n", + ina_0.getCurrent(INA3221_CH3) * 1000, ina_0.getVoltage(INA3221_CH3), + ina_1.getCurrent(INA3221_CH1) * 1000, ina_1.getVoltage(INA3221_CH1)); + Serial.printf( + "C1%3.0fma %1.1fV C2%3.0fma %1.1fV\r\n\n", + ina_1.getCurrent(INA3221_CH2) * 1000, ina_1.getVoltage(INA3221_CH2), + ina_1.getCurrent(INA3221_CH3) * 1000, ina_1.getVoltage(INA3221_CH3)); + + delay(1000); +} diff --git a/examples/offset_compensation/offset_compensation.ino b/examples/offset_compensation/offset_compensation.ino new file mode 100644 index 0000000..844ed50 --- /dev/null +++ b/examples/offset_compensation/offset_compensation.ino @@ -0,0 +1,72 @@ +#include +#include + +#define SERIAL_SPEED 115200 // serial baud rate +#define PRINT_DEC_POINTS 3 // decimal points to print + +// Set I2C address to 0x41 (A0 pin -> VCC) +INA3221 ina3221(INA3221_ADDR40_GND); + +void setup() { + Serial.begin(SERIAL_SPEED); + + while (!Serial) { + delay(1); + } + + ina3221.begin(); + ina3221.reset(); + + // Set shunt resistors to 10 mOhm for all channels + ina3221.setShuntRes(10, 10, 10); + + // Set series filter resistors to 10 Ohm for all channels. + // Series filter resistors introduce error to the current measurement. + // The error can be estimated and depends on the resitor values and the bus + // voltage. + ina3221.setFilterRes(10, 10, 10); +} + +void loop() { + float current[3]; + float current_compensated[3]; + float voltage[3]; + + current[0] = ina3221.getCurrent(INA3221_CH1); + current_compensated[0] = ina3221.getCurrentCompensated(INA3221_CH1); + voltage[0] = ina3221.getVoltage(INA3221_CH1); + + current[1] = ina3221.getCurrent(INA3221_CH2); + current_compensated[1] = ina3221.getCurrentCompensated(INA3221_CH2); + voltage[1] = ina3221.getVoltage(INA3221_CH2); + + current[2] = ina3221.getCurrent(INA3221_CH3); + current_compensated[2] = ina3221.getCurrentCompensated(INA3221_CH3); + voltage[2] = ina3221.getVoltage(INA3221_CH3); + + Serial.print("Channel 1: \n Current: "); + Serial.print(current[0], PRINT_DEC_POINTS); + Serial.print("A\n Compensated current: "); + Serial.print(current_compensated[0], PRINT_DEC_POINTS); + Serial.print("\n Voltage: "); + Serial.print(voltage[0], PRINT_DEC_POINTS); + Serial.println("V"); + + Serial.print("Channel 2: \n Current: "); + Serial.print(current[1], PRINT_DEC_POINTS); + Serial.print("A\n Compensated current: "); + Serial.print(current_compensated[1], PRINT_DEC_POINTS); + Serial.print("\n Voltage: "); + Serial.print(voltage[1], PRINT_DEC_POINTS); + Serial.println("V"); + + Serial.print("Channel 3: \n Current: "); + Serial.print(current[2], PRINT_DEC_POINTS); + Serial.print("A\n Compensated current: "); + Serial.print(current_compensated[2], PRINT_DEC_POINTS); + Serial.print("\n Voltage: "); + Serial.print(voltage[2], PRINT_DEC_POINTS); + Serial.println("V\n"); + + delay(1000); +} diff --git a/library.json b/library.json new file mode 100755 index 0000000..73a869c --- /dev/null +++ b/library.json @@ -0,0 +1,16 @@ +{ + "name": "INA3221", + "description": "Library for INA3221 Chip", + "keywords": "INA3221 current and voltage sensor", + "authors": { + "name": "Tinyu", + "url": "https://github.com/Tinyu-Zhao" + }, + "repository": { + "type": "git", + "url": "https://github.com/Tinyu-Zhao/INA3221.git" + }, + "version": "0.0.1", + "frameworks": "arduino", + "platforms": "espressif32" +} \ No newline at end of file diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..6239051 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=INA3221 +version=0.0.1 +author=Tinyu +maintainer=Tinyu +sentence=INA3221 Triple-Channel Sensor Driver. +paragraph=INA3221 Triple-Channel Sensor Driver. +category=Sensors +url=https://github.com/Tinyu-Zhao/INA3221 +architectures=* \ No newline at end of file diff --git a/src/INA3221.cpp b/src/INA3221.cpp new file mode 100644 index 0000000..6c8a498 --- /dev/null +++ b/src/INA3221.cpp @@ -0,0 +1,518 @@ +/* + + Arduino library for INA3221 current and voltage sensor. + + MIT License + + Copyright (c) 2020 Beast Devices, Andrejs Bondarevs + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "INA3221.h" + +void INA3221::_read(ina3221_reg_t reg, uint16_t *val) { + _i2c->beginTransmission(_i2c_addr); + _i2c->write(reg); // Register + _i2c->endTransmission(false); + + _i2c->requestFrom((uint8_t)_i2c_addr, (uint8_t)2); + + if (_i2c->available()) { + *val = ((_i2c->read() << 8) | _i2c->read()); + } +} + +void INA3221::_write(ina3221_reg_t reg, uint16_t *val) { + _i2c->beginTransmission(_i2c_addr); + _i2c->write(reg); // Register + _i2c->write((*val >> 8) & 0xFF); // Upper 8-bits + _i2c->write(*val & 0xFF); // Lower 8-bits + _i2c->endTransmission(); +} + +void INA3221::begin(TwoWire *theWire) { + _i2c = theWire; + + _shuntRes[0] = 10; + _shuntRes[1] = 10; + _shuntRes[2] = 10; + + _filterRes[0] = 0; + _filterRes[1] = 0; + _filterRes[2] = 0; + + _i2c->begin(); +} + +void INA3221::setShuntRes(uint32_t res_ch1, uint32_t res_ch2, + uint32_t res_ch3) { + _shuntRes[0] = res_ch1; + _shuntRes[1] = res_ch2; + _shuntRes[2] = res_ch3; +} + +void INA3221::setFilterRes(uint32_t res_ch1, uint32_t res_ch2, + uint32_t res_ch3) { + _filterRes[0] = res_ch1; + _filterRes[1] = res_ch2; + _filterRes[2] = res_ch3; +} + +uint16_t INA3221::getReg(ina3221_reg_t reg) { + uint16_t val = 0; + _read(reg, &val); + return val; +} + +void INA3221::reset() { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + conf_reg.reset = 1; + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setModePowerDown() { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + conf_reg.mode_bus_en = 0; + conf_reg.mode_continious_en = 0; + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setModeContinious() { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + conf_reg.mode_continious_en = 1; + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setModeTriggered() { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + conf_reg.mode_continious_en = 0; + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setShuntMeasEnable() { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + conf_reg.mode_shunt_en = 1; + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setShuntMeasDisable() { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + conf_reg.mode_shunt_en = 0; + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setBusMeasEnable() { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + conf_reg.mode_bus_en = 1; + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setBusMeasDisable() { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + conf_reg.mode_bus_en = 0; + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setAveragingMode(ina3221_avg_mode_t mode) { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + conf_reg.avg_mode = mode; + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setBusConversionTime(ina3221_conv_time_t convTime) { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + conf_reg.bus_conv_time = convTime; + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setShuntConversionTime(ina3221_conv_time_t convTime) { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + conf_reg.shunt_conv_time = convTime; + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setPwrValidUpLimit(int16_t voltagemV) { + _write(INA3221_REG_PWR_VALID_HI_LIM, (uint16_t *)&voltagemV); +} + +void INA3221::setPwrValidLowLimit(int16_t voltagemV) { + _write(INA3221_REG_PWR_VALID_LO_LIM, (uint16_t *)&voltagemV); +} + +void INA3221::setShuntSumAlertLimit(int32_t voltageuV) { + int16_t val = 0; + val = voltageuV / 20; + _write(INA3221_REG_SHUNTV_SUM_LIM, (uint16_t *)&val); +} + +void INA3221::setCurrentSumAlertLimit(int32_t currentmA) { + int16_t shuntuV = 0; + shuntuV = currentmA * (int32_t)_shuntRes[INA3221_CH1]; + setShuntSumAlertLimit(shuntuV); +} + +void INA3221::setWarnAlertLatchEnable() { + masken_reg_t masken_reg; + + _read(INA3221_REG_MASK_ENABLE, (uint16_t *)&masken_reg); + masken_reg.warn_alert_latch_en = 1; + _write(INA3221_REG_MASK_ENABLE, (uint16_t *)&masken_reg); + _masken_reg = masken_reg; +} + +void INA3221::setWarnAlertLatchDisable() { + masken_reg_t masken_reg; + + _read(INA3221_REG_MASK_ENABLE, (uint16_t *)&masken_reg); + masken_reg.warn_alert_latch_en = 1; + _write(INA3221_REG_MASK_ENABLE, (uint16_t *)&masken_reg); + _masken_reg = masken_reg; +} + +void INA3221::setCritAlertLatchEnable() { + masken_reg_t masken_reg; + + _read(INA3221_REG_MASK_ENABLE, (uint16_t *)&masken_reg); + masken_reg.crit_alert_latch_en = 1; + _write(INA3221_REG_MASK_ENABLE, (uint16_t *)&masken_reg); + _masken_reg = masken_reg; +} + +void INA3221::setCritAlertLatchDisable() { + masken_reg_t masken_reg; + + _read(INA3221_REG_MASK_ENABLE, (uint16_t *)&masken_reg); + masken_reg.crit_alert_latch_en = 1; + _write(INA3221_REG_MASK_ENABLE, (uint16_t *)&masken_reg); + _masken_reg = masken_reg; +} + +void INA3221::readFlags() { + _read(INA3221_REG_MASK_ENABLE, (uint16_t *)&_masken_reg); +} + +bool INA3221::getTimingCtrlAlertFlag() { + return _masken_reg.timing_ctrl_alert; +} + +bool INA3221::getPwrValidAlertFlag() { + return _masken_reg.pwr_valid_alert; +} + +bool INA3221::getCurrentSumAlertFlag() { + return _masken_reg.shunt_sum_alert; +} + +bool INA3221::getConversionReadyFlag() { + return _masken_reg.conv_ready; +} + +uint16_t INA3221::getManufID() { + uint16_t id = 0; + _read(INA3221_REG_MANUF_ID, &id); + return id; +} + +uint16_t INA3221::getDieID() { + uint16_t id = 0; + _read(INA3221_REG_DIE_ID, &id); + return id; +} + +void INA3221::setChannelEnable(ina3221_ch_t channel) { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + + switch (channel) { + case INA3221_CH1: + conf_reg.ch1_en = 1; + break; + case INA3221_CH2: + conf_reg.ch2_en = 1; + break; + case INA3221_CH3: + conf_reg.ch3_en = 1; + break; + } + + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setChannelDisable(ina3221_ch_t channel) { + conf_reg_t conf_reg; + + _read(INA3221_REG_CONF, (uint16_t *)&conf_reg); + + switch (channel) { + case INA3221_CH1: + conf_reg.ch1_en = 0; + break; + case INA3221_CH2: + conf_reg.ch2_en = 0; + break; + case INA3221_CH3: + conf_reg.ch3_en = 0; + break; + } + + _write(INA3221_REG_CONF, (uint16_t *)&conf_reg); +} + +void INA3221::setWarnAlertShuntLimit(ina3221_ch_t channel, int32_t voltageuV) { + ina3221_reg_t reg; + int16_t val = 0; + + switch (channel) { + case INA3221_CH1: + reg = INA3221_REG_CH1_WARNING_ALERT_LIM; + break; + case INA3221_CH2: + reg = INA3221_REG_CH2_WARNING_ALERT_LIM; + break; + case INA3221_CH3: + reg = INA3221_REG_CH3_WARNING_ALERT_LIM; + break; + } + + val = voltageuV / 5; + _write(reg, (uint16_t *)&val); +} + +void INA3221::setCritAlertShuntLimit(ina3221_ch_t channel, int32_t voltageuV) { + ina3221_reg_t reg; + int16_t val = 0; + + switch (channel) { + case INA3221_CH1: + reg = INA3221_REG_CH1_CRIT_ALERT_LIM; + break; + case INA3221_CH2: + reg = INA3221_REG_CH2_CRIT_ALERT_LIM; + break; + case INA3221_CH3: + reg = INA3221_REG_CH3_CRIT_ALERT_LIM; + break; + } + + val = voltageuV / 5; + _write(reg, (uint16_t *)&val); +} + +void INA3221::setWarnAlertCurrentLimit(ina3221_ch_t channel, + int32_t currentmA) { + int32_t shuntuV = 0; + shuntuV = currentmA * (int32_t)_shuntRes[channel]; + setWarnAlertShuntLimit(channel, shuntuV); +} + +void INA3221::setCritAlertCurrentLimit(ina3221_ch_t channel, + int32_t currentmA) { + int32_t shuntuV = 0; + shuntuV = currentmA * (int32_t)_shuntRes[channel]; + setCritAlertShuntLimit(channel, shuntuV); +} + +void INA3221::setCurrentSumEnable(ina3221_ch_t channel) { + masken_reg_t masken_reg; + + _read(INA3221_REG_MASK_ENABLE, (uint16_t *)&masken_reg); + + switch (channel) { + case INA3221_CH1: + masken_reg.shunt_sum_en_ch1 = 1; + break; + case INA3221_CH2: + masken_reg.shunt_sum_en_ch2 = 1; + break; + case INA3221_CH3: + masken_reg.shunt_sum_en_ch3 = 1; + break; + } + + _write(INA3221_REG_MASK_ENABLE, (uint16_t *)&masken_reg); + _masken_reg = masken_reg; +} + +void INA3221::setCurrentSumDisable(ina3221_ch_t channel) { + masken_reg_t masken_reg; + + _read(INA3221_REG_MASK_ENABLE, (uint16_t *)&masken_reg); + + switch (channel) { + case INA3221_CH1: + masken_reg.shunt_sum_en_ch1 = 0; + break; + case INA3221_CH2: + masken_reg.shunt_sum_en_ch2 = 0; + break; + case INA3221_CH3: + masken_reg.shunt_sum_en_ch3 = 0; + break; + } + + _write(INA3221_REG_MASK_ENABLE, (uint16_t *)&masken_reg); + _masken_reg = masken_reg; +} + +int32_t INA3221::getShuntVoltage(ina3221_ch_t channel) { + int32_t res; + ina3221_reg_t reg; + uint16_t val_raw = 0; + + switch (channel) { + case INA3221_CH1: + reg = INA3221_REG_CH1_SHUNTV; + break; + case INA3221_CH2: + reg = INA3221_REG_CH2_SHUNTV; + break; + case INA3221_CH3: + reg = INA3221_REG_CH3_SHUNTV; + break; + } + + _read(reg, &val_raw); + + // 1 LSB = 5uV + res = (int16_t)val_raw * 5; + + return res; +} + +bool INA3221::getWarnAlertFlag(ina3221_ch_t channel) { + switch (channel) { + case INA3221_CH1: + return _masken_reg.warn_alert_ch1; + case INA3221_CH2: + return _masken_reg.warn_alert_ch2; + case INA3221_CH3: + return _masken_reg.warn_alert_ch3; + default: + return false; + } +} + +bool INA3221::getCritAlertFlag(ina3221_ch_t channel) { + switch (channel) { + case INA3221_CH1: + return _masken_reg.crit_alert_ch1; + case INA3221_CH2: + return _masken_reg.crit_alert_ch2; + case INA3221_CH3: + return _masken_reg.crit_alert_ch3; + default: + return false; + } +} + +int32_t INA3221::estimateOffsetVoltage(ina3221_ch_t channel, uint32_t busV) { + float bias_in = 10.0; // Input bias current at IN– in uA + float r_in = 0.670; // Input resistance at IN– in MOhm + uint32_t adc_step = 40; // smallest shunt ADC step in uV + float shunt_res = _shuntRes[channel] / 1000.0; // convert to Ohm + float filter_res = _filterRes[channel]; + int32_t offset = 0.0; + float reminder; + + offset = (shunt_res + filter_res) * (busV / r_in + bias_in) - + bias_in * filter_res; + + // Round the offset to the closest shunt ADC value + reminder = offset % adc_step; + if (reminder < adc_step / 2) { + offset -= reminder; + } else { + offset += adc_step - reminder; + } + + return offset; +} + +float INA3221::getCurrent(ina3221_ch_t channel) { + int32_t shunt_uV = 0; + float current_A = 0; + + shunt_uV = getShuntVoltage(channel); + current_A = shunt_uV / (int32_t)_shuntRes[channel] / 1000.0; + return current_A; +} + +float INA3221::getCurrentCompensated(ina3221_ch_t channel) { + int32_t shunt_uV = 0; + int32_t bus_V = 0; + float current_A = 0.0; + int32_t offset_uV = 0; + + shunt_uV = getShuntVoltage(channel); + bus_V = getVoltage(channel); + offset_uV = estimateOffsetVoltage(channel, bus_V); + + current_A = (shunt_uV - offset_uV) / (int32_t)_shuntRes[channel] / 1000.0; + + return current_A; +} + +float INA3221::getVoltage(ina3221_ch_t channel) { + float voltage_V = 0.0; + ina3221_reg_t reg; + uint16_t val_raw = 0; + + switch (channel) { + case INA3221_CH1: + reg = INA3221_REG_CH1_BUSV; + break; + case INA3221_CH2: + reg = INA3221_REG_CH2_BUSV; + break; + case INA3221_CH3: + reg = INA3221_REG_CH3_BUSV; + break; + } + + _read(reg, &val_raw); + + voltage_V = val_raw / 1000.0; + + return voltage_V; +} \ No newline at end of file diff --git a/src/INA3221.h b/src/INA3221.h new file mode 100644 index 0000000..edd6c92 --- /dev/null +++ b/src/INA3221.h @@ -0,0 +1,311 @@ +/* + + Arduino library for INA3221 current and voltage sensor. + + MIT License + + Copyright (c) 2020 Beast Devices, Andrejs Bondarevs + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef _INA3221_H_ +#define _INA3221_H_ + +#include "Arduino.h" +#include "Wire.h" + +typedef enum { + INA3221_ADDR40_GND = 0b1000000, // A0 pin -> GND + INA3221_ADDR41_VCC = 0b1000001, // A0 pin -> VCC + INA3221_ADDR42_SDA = 0b1000010, // A0 pin -> SDA + INA3221_ADDR43_SCL = 0b1000011 // A0 pin -> SCL +} ina3221_addr_t; + +// Channels +typedef enum { + INA3221_CH1 = 0, + INA3221_CH2, + INA3221_CH3, + INA3221_CH_NUM +} ina3221_ch_t; + +// Registers +typedef enum { + INA3221_REG_CONF = 0, + INA3221_REG_CH1_SHUNTV, + INA3221_REG_CH1_BUSV, + INA3221_REG_CH2_SHUNTV, + INA3221_REG_CH2_BUSV, + INA3221_REG_CH3_SHUNTV, + INA3221_REG_CH3_BUSV, + INA3221_REG_CH1_CRIT_ALERT_LIM, + INA3221_REG_CH1_WARNING_ALERT_LIM, + INA3221_REG_CH2_CRIT_ALERT_LIM, + INA3221_REG_CH2_WARNING_ALERT_LIM, + INA3221_REG_CH3_CRIT_ALERT_LIM, + INA3221_REG_CH3_WARNING_ALERT_LIM, + INA3221_REG_SHUNTV_SUM, + INA3221_REG_SHUNTV_SUM_LIM, + INA3221_REG_MASK_ENABLE, + INA3221_REG_PWR_VALID_HI_LIM, + INA3221_REG_PWR_VALID_LO_LIM, + INA3221_REG_MANUF_ID = 0xFE, + INA3221_REG_DIE_ID = 0xFF +} ina3221_reg_t; + +// Conversion times +typedef enum { + INA3221_REG_CONF_CT_140US = 0, + INA3221_REG_CONF_CT_204US, + INA3221_REG_CONF_CT_332US, + INA3221_REG_CONF_CT_588US, + INA3221_REG_CONF_CT_1100US, + INA3221_REG_CONF_CT_2116US, + INA3221_REG_CONF_CT_4156US, + INA3221_REG_CONF_CT_8244US +} ina3221_conv_time_t; + +// Averaging modes +typedef enum { + INA3221_REG_CONF_AVG_1 = 0, + INA3221_REG_CONF_AVG_4, + INA3221_REG_CONF_AVG_16, + INA3221_REG_CONF_AVG_64, + INA3221_REG_CONF_AVG_128, + INA3221_REG_CONF_AVG_256, + INA3221_REG_CONF_AVG_512, + INA3221_REG_CONF_AVG_1024 +} ina3221_avg_mode_t; + +class INA3221 { + // Configuration register + typedef struct { + uint16_t mode_shunt_en : 1; + uint16_t mode_bus_en : 1; + uint16_t mode_continious_en : 1; + uint16_t shunt_conv_time : 3; + uint16_t bus_conv_time : 3; + uint16_t avg_mode : 3; + uint16_t ch3_en : 1; + uint16_t ch2_en : 1; + uint16_t ch1_en : 1; + uint16_t reset : 1; + } __attribute__((packed)) conf_reg_t; + + // Mask/Enable register + typedef struct { + uint16_t conv_ready : 1; + uint16_t timing_ctrl_alert : 1; + uint16_t pwr_valid_alert : 1; + uint16_t warn_alert_ch3 : 1; + uint16_t warn_alert_ch2 : 1; + uint16_t warn_alert_ch1 : 1; + uint16_t shunt_sum_alert : 1; + uint16_t crit_alert_ch3 : 1; + uint16_t crit_alert_ch2 : 1; + uint16_t crit_alert_ch1 : 1; + uint16_t crit_alert_latch_en : 1; + uint16_t warn_alert_latch_en : 1; + uint16_t shunt_sum_en_ch3 : 1; + uint16_t shunt_sum_en_ch2 : 1; + uint16_t shunt_sum_en_ch1 : 1; + uint16_t reserved : 1; + } __attribute__((packed)) masken_reg_t; + + // Arduino's I2C library + TwoWire *_i2c; + + // I2C address + ina3221_addr_t _i2c_addr; + + // Shunt resistance in mOhm + uint32_t _shuntRes[INA3221_CH_NUM]; + + // Series filter resistance in Ohm + uint32_t _filterRes[INA3221_CH_NUM]; + + // Value of Mask/Enable register. + masken_reg_t _masken_reg; + + // Reads 16 bytes from a register. + void _read(ina3221_reg_t reg, uint16_t *val); + + // Writes 16 bytes to a register. + void _write(ina3221_reg_t reg, uint16_t *val); + + public: + INA3221(ina3221_addr_t addr) : _i2c_addr(addr){}; + // Initializes INA3221 + void begin(TwoWire *theWire = &Wire); + + // Sets shunt resistor value in mOhm + void setShuntRes(uint32_t res_ch1, uint32_t res_ch2, uint32_t res_ch3); + + // Sets filter resistors value in Ohm + void setFilterRes(uint32_t res_ch1, uint32_t res_ch2, uint32_t res_ch3); + + // Sets I2C address of INA3221 + void setAddr(ina3221_addr_t addr) { + _i2c_addr = addr; + } + + // Gets a register value. + uint16_t getReg(ina3221_reg_t reg); + + // Resets INA3221 + void reset(); + + // Sets operating mode to power-down + void setModePowerDown(); + + // Sets operating mode to continious + void setModeContinious(); + + // Sets operating mode to triggered (single-shot) + void setModeTriggered(); + + // Enables shunt-voltage measurement + void setShuntMeasEnable(); + + // Disables shunt-voltage mesurement + void setShuntMeasDisable(); + + // Enables bus-voltage measurement + void setBusMeasEnable(); + + // Disables bus-voltage measureement + void setBusMeasDisable(); + + // Sets averaging mode. Sets number of samples that are collected + // and averaged togehter. + void setAveragingMode(ina3221_avg_mode_t mode); + + // Sets bus-voltage conversion time. + void setBusConversionTime(ina3221_conv_time_t convTime); + + // Sets shunt-voltage conversion time. + void setShuntConversionTime(ina3221_conv_time_t convTime); + + // Sets power-valid upper-limit voltage. The power-valid condition + // is reached when all bus-voltage channels exceed the value set. + // When the powervalid condition is met, the PV alert pin asserts high. + void setPwrValidUpLimit(int16_t voltagemV); + + // Sets power-valid lower-limit voltage. If any bus-voltage channel drops + // below the power-valid lower-limit, the PV alert pin pulls low. + void setPwrValidLowLimit(int16_t voltagemV); + + // Sets the value that is compared to the Shunt-Voltage Sum register value + // following each completed cycle of all selected channels to detect + // for system overcurrent events. + void setShuntSumAlertLimit(int32_t voltagemV); + + // Sets the current value that is compared to the sum all currents. + // This function is a helper for setShuntSumAlertLim(). It onverts current + // value to shunt voltage value. + void setCurrentSumAlertLimit(int32_t currentmA); + + // Enables warning alert latch. + void setWarnAlertLatchEnable(); + + // Disables warning alert latch. + void setWarnAlertLatchDisable(); + + // Enables critical alert latch. + void setCritAlertLatchEnable(); + + // Disables critical alert latch. + void setCritAlertLatchDisable(); + + // Reads flags from Mask/Enable register. + // When Mask/Enable register is read, flags are cleared. + // Use getTimingCtrlAlertFlag(), getPwrValidAlertFlag(), + // getCurrentSumAlertFlag() and getConvReadyFlag() to get flags after + // readFlags() is called. + void readFlags(); + + // Gets timing-control-alert flag indicator. + bool getTimingCtrlAlertFlag(); + + // Gets power-valid-alert flag indicator. + bool getPwrValidAlertFlag(); + + // Gets summation-alert flag indicator. + bool getCurrentSumAlertFlag(); + + // Gets Conversion-ready flag. + bool getConversionReadyFlag(); + + // Gets manufacturer ID. + // Should read 0x5449. + uint16_t getManufID(); + + // Gets die ID. + // Should read 0x3220. + uint16_t getDieID(); + + // Enables channel measurements + void setChannelEnable(ina3221_ch_t channel); + + // Disables channel measurements + void setChannelDisable(ina3221_ch_t channel); + + // Sets warning alert shunt voltage limit + void setWarnAlertShuntLimit(ina3221_ch_t channel, int32_t voltageuV); + + // Sets critical alert shunt voltage limit + void setCritAlertShuntLimit(ina3221_ch_t channel, int32_t voltageuV); + + // Sets warning alert current limit + void setWarnAlertCurrentLimit(ina3221_ch_t channel, int32_t currentmA); + + // Sets critical alert current limit + void setCritAlertCurrentLimit(ina3221_ch_t channel, int32_t currentmA); + + // Includes channel to fill Shunt-Voltage Sum register. + void setCurrentSumEnable(ina3221_ch_t channel); + + // Excludes channel from filling Shunt-Voltage Sum register. + void setCurrentSumDisable(ina3221_ch_t channel); + + // Gets shunt voltage in uV. + int32_t getShuntVoltage(ina3221_ch_t channel); + + // Gets warning alert flag. + bool getWarnAlertFlag(ina3221_ch_t channel); + + // Gets critical alert flag. + bool getCritAlertFlag(ina3221_ch_t channel); + + // Estimates offset voltage added by the series filter resitors + int32_t estimateOffsetVoltage(ina3221_ch_t channel, uint32_t busVoltage); + + // Gets current in A. + float getCurrent(ina3221_ch_t channel); + + // Gets current compensated with calculated offset voltage. + float getCurrentCompensated(ina3221_ch_t channel); + + // Gets bus voltage in V. + float getVoltage(ina3221_ch_t channel); +}; + +#endif \ No newline at end of file