Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/libraries/AP_BattMonitor/AP_BattMonitor_INA3221.cpp
Views: 1798
#include "AP_BattMonitor_config.h"12#if AP_BATTERY_INA3221_ENABLED34#include "AP_BattMonitor_INA3221.h"56#include <AP_HAL/utility/sparse-endian.h>78#include <AP_HAL/AP_HAL.h>9#include <AP_HAL/I2CDevice.h>10#include <GCS_MAVLink/GCS.h>1112#define INA3221_DEBUGGING 01314#if INA3221_DEBUGGING15#include <stdio.h>16#define debug(fmt, args ...) do {printf("INA3221: %s:%d: " fmt "\n", __FUNCTION__, __LINE__, ## args); } while(0)17// #define debug(fmt, args ...) do {gcs().send_text(MAV_SEVERITY_INFO, "INA3221: %s:%d: " fmt "\n", __FUNCTION__, __LINE__, ## args); } while(0)18#else19#define debug(fmt, args ...)20#endif2122#ifndef HAL_BATTMON_INA3221_BUS23#define HAL_BATTMON_INA3221_BUS 024#endif2526#ifndef HAL_BATTMON_INA3221_ADDR27#define HAL_BATTMON_INA3221_ADDR 6428#endif2930#ifndef HAL_BATTMON_INA3221_SHUNT_OHMS31#define HAL_BATTMON_INA3221_SHUNT_OHMS 0.00132#endif3334#define HAL_BATTMON_INA3221_CONV_TIME_140US 0b00035#define HAL_BATTMON_INA3221_CONV_TIME_204US 0b00136#define HAL_BATTMON_INA3221_CONV_TIME_332US 0b01037#define HAL_BATTMON_INA3221_CONV_TIME_588US 0b01138#define HAL_BATTMON_INA3221_CONV_TIME_1100US 0b10039#define HAL_BATTMON_INA3221_CONV_TIME_2116US 0b10140#define HAL_BATTMON_INA3221_CONV_TIME_4156US 0b11041#define HAL_BATTMON_INA3221_CONV_TIME_8244US 0b1114243#define HAL_BATTMON_INA3221_AVG_MODE_1 0b00044#define HAL_BATTMON_INA3221_AVG_MODE_4 0b00145#define HAL_BATTMON_INA3221_AVG_MODE_16 0b01046#define HAL_BATTMON_INA3221_AVG_MODE_64 0b01147#define HAL_BATTMON_INA3221_AVG_MODE_128 0b10048#define HAL_BATTMON_INA3221_AVG_MODE_256 0b10149#define HAL_BATTMON_INA3221_AVG_MODE_512 0b11050#define HAL_BATTMON_INA3221_AVG_MODE_1024 0b1115152#ifndef HAL_BATTMON_INA3221_SHUNT_CONV_TIME_SEL53#define HAL_BATTMON_INA3221_SHUNT_CONV_TIME_SEL HAL_BATTMON_INA3221_CONV_TIME_8244US54#endif5556#ifndef HAL_BATTMON_INA3221_BUS_CONV_TIME_SEL57#define HAL_BATTMON_INA3221_BUS_CONV_TIME_SEL HAL_BATTMON_INA3221_CONV_TIME_8244US58#endif5960#ifndef HAL_BATTMON_INA3221_AVG_MODE_SEL61#define HAL_BATTMON_INA3221_AVG_MODE_SEL HAL_BATTMON_INA3221_AVG_MODE_102462#endif6364struct AP_BattMonitor_INA3221::AddressDriver AP_BattMonitor_INA3221::address_driver[HAL_BATTMON_INA3221_MAX_DEVICES];65uint8_t AP_BattMonitor_INA3221::address_driver_count;6667const AP_Param::GroupInfo AP_BattMonitor_INA3221::var_info[] = {6869// @Param: I2C_BUS70// @DisplayName: Battery monitor I2C bus number71// @Description: Battery monitor I2C bus number72// @Range: 0 373// @User: Advanced74// @RebootRequired: True75AP_GROUPINFO("I2C_BUS", 22, AP_BattMonitor_INA3221, i2c_bus, HAL_BATTMON_INA3221_BUS),7677// @Param: I2C_ADDR78// @DisplayName: Battery monitor I2C address79// @Description: Battery monitor I2C address. If this is zero then probe list of supported addresses80// @Range: 0 12781// @User: Advanced82// @RebootRequired: True83AP_GROUPINFO("I2C_ADDR", 23, AP_BattMonitor_INA3221, i2c_address, HAL_BATTMON_INA3221_ADDR),8485// @Param: CHANNEL86// @DisplayName: INA3221 channel87// @Description: INA3221 channel to return data for88// @Range: 1 389// @User: Advanced90// @RebootRequired: True91AP_GROUPINFO("CHANNEL", 24, AP_BattMonitor_INA3221, channel, 1),9293// CHECK/UPDATE INDEX TABLE IN AP_BattMonitor_Backend.cpp WHEN CHANGING OR ADDING PARAMETERS9495AP_GROUPEND96};9798extern const AP_HAL::HAL &hal;99100AP_BattMonitor_INA3221::AP_BattMonitor_INA3221(101AP_BattMonitor &mon,102AP_BattMonitor::BattMonitor_State &mon_state,103AP_BattMonitor_Params ¶ms) :104AP_BattMonitor_Backend(mon, mon_state, params)105{106AP_Param::setup_object_defaults(this, var_info);107_state.var_info = var_info;108}109110bool AP_BattMonitor_INA3221::AddressDriver::read_register(uint8_t addr, uint16_t &ret)111{112if (!dev->transfer(&addr, 1, (uint8_t*)&ret, 2)) {113return false;114}115ret = be16toh(ret);116return true;117}118119bool AP_BattMonitor_INA3221::AddressDriver::write_register(uint8_t addr, uint16_t val)120{121uint8_t buf[3] { addr, uint8_t(val >> 8), uint8_t(val & 0xFF) };122123return dev->transfer(buf, sizeof(buf), nullptr, 0);124}125126#define REG_CONFIGURATION 0x00127#define REG_MANUFACTURER_ID 0xFE128#define REG_DIE_ID 0xFF129130bool AP_BattMonitor_INA3221::AddressDriver::write_config(void)131{132// update device configuration133union {134struct PACKED {135uint16_t mode : 3;136uint16_t shunt_voltage_conversiontime : 3;137uint16_t bus_voltage_conversiontime : 3;138uint16_t averaging_mode : 3;139uint16_t ch1_enable : 1;140uint16_t ch2_enable : 1;141uint16_t ch3_enable : 1;142uint16_t reset : 1;143} bits;144uint16_t word;145} configuration {{1460b111, // continuous operation147HAL_BATTMON_INA3221_SHUNT_CONV_TIME_SEL,148HAL_BATTMON_INA3221_BUS_CONV_TIME_SEL,149HAL_BATTMON_INA3221_AVG_MODE_SEL,150// dynamically enable channels to not waste time converting unused data151(channel_mask & (1 << 1)) != 0, // enable ch1?152(channel_mask & (1 << 2)) != 0, // enable ch2?153(channel_mask & (1 << 3)) != 0, // enable ch3?1540b0, // don't reset...155}};156157if (!write_register(REG_CONFIGURATION, configuration.word)) {158return false;159}160161dev_channel_mask = channel_mask; // what's actually in the device now162163return true;164}165166void AP_BattMonitor_INA3221::init()167{168uint8_t dev_address = i2c_address.get();169uint8_t dev_bus = i2c_bus.get();170uint8_t dev_channel = channel.get();171172if ((dev_channel < 1) || (dev_channel > 3)) {173debug("INA3221: nonexistent channel");174return;175}176177debug("INA3221: probe ch%d@0x%02x on bus %u", dev_channel, dev_address, dev_bus);178179// check to see if we already have the underlying driver reading the bus:180for (uint8_t i=0; i<address_driver_count; i++) {181AddressDriver *d = &address_driver[i];182if (!d->dev) {183continue;184}185if (d->address != dev_address) {186continue;187}188if (d->bus != dev_bus) {189continue;190}191debug("Reusing INA3221 driver @0x%02x on bus %u", dev_address, dev_bus);192address_driver_state = NEW_NOTHROW AddressDriver::StateList;193if (address_driver_state == nullptr) {194return;195}196address_driver_state->channel = dev_channel;197address_driver_state->next = d->statelist;198d->statelist = address_driver_state;199d->channel_mask |= (1 << dev_channel);200return;201}202203if (address_driver_count == ARRAY_SIZE(address_driver)) {204debug("INA3221: out of address drivers");205return;206}207208AddressDriver *d = &address_driver[address_driver_count];209d->dev = std::move(hal.i2c_mgr->get_device(i2c_bus, i2c_address, 100000, true, 20));210if (!d->dev) {211return;212}213d->bus = i2c_bus;214d->address = i2c_address;215216WITH_SEMAPHORE(d->dev->get_semaphore());217218// check manufacturer_id219uint16_t manufacturer_id;220if (!d->read_register(REG_MANUFACTURER_ID, manufacturer_id)) {221debug("read register (%u (0x%02x)) failed", REG_MANUFACTURER_ID, REG_MANUFACTURER_ID);222return;223}224if (manufacturer_id != 0x5449) { // 8.6.1 p24225debug("Bad manufacturer_id: 0x%02x", manufacturer_id);226return;227}228229uint16_t die_id;230if (!d->read_register(REG_DIE_ID, die_id)) {231debug("Bad die: 0x%02x", manufacturer_id);232return;233}234if (die_id != 0x3220) { // 8.6.1 p24235return;236}237238d->channel_mask = (1 << dev_channel);239if (!d->write_config()) {240return;241}242243address_driver_state = NEW_NOTHROW AddressDriver::StateList;244if (address_driver_state == nullptr) {245return;246}247address_driver_state->channel = dev_channel;248address_driver_state->next = d->statelist;249d->statelist = address_driver_state;250251debug("Found INA3221 ch%d@0x%02x on bus %u", dev_channel, dev_address, dev_bus);252253address_driver_count++;254255d->register_timer();256}257258void AP_BattMonitor_INA3221::AddressDriver::register_timer(void)259{260dev->register_periodic_callback(261100000,262FUNCTOR_BIND_MEMBER(&AP_BattMonitor_INA3221::AddressDriver::timer, void));263}264265void AP_BattMonitor_INA3221::AddressDriver::timer(void)266{267bool healthy = true;268269if (channel_mask != dev_channel_mask) {270if (write_config()) { // update enabled channels271return; // data is now out of date, read it next time272}273// continue on to reading if update failed so health gets cleared274healthy = false;275}276277for (uint8_t i=1; i<=3; i++) {278if ((channel_mask & (1U<<i)) == 0) {279continue;280}281const uint8_t channel_offset = (i-1)*2;282const uint8_t reg_shunt = 1 + channel_offset;283const uint8_t reg_bus = 2 + channel_offset;284285uint16_t shunt_val;286if (!read_register(reg_shunt, shunt_val)) {287healthy = false;288shunt_val = 0;289}290uint16_t bus_val;291if (!read_register(reg_bus, bus_val)) {292healthy = false;293bus_val = 0;294}295296// 2s complement number with 3 lowest bits not used, 1 count is 8mV297const float bus_voltage = ((int16_t)bus_val >> 3)*8e-3;298// 2s complement number with 3 lowest bits not used, 1 count is 40uV299const float shunt_voltage = ((int16_t)shunt_val >> 3)*40e-6;300const float shunt_resistance = HAL_BATTMON_INA3221_SHUNT_OHMS;301const float shunt_current = shunt_voltage/shunt_resistance; // I = V/R302303// transfer readings to state304for (auto *state = statelist; state != nullptr; state = state->next) {305if (state->channel != i) {306continue;307}308WITH_SEMAPHORE(state->sem);309310// calculate time since last data read311const uint32_t tnow = AP_HAL::micros();312const uint32_t dt_us = tnow - state->last_time_micros;313314state->healthy = healthy;315state->voltage = bus_voltage;316state->current_amps = shunt_current;317318// update current drawn since last reading for read to accumulate319if (state->last_time_micros != 0 && dt_us < 2000000) {320const float mah = calculate_mah(state->current_amps, dt_us);321state->delta_mah += mah;322state->delta_wh += 0.001 * mah * state->voltage;323}324325state->last_time_micros = tnow;326}327}328}329330void AP_BattMonitor_INA3221::read()331{332if (address_driver_state == nullptr) {333return;334}335336WITH_SEMAPHORE(address_driver_state->sem);337// copy state data to front-end under semaphore338_state.healthy = address_driver_state->healthy;339_state.voltage = address_driver_state->voltage;340_state.current_amps = address_driver_state->current_amps;341_state.consumed_mah += address_driver_state->delta_mah;342_state.consumed_wh += address_driver_state->delta_wh;343_state.last_time_micros = address_driver_state->last_time_micros;344345address_driver_state->delta_mah = 0;346address_driver_state->delta_wh = 0;347}348349#endif // AP_BATTERY_INA3221_ENABLED350351352