nodepower/bin/i2cmux/ina260.py
bcffd916
 #!/usr/bin/python3
8623d74e
 #
 # Module: ina260.py
 #
 # Description: This module acts as an interface between the INA260 sensor
 # and downstream applications that use the data.  Class methods get
 # current, voltage, and power data from the INA260 sensor.  It acts as a
 # library module that can be imported into and called from other Python
 # programs.
 #
bcffd916
 # Copyright 2022 Jeff Owrey
8623d74e
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU General Public License as published by
 #    the Free Software Foundation, either version 3 of the License, or
 #    (at your option) any later version.
 #
 #    This program is distributed in the hope that it will be useful,
 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 #    GNU General Public License for more details.
 #
 #    You should have received a copy of the GNU General Public License
 #    along with this program.  If not, see http://www.gnu.org/license.
 #
 # Revision History
 #   * v10 released 01 June 2021 by J L Owrey; first release
bcffd916
 #   * v20 released 16 March 2022 by J L Owrey; major revision to add
 #     fuctionality allowing i2c serial devices to use i2c serial bus
 #     multiplexer.  Also upgraded to python 3. 
8623d74e
 #
 #2345678901234567890123456789012345678901234567890123456789012345678901234567890
 
 # Import the I2C interface library
bcffd916
 import i2cmux
8623d74e
 
 # Define Device Registers
 CONFIG_REG = 0x0
 ID_REG = 0xFE
 CUR_REG = 0x1
 VOLT_REG = 0x2
 PWR_REG = 0x3
 
b98e8b95
 # Define default sm bus address.
bcffd916
 DEFAULT_MUX_CHANNEL = 0
b98e8b95
 DEFAULT_BUS_ADDRESS = 0x40
 DEFAULT_BUS_NUMBER = 1
 
 # Define the default sensor configuration.  See the INA260 data sheet
 # for meaning of each bit.  The following bytes are written to the
 # configuration register
 #     byte 1: 11100000
 #     byte 2: 00100111
 DEFAULT_CONFIG = 0xE027
 
8623d74e
 class ina260:
     # Initialize the INA260 sensor at the supplied address (default
     # address is 0x40), and supplied bus (default is 1).  Creates
     # a new SMBus object for each instance of this class.  Writes
     # configuration data (two bytes) to the INA260 configuration
     # register.
bcffd916
     def __init__(self, objMux, sAddr=DEFAULT_BUS_ADDRESS,
                        chan=DEFAULT_MUX_CHANNEL,
3e66b3fa
                        config=DEFAULT_CONFIG,
                        debug=False):
b98e8b95
         # Instantiate a smbus object.
bcffd916
         self.mux = objMux
8623d74e
         self.sensorAddr = sAddr
bcffd916
         self.channel = chan
3e66b3fa
         self.debugMode = debug
 
b98e8b95
         # Initialize INA260 sensor.  
         initData = [(config >> 8), (config & 0x00FF)]
bcffd916
         self.mux.write_i2c_block_data(self.channel, self.sensorAddr, \
                 CONFIG_REG, initData)
3e66b3fa
 
         if self.debugMode:
             data = self.getInfo()
cd727c58
             print(self)
3e66b3fa
             print("manufacturer ID: %s %s\n"\
cd727c58
                   "INA260 configuration register: %s %s\n" % data)
8623d74e
     ## end def
 
3e66b3fa
     def getInfo(self):
b98e8b95
         # Read manufacture identification data.
bcffd916
         mfcid = self.mux.read_i2c_block_data(self.channel, \
                 self.sensorAddr, ID_REG, 2)
8623d74e
         mfcidB1 = format(mfcid[0], "08b")
         mfcidB2 = format(mfcid[1], "08b")
b98e8b95
         # Read configuration data.
bcffd916
         config = self.mux.read_i2c_block_data(self.channel, \
                  self.sensorAddr, CONFIG_REG, 2)
8623d74e
         configB1 = format(config[0], "08b")
         configB2 = format(config[1], "08b")
         return (mfcidB1, mfcidB2, configB1, configB2)
     ## end def
 
d65ec281
     def getCurrentReg(self):
         # Read current register and return raw binary data for test and
         # debug.
bcffd916
         data = self.mux.read_i2c_block_data(self.channel, \
                self.sensorAddr, CUR_REG, 2)
d65ec281
         dataB1 = format(data[0], "08b")
         dataB2 = format(data[1], "08b")
         return (dataB1, dataB2)
     ## end def
 
8623d74e
     def getCurrent(self):
         # Get the current data from the sensor.
         # INA260 returns the data in two bytes formatted as follows
         #        -------------------------------------------------
3e66b3fa
         #    bit |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
