//    FILE: INA3221.cpp
//  AUTHOR: Rob Tillaart
// VERSION: 0.4.1
//    DATE: 2024-02-05
// PURPOSE: Arduino library for the I2C INA3221 3 channel voltage and current sensor.
//     URL: https://github.com/RobTillaart/INA3221_RT
//
//  Read the datasheet for the details


#include "INA3221.h"


//  REGISTERS
#define INA3221_CONFIGURATION              (0x00)
#define INA3221_SHUNT_VOLTAGE(x)           (0x01 + (x*2))
#define INA3221_BUS_VOLTAGE(x)             (0x02 + (x*2))
#define INA3221_CRITICAL_ALERT(x)          (0x07 + (x*2))
#define INA3221_WARNING_ALERT(x)           (0x08 + (x*2))
#define INA3221_SHUNT_VOLTAGE_SUM          (0x0D)
#define INA3221_SHUNT_VOLTAGE_LIMIT        (0x0E)
#define INA3221_MASK_ENABLE                (0x0F)
#define INA3221_POWER_VALID_UPPER          (0x10)
#define INA3221_POWER_VALID_LOWER          (0x11)
#define INA3221_MANUFACTURER               (0xFE)
#define INA3221_DIE_ID                     (0xFF)


//  CONFIGURATION MASKS (not used)
//  #define INA3221_CONF_RESET_MASK            0x8000
//  #define INA3221_CONF_CHANNEL_ENABLE_MASK   0x7000
//  #define INA3221_CONF_AVERAGE_MASK          0x0E00
//  #define INA3221_CONF_BUSVC_MASK            0x01C0
//  #define INA3221_CONF_SHUNTVC_MASK          0x0038
//  #define INA3221_CONF_MODE_MASK             0x0007


#define INA3221_SHUNT_LSB                  40


////////////////////////////////////////////////////////
//
//  CONSTRUCTOR
//
INA3221::INA3221(const uint8_t address, TwoWire *wire)
{
  _address     = address;
  _wire        = wire;
  _shunt[0]    = 0.1;  //  Ohm
  _shunt[1]    = 0.1;  //  Ohm
  _shunt[2]    = 0.1;  //  Ohm
  _error       = 0;
}

bool INA3221::begin()
{
  if (! isConnected()) return false;
  return true;
}

bool INA3221::isConnected()
{
  if ((_address < 0x40) || (_address > 0x43)) return false;
  _wire->beginTransmission(_address);
  return ( _wire->endTransmission() == 0);
}

uint8_t INA3221::getAddress()
{
  return _address;
}


////////////////////////////////////////////////////////
//
//  CORE
//
float INA3221::getBusVoltage(uint8_t channel)
{
  if (channel > 2) return -1;
  uint16_t value = _readRegister(INA3221_BUS_VOLTAGE(channel));
  return (value >> 3) * 8.0e-3;  //  fixed 8 mV  (Page 6)
}

float INA3221::getShuntVoltage(uint8_t channel)
{
  if (channel > 2) return -1;
  int16_t val = _readRegister(INA3221_SHUNT_VOLTAGE(channel));
  return (val >> 3) * 40e-6;   //  fixed 40 uV  (Page 6)
}

float INA3221::getCurrent(uint8_t channel)
{
  if (channel > 2) return -1;
  return getShuntVoltage(channel) / _shunt[channel];  //  Ohms law
}

float INA3221::getPower(uint8_t channel)
{
  if (channel > 2) return -1;
  return getBusVoltage(channel) * getCurrent(channel);
}


////////////////////////////////////////////////////////
//
//  CONFIGURATION
//

////////////////////////////////////////////////////////
//
//  SHUNT
//
int INA3221::setShuntR(uint8_t channel, float ohm)
{
  if (channel > 2) return -1;
  _shunt[channel] = ohm;
  return 0;
}

float INA3221::getShuntR(uint8_t channel)
{
  if (channel > 2) return -1;
  return _shunt[channel];
}


////////////////////////////////////////////////////////
//
//  SHUNT ALERT WARNINGS & CRITICAL
//
int INA3221::setCriticalAlert(uint8_t channel, uint32_t microVolt)
{
  if (channel > 2) return -1;
  // Check for the full scale voltage = 163.8 mV == 163800 uV
  if (microVolt > 163800) return -2;
  uint16_t value = (microVolt / 40) << 3;  //  LSB 40uV  shift 3
  return _writeRegister(INA3221_CRITICAL_ALERT(channel), value);
}

