Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 73 additions & 34 deletions components/ina226/include/ina226.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,12 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
/// SDA, or SCL, address can be 0x40..0x4F
static constexpr uint8_t DEFAULT_ADDRESS = 0x40; ///< Default I2C address if A0/A1=GND

// Register map (datasheet)
enum class Reg : uint8_t {
CONFIG = 0x00,
SHUNT_VOLTAGE = 0x01, // 16-bit signed, 2.5uV/LSB
BUS_VOLTAGE = 0x02, // 16-bit unsigned, 1.25mV/LSB (bits 15..3)
POWER = 0x03, // 16-bit unsigned, 25*CURRENT_LSB per LSB
CURRENT = 0x04, // 16-bit signed, CURRENT_LSB per LSB
CALIBRATION = 0x05, // 16-bit unsigned
MASK_ENABLE = 0x06,
ALERT_LIMIT = 0x07,
MANUFACTURER_ID = 0xFE, // 0x5449
DIE_ID = 0xFF, // 0x2260
};
static constexpr uint16_t MANUFACTURER_ID_TI = 0x5449; ///< Texas Instruments Manufacturer ID
static constexpr uint16_t DIE_ID_INA226 = 0x2260; ///< INA226 Die ID

/// Averaging (AVG) field values (bits 14..12 of CONFIG)
enum class Avg : uint16_t {
AVG_1 = 0,
AVG_1 = 0, // NOTE: default on chip reset
AVG_4 = 1,
AVG_16 = 2,
AVG_64 = 3,
Expand All @@ -52,7 +41,7 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
US_204 = 1,
US_332 = 2,
US_588 = 3,
MS_1_1 = 4,
MS_1_1 = 4, // 1.1 ms, NOTE: this is the default on chip reset
MS_2_116 = 5,
MS_4_156 = 6,
MS_8_244 = 7,
Expand All @@ -67,7 +56,7 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
ADC_OFF = 4,
SHUNT_CONT = 5,
BUS_CONT = 6,
SHUNT_BUS_CONT = 7,
SHUNT_BUS_CONT = 7, // NOTE: default on chip reset
};

