Skip to content

Commit 89f173f

Browse files
authored
fix(ina226): Fix configuration / initialization of INA226, as well as bus voltage calculation (#541)
1 parent e9296b5 commit 89f173f

File tree

1 file changed

+73
-34
lines changed

1 file changed

+73
-34
lines changed

components/ina226/include/ina226.hpp

Lines changed: 73 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,12 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
2020
/// SDA, or SCL, address can be 0x40..0x4F
2121
static constexpr uint8_t DEFAULT_ADDRESS = 0x40; ///< Default I2C address if A0/A1=GND
2222

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

3726
/// Averaging (AVG) field values (bits 14..12 of CONFIG)
3827
enum class Avg : uint16_t {
39-
AVG_1 = 0,
28+
AVG_1 = 0, // NOTE: default on chip reset
4029
AVG_4 = 1,
4130
AVG_16 = 2,
4231
AVG_64 = 3,
@@ -52,7 +41,7 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
5241
US_204 = 1,
5342
US_332 = 2,
5443
US_588 = 3,
55-
MS_1_1 = 4,
44+
MS_1_1 = 4, // 1.1 ms, NOTE: this is the default on chip reset
5645
MS_2_116 = 5,
5746
MS_4_156 = 6,
5847
MS_8_244 = 7,
@@ -67,7 +56,7 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
6756
ADC_OFF = 4,
6857
SHUNT_CONT = 5,
6958
BUS_CONT = 6,
70-
SHUNT_BUS_CONT = 7,
59+
SHUNT_BUS_CONT = 7, // NOTE: default on chip reset
7160
};
7261

7362
/// Configuration structure for INA226
@@ -124,13 +113,15 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
124113
/// Read manufacturer ID (0x5449 for Texas Instruments)
125114
/// @param ec Error code to capture any read errors
126115
/// @return Manufacturer ID (0x5449) or 0 on error
127-
uint16_t manufacturer_id(std::error_code &ec) {
116+
uint16_t manufacturer_id(std::error_code &ec) const {
128117
return read_u16_from_register((uint8_t)Reg::MANUFACTURER_ID, ec);
129118
}
130119
/// Read die ID (0x2260 for INA226)
131120
/// @param ec Error code to capture any read errors
132121
/// @return Die ID (0x2260) or 0 on error
133-
uint16_t die_id(std::error_code &ec) { return read_u16_from_register((uint8_t)Reg::DIE_ID, ec); }
122+
uint16_t die_id(std::error_code &ec) const {
123+
return read_u16_from_register((uint8_t)Reg::DIE_ID, ec);
124+
}
134125

135126
// Engineering-unit helpers
136127

@@ -139,7 +130,7 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
139130
/// @return Shunt voltage in volts, or 0.0f on error
140131
/// @note The shunt voltage is signed, so it can be negative if the current
141132
/// flows in the reverse direction. The LSB is 2.5 uV
142-
float shunt_voltage_volts(std::error_code &ec) {
133+
float shunt_voltage_volts(std::error_code &ec) const {
143134
int16_t raw = read_shunt_raw(ec);
144135
if (ec)
145136
return 0.0f;
@@ -151,12 +142,12 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
151142
/// @return Bus voltage in volts, or 0.0f on error
152143
/// @note The bus voltage is unsigned, so it cannot be negative. The LSB is
153144
/// 1.25 mV
154-
float bus_voltage_volts(std::error_code &ec) {
145+
float bus_voltage_volts(std::error_code &ec) const {
155146
uint16_t raw = read_bus_raw(ec);
156147
if (ec)
157148
return 0.0f;
158-
// Bits [2:0] are reserved/zero; each LSB (of bit 3) is 1.25mV
159-
return ((raw >> 3) * 1.25e-3f);
149+
// each LSB is 1.25mV
150+
return (raw * 1.25e-3f);
160151
}
161152

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

180+
/// Reset the INA226 to default settings
181+
/// @param ec Error code to capture any write errors
182+
/// @return true if reset succeeded, false if it failed
183+
bool reset(std::error_code &ec) {
184+
// Set bit 15 of CONFIG to reset
185+
static constexpr uint16_t RESET_BIT = 1 << 15;
186+
uint16_t word = RESET_BIT;
187+
logger_.info("Resetting INA226 to default settings");
188+
write_u16_to_register((uint8_t)Reg::CONFIG, word, ec);
189+
return !ec;
190+
}
191+
189192
/// Configure the INA226 with averaging, conversion times, and mode
190193
/// @param avg Averaging mode
191194
/// @param vbus Bus voltage conversion time
@@ -196,18 +199,16 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
196199
bool configure(Avg avg, ConvTime vbus, ConvTime vshunt, Mode mode, std::error_code &ec) {
197200
// Build config: AVG[14:12], VBUSCT[11:9], VSHCT[8:6], MODE[2:0]
198201
uint16_t word = 0;
199-
word |= (static_cast<uint16_t>(avg) & 0x7) << 12;
200-
word |= (static_cast<uint16_t>(vbus) & 0x7) << 9;
201-
word |= (static_cast<uint16_t>(vshunt) & 0x7) << 6;
202+
// NOTE: bit 15 is used to reset, so we don't set it here
203+
// NOTE: bits 12-14 are not used, and should always be 0b100 << 12
204+
word |= (static_cast<uint16_t>(avg) & 0x7) << 9;
205+
word |= (static_cast<uint16_t>(vbus) & 0x7) << 6;
206+
word |= (static_cast<uint16_t>(vshunt) & 0x7) << 3;
202207
word |= (static_cast<uint16_t>(mode) & 0x7);
203208
write_u16_to_register((uint8_t)Reg::CONFIG, word, ec);
204209
return !ec;
205210
}
206211

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

244248
protected:
249+
// Register map (datasheet)
250+
enum class Reg : uint8_t {
251+
CONFIG = 0x00,
252+
SHUNT_VOLTAGE = 0x01, // 16-bit signed, 2.5uV/LSB
253+
BUS_VOLTAGE = 0x02, // 16-bit unsigned, 1.25mV/LSB
254+
POWER = 0x03, // 16-bit unsigned, 25*CURRENT_LSB per LSB
255+
CURRENT = 0x04, // 16-bit signed, CURRENT_LSB per LSB
256+
CALIBRATION = 0x05, // 16-bit unsigned
257+
MASK_ENABLE = 0x06,
258+
ALERT_LIMIT = 0x07,
259+
MANUFACTURER_ID = 0xFE, // 0x5449
260+
DIE_ID = 0xFF, // 0x2260
261+
};
262+
245263
bool init(const Config &c, std::error_code &ec) {
246264
std::lock_guard<std::recursive_mutex> lock(base_mutex_);
265+
// read manufacturer and die ID to verify presence
266+
uint16_t manufacturer = manufacturer_id(ec);
267+
if (ec)
268+
return false;
269+
if (manufacturer != MANUFACTURER_ID_TI) {
270+
logger_.error("INA226 manufacturer ID mismatch: expected 0x{:04X}, got 0x{:04X}",
271+
MANUFACTURER_ID_TI, manufacturer);
272+
ec = make_error_code(std::errc::no_such_device);
273+
return false;
274+
}
275+
uint16_t die = die_id(ec);
276+
if (ec)
277+
return false;
278+
if (die != DIE_ID_INA226) {
279+
logger_.error("INA226 die ID mismatch: expected 0x{:04X}, got 0x{:04X}", DIE_ID_INA226, die);
280+
ec = make_error_code(std::errc::no_such_device);
281+
return false;
282+
}
283+
// if here, device is present, so reset it
284+
if (!reset(ec))
285+
return false;
247286
// Program config
248287
configure(c.averaging, c.bus_conv_time, c.shunt_conv_time, c.mode, ec);
249288
if (ec)
@@ -259,16 +298,16 @@ class Ina226 : public BasePeripheral<uint8_t, true> {
259298
}
260299

261300
// Raw register reads (signed/unsigned as appropriate by datasheet)
262-
int16_t read_shunt_raw(std::error_code &ec) {
301+
int16_t read_shunt_raw(std::error_code &ec) const {
263302
return (int16_t)read_u16_from_register((uint8_t)Reg::SHUNT_VOLTAGE, ec);
264303
}
265-
uint16_t read_bus_raw(std::error_code &ec) {
304+
uint16_t read_bus_raw(std::error_code &ec) const {
266305
return read_u16_from_register((uint8_t)Reg::BUS_VOLTAGE, ec);
267306
}
268-
int16_t read_current_raw(std::error_code &ec) {
307+
int16_t read_current_raw(std::error_code &ec) const {
269308
return (int16_t)read_u16_from_register((uint8_t)Reg::CURRENT, ec);
270309
}
271-
uint16_t read_power_raw(std::error_code &ec) {
310+
uint16_t read_power_raw(std::error_code &ec) const {
272311
return read_u16_from_register((uint8_t)Reg::POWER, ec);
273312
}
274313

0 commit comments

Comments
 (0)