uint32_t INA3221::getCriticalAlert(uint8_t channel)
{
  if (channel > 2) return -1;
  uint32_t value = _readRegister(INA3221_CRITICAL_ALERT(channel));
  return (value >> 3) * 40;  //  LSB 40uV
}

int INA3221::setWarningAlert(uint8_t channel, uint32_t microVolt)
{
  if (channel > 2) return -1;
  // Check for the full scale voltage = 163.8 mV == 163800 uV
  if (microVolt > 163800) return -2;
  uint16_t value = (microVolt / 40) << 3;  //  LSB 40uV  shift 3
  return _writeRegister(INA3221_WARNING_ALERT(channel), value);
}

uint32_t INA3221::getWarningAlert(uint8_t channel)
{
  if (channel > 2) return -1;
  uint32_t value = _readRegister(INA3221_WARNING_ALERT(channel));
  return (value >> 3) * 40;  //  LSB 40uV
}

//  mA wrappers

int INA3221::setCriticalCurrent(uint8_t channel, float milliAmpere)
{
  return setCriticalAlert(channel, 1000.0 * milliAmpere * _shunt[channel]);
}

float INA3221::getCriticalCurrent(uint8_t channel)
{
  return getCriticalAlert(channel) * 0.001 / _shunt[channel];
}

int INA3221::setWarningCurrent(uint8_t channel, float milliAmpere)
{
  return setWarningAlert(channel, 1000.0 * milliAmpere * _shunt[channel]);
}

float INA3221::getWarningCurrent(uint8_t channel)
{
  return getWarningAlert(channel) * 0.001 / _shunt[channel];
}


////////////////////////////////////////////////////////
//
//  SHUNT VOLTAGE SUM
//
//  LSB 40 uV;
//
int32_t INA3221::getShuntVoltageSum()
{
  int16_t value = _readRegister(INA3221_SHUNT_VOLTAGE_SUM);
  return (value >> 1) * 40L;
}

int INA3221::setShuntVoltageSumLimit(int32_t microVolt)
{
  //  15 bit signed.
  if (abs(microVolt) > (16383L * 40L)) return -10;
  int16_t value = (microVolt / 40) << 1;
  return _writeRegister(INA3221_SHUNT_VOLTAGE_LIMIT, value);
}

int32_t INA3221::getShuntVoltageSumLimit()
{
  int32_t value = _readRegister(INA3221_SHUNT_VOLTAGE_LIMIT);
  return (value >> 1) * 40L;
}


////////////////////////////////////////////////////////
//
//  CONFIGURATION
//
int INA3221::setConfiguration(uint16_t mask)
{
  return _writeRegister(INA3221_CONFIGURATION, mask);
}

uint16_t INA3221::getConfiguration()
{
  return _readRegister(INA3221_CONFIGURATION);
}

int INA3221::reset()
{
  return _writeRegister(INA3221_CONFIGURATION, 0xF127);
}

int INA3221::enableChannel(uint8_t channel)
{
  if (channel > 2) return -1;
  uint16_t mask = _readRegister(INA3221_CONFIGURATION);
  mask |= (1 << (14 - channel));
  return _writeRegister(INA3221_CONFIGURATION, mask);
}

int INA3221::disableChannel(uint8_t channel)
{
  if (channel > 2) return -1;
  uint16_t mask = _readRegister(INA3221_CONFIGURATION);
  mask &= ~(1 << (14 - channel));
  return _writeRegister(INA3221_CONFIGURATION, mask);
}

bool INA3221::getEnableChannel(uint8_t channel)
{
  if (channel > 2) return -1;
  uint16_t mask = _readRegister(INA3221_CONFIGURATION);
  return (mask & (1 << (14 - channel))) > 0;
}

int INA3221::setAverage(uint16_t avg)
{
  if (avg > 7) return -10;
  uint16_t mask = _readRegister(INA3221_CONFIGURATION);
  mask &= ~(7 << 9);
  mask |= (avg << 9);
  //  in one line mask &= ~((~avg) << 9)
  return _writeRegister(INA3221_CONFIGURATION, mask);
}

uint16_t INA3221::getAverage()
{
  uint16_t mask = _readRegister(INA3221_CONFIGURATION);
  return (mask >> 9) & 7;
}

