Wednesday, January 20, 2016

Detect Current and Voltage from a NodeMCU ESP8266 Device Using a TI ina219 Current Sensor

I needed to get real-time data from an INA219 current sense module using a NodeMCU ESP8266 device running the Lua programming language and interpreter. I couldn't find anybody who had written a module yet so I wrote my own. Here you go.

-- ChiliPeppr INA219 Module ina219.lua v4
local ina219 = {}
ina219.id = 0
ina219.sda = 1
ina219.scl = 2
ina219.devaddr = 0x40   -- 1000000 (A0+A1=GND)

-- Set multipliers to convert raw current/power values
ina219.maxVoltage = 0 -- configured for max 32volts by default after init
ina219.maxCurrentmA = 0 -- configured for max 2A by default after init
ina219.currentDivider_mA = 0 -- e.g. Current LSB = 50uA per bit (1000/50 = 20)
ina219.powerDivider_mW = 0  -- e.g. Power LSB = 1mW per bit
ina219.currentLsb = 0 -- uA per bit
ina219.powerLsb = 1 -- mW per bit

function ina219.init()
  ina219.begin()
  ina219.setCalibration_32V_2A()
  reg = ina219.read_reg_str(0x00)
  print("Config:" .. ina219.getHex(reg))
end

-- user defined function: read from reg_addr content of dev_addr
function ina219.read_reg_str(reg_addr)
  i2c.start(ina219.id)
  i2c.address(ina219.id, ina219.devaddr, i2c.TRANSMITTER)
  i2c.write(ina219.id,reg_addr)
  i2c.stop(ina219.id)
  tmr.delay(1)
  i2c.start(ina219.id)
  i2c.address(ina219.id, ina219.devaddr, i2c.RECEIVER)
  c=i2c.read(ina219.id, 16) -- read 16bit val
  i2c.stop(ina219.id)
  return c
end

-- returns 16 bit int
function ina219.read_reg_int(reg_addr)
  i2c.start(ina219.id)
  i2c.address(ina219.id, ina219.devaddr, i2c.TRANSMITTER)
  i2c.write(ina219.id,reg_addr)
  i2c.stop(ina219.id)
  tmr.delay(1)
  i2c.start(ina219.id)
  i2c.address(ina219.id, ina219.devaddr, i2c.RECEIVER)
  local c = i2c.read(ina219.id, 16) -- read 16bit val
  i2c.stop(ina219.id)
  -- convert to 16 bit int
  local val = bit.lshift(string.byte(c, 1), 8)
  local val2 = bit.bor(val, string.byte(c, 2))
  return val2
end

function ina219.write_reg(reg_addr, reg_val)
  print("writing reg:" .. reg_addr .. ", reg_val:" .. reg_val)
  i2c.start(ina219.id)
  i2c.address(ina219.id, ina219.devaddr, i2c.TRANSMITTER)
  local bw = i2c.write(ina219.id, reg_addr)
  --print("Bytes written: " .. bw)
  -- upper 8 bits
  local bw2 = i2c.write(ina219.id, bit.rshift(reg_val, 8))
  --print("Bytes written: " .. bw2)
  -- lower 8 bits
  local bw3 = i2c.write(ina219.id, bit.band(reg_val, 0xFF))
  --print("Bytes written: " .. bw3)
  i2c.stop(ina219.id)
end

function ina219.begin()
  -- initialize i2c, set pin1 as sda, set pin2 as scl
  i2c.setup(ina219.id, ina219.sda, ina219.scl, i2c.SLOW)
end

function ina219.reset()
  ina219.write_reg(0x00, 0xFFFF)
end

function ina219.setCalibration_16V_400mA()
  ina219.maxVoltage = 16
  ina219.maxCurrentmA = 400
  ina219.currentDivider_mA = 20 -- Current LSB = 50uA per bit (1000/50 = 20)
  ina219.powerDivider_mW = 1  -- Power LSB = 1mW per bit
  ina219.currentLsb = 50 -- uA per bit
  ina219.powerLsb = 1 -- mW per bit
  ina219.write_reg(0x05, 8192)
  -- INA219_CONFIG_BVOLTAGERANGE_16V |
  --                  INA219_CONFIG_GAIN_1_40MV |
  --                  INA219_CONFIG_BADCRES_12BIT |
  --                  INA219_CONFIG_SADCRES_12BIT_1S_532US |
  --                  INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS;
  -- write_reg(0x05, 0x0000 | 0x0000 | 0x0400 | 0x0018 | 0x0007)
  ina219.write_reg(0x00, 0x41F)
end

function ina219.setCalibration_32V_1A()
  ina219.maxVoltage = 32
  ina219.maxCurrentmA = 1000
  -- Compute the calibration register
  -- Cal = trunc (0.04096 / (Current_LSB * RSHUNT))
  -- Cal = 10240 (0x2800)
  ina219.write_reg(0x05, 10240)
  -- Set multipliers to convert raw current/power values
  ina219.currentDivider_mA = 25   -- Current LSB = 40uA per bit (1000/40 = 25)
  ina219.powerDivider_mW = 1      -- Power LSB = 800uW per bit
  ina219.currentLsb = 40 -- uA per bit
  ina219.powerLsb = 0.8 -- mW per bit
  -- INA219_CONFIG_BVOLTAGERANGE_32V |
  --                  INA219_CONFIG_GAIN_8_320MV |
  --                  INA219_CONFIG_BADCRES_12BIT |
  --                  INA219_CONFIG_SADCRES_12BIT_1S_532US |
  --                  INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS;
  local config = bit.bor(0x2000, 0x1800, 0x0400, 0x0018, 0x0007)
  ina219.write_reg(0x00, config)
