refactor(ina226): rewrite driver for robustness and clarity (#27364)

* refactor(ina226): rewrite driver for robustness and clarity

Mirrors the INA238 rewrite (#27359):

- Non-blocking UNINITIALIZED -> RESET -> CONFIGURE -> MEASURE state
  machine. Each transition is its own RunImpl tick with an explicit
  ScheduleDelayed; init failures retry without losing the driver
  instance.
- Tolerate ~2 s of consecutive collect() failures
  (MAX_CONSECUTIVE_FAILURES = DISCONNECT_DEBOUNCE_US / SAMPLE_INTERVAL_US)
  before a full reinit. Isolated I2C glitches just skip a cycle instead
  of dropping battery_status.
- SAMPLE_INTERVAL_US = 100 ms, comfortably above the default ADC period
  of ~75.3 ms (588 us * 2 channels * 64 avg). MEASURE compensates for
  in-tick I2C time so each tick is exactly one interval apart.
- CONFIGURE waits SAMPLE_INTERVAL_US + 5 ms before the first MEASURE
  read so the first averaged sample is ready.
- checkConfigurationRotating() reads one of {CONFIGURATION, CALIBRATION}
  per cycle and compares it against the value we wrote, so an externally
  reset device is detected within two cycles.
- '-t' arg validated against 1-3 at parse time; out-of-range values now
  exit with an error.
- New perf counters ina226_bad_register and ina226_reinit for field
  diagnosis; current driver state is surfaced in 'ina226 status'.

File layout: ina226_main.cpp folded into ina226.cpp. Constants/enums
namespaced under ina226. Params switched to DEFINE_PARAMETERS, and the
INA226_CONFIG raw-register override is removed in favor of a hardcoded
continuous-conversion config.

AuterionAutostarter::ina226_probe() updated to the new namespaced
ina226::Register and ina226::MANFID / ina226::DIEID constants.
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ina226): mask reserved bits when verifying CONFIGURATION register

The INA226 CONFIGURATION register has D15 (RST) self-clearing and
D14:D12 reserved, with D14 reading back as 1 regardless of what is
written. The verify step compared the raw read-back to the value
we wrote, so the check failed on every cycle and the driver
re-initialized itself every ~2 s. Mask the non-R/W bits before
comparing.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
This commit is contained in:
Jacob Dahl
2026-05-26 13:51:44 -06:00
committed by GitHub
parent 98ed6adafc
commit ad26499a36
6 changed files with 413 additions and 534 deletions
@@ -173,19 +173,19 @@ int AuterionAutostarter::ina226_probe(const uint32_t instance) const
msgv[1].buffer = rxdata;
msgv[1].length = sizeof(rxdata);
txdata[0] = {INA226_MFG_ID};
txdata[0] = static_cast<uint8_t>(ina226::Register::MFG_ID);
ret = I2C_TRANSFER(i2c.get(), msgv, 2);
uint16_t value = static_cast<uint16_t>(rxdata[1] | rxdata[0] << 8);
if (ret != PX4_OK || value != INA226_MFG_ID_TI) {
if (ret != PX4_OK || value != ina226::MANFID) {
ret = PX4_ERROR;
} else {
txdata[0] = {INA226_MFG_DIEID};
txdata[0] = static_cast<uint8_t>(ina226::Register::DIE_ID);
ret = I2C_TRANSFER(i2c.get(), msgv, 2);
value = static_cast<uint16_t>(rxdata[1] | rxdata[0] << 8);
if (ret != PX4_OK || value != INA226_MFG_DIE) {
if (ret != PX4_OK || value != ina226::DIEID) {
ret = PX4_ERROR;
}
}
@@ -33,10 +33,7 @@
px4_add_module(
MODULE drivers__ina226
MAIN ina226
COMPILE_FLAGS
-Wno-cast-align # TODO: fix and enable
SRCS
ina226_main.cpp
ina226.cpp
MODULE_CONFIG
ina226_params.yaml
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,130 +0,0 @@
/****************************************************************************
*
* Copyright (C) 2021 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include <px4_platform_common/getopt.h>
#include <px4_platform_common/module.h>
#include "ina226.h"
I2CSPIDriverBase *INA226::instantiate(const I2CSPIDriverConfig &config, int runtime_instance)
{
INA226 *instance = new INA226(config, config.custom1);
if (instance == nullptr) {
PX4_ERR("alloc failed");
return nullptr;
}
if (config.keep_running) {
if (instance->force_init() != PX4_OK) {
PX4_INFO("Failed to init INA226 on bus %d, but will try again periodically.", config.bus);
}
} else if (instance->init() != PX4_OK) {
delete instance;
return nullptr;
}
return instance;
}
void
INA226::print_usage()
{
PRINT_MODULE_DESCRIPTION(
R"DESCR_STR(
### Description
Driver for the INA226 power monitor.
Multiple instances of this driver can run simultaneously, if each instance has a separate bus OR I2C address.
For example, one instance can run on Bus 2, address 0x41, and one can run on Bus 2, address 0x43.
If the INA226 module is not powered, then by default, initialization of the driver will fail. To change this, use
the -f flag. If this flag is set, then if initialization fails, the driver will keep trying to initialize again
every 0.5 seconds. With this flag set, you can plug in a battery after the driver starts, and it will work. Without
this flag set, the battery must be plugged in before starting the driver.
)DESCR_STR");
PRINT_MODULE_USAGE_NAME("ina226", "driver");
PRINT_MODULE_USAGE_COMMAND("start");
PRINT_MODULE_USAGE_PARAMS_I2C_SPI_DRIVER(true, false);
PRINT_MODULE_USAGE_PARAMS_I2C_ADDRESS(0x41);
PRINT_MODULE_USAGE_PARAMS_I2C_KEEP_RUNNING_FLAG();
PRINT_MODULE_USAGE_PARAM_INT('t', 1, 1, 3, "battery index for calibration values (1 or 3)", true);
PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
}
extern "C" int
ina226_main(int argc, char *argv[])
{
int ch;
using ThisDriver = INA226;
BusCLIArguments cli{true, false};
cli.i2c_address = INA226_BASEADDR;
cli.default_i2c_frequency = 100000;
cli.support_keep_running = true;
cli.custom1 = 1;
while ((ch = cli.getOpt(argc, argv, "t:")) != EOF) {
switch (ch) {
case 't': // battery index
cli.custom1 = (int)strtol(cli.optArg(), NULL, 0);
break;
}
}
const char *verb = cli.optArg();
if (!verb) {
ThisDriver::print_usage();
return -1;
}
BusInstanceIterator iterator(MODULE_NAME, cli, DRV_POWER_DEVTYPE_INA226);
if (!strcmp(verb, "start")) {
return ThisDriver::module_start(cli, iterator);
}
if (!strcmp(verb, "stop")) {
return ThisDriver::module_stop(iterator);
}
if (!strcmp(verb, "status")) {
return ThisDriver::module_status(iterator);
}
ThisDriver::print_usage();
return -1;
}
@@ -9,16 +9,6 @@ parameters:
type: boolean
default: 0
reboot_required: true
INA226_CONFIG:
description:
short: INA226 Power Monitor Config
type: int32
default: 18139
min: 0
max: 65535
decimal: 1
increment: 1
reboot_required: true
INA226_CURRENT:
description:
short: INA226 Power Monitor Max Current