diff --git a/components/ina226/include/ina226.hpp b/components/ina226/include/ina226.hpp index c3322c628..22d2fced7 100644 --- a/components/ina226/include/ina226.hpp +++ b/components/ina226/include/ina226.hpp @@ -20,23 +20,12 @@ class Ina226 : public BasePeripheral { /// 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, @@ -52,7 +41,7 @@ class Ina226 : public BasePeripheral { 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, @@ -67,7 +56,7 @@ class Ina226 : public BasePeripheral { 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 @@ -124,13 +113,15 @@ class Ina226 : public BasePeripheral { /// 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 @@ -139,7 +130,7 @@ class Ina226 : public BasePeripheral { /// @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; @@ -151,12 +142,12 @@ class Ina226 : public BasePeripheral { /// @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 @@ -165,7 +156,7 @@ class Ina226 : public BasePeripheral { /// @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 lock(base_mutex_); int16_t raw = read_current_raw(ec); if (ec) @@ -186,6 +177,18 @@ class Ina226 : public BasePeripheral { 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 @@ -196,18 +199,16 @@ class Ina226 : public BasePeripheral { 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(avg) & 0x7) << 12; - word |= (static_cast(vbus) & 0x7) << 9; - word |= (static_cast(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(avg) & 0x7) << 9; + word |= (static_cast(vbus) & 0x7) << 6; + word |= (static_cast(vshunt) & 0x7) << 3; word |= (static_cast(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 @@ -229,6 +230,9 @@ class Ina226 : public BasePeripheral { /// 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 lock(base_mutex_); current_lsb_ = current_lsb; shunt_res_ohms_ = shunt_res_ohms; @@ -242,8 +246,43 @@ class Ina226 : public BasePeripheral { } 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 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) @@ -259,16 +298,16 @@ class Ina226 : public BasePeripheral { } // 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); }