/// Configuration structure for INA226
Expand Down Expand Up @@ -124,13 +113,15 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
/// Read manufacturer ID (0x5449 for Texas Instruments)
/// @param ec Error code to capture any read errors
/// @return Manufacturer ID (0x5449) or 0 on error
uint16_t manufacturer_id(std::error_code &ec) {
uint16_t manufacturer_id(std::error_code &ec) const {
return read_u16_from_register((uint8_t)Reg::MANUFACTURER_ID, ec);
}
/// Read die ID (0x2260 for INA226)
/// @param ec Error code to capture any read errors
/// @return Die ID (0x2260) or 0 on error
uint16_t die_id(std::error_code &ec) { return read_u16_from_register((uint8_t)Reg::DIE_ID, ec); }
uint16_t die_id(std::error_code &ec) const {
return read_u16_from_register((uint8_t)Reg::DIE_ID, ec);
}

// Engineering-unit helpers

Expand All @@ -139,7 +130,7 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
/// @return Shunt voltage in volts, or 0.0f on error
/// @note The shunt voltage is signed, so it can be negative if the current
/// flows in the reverse direction. The LSB is 2.5 uV
float shunt_voltage_volts(std::error_code &ec) {
float shunt_voltage_volts(std::error_code &ec) const {
int16_t raw = read_shunt_raw(ec);
if (ec)
return 0.0f;
Expand All @@ -151,12 +142,12 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
/// @return Bus voltage in volts, or 0.0f on error
/// @note The bus voltage is unsigned, so it cannot be negative. The LSB is
/// 1.25 mV
float bus_voltage_volts(std::error_code &ec) {
float bus_voltage_volts(std::error_code &ec) const {
uint16_t raw = read_bus_raw(ec);
if (ec)
return 0.0f;
// Bits [2:0] are reserved/zero; each LSB (of bit 3) is 1.25mV
return ((raw >> 3) * 1.25e-3f);
// each LSB is 1.25mV
return (raw * 1.25e-3f);
}

/// Read current in amps
Expand All @@ -165,7 +156,7 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
/// @note The current is signed, so it can be negative if the current flows in
/// the reverse direction. The LSB is set via the calibrate() method and
/// is in Amps/LSB.
float current_amps(std::error_code &ec) {
float current_amps(std::error_code &ec) const {
std::lock_guard<std::recursive_mutex> lock(base_mutex_);
int16_t raw = read_current_raw(ec);
if (ec)
Expand All @@ -186,6 +177,18 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
return raw * (25.0f * current_lsb_);
}

/// Reset the INA226 to default settings
/// @param ec Error code to capture any write errors
/// @return true if reset succeeded, false if it failed
bool reset(std::error_code &ec) {
// Set bit 15 of CONFIG to reset
static constexpr uint16_t RESET_BIT = 1 << 15;
uint16_t word = RESET_BIT;
logger_.info("Resetting INA226 to default settings");
write_u16_to_register((uint8_t)Reg::CONFIG, word, ec);
return !ec;
}

/// Configure the INA226 with averaging, conversion times, and mode
/// @param avg Averaging mode
/// @param vbus Bus voltage conversion time
Expand All @@ -196,18 +199,16 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
bool configure(Avg avg, ConvTime vbus, ConvTime vshunt, Mode mode, std::error_code &ec) {
// Build config: AVG[14:12], VBUSCT[11:9], VSHCT[8:6], MODE[2:0]
uint16_t word = 0;
word |= (static_cast<uint16_t>(avg) & 0x7) << 12;
word |= (static_cast<uint16_t>(vbus) & 0x7) << 9;
word |= (static_cast<uint16_t>(vshunt) & 0x7) << 6;
// NOTE: bit 15 is used to reset, so we don't set it here
// NOTE: bits 12-14 are not used, and should always be 0b100 << 12
word |= (static_cast<uint16_t>(avg) & 0x7) << 9;
word |= (static_cast<uint16_t>(vbus) & 0x7) << 6;
word |= (static_cast<uint16_t>(vshunt) & 0x7) << 3;
word |= (static_cast<uint16_t>(mode) & 0x7);
write_u16_to_register((uint8_t)Reg::CONFIG, word, ec);
return !ec;
}

// Set calibration based on current_lsb (A/LSB) and shunt resistance (Ohms)
// CAL = floor(0x8000 / (current_lsb * Rshunt)) per datasheet (5120/ (curr_lsb*R) is common for
// INA219, but INA226 uses 0.00512/ (curr_lsb*R) scaled for 16-bit: 0.00512 / (A/LSB * Ohms))

/// Calibrate the INA226 with current LSB and shunt resistance
/// @param current_lsb Current LSB in Amps/LSB
/// @param shunt_res_ohms Shunt resistance in Ohms
Expand All @@ -229,6 +230,9 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
/// avoid issues. This function should be called after configuring the
/// INA226.
bool calibrate(float current_lsb, float shunt_res_ohms, std::error_code &ec) {
// Set calibration based on current_lsb (A/LSB) and shunt resistance (Ohms)
// CAL = floor(0x8000 / (current_lsb * Rshunt)) per datasheet (5120/ (curr_lsb*R) is common for
// INA219, but INA226 uses 0.00512/ (curr_lsb*R) scaled for 16-bit: 0.00512 / (A/LSB * Ohms))
std::lock_guard<std::recursive_mutex> lock(base_mutex_);
current_lsb_ = current_lsb;
shunt_res_ohms_ = shunt_res_ohms;
Expand All @@ -242,8 +246,43 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
}

protected:
// Register map (datasheet)
enum class Reg : uint8_t {
CONFIG = 0x00,
SHUNT_VOLTAGE = 0x01, // 16-bit signed, 2.5uV/LSB
BUS_VOLTAGE = 0x02, // 16-bit unsigned, 1.25mV/LSB
POWER = 0x03, // 16-bit unsigned, 25*CURRENT_LSB per LSB
CURRENT = 0x04, // 16-bit signed, CURRENT_LSB per LSB
CALIBRATION = 0x05, // 16-bit unsigned
MASK_ENABLE = 0x06,
ALERT_LIMIT = 0x07,
MANUFACTURER_ID = 0xFE, // 0x5449
DIE_ID = 0xFF, // 0x2260
};

bool init(const Config &c, std::error_code &ec) {
std::lock_guard<std::recursive_mutex> lock(base_mutex_);
// read manufacturer and die ID to verify presence
uint16_t manufacturer = manufacturer_id(ec);
if (ec)
return false;
if (manufacturer != MANUFACTURER_ID_TI) {
logger_.error("INA226 manufacturer ID mismatch: expected 0x{:04X}, got 0x{:04X}",
MANUFACTURER_ID_TI, manufacturer);
ec = make_error_code(std::errc::no_such_device);
return false;
}
uint16_t die = die_id(ec);
if (ec)
return false;
if (die != DIE_ID_INA226) {
logger_.error("INA226 die ID mismatch: expected 0x{:04X}, got 0x{:04X}", DIE_ID_INA226, die);
ec = make_error_code(std::errc::no_such_device);
return false;
}
// if here, device is present, so reset it
if (!reset(ec))
return false;
// Program config
configure(c.averaging, c.bus_conv_time, c.shunt_conv_time, c.mode, ec);
if (ec)
Expand All @@ -259,16 +298,16 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
}

// Raw register reads (signed/unsigned as appropriate by datasheet)
int16_t read_shunt_raw(std::error_code &ec) {
int16_t read_shunt_raw(std::error_code &ec) const {
return (int16_t)read_u16_from_register((uint8_t)Reg::SHUNT_VOLTAGE, ec);
}
uint16_t read_bus_raw(std::error_code &ec) {
uint16_t read_bus_raw(std::error_code &ec) const {
return read_u16_from_register((uint8_t)Reg::BUS_VOLTAGE, ec);
}
int16_t read_current_raw(std::error_code &ec) {
int16_t read_current_raw(std::error_code &ec) const {
return (int16_t)read_u16_from_register((uint8_t)Reg::CURRENT, ec);
}
uint16_t read_power_raw(std::error_code &ec) {
uint16_t read_power_raw(std::error_code &ec) const {
return read_u16_from_register((uint8_t)Reg::POWER, ec);
}

Expand Down