end

function ina219.setCalibration_32V_2A()
  ina219.maxVoltage = 32
  ina219.maxCurrentmA = 2000
  -- Compute the calibration register
  -- Cal = trunc (0.04096 / (Current_LSB * RSHUNT))
  -- Cal = 4096 (0x1000)
  ina219.write_reg(0x05, 4096)
  -- Set multipliers to convert raw current/power values
  ina219.currentDivider_mA = 10 -- Current LSB = 100uA per bit (1000/100 = 10)
  ina219.powerDivider_mW = 1      --Power LSB = 1mW per bit (2/1)
  ina219.currentLsb = 100 -- uA per bit
  ina219.powerLsb = 1 -- mW per bit
  -- INA219_CONFIG_BVOLTAGERANGE_32V |
  --                  INA219_CONFIG_GAIN_8_320MV |
  --                  INA219_CONFIG_BADCRES_12BIT |
  --                  INA219_CONFIG_SADCRES_12BIT_1S_532US |
  --                  INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS;
  local config = bit.bor(0x2000, 0x1800, 0x0400, 0x0018, 0x0007)
  ina219.write_reg(0x00, config)
end

function ina219.getCurrent_mA()
  -- Gets the raw current value (16-bit signed integer, so +-32767)
  local valueInt = ina219.read_reg_int(0x04)
  return valueInt / ina219.currentDivider_mA
end

function ina219.getBusVoltage_V()
  -- Gets the raw bus voltage (16-bit signed integer, so +-32767)
  local valueInt = ina219.read_reg_int(0x02)
  -- Shift to the right 3 to drop CNVR and OVF and multiply by LSB
  local val2 = bit.rshift(valueInt, 3) * 4
  return val2 * 0.001
end

function ina219.getShuntVoltage_mV()
  -- Gets the raw shunt voltage (16-bit signed integer, so +-32767)
  local valueInt = ina219.read_reg_int(0x01)
  return valueInt * 0.01
end

-- returns the bus power in watts
-- actually, i don't think i have the calculation correct yet
-- cuz this ain't watts or milliwatts. TODO
function ina219.getBusPowerWatts()
  local valueInt = ina219.read_reg_int(0x03)
  return valueInt * ina219.powerLsb
end

function ina219.checkVals()

  reg = ina219.read_reg_str(0x00)
  print("Config: " .. ina219.getHex(reg))

  -- get Shunt Voltage
  --reg = read_reg_int(0x01)
  --print("Shunt Voltage")
  --print(reg)
  --printHex(reg)
  print("Shunt Voltage mV: " .. ina219.getShuntVoltage_mV())

  -- get Bus Voltage
  --reg = read_reg_int(0x02)
  --print("Bus Voltage")
  --print(reg)
  --printHex(reg)
  print("Bus Voltage V: " .. ina219.getBusVoltage_V())

  -- get Power
  reg = ina219.read_reg_int(0x03)
  print("Power: " .. reg)
  --print(reg)
  --printHex(reg)
  print("Power watts: " .. ina219.getBusPowerWatts())

  -- get Current
  --reg = read_reg_int(0x04)
  --print("Current")
  --print(reg)
  --printHex(reg)

  print("Current mA:" .. ina219.getCurrent_mA())

  print("")
end

-- returns an object of vals
function ina219.getVals()
  local val = {}
  val.voltageV = ina219.getBusVoltage_V()
  val.shuntmV = ina219.getShuntVoltage_mV()
  val.powerW = ina219.getBusPowerWatts()
  -- sometimes the ina219 returns false current data
  -- where the value is pegged at max so toss it if the
  -- powerW is at 0 because that is usually when it happens
  if val.powerW == 0 then
    val.currentmA = 0
  else
    val.currentmA = ina219.getCurrent_mA()
  end
  return val
end

function ina219.getHex(val)
  --print("len of val:" .. string.len(val))
  local s = ""
  for i = 1, string.len(val) do
    if string.byte(val, i - 1) then
      s = s .. string.format("%2X", string.byte(val, i - 1)) .. " "
    end
  end
  --print(s)
  return s
end

--ina219.init()
return ina219


Then to use the module you should save this file to your NodeMCU device as ina219.lua and then compile it to ina219.lc with docompile("ina219.lua")

Then you can invoke it with code like the following:

ina = require("ina219")
ina.init()

function check()
  ok, json = pcall(cjson.encode, ina.getVals())
  if ok then
    print(json)
  else
    print("failed to encode!")
  end
end


tmr.alarm(0, 100, 1, check)

Enjoy. Hope it helps you in your project.