Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

INA219 I2C Power Monitor #31

Open
10 tasks
Tracked by #30
Mikefly123 opened this issue Oct 21, 2024 · 1 comment
Open
10 tasks
Tracked by #30

INA219 I2C Power Monitor #31

Mikefly123 opened this issue Oct 21, 2024 · 1 comment
Assignees
Labels
new component Creating a new component for this task PRIORITY Really important! sensor Relating to a sensor implementation

Comments

@Mikefly123
Copy link
Member

Mikefly123 commented Oct 21, 2024

What Does This Component Do?

The INA219 is an I2C Power Monitor that is currently used in the PROVES Kit to monitor the total power consumption of the system and also monitor how much power is coming from the solar charge circuit. This is accomplished by placing two INA219s on the Battery Board, one right after the battery inhibits and the other right before the solar charge circuit.

We want to create a generic INA219 component that can be duplicated to poll data from both of the sensors individually. This component should have the following functionality:

  • Differentiate between difference INA219's based on their I2C address
  • Identify that the INA219 is on the specified I2C bus
  • Collect Bus Voltage in V
  • Collect Shunt Voltage in mV
  • Collect Current in mA
  • Command the sensor to SLEEP and WAKE
  • Calculation and report Power in mW
  • Log the Bus Voltage, Shunt Voltage, Current, and Power as telemetry on a 1hz rate group
  • Handle and log errors if the device stops responding or is not found on the bus
  • Calculate a sensor calibration value when given an input shunt resistance in ohms

Design Notes

Link to INA219 Product Page
Adafruit C++ Library
Adafruit C++ Library Reference

