nodepower/bin/ina260.py
8623d74e
 #!/usr/bin/python
 #
 # 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.
 #
 # Copyright 2021 Jeff Owrey
 #    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
 #
 #2345678901234567890123456789012345678901234567890123456789012345678901234567890
 
 # Import the I2C interface library
 import smbus
 import time
 
 # Define Device Registers
 CONFIG_REG = 0x0
 ID_REG = 0xFE
 CUR_REG = 0x1
 VOLT_REG = 0x2
 PWR_REG = 0x3
 
 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.
     def __init__(self, sAddr=0x40, sbus=1):
         # Instantiate a smbus object
         self.sensorAddr = sAddr
         self.bus = smbus.SMBus(sbus)
         # Initialize INA260 sensor.  See the data sheet for meaning of
         # each bit.  The following bytes are written to the configuration
         # register
d65ec281
         #     byte 1: 11100000
8623d74e
         #     byte 2: 00100111
d65ec281
         initData = [0b11100000, 0b00100111]
         #initData = [0x60, 0x27]
8623d74e
         self.bus.write_i2c_block_data(self.sensorAddr, CONFIG_REG, initData)
     ## end def
 
     def status(self):
         # Read configuration data
         mfcid = self.bus.read_i2c_block_data(self.sensorAddr, ID_REG, 2)
         mfcidB1 = format(mfcid[0], "08b")
         mfcidB2 = format(mfcid[1], "08b")
         # Read configuration data
         config = self.bus.read_i2c_block_data(self.sensorAddr, CONFIG_REG, 2)
         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.
         data = self.bus.read_i2c_block_data(self.sensorAddr, CUR_REG, 2)
         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
         #        -------------------------------------------------
         #    bit | b7  | b6  | b5  | b4  | b3  | b2  | b1  | b0  |
         #        -------------------------------------------------
         # 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.        
         data=self.bus.read_i2c_block_data(self.sensorAddr, CUR_REG, 2)
         # 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
8623d74e
         # of d14-d0 taken as a positive number.
         if bdata > 0x7FFF:
d65ec281
             bdata = -(0xFFFF - bdata) # 0xFFFF is 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.
         data = self.bus.read_i2c_block_data(self.sensorAddr, VOLT_REG, 2)
         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
         #        -------------------------------------------------
         #    bit | b7  | b6  | b5  | b4  | b3  | b2  | b1  | b0  |
         #        -------------------------------------------------
         # 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.
         data=self.bus.read_i2c_block_data(self.sensorAddr, VOLT_REG, 2)
         # 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
         #        -------------------------------------------------
         #    bit | b7  | b6  | b5  | b4  | b3  | b2  | b1  | b0  |
         #        -------------------------------------------------
         # 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.
         data=self.bus.read_i2c_block_data(self.sensorAddr, PWR_REG, 2)
         # 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():
     # Initialize the smbus and INA260 sensor.
     pwr1 = ina260(0x40, 1)
     # Read the INA260 configuration register and manufacturer's ID.
     data = pwr1.status()
     print "manufacturer ID: %s %s\nconfiguration register: %s %s\n" % data
     # Print out sensor values.
     while True:
d65ec281
         print "current register: %s %s" % pwr1.getCurrentReg()
8623d74e
         print "%6.2f mA" % pwr1.getCurrent()
d65ec281
         print "volt register: %s %s" % pwr1.getVoltageReg()
8623d74e
         print "%6.2f V" % pwr1.getVoltage()
         print "%6.2f mW\n" % pwr1.getPower()
         time.sleep(2)
 ## end def
 
 if __name__ == '__main__':
     test()