8623d74e
         #        -------------------------------------------------
         # byte 1 | d15 | d14 | d13 | d12 | d11 | d10 | d9  | d8  |
         #        -------------------------------------------------
         # byte 2 | d7  | d6  | d5  | d4  | d3  |  d2 | d1  | d0  |
         #        -------------------------------------------------
         # The current is returned in d15-d0, a two's complement,
         # 16 bit number.  This means that d15 is the sign bit.        
bcffd916
         data=self.mux.read_i2c_block_data(self.channel, \
              self.sensorAddr, CUR_REG, 2)
3e66b3fa
 
         if self.debugMode:
             dataB1 = format(data[0], "08b")
             dataB2 = format(data[1], "08b")
             print("current register: %s %s" % (dataB1, dataB2))
 
8623d74e
         # Format into a 16 bit word.
         bdata = data[0] << 8 | data[1]
         # Convert from two's complement to integer.
         # If d15 is 1, the the number is a negative two's complement
d65ec281
         # number.  The absolute value is 2^16 - 1 minus the value
b98e8b95
         # of d15-d0 taken as a positive number.
8623d74e
         if bdata > 0x7FFF:
b98e8b95
             bdata = -(0xFFFF - bdata) # 0xFFFF equals 2^16 - 1
8623d74e
         # Convert integer data to mAmps.
         mAmps = bdata * 1.25  # LSB is 1.25 mA
         return mAmps
     ## end def
 
d65ec281
     def getVoltageReg(self):
         # Read voltage register and return raw binary data for test
         # and debug.
bcffd916
         data = self.mux.read_i2c_block_data(self.sensorAddr, VOLT_REG, 2)
d65ec281
         dataB1 = format(data[0], "08b")
         dataB2 = format(data[1], "08b")
         return (dataB1, dataB2)
     ## end def
 
8623d74e
     def getVoltage(self):
         # Get the voltage data from the sensor.
         # INA260 returns the data in two bytes formatted as follows
         #        -------------------------------------------------
3e66b3fa
         #    bit |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
8623d74e
         #        -------------------------------------------------
         # byte 1 | d15 | d14 | d13 | d12 | d11 | d10 | d9  | d8  |
         #        -------------------------------------------------
         # byte 2 | d7  | d6  | d5  | d4  | d3  |  d2 | d1  | d0  |
         #        -------------------------------------------------
         # The voltage is returned in d15-d0 as an unsigned integer.
bcffd916
         data=self.mux.read_i2c_block_data(self.channel, \
              self.sensorAddr, VOLT_REG, 2)
3e66b3fa
 
         if self.debugMode:
             dataB1 = format(data[0], "08b")
             dataB2 = format(data[1], "08b")
             print("voltage register: %s %s" % (dataB1, dataB2))
 
8623d74e
         # Convert data to volts.
         volts = (data[0] << 8 | data[1]) * 0.00125 # LSB is 1.25 mV
         return volts
     ## end def
 
     def getPower(self):
         # Get the wattage data from the sensor.
         # INA260 returns the data in two bytes formatted as follows
         #        -------------------------------------------------
3e66b3fa
         #    bit | 7   |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
8623d74e
         #        -------------------------------------------------
         # byte 1 | d15 | d14 | d13 | d12 | d11 | d10 | d9  | d8  |
         #        -------------------------------------------------
         # byte 2 | d7  | d6  | d5  | d4  | d3  |  d2 | d1  | d0  |
         #        -------------------------------------------------
         # The wattage is returned in d15-d0 as an unsigned integer.
bcffd916
         data=self.mux.read_i2c_block_data(self.channel, \
                 self.sensorAddr, PWR_REG, 2)
3e66b3fa
 
         if self.debugMode:
             dataB1 = format(data[0], "08b")
             dataB2 = format(data[1], "08b")
             print("power register: %s %s" % (dataB1, dataB2))
 
8623d74e
         # Convert data to milliWatts. 
         mW = (data[0] << 8 | data[1]) * 10.0  # LSB is 10.0 mW
         return mW
    ## end def
 ## end class
 
 def test():
bcffd916
     import time
 
8623d74e
     # Initialize the smbus and INA260 sensor.
bcffd916
     mux = i2cmux.i2cmux()
     pwr1 = ina260(mux, sAddr=0x40, chan=0, debug=True)
8623d74e
     # Print out sensor values.
     while True:
b98e8b95
         print("%6.2f mA" % pwr1.getCurrent())
         print("%6.2f V" % pwr1.getVoltage())
         print("%6.2f mW\n" % pwr1.getPower())
8623d74e
         time.sleep(2)
 ## end def
 
 if __name__ == '__main__':
     test()