int INA3221::setBusVoltageConversionTime(uint16_t bvct)
{
  if (bvct > 7) return -10;
  uint16_t mask = _readRegister(INA3221_CONFIGURATION);
  mask &= ~(7 << 6);
  mask |= (bvct << 6);
  return _writeRegister(INA3221_CONFIGURATION, mask);
}

uint16_t INA3221::getBusVoltageConversionTime()
{
  uint16_t mask = _readRegister(INA3221_CONFIGURATION);
  return (mask >> 6) & 7;
}

int INA3221::setShuntVoltageConversionTime(uint16_t svct)
{
  if (svct > 7) return -10;
  uint16_t mask = _readRegister(INA3221_CONFIGURATION);
  mask &= ~(7 << 3);
  mask |= (svct << 3);
  return _writeRegister(INA3221_CONFIGURATION, mask);
}

uint16_t INA3221::getShuntVoltageConversionTime()
{
  uint16_t mask = _readRegister(INA3221_CONFIGURATION);
  return (mask >> 3) & 7;
}


//  operating mode
int INA3221::setMode(uint16_t mode)
{
  if (mode > 7) return -10;
  uint16_t mask = _readRegister(INA3221_CONFIGURATION);
  mask &= ~7;
  mask |= mode;
  return _writeRegister(INA3221_CONFIGURATION, mask);
}

uint16_t INA3221::getMode()
{
  uint16_t mask = _readRegister(INA3221_CONFIGURATION);
  return mask & 7;
}


////////////////////////////////////////////////////////
//
//  MASK/ENABLE
//
int INA3221::setMaskEnable(uint16_t mask)
{
  return _writeRegister(INA3221_MASK_ENABLE, mask);
}

uint16_t INA3221::getMaskEnable()
{
  return _readRegister(INA3221_MASK_ENABLE);
}


////////////////////////////////////////////////////////
//
//  POWER LIMIT
//
//  LSB 8mV  shift 3
//
int INA3221::setPowerUpperLimit(int16_t milliVolt)
{
  //  int16_t is always within the range (after masking) 32760
  //  if (milliVolt > 4095 * 8) return -10;    //  LSB 8mV  shift 3
  int16_t value = milliVolt & 0xFFF8;  //  mask reserved bits
  return _writeRegister(INA3221_POWER_VALID_UPPER, value);
}

int16_t INA3221::getPowerUpperLimit()
{
  int16_t value = _readRegister(INA3221_POWER_VALID_UPPER);
  //  (value >> 3) * 8mV;  shift 3 compensates 8 mV
  return value;
}

int INA3221::setPowerLowerLimit(int16_t milliVolt)
{
  //  int16_t is always within the range (after masking) 32760
  //  if (milliVolt > 4095 * 8) return -10;    //  LSB 8mV  shift 3
  int16_t value = milliVolt & 0xFFF8;
  return _writeRegister(INA3221_POWER_VALID_LOWER, value);
}

int16_t INA3221::getPowerLowerLimit()
{
  int16_t value = _readRegister(INA3221_POWER_VALID_LOWER);
  //  (value >> 3) * 8mV;  shift 3 compensates 8 mV
  return value;
}


////////////////////////////////////////////////////////
//
//  META information
//
uint16_t INA3221::getManufacturerID()
{
  return _readRegister(INA3221_MANUFACTURER);
}


uint16_t INA3221::getDieID()
{
  return _readRegister(INA3221_DIE_ID);
}


////////////////////////////////////////////////////////
//
//  ERROR HANDLING
//
int INA3221::getLastError()
{
  int e = _error;
  _error = 0;
  return e;
}


////////////////////////////////////////////////////////
//
//  PRIVATE
//
uint16_t INA3221::_readRegister(uint8_t reg)
{
  _wire->beginTransmission(_address);
  _wire->write(reg);
  int n = _wire->endTransmission();
  if (n != 0)
  {
    _error = -1;
    return 0;
  }

  uint16_t value = 0;
  if (2 == _wire->requestFrom(_address, (uint8_t)2))
  {
    value = _wire->read();
    value <<= 8;
    value |= _wire->read();
  }
  else
  {
    _error = -2;
    return 0;
  }
  return value;
}


uint16_t INA3221::_writeRegister(uint8_t reg, uint16_t value)
{
  _wire->beginTransmission(_address);
  _wire->write(reg);
  _wire->write(value >> 8);
  _wire->write(value & 0xFF);
  int n = _wire->endTransmission();
  if (n != 0)
  {
    _error = -1;
  }
  return n;
}


//  -- END OF FILE --