This should be a pretty straightforward sensor to implement the only tricky part will probably be to setup the logging and error handling. Power consumption is one of the most important pieces of telemetry to pull from the satellite because it informs us about whether our power budget is positive (generating more power than is used) or is negative (using more power than we generate. Additionally, sensing the Bus Voltage on the system power monitor is an indirect way for us to determine the battery voltage and estimate the state of charge on the batteries using that information.

Eventually we will want to link this component with a Satellite Health Component that receives information from all the health sensors and is able to set power modes and deactivate components using too much power. Right now though we will start by just making a generic component that can get the data from the sensor and send it into logs.

Example CircuitPython Implementation

Pretty straightforward stuff, on the CircuitPy code we just directly pass through sensor calls from the library into the pysquared_eps Satellite object:

import adafruit_ina219  # Power Monitor
...
class Satellite: 
...
    def __init__(self):
    ...
        # Initialize Power Monitor
        try:
            time.sleep(1)
            self.pwr = adafruit_ina219.INA219(self.i2c0, addr=int(0x40))
            self.hardware["PWR"] = True
        except Exception as e:
            self.debug_print(
                "[ERROR][Power Monitor]" + "".join(traceback.format_exception(e))
            )
    ...
    @property
    def battery_voltage(self):
        if self.hardware["PWR"]:
            voltage = 0
            try:
                for _ in range(50):
                    voltage += self.pwr.bus_voltage
                return voltage / 50 + 0.2  # volts and corection factor
            except Exception as e:
                self.debug_print(
                    "[WARNING][PWR Monitor]" + "".join(traceback.format_exception(e))
                )
        else:
            self.debug_print("[WARNING] Power monitor not initialized")
   ...

Repeating as needed for the other voltage and current parameters. One of the interesting things to observe here is that we actually use a simple moving average to great 50 polling cycles of the power monitor before condensing it into a single number. This helps filter out moment to moment uncertainty in the measurement and would probably be a good idea to implement in some formal way in the F' component as well.

Example PicoSDK Implementation

This is one of the few parts of the satellite that currently has a working PicoSDK C++ Implementation!

From ina219.h

#include "pico/stdlib.h"
#include "hardware/i2c.h"

class INA219 {
public:
    INA219(i2c_inst_t* i2c_instance, uint8_t i2c_address);

    void configure();
    float readShuntVoltage();
    float readBusVoltage();
    float readCurrent();

private:
    i2c_inst_t* i2c;
    uint8_t address;

    
    void calibrate(float max_current_amps, float max_voltage);
    void writeRegister(uint8_t reg, uint16_t value);
    uint16_t readRegister(uint8_t reg);
};

From ina219.cpp

#include "INA219.h"

INA219::INA219(i2c_inst_t* i2c_instance, uint8_t i2c_address) {
    i2c = i2c_instance;
    address = i2c_address;
}

void INA219::configure() {
    // Configuration Register (default values for now)
    uint16_t config = 0x019F;  // 32V, 1A, Continuous Shunt and Bus, 12-bit resolution
    writeRegister(0x00, config);
    calibrate(32.767,12);
}

float INA219::readShuntVoltage() {
    int16_t value = readRegister(0x01);
    return value * 0.00001;  // LSB = 10uV
}

float INA219::readBusVoltage() {
    uint16_t value = readRegister(0x02);
    // Shift right to get rid of the reserved bits, then multiply by 4 to get mV
    return (value >> 3) * 0.004;
}

float INA219::readCurrent() {
    int16_t value = readRegister(0x04);
    return value;  // LSB = 1mA
}

void INA219::calibrate(float max_current_amps, float max_voltage) {
    // Calculate the calibration values for microamp measurements
    // See the datasheet for the formulas and additional details
    float current_lsb = max_current_amps / 32767.0;  // Current LSB

    // Set calibration register
    uint16_t calibration_value = static_cast<uint16_t>(0.04096 / (current_lsb * 0.002));
    writeRegister(0x05, calibration_value);
}

void INA219::writeRegister(uint8_t reg, uint16_t value) {
    uint8_t data[3];
    data[0] = reg;
    data[1] = (value >> 8) & 0xFF;
    data[2] = value & 0xFF;
    i2c_write_blocking(i2c, address, data, 3, false);
}

uint16_t INA219::readRegister(uint8_t reg) {
    uint8_t data[2];
    i2c_write_blocking(i2c, address, &reg, 1, true);
    i2c_read_blocking(i2c, address, data, 2, false);
    return (data[0] << 8) | data[1];
}

In pysquared.cpp.

 //instantiate tools class in pysquared
pysquared::pysquared(neopixel neo) : 
battery_power(i2c0, 0x40), solar_power(i2c0, 0x44), internal_temp(i2c0, 0x4f), led_driver(i2c0, 0x56), i2c_mux(i2c0,0x77),
adc(i2c0,0x48),
t(true, "[PYSQUARED] "){
    /*
        Pin Definitions
    */
    relay_pin = 15;
    vbus_reset_pin = 14;
    i2c_reset_pin = 7;
    rf_enable_pin = 12;
    d0_pin = 29;
    i2c_sda1_pin = 2;
    i2c_scl1_pin = 3;
    i2c_sda0_pin = 4;
    i2c_scl0_pin = 5;
    /*
        Initialize hardware core to operations on satellite.
    */
    try {
         ...
        /*
            I2C init
        */
        i2c_init(i2c0, 400*1000);
        gpio_set_function(i2c_sda0_pin, GPIO_FUNC_I2C);
        gpio_set_function(i2c_scl0_pin, GPIO_FUNC_I2C);
        t.i2c_scan(i2c0);
        i2c_init(i2c1, 400*1000);
        gpio_set_function(i2c_sda1_pin, GPIO_FUNC_I2C);
        gpio_set_function(i2c_scl1_pin, GPIO_FUNC_I2C);
        t.i2c_scan(i2c1);
        t.debug_print("I2C Bus Initialized!\n");
        
        ...

        /*
            Battery Power Monitor init
        */
        try{
            battery_power.configure();
            t.debug_print("Battery Power Monitor Initialized!\n");
        }
        catch(...){
            t.debug_print("ERROR initializing Battery Power Monitor!\n");
            error_count++;
        }
        /*
            Solar Power Monitor init
        */
       try{
            solar_power.configure();
            t.debug_print("Solar Power Monitor Initialized!\n");
        }
        catch(...){
            t.debug_print("ERROR initializing Solar Power Monitor!\n");
            error_count++;
        }
        t.debug_print("Hardware fully initialized!\n");
    }
    catch(...){
        t.debug_print("ERROR Initializing Hardware: \n");
        error_count++;
    }
}
...
float pysquared::get_battery_voltage(){
    try{
        float temp;
        for(int i = 0; i<50; i++){
            temp+=battery_power.readBusVoltage();
        }
        return temp/50;
    }
    catch(...){
        t.debug_print("ERROR while getting battery voltage!\n");
        error_count++;
        return 0;
    }
}
float pysquared::get_draw_current(){
    try{
        float temp;
        for(int i = 0; i<50; i++){
            temp+=battery_power.readCurrent();
        }
        return temp/50;
    }
    catch(...){
        t.debug_print("ERROR while getting draw current!\n");
        error_count++;
        return 0;
    }
}
float pysquared::get_charge_voltage(){
    try{
        float temp;
        for(int i = 0; i<50; i++){
            temp+=solar_power.readBusVoltage();
        }
        return temp/50;
    }
    catch(...){
        t.debug_print("ERROR while getting charge voltage!\n");
        error_count++;
        return 0;
    }
}
float pysquared::get_charge_current(){
    try{
        float temp;
        for(int i = 0; i<50; i++){
            temp+=solar_power.readCurrent();
        }
    return temp/50;
    }
    catch(...){
        t.debug_print("ERROR while getting charge current!\n");
        error_count++;
        return 0;
    }
}

Reference Schematic

Screenshot 2024-10-21 at 1 55 50 PM
@Mikefly123 Mikefly123 mentioned this issue Oct 21, 2024
12 tasks
@Mikefly123 Mikefly123 added this to the fprime-proves-v1.0 milestone Oct 21, 2024
@Mikefly123 Mikefly123 added sensor Relating to a sensor implementation new component Creating a new component for this task PRIORITY Really important! labels Oct 21, 2024
@dbgen1 dbgen1 self-assigned this Oct 24, 2024
@Mikefly123
Copy link
Member Author

@dbgen1 have you had any ideas about what the hiccup with getting your INA219 component working last week was?

I had added to the initial post a reference to how we can currently ready the INA219 using the PicoSDK as a reference in case that would be helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new component Creating a new component for this task PRIORITY Really important! sensor Relating to a sensor implementation
Projects
None yet
Development

When branches are created from issues, their pull requests are automatically linked.